Merge remote-tracking branch 'upstream/master' into core

This commit is contained in:
Bradley Austin Davis 2015-08-04 10:23:54 -07:00
commit 3c76c0d4b6
93 changed files with 3597 additions and 1752 deletions

214
examples/controlPanel.js Normal file
View file

@ -0,0 +1,214 @@
//
// controlPanel.js
// examples
//
// Created by Zander Otavka on 7/15/15.
// Copyright 2015 High Fidelity, Inc.
//
// Shows a few common controls in a FloatingUIPanel on right click.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include([
"libraries/globals.js",
"libraries/overlayManager.js",
]);
var BG_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/card-bg.svg";
var CLOSE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/close.svg";
var MIC_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/mic-toggle.svg";
var FACE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/face-toggle.svg";
var ADDRESS_BAR_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/tools/address-bar-toggle.svg";
var panel = new FloatingUIPanel({
anchorPosition: {
bind: "myAvatar"
},
offsetPosition: { x: 0, y: 0.4, z: 1 }
});
var background = new BillboardOverlay({
url: BG_IMAGE_URL,
dimensions: {
x: 0.5,
y: 0.5,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false
});
panel.addChild(background);
var closeButton = new BillboardOverlay({
url: CLOSE_IMAGE_URL,
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.1,
y: 0.1,
z: -0.001
}
});
closeButton.onClick = function(event) {
panel.visible = false;
};
panel.addChild(closeButton);
var micMuteButton = new BillboardOverlay({
url: MIC_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.1,
y: 0.1,
z: -0.001
}
});
micMuteButton.onClick = function(event) {
AudioDevice.toggleMute();
};
panel.addChild(micMuteButton);
var faceMuteButton = new BillboardOverlay({
url: FACE_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.1,
y: -0.1,
z: -0.001
}
});
faceMuteButton.onClick = function(event) {
FaceTracker.toggleMute();
};
panel.addChild(faceMuteButton);
var addressBarButton = new BillboardOverlay({
url: ADDRESS_BAR_IMAGE_URL,
subImage: {
x: 0,
y: 0,
width: 45,
height: 45
},
dimensions: {
x: 0.15,
y: 0.15,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.1,
y: -0.1,
z: -0.001
}
});
addressBarButton.onClick = function(event) {
DialogsManager.toggleAddressBar();
};
panel.addChild(addressBarButton);
function onMicMuteToggled() {
var offset;
if (AudioDevice.getMuted()) {
offset = 45;
} else {
offset = 0;
}
micMuteButton.subImage = {
x: offset,
y: 0,
width: 45,
height: 45
};
}
onMicMuteToggled();
function onFaceMuteToggled() {
var offset;
if (FaceTracker.getMuted()) {
offset = 45;
} else {
offset = 0;
}
faceMuteButton.subImage = {
x: offset,
y: 0,
width: 45,
height: 45
};
}
onFaceMuteToggled();
var mouseDown = {};
function onMouseDown(event) {
if (event.isLeftButton) {
mouseDown.overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
}
if (event.isRightButton) {
mouseDown.pos = { x: event.x, y: event.y };
}
}
function onMouseUp(event) {
if (event.isLeftButton) {
var overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
if (overlay && overlay === mouseDown.overlay && overlay.onClick) {
overlay.onClick(event);
}
}
if (event.isRightButton && Vec3.distance(mouseDown.pos, { x: event.x, y: event.y }) < 5) {
panel.setProperties({
visible: !panel.visible,
offsetRotation: {
bind: "quat",
value: Quat.multiply(MyAvatar.orientation, { x: 0, y: 1, z: 0, w: 0 })
}
});
}
mouseDown = {};
}
function onScriptEnd(event) {
panel.destroy();
}
Controller.mousePressEvent.connect(onMouseDown);
Controller.mouseReleaseEvent.connect(onMouseUp);
AudioDevice.muteToggled.connect(onMicMuteToggled);
FaceTracker.muteToggled.connect(onFaceMuteToggled);
Script.scriptEnding.connect(onScriptEnd);

View file

@ -0,0 +1,169 @@
//
// floatingUI.js
// examples/example/ui
//
// Created by Alexander Otavka
// 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
//
Script.include([
"../../libraries/globals.js",
"../../libraries/overlayManager.js",
]);
var BG_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/card-bg.svg";
var RED_DOT_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/red-dot.svg";
var BLUE_SQUARE_IMAGE_URL = HIFI_PUBLIC_BUCKET + "images/blue-square.svg";
var mainPanel = new FloatingUIPanel({
offsetRotation: {
bind: "quat",
value: { w: 1, x: 0, y: 0, z: 0 }
},
offsetPosition: { x: 0, y: 0.4, z: 1 }
});
var bluePanel = mainPanel.addChild(new FloatingUIPanel ({
offsetPosition: { x: 0.1, y: 0.1, z: -0.2 }
}));
var mainPanelBackground = new BillboardOverlay({
url: BG_IMAGE_URL,
dimensions: {
x: 0.5,
y: 0.5,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0,
y: 0,
z: 0.001
}
});
var bluePanelBackground = mainPanelBackground.clone();
bluePanelBackground.dimensions = {
x: 0.3,
y: 0.3
};
mainPanel.addChild(mainPanelBackground);
bluePanel.addChild(bluePanelBackground);
var redDot = mainPanel.addChild(new BillboardOverlay({
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.15,
y: -0.15,
z: 0
}
}));
var redDot2 = mainPanel.addChild(new BillboardOverlay({
url: RED_DOT_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: -0.155,
y: 0.005,
z: 0
}
}));
var blueSquare = bluePanel.addChild(new BillboardOverlay({
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.055,
y: -0.055,
z: 0
}
}));
var blueSquare2 = bluePanel.addChild(new BillboardOverlay({
url: BLUE_SQUARE_IMAGE_URL,
dimensions: {
x: 0.1,
y: 0.1,
},
isFacingAvatar: false,
alpha: 1.0,
ignoreRayIntersection: false,
offsetPosition: {
x: 0.055,
y: 0.055,
z: 0
}
}));
var blueSquare3 = blueSquare2.clone();
blueSquare3.offsetPosition = {
x: -0.055,
y: 0.055,
z: 0
};
var mouseDown = {};
function onMouseDown(event) {
if (event.isLeftButton) {
mouseDown.overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
}
if (event.isRightButton) {
mouseDown.pos = { x: event.x, y: event.y };
}
}
function onMouseUp(event) {
if (event.isLeftButton) {
var overlay = OverlayManager.findAtPoint({ x: event.x, y: event.y });
if (overlay === mouseDown.overlay) {
if (overlay.attachedPanel === bluePanel) {
overlay.destroy();
} else if (overlay) {
var oldPos = overlay.offsetPosition;
var newPos = {
x: Number(oldPos.x),
y: Number(oldPos.y),
z: Number(oldPos.z) + 0.1
};
overlay.offsetPosition = newPos;
}
}
}
if (event.isRightButton && Vec3.distance(mouseDown.pos, { x: event.x, y: event.y }) < 5) {
mainPanel.visible = !mainPanel.visible;
}
}
function onScriptEnd() {
mainPanel.destroy();
}
Controller.mousePressEvent.connect(onMouseDown);
Controller.mouseReleaseEvent.connect(onMouseUp);
Script.scriptEnding.connect(onScriptEnd);

View file

@ -9,7 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MOVE_DISTANCE = 10.0;
var MOVE_DISTANCE = 2.0;
var PITCH_INCREMENT = 0.5; // degrees
var pitchChange = 0; // degrees
var YAW_INCREMENT = 0.5; // degrees

View file

@ -0,0 +1,455 @@
//
// overlayManager.js
// examples/libraries
//
// Created by Zander Otavka on 7/24/15
// Copyright 2015 High Fidelity, Inc.
//
// Manage overlays with object oriented goodness, instead of ugly `Overlays.h` methods.
// Instead of:
//
// var billboard = Overlays.addOverlay("billboard", { visible: false });
// ...
// Overlays.editOverlay(billboard, { visible: true });
// ...
// Overlays.deleteOverlay(billboard);
//
// You can now do:
//
// var billboard = new BillboardOverlay({ visible: false });
// ...
// billboard.visible = true;
// ...
// billboard.destroy();
//
// See more on usage below.
//
// Note that including this file will delete Overlays from the global scope. All the
// functionality of Overlays is represented here, just better. If you try to use Overlays in
// tandem, there may be performance problems or nasty surprises.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
// Delete `Overlays` from the global scope.
var Overlays = this.Overlays;
delete this.Overlays;
var overlays = {};
var panels = {};
var overlayTypes;
var Overlay, Overlay2D, Base3DOverlay, Planar3DOverlay, Volume3DOverlay;
//
// Create a new JavaScript object for an overlay of given ID.
//
function makeOverlayFromId(id) {
var type = Overlays.getOverlayType(id);
if (!type) {
return null;
}
var overlay = new overlayTypes[type]();
overlay._id = id;
var panelID = Overlays.getAttachedPanel(id)
if (panelID && panelID in panels) {
panels[panelID].addChild(overlay);
}
overlays[id] = overlay;
return overlay;
}
//
// Get or create an overlay object from the id.
//
// @param knownOverlaysOnly (Optional: Boolean)
// If true, a new object will not be created.
// @param searchList (Optional: Object)
// Map of overlay id's and overlay objects. Can be generated with
// `OverlayManager.makeSearchList`.
//
function findOverlay(id, knownOverlaysOnly, searchList) {
if (id > 0) {
knownOverlaysOnly = Boolean(knownOverlaysOnly) || Boolean(searchList);
searchList = searchList || overlays;
var foundOverlay = searchList[id];
if (foundOverlay) {
return foundOverlay;
}
if (!knownOverlaysOnly) {
return makeOverlayFromId(id);
}
}
return null;
}
//
// Perform global scoped operations on overlays, such as finding by ray intersection.
//
OverlayManager = {
findOnRay: function(pickRay, knownOverlaysOnly, searchList) {
var rayPickResult = Overlays.findRayIntersection(pickRay);
print("raypick " + rayPickResult.overlayID);
if (rayPickResult.intersects) {
return findOverlay(rayPickResult.overlayID, knownOverlaysOnly, searchList);
}
return null;
},
findAtPoint: function(point, knownOverlaysOnly, searchList) {
var foundID = Overlays.getOverlayAtPoint(point);
print("at point " + foundID);
if (foundID) {
return findOverlay(foundID, knownOverlaysOnly, searchList);
} else {
var pickRay = Camera.computePickRay(point.x, point.y);
return OverlayManager.findOnRay(pickRay, knownOverlaysOnly, searchList);
}
},
makeSearchList: function(overlayArray) {
var searchList = {};
overlayArray.forEach(function(overlay){
searchList[overlay._id] = overlay;
});
return searchList;
}
};
//
// Object oriented abstraction layer for overlays.
//
// Usage:
// // Create an overlay
// var billboard = new BillboardOverlay({
// visible: true,
// isFacingAvatar: true,
// ignoreRayIntersections: false
// });
//
// // Get a property
// var isVisible = billboard.visible;
//
// // Set a single property
// billboard.position = { x: 1, y: 3, z: 2 };
//
// // Set multiple properties at the same time
// billboard.setProperties({
// url: "http://images.com/overlayImage.jpg",
// dimensions: { x: 2, y: 2 }
// });
//
// // Clone an overlay
// var clonedBillboard = billboard.clone();
//
// // Remove an overlay from the world
// billboard.destroy();
//
// // Remember, there is a poor orphaned JavaScript object left behind. You should
// // remove any references to it so you don't accidentally try to modify an overlay that
// // isn't there.
// billboard = undefined;
//
(function() {
var ABSTRACT = null;
overlayTypes = {};
function generateOverlayClass(superclass, type, properties) {
var that;
if (type == ABSTRACT) {
that = function(type, params) {
superclass.call(this, type, params);
};
} else {
that = function(params) {
superclass.call(this, type, params);
};
overlayTypes[type] = that;
}
that.prototype = new superclass();
that.prototype.constructor = that;
properties.forEach(function(prop) {
Object.defineProperty(that.prototype, prop, {
get: function() {
return Overlays.getProperty(this._id, prop);
},
set: function(newValue) {
var keyValuePair = {};
keyValuePair[prop] = newValue;
this.setProperties(keyValuePair);
},
configurable: false
});
});
return that;
}
// Supports multiple inheritance of properties. Just `concat` them onto the end of the
// properties list.
var PANEL_ATTACHABLE_FIELDS = ["offsetPosition", "facingRotation"];
Overlay = (function() {
var that = function(type, params) {
if (type && params) {
this._id = Overlays.addOverlay(type, params);
overlays[this._id] = this;
} else {
this._id = 0;
}
this._attachedPanelPointer = null;
};
that.prototype.constructor = that;
Object.defineProperty(that.prototype, "isLoaded", {
get: function() {
return Overlays.isLoaded(this._id);
}
});
Object.defineProperty(that.prototype, "attachedPanel", {
get: function() {
return this._attachedPanelPointer;
}
});
that.prototype.getTextSize = function(text) {
return Overlays.textSize(this._id, text);
};
that.prototype.setProperties = function(properties) {
Overlays.editOverlay(this._id, properties);
};
that.prototype.clone = function() {
return makeOverlayFromId(Overlays.cloneOverlay(this._id));
};
that.prototype.destroy = function() {
Overlays.deleteOverlay(this._id);
};
return generateOverlayClass(that, ABSTRACT, [
"alpha", "glowLevel", "pulseMax", "pulseMin", "pulsePeriod", "glowLevelPulse",
"alphaPulse", "colorPulse", "visible", "anchor"
]);
})();
Overlay2D = generateOverlayClass(Overlay, ABSTRACT, [
"bounds", "x", "y", "width", "height"
]);
Base3DOverlay = generateOverlayClass(Overlay, ABSTRACT, [
"position", "lineWidth", "rotation", "isSolid", "isFilled", "isWire", "isDashedLine",
"ignoreRayIntersection", "drawInFront", "drawOnHUD"
]);
Planar3DOverlay = generateOverlayClass(Base3DOverlay, ABSTRACT, [
"dimensions"
]);
Volume3DOverlay = generateOverlayClass(Base3DOverlay, ABSTRACT, [
"dimensions"
]);
generateOverlayClass(Overlay2D, "image", [
"subImage", "imageURL"
]);
generateOverlayClass(Overlay2D, "text", [
"font", "text", "backgroundColor", "backgroundAlpha", "leftMargin", "topMargin"
]);
generateOverlayClass(Planar3DOverlay, "text3d", [
"text", "backgroundColor", "backgroundAlpha", "lineHeight", "leftMargin", "topMargin",
"rightMargin", "bottomMargin", "isFacingAvatar"
]);
generateOverlayClass(Volume3DOverlay, "cube", [
"borderSize"
]);
generateOverlayClass(Volume3DOverlay, "sphere", [
]);
generateOverlayClass(Planar3DOverlay, "circle3d", [
"startAt", "endAt", "outerRadius", "innerRadius", "hasTickMarks",
"majorTickMarksAngle", "minorTickMarksAngle", "majorTickMarksLength",
"minorTickMarksLength", "majorTickMarksColor", "minorTickMarksColor"
]);
generateOverlayClass(Planar3DOverlay, "rectangle3d", [
]);
generateOverlayClass(Base3DOverlay, "line3d", [
"start", "end"
]);
generateOverlayClass(Planar3DOverlay, "grid", [
"minorGridWidth", "majorGridEvery"
]);
generateOverlayClass(Volume3DOverlay, "localmodels", [
]);
generateOverlayClass(Volume3DOverlay, "model", [
"url", "dimensions", "textures"
]);
generateOverlayClass(Planar3DOverlay, "billboard", [
"url", "subImage", "isFacingAvatar"
].concat(PANEL_ATTACHABLE_FIELDS));
})();
ImageOverlay = overlayTypes["image"];
TextOverlay = overlayTypes["text"];
Text3DOverlay = overlayTypes["text3d"];
Cube3DOverlay = overlayTypes["cube"];
Sphere3DOverlay = overlayTypes["sphere"];
Circle3DOverlay = overlayTypes["circle3d"];
Rectangle3DOverlay = overlayTypes["rectangle3d"];
Line3DOverlay = overlayTypes["line3d"];
Grid3DOverlay = overlayTypes["grid"];
LocalModelsOverlay = overlayTypes["localmodels"];
ModelOverlay = overlayTypes["model"];
BillboardOverlay = overlayTypes["billboard"];
//
// Object oriented abstraction layer for panels.
//
FloatingUIPanel = (function() {
var that = function(params) {
this._id = Overlays.addPanel(params);
this._children = [];
this._visible = Boolean(params.visible);
panels[this._id] = this;
this._attachedPanelPointer = null;
};
that.prototype.constructor = that;
var FIELDS = ["offsetPosition", "offsetRotation", "facingRotation"];
FIELDS.forEach(function(prop) {
Object.defineProperty(that.prototype, prop, {
get: function() {
return Overlays.getPanelProperty(this._id, prop);
},
set: function(newValue) {
var keyValuePair = {};
keyValuePair[prop] = newValue;
this.setProperties(keyValuePair);
},
configurable: false
});
});
var PSEUDO_FIELDS = [];
PSEUDO_FIELDS.push("children");
Object.defineProperty(that.prototype, "children", {
get: function() {
return this._children.slice();
}
});
PSEUDO_FIELDS.push("visible");
Object.defineProperty(that.prototype, "visible", {
get: function() {
return this._visible;
},
set: function(visible) {
this._visible = visible;
this._children.forEach(function(child) {
child.visible = visible;
});
}
});
that.prototype.addChild = function(child) {
if (child instanceof Overlay) {
Overlays.setAttachedPanel(child._id, this._id);
} else if (child instanceof FloatingUIPanel) {
child.setProperties({
anchorPosition: {
bind: "panel",
value: this._id
},
offsetRotation: {
bind: "panel",
value: this._id
}
});
}
child._attachedPanelPointer = this;
child.visible = this.visible;
this._children.push(child);
return child;
};
that.prototype.removeChild = function(child) {
var i = this._children.indexOf(child);
if (i >= 0) {
if (child instanceof Overlay) {
Overlays.setAttachedPanel(child._id, 0);
} else if (child instanceof FloatingUIPanel) {
child.setProperties({
anchorPosition: {
bind: "myAvatar"
},
offsetRotation: {
bind: "myAvatar"
}
});
}
child._attachedPanelPointer = null;
this._children.splice(i, 1);
}
};
that.prototype.setProperties = function(properties) {
for (var i in PSEUDO_FIELDS) {
if (properties[PSEUDO_FIELDS[i]] !== undefined) {
this[PSEUDO_FIELDS[i]] = properties[PSEUDO_FIELDS[i]];
}
}
Overlays.editPanel(this._id, properties);
};
that.prototype.destroy = function() {
Overlays.deletePanel(this._id);
};
return that;
})();
function onOverlayDeleted(id) {
if (id in overlays) {
if (overlays[id]._attachedPanelPointer) {
overlays[id]._attachedPanelPointer.removeChild(overlays[id]);
}
delete overlays[id];
}
}
function onPanelDeleted(id) {
if (id in panels) {
panels[id]._children.forEach(function(child) {
print(JSON.stringify(child.destroy));
child.destroy();
});
delete panels[id];
}
}
Overlays.overlayDeleted.connect(onOverlayDeleted);
Overlays.panelDeleted.connect(onPanelDeleted);
})();

View file

@ -1,7 +1,17 @@
/**
* OverlayGroup provides a way to create composite overlays and control their
* position relative to a settable rootPosition and rootRotation.
*/
//
// overlayUtils.js
// examples/libraries
//
// Copyright 2015 High Fidelity, Inc.
//
//
// DEPRECATION WARNING: Will be deprecated soon in favor of FloatingUIPanel.
//
// OverlayGroup provides a way to create composite overlays and control their
// position relative to a settable rootPosition and rootRotation.
//
OverlayGroup = function(opts) {
var that = {};
@ -59,6 +69,6 @@ OverlayGroup = function(opts) {
}
overlays = {};
}
return that;
};

View file

@ -3758,10 +3758,13 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface);
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data());
qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue);

View file

@ -49,6 +49,7 @@
#include "avatar/MyAvatar.h"
#include "devices/SixenseManager.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/DialogsManagerScriptingInterface.h"
#include "scripting/WebWindowClass.h"
#include "ui/AudioStatsDialog.h"
#include "ui/BandwidthDialog.h"
@ -69,6 +70,7 @@
#include "UndoStackScriptingInterface.h"
#include "gpu/Context.h"
#include "render/Engine.h"
class QGLWidget;
@ -643,6 +645,8 @@ private:
ApplicationOverlay _applicationOverlay;
ApplicationCompositor _compositor;
int _numFramesSinceLastResize = 0;
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
};
#endif // hifi_Application_h

View file

@ -45,6 +45,7 @@
#include "Util.h"
#include "world.h"
#include "InterfaceLogging.h"
#include "EntityRig.h"
using namespace std;
@ -75,9 +76,9 @@ namespace render {
}
}
Avatar::Avatar() :
Avatar::Avatar(RigPointer rig) :
AvatarData(),
_skeletonModel(this),
_skeletonModel(this, nullptr, rig),
_skeletonOffset(0.0f),
_bodyYawDelta(0.0f),
_positionDeltaAccumulator(0.0f),
@ -430,15 +431,17 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
}
}
/*
// TODO: re-implement these when we have more detailed avatar collision shapes
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderSkeleton) {
_skeletonModel.renderJointCollisionShapes(0.7f);
}
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
if (renderHead && shouldRenderHead(renderArgs)) {
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
}
*/
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderBounding && shouldRenderHead(renderArgs)) {
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f);
}
@ -793,33 +796,6 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor);
}
bool Avatar::findRayIntersection(RayIntersectionInfo& intersection) const {
bool hit = _skeletonModel.findRayIntersection(intersection);
hit = getHead()->getFaceModel().findRayIntersection(intersection) || hit;
return hit;
}
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) {
return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
// TODO: Andrew to fix: Temporarily disabling collisions against the head
//return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
}
bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
return _skeletonModel.findPlaneCollisions(plane, collisions) ||
getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
}
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
// TODO: Andrew to fix: also collide against _skeleton
//bool collided = _skeletonModel.findCollisions(shapes, collisions);
Model& headModel = getHead()->getFaceModel();
//collided = headModel.findCollisions(shapes, collisions) || collided;
bool collided = headModel.findCollisions(shapes, collisions);
return collided;
}
void Avatar::setSkeletonOffset(const glm::vec3& offset) {
const float MAX_OFFSET_LENGTH = _scale * 0.5f;
float offsetLength = glm::length(offset);
@ -843,7 +819,7 @@ QVector<glm::quat> Avatar::getJointRotations() const {
}
QVector<glm::quat> jointRotations(_skeletonModel.getJointStateCount());
for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) {
_skeletonModel.getJointState(i, jointRotations[i]);
_skeletonModel.getJointRotation(i, jointRotations[i]);
}
return jointRotations;
}
@ -853,7 +829,7 @@ glm::quat Avatar::getJointRotation(int index) const {
return AvatarData::getJointRotation(index);
}
glm::quat rotation;
_skeletonModel.getJointState(index, rotation);
_skeletonModel.getJointRotation(index, rotation);
return rotation;
}
@ -977,7 +953,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (_unusedAttachments.size() > 0) {
model = _unusedAttachments.takeFirst();
} else {
model = new Model(this);
model = new Model(std::make_shared<EntityRig>(), this);
}
model->init();
_attachmentModels.append(model);
@ -1139,9 +1115,8 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
// virtual
void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
shapeInfo.setCapsuleY(capsule.getRadius(), capsule.getHalfHeight());
shapeInfo.setOffset(_skeletonModel.getBoundingShapeOffset());
shapeInfo.setCapsuleY(_skeletonModel.getBoundingCapsuleRadius(), 0.5f * _skeletonModel.getBoundingCapsuleHeight());
shapeInfo.setOffset(_skeletonModel.getBoundingCapsuleOffset());
}
// virtual

View file

@ -72,7 +72,7 @@ class Avatar : public AvatarData {
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
Avatar();
Avatar(RigPointer rig = nullptr);
~Avatar();
typedef render::Payload<AvatarData> Payload;
@ -110,26 +110,6 @@ public:
/// Returns the distance to use as a LOD parameter.
float getLODDistance() const;
bool findRayIntersection(RayIntersectionInfo& intersection) const;
/// \param shapes list of shapes to collide against avatar
/// \param collisions list to store collision results
/// \return true if at least one shape collided with avatar
bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
/// Checks for penetration between the a sphere and the avatar's models.
/// \param penetratorCenter the center of the penetration test sphere
/// \param penetratorRadius the radius of the penetration test sphere
/// \param collisions[out] a list to which collisions get appended
/// \return whether or not the sphere penetrated
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions);
/// Checks for penetration between the described plane and the avatar.
/// \param plane the penetration plane
/// \param collisions[out] a list to which collisions get appended
/// \return whether or not the plane penetrated
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
virtual bool isMyAvatar() const { return false; }
virtual QVector<glm::quat> getJointRotations() const;
@ -186,9 +166,10 @@ public:
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
friend class AvatarManager;
void setMotionState(AvatarMotionState* motionState) { _motionState = motionState; }
AvatarMotionState* getMotionState() { return _motionState; }
signals:
signals:
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
protected:
@ -218,7 +199,7 @@ protected:
glm::vec3 _worldUpDirection;
float _stringLength;
bool _moving; ///< set when position is changing
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
@ -243,7 +224,7 @@ protected:
virtual void updateJointMappings();
render::ItemID _renderItemID;
private:
bool _initialized;
NetworkTexturePointer _billboardTexture;
@ -251,9 +232,9 @@ private:
bool _isLookAtTarget;
void renderBillboard(RenderArgs* renderArgs);
float getBillboardSize() const;
static int _jointConesID;
int _voiceSphereID;

View file

@ -35,6 +35,7 @@
#include "Menu.h"
#include "MyAvatar.h"
#include "SceneScriptingInterface.h"
#include "AvatarRig.h"
// 70 times per second - target is 60hz, but this helps account for any small deviations
// in the update loop
@ -65,7 +66,7 @@ AvatarManager::AvatarManager(QObject* parent) :
{
// 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>();
_myAvatar = std::make_shared<MyAvatar>(std::make_shared<AvatarRig>());
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
@ -160,7 +161,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
}
AvatarSharedPointer AvatarManager::newSharedAvatar() {
return AvatarSharedPointer(std::make_shared<Avatar>());
return AvatarSharedPointer(std::make_shared<Avatar>(std::make_shared<AvatarRig>()));
}
// virtual
@ -178,11 +179,11 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
// protected
void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) {
auto rawPointer = std::static_pointer_cast<Avatar>(avatar);
AvatarMotionState* motionState= rawPointer->_motionState;
AvatarMotionState* motionState = rawPointer->getMotionState();
if (motionState) {
// clean up physics stuff
motionState->clearObjectBackPointer();
rawPointer->_motionState = nullptr;
rawPointer->setMotionState(nullptr);
_avatarMotionStates.remove(motionState);
_motionStatesToAdd.remove(motionState);
_motionStatesToDelete.push_back(motionState);
@ -306,7 +307,7 @@ void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
AvatarHash::iterator avatarItr = _avatarHash.find(id);
if (avatarItr != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<Avatar>(avatarItr.value());
AvatarMotionState* motionState = avatar->_motionState;
AvatarMotionState* motionState = avatar->getMotionState();
if (motionState) {
motionState->addDirtyFlags(EntityItem::DIRTY_SHAPE);
} else {
@ -315,7 +316,7 @@ void AvatarManager::updateAvatarPhysicsShape(const QUuid& id) {
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar.get(), shape);
avatar->_motionState = motionState;
avatar->setMotionState(motionState);
_motionStatesToAdd.insert(motionState);
_avatarMotionStates.insert(motionState);
}

View file

@ -16,13 +16,16 @@
#include "Head.h"
#include "Menu.h"
FaceModel::FaceModel(Head* owningHead) :
FaceModel::FaceModel(Head* owningHead, RigPointer rig) :
Model(rig, nullptr),
_owningHead(owningHead)
{
assert(_rig);
}
void FaceModel::simulate(float deltaTime, bool fullUpdate) {
updateGeometry();
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
glm::vec3 neckPosition;
if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) {
@ -35,67 +38,73 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
}
setRotation(neckParentRotation);
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale());
setPupilDilation(_owningHead->getPupilDilation());
setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients());
// FIXME - this is very expensive, we shouldn't do it if we don't have to
//invalidCalculatedMeshBoxes();
if (isActive()) {
setOffset(-_geometry->getFBXGeometry().neckPivot);
for (int i = 0; i < _rig->getJointStateCount(); i++) {
maybeUpdateNeckAndEyeRotation(i);
}
Model::simulateInternal(deltaTime);
}
}
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index) {
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() *
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation)));
glm::vec3 pitchYawRoll = safeEulerAngles(_owningHead->getFinalOrientationInLocalFrame());
glm::vec3 lean = glm::radians(glm::vec3(_owningHead->getFinalLeanForward(),
_owningHead->getTorsoTwist(),
_owningHead->getFinalLeanSideways()));
pitchYawRoll -= lean;
state.setRotationInConstrainedFrame(glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2]))
* glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1]))
* glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0]))
* joint.rotation, DEFAULT_PRIORITY);
_rig->setJointRotationInConstrainedFrame(index,
glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2]))
* glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1]))
* glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0]))
* joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state) {
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index) {
// likewise with the eye joints
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
glm::translate(_rig->getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAtDelta = _owningHead->getCorrectedLookAtPosition() - model->getTranslation();
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * _owningHead->getSaccade(), 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation, DEFAULT_PRIORITY);
_rig->setJointRotationInConstrainedFrame(index, glm::angleAxis(glm::clamp(glm::angle(between),
-MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
joint.rotation, DEFAULT_PRIORITY);
}
void FaceModel::updateJointState(int index) {
JointState& state = _jointStates[index];
void FaceModel::maybeUpdateNeckAndEyeRotation(int index) {
const JointState& state = _rig->getJointState(index);
const FBXJoint& joint = state.getFBXJoint();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (joint.parentIndex != -1 && joint.parentIndex >= 0 && joint.parentIndex < _rig->getJointStateCount()) {
const JointState& parentState = _rig->getJointState(joint.parentIndex);
if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, joint, state);
maybeUpdateNeckRotation(parentState, joint, index);
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
maybeUpdateEyeRotation(this, parentState, joint, state);
maybeUpdateEyeRotation(this, parentState, joint, index);
}
}
Model::updateJointState(index);
}
bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {

View file

@ -19,23 +19,23 @@ class Head;
/// A face formed from a linear mix of blendshapes according to a set of coefficients.
class FaceModel : public Model {
Q_OBJECT
public:
FaceModel(Head* owningHead);
FaceModel(Head* owningHead, RigPointer rig);
virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state);
virtual void updateJointState(int index);
void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, int index);
void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, int index);
void maybeUpdateNeckAndEyeRotation(int index);
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
private:
Head* _owningHead;
};

View file

@ -40,65 +40,6 @@ void Hand::simulate(float deltaTime, bool isMine) {
}
}
// We create a static CollisionList that is recycled for each collision test.
const float MAX_COLLISIONS_PER_AVATAR = 32;
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
if (!avatar || avatar == _owningAvatar) {
// don't collide hands against ourself (that is done elsewhere)
return;
}
const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
int jointIndices[2];
jointIndices[0] = skeletonModel.getLeftHandJointIndex();
jointIndices[1] = skeletonModel.getRightHandJointIndex();
for (size_t i = 0; i < 2; i++) {
int jointIndex = jointIndices[i];
if (jointIndex < 0) {
continue;
}
handCollisions.clear();
QVector<const Shape*> shapes;
skeletonModel.getHandShapes(jointIndex, shapes);
if (avatar->findCollisions(shapes, handCollisions)) {
glm::vec3 totalPenetration(0.0f);
glm::vec3 averageContactPoint;
for (int j = 0; j < handCollisions.size(); ++j) {
CollisionInfo* collision = handCollisions.getCollision(j);
totalPenetration += collision->_penetration;
averageContactPoint += collision->_contactPoint;
}
if (isMyHand) {
// our hand against other avatar
// TODO: resolve this penetration when we don't think the other avatar will yield
//palm.addToPenetration(averagePenetration);
} else {
// someone else's hand against MyAvatar
// TODO: submit collision info to MyAvatar which should lean accordingly
averageContactPoint /= (float)handCollisions.size();
avatar->applyCollision(averageContactPoint, totalPenetration);
CollisionInfo collision;
collision._penetration = totalPenetration;
collision._contactPoint = averageContactPoint;
emit avatar->collisionWithAvatar(avatar->getSessionUUID(), _owningAvatar->getSessionUUID(), collision);
}
}
}
}
void Hand::resolvePenetrations() {
for (size_t i = 0; i < getNumPalms(); ++i) {
PalmData& palm = getPalms()[i];
palm.resolvePenetrations();
}
}
void Hand::render(RenderArgs* renderArgs, bool isMine) {
gpu::Batch& batch = *renderArgs->_batch;
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&

View file

@ -29,10 +29,6 @@
class Avatar;
const float HAND_PADDLE_OFFSET = 0.1f;
const float HAND_PADDLE_THICKNESS = 0.01f;
const float HAND_PADDLE_RADIUS = 0.15f;
class Hand : public HandData {
public:
Hand(Avatar* owningAvatar);
@ -40,10 +36,6 @@ public:
void simulate(float deltaTime, bool isMine);
void render(RenderArgs* renderArgs, bool isMine);
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
void resolvePenetrations();
private:
// disallow copies of the Hand, copy of owning Avatar is disallowed too
Hand(const Hand&);

View file

@ -23,6 +23,7 @@
#include "Util.h"
#include "devices/DdeFaceTracker.h"
#include "devices/Faceshift.h"
#include "AvatarRig.h"
using namespace std;
@ -57,11 +58,10 @@ Head::Head(Avatar* owningAvatar) :
_isLookingAtMe(false),
_lookingAtMeStarted(0),
_wasLastLookingAtMe(0),
_faceModel(this),
_faceModel(this, std::make_shared<AvatarRig>()),
_leftEyeLookAtID(DependencyManager::get<GeometryCache>()->allocateID()),
_rightEyeLookAtID(DependencyManager::get<GeometryCache>()->allocateID())
{
}
void Head::init() {

View file

@ -78,8 +78,8 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
MyAvatar::MyAvatar() :
Avatar(),
MyAvatar::MyAvatar(RigPointer rig) :
Avatar(rig),
_gravity(0.0f, 0.0f, 0.0f),
_wasPushing(false),
_isPushing(false),
@ -101,18 +101,14 @@ MyAvatar::MyAvatar() :
_eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
_firstPersonSkeletonModel(this),
_rig(rig),
_prevShouldDrawHead(true)
{
_firstPersonSkeletonModel.setIsFirstPerson(true);
ShapeCollider::initDispatchTable();
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
_skeletonModel.setEnableShapes(true);
// connect to AddressManager signal for location jumps
connect(DependencyManager::get<AddressManager>().data(), &AddressManager::locationChangeRequired,
this, &MyAvatar::goToLocation);
@ -139,7 +135,6 @@ QByteArray MyAvatar::toByteArray() {
void MyAvatar::reset() {
_skeletonModel.reset();
_firstPersonSkeletonModel.reset();
getHead()->reset();
_targetVelocity = glm::vec3(0.0f);
@ -195,7 +190,6 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("skeleton");
_skeletonModel.simulate(deltaTime);
_firstPersonSkeletonModel.simulate(deltaTime);
}
if (!_skeletonModel.hasSkeleton()) {
@ -211,10 +205,10 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("joints");
// copy out the skeleton joints from the model
_jointData.resize(_skeletonModel.getJointStateCount());
_jointData.resize(_rig->getJointStateCount());
for (int i = 0; i < _jointData.size(); i++) {
JointData& data = _jointData[i];
data.valid = _skeletonModel.getJointState(i, data.rotation);
data.valid = _rig->getJointStateRotation(i, data.rotation);
}
}
@ -491,17 +485,6 @@ void MyAvatar::loadLastRecording() {
_player->loadRecording(_recorder->getRecording());
}
AnimationHandlePointer MyAvatar::addAnimationHandle() {
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
_animationHandles.append(handle);
return handle;
}
void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) {
handle->stop();
_animationHandles.removeOne(handle);
}
void MyAvatar::startAnimation(const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
if (QThread::currentThread() != thread()) {
@ -510,16 +493,7 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority,
Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints));
return;
}
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
_rig->startAnimation(url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints);
}
void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
@ -530,25 +504,7 @@ void MyAvatar::startAnimationByRole(const QString& role, const QString& url, flo
Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints));
return;
}
// check for a configured animation for the role
foreach (const AnimationHandlePointer& handle, _animationHandles) {
if (handle->getRole() == role) {
handle->start();
return;
}
}
// no joy; use the parameters provided
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
handle->setRole(role);
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
_rig->startAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints);
}
void MyAvatar::stopAnimationByRole(const QString& role) {
@ -556,11 +512,7 @@ void MyAvatar::stopAnimationByRole(const QString& role) {
QMetaObject::invokeMethod(this, "stopAnimationByRole", Q_ARG(const QString&, role));
return;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getRole() == role) {
handle->stop();
}
}
_rig->stopAnimationByRole(role);
}
void MyAvatar::stopAnimation(const QString& url) {
@ -568,11 +520,7 @@ void MyAvatar::stopAnimation(const QString& url) {
QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url));
return;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getURL() == url) {
handle->stop();
}
}
_rig->stopAnimation(url);
}
AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) {
@ -583,7 +531,7 @@ AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) {
Q_ARG(const QString&, role));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) {
if (handle->getRole() == role) {
result = handle->getAnimationDetails();
break;
@ -600,7 +548,7 @@ AnimationDetails MyAvatar::getAnimationDetails(const QString& url) {
Q_ARG(const QString&, url));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
foreach (const AnimationHandlePointer& handle, _rig->getRunningAnimations()) {
if (handle->getURL() == url) {
result = handle->getAnimationDetails();
break;
@ -646,9 +594,10 @@ void MyAvatar::saveData() {
settings.endArray();
settings.beginWriteArray("animationHandles");
for (int i = 0; i < _animationHandles.size(); i++) {
auto animationHandles = _rig->getAnimationHandles();
for (int i = 0; i < animationHandles.size(); i++) {
settings.setArrayIndex(i);
const AnimationHandlePointer& pointer = _animationHandles.at(i);
const AnimationHandlePointer& pointer = animationHandles.at(i);
settings.setValue("role", pointer->getRole());
settings.setValue("url", pointer->getURL());
settings.setValue("fps", pointer->getFPS());
@ -766,25 +715,19 @@ void MyAvatar::loadData() {
setAttachmentData(attachmentData);
int animationCount = settings.beginReadArray("animationHandles");
while (_animationHandles.size() > animationCount) {
_animationHandles.takeLast()->stop();
}
while (_animationHandles.size() < animationCount) {
addAnimationHandle();
}
_rig->deleteAnimations();
for (int i = 0; i < animationCount; i++) {
settings.setArrayIndex(i);
const AnimationHandlePointer& handle = _animationHandles.at(i);
handle->setRole(settings.value("role", "idle").toString());
handle->setURL(settings.value("url").toUrl());
handle->setFPS(loadSetting(settings, "fps", 30.0f));
handle->setPriority(loadSetting(settings, "priority", 1.0f));
handle->setLoop(settings.value("loop", true).toBool());
handle->setHold(settings.value("hold", false).toBool());
handle->setFirstFrame(settings.value("firstFrame", 0.0f).toFloat());
handle->setLastFrame(settings.value("lastFrame", INT_MAX).toFloat());
handle->setMaskedJoints(settings.value("maskedJoints").toStringList());
handle->setStartAutomatically(settings.value("startAutomatically", true).toBool());
_rig->addAnimationByRole(settings.value("role", "idle").toString(),
settings.value("url").toString(),
loadSetting(settings, "fps", 30.0f),
loadSetting(settings, "priority", 1.0f),
settings.value("loop", true).toBool(),
settings.value("hold", false).toBool(),
settings.value("firstFrame", 0.0f).toFloat(),
settings.value("lastFrame", INT_MAX).toFloat(),
settings.value("maskedJoints").toStringList(),
settings.value("startAutomatically", true).toBool());
}
settings.endArray();
@ -792,6 +735,7 @@ void MyAvatar::loadData() {
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
settings.endGroup();
_rig->setEnableRig(settings.value("enableRig").toBool());
}
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
@ -961,15 +905,15 @@ void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, true, rotation, SCRIPT_PRIORITY);
_rig->setJointState(index, true, rotation, SCRIPT_PRIORITY);
}
}
void MyAvatar::clearJointData(int index) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
_skeletonModel.setJointState(index, false, glm::quat(), 0.0f);
_skeletonModel.clearJointAnimationPriority(index);
_rig->setJointState(index, false, glm::quat(), 0.0f);
_rig->clearJointAnimationPriority(index);
}
}
@ -980,7 +924,7 @@ void MyAvatar::clearJointsData() {
void MyAvatar::clearJointAnimationPriorities() {
int numStates = _skeletonModel.getJointStateCount();
for (int i = 0; i < numStates; ++i) {
_skeletonModel.clearJointAnimationPriority(i);
_rig->clearJointAnimationPriority(i);
}
}
@ -1020,19 +964,8 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
Avatar::setSkeletonModelURL(skeletonModelURL);
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
_billboardValid = false;
if (_useFullAvatar) {
_skeletonModel.setVisibleInScene(_prevShouldDrawHead, scene);
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_body.fst");
_firstPersonSkeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL, true, !isMyAvatar());
_firstPersonSkeletonModel.setVisibleInScene(!_prevShouldDrawHead, scene);
} else {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.reset();
}
_skeletonModel.setVisibleInScene(true, scene);
_headBoneSet.clear();
}
void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName) {
@ -1143,11 +1076,10 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
void MyAvatar::rebuildSkeletonBody() {
// compute localAABox
const CapsuleShape& capsule = _skeletonModel.getBoundingShape();
float radius = capsule.getRadius();
float height = 2.0f * (capsule.getHalfHeight() + radius);
float radius = _skeletonModel.getBoundingCapsuleRadius();
float height = _skeletonModel.getBoundingCapsuleHeight() + 2.0f * radius;
glm::vec3 corner(-radius, -0.5f * height, -radius);
corner += _skeletonModel.getBoundingShapeOffset();
corner += _skeletonModel.getBoundingCapsuleOffset();
glm::vec3 scale(2.0f * radius, height, 2.0f * radius);
_characterController.setLocalBoundingBox(corner, scale);
}
@ -1239,29 +1171,48 @@ void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene
}
}
void MyAvatar::initHeadBones() {
int neckJointIndex = -1;
if (_skeletonModel.getGeometry()) {
neckJointIndex = _skeletonModel.getGeometry()->getFBXGeometry().neckJointIndex;
}
if (neckJointIndex == -1) {
return;
}
_headBoneSet.clear();
std::queue<int> q;
q.push(neckJointIndex);
_headBoneSet.insert(neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < _skeletonModel.getJointStateCount(); i++) {
if (jointIndex == _skeletonModel.getParentJointIndex(i)) {
_headBoneSet.insert(i);
q.push(i);
}
}
q.pop();
}
}
void MyAvatar::preRender(RenderArgs* renderArgs) {
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
const bool shouldDrawHead = shouldRenderHead(renderArgs);
_skeletonModel.initWhenReady(scene);
if (_useFullAvatar) {
_firstPersonSkeletonModel.initWhenReady(scene);
if (_skeletonModel.initWhenReady(scene)) {
initHeadBones();
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
}
if (shouldDrawHead != _prevShouldDrawHead) {
if (_useFullAvatar) {
if (shouldDrawHead) {
_skeletonModel.setVisibleInScene(true, scene);
_firstPersonSkeletonModel.setVisibleInScene(false, scene);
} else {
_skeletonModel.setVisibleInScene(false, scene);
_firstPersonSkeletonModel.setVisibleInScene(true, scene);
}
_skeletonModel.setCauterizeBones(!shouldDrawHead);
} else {
getHead()->getFaceModel().setVisibleInScene(shouldDrawHead, scene);
}
}
_prevShouldDrawHead = shouldDrawHead;
}

View file

@ -14,6 +14,7 @@
#include <SettingHandle.h>
#include <DynamicCharacterController.h>
#include <Rig.h>
#include "Avatar.h"
@ -35,54 +36,43 @@ class MyAvatar : public Avatar {
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
public:
MyAvatar();
MyAvatar(RigPointer rig);
~MyAvatar();
QByteArray toByteArray();
void reset();
void update(float deltaTime);
void simulate(float deltaTime);
void preRender(RenderArgs* renderArgs);
void updateFromTrackers(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
// setters
void setLeanScale(float scale) { _leanScale = scale; }
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
// getters
float getLeanScale() const { return _leanScale; }
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
bool getShouldRenderLocally() const { return _shouldRender; }
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
AnimationHandlePointer addAnimationHandle();
void removeAnimationHandle(const AnimationHandlePointer& handle);
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _rig->getAnimationHandles(); }
AnimationHandlePointer addAnimationHandle() { return _rig->createAnimationHandle(); }
void removeAnimationHandle(const AnimationHandlePointer& handle) { _rig->removeAnimationHandle(handle); }
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
/// Stops an animation as identified by a URL.
Q_INVOKABLE void stopAnimation(const QString& url);
/// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom
/// animation for the role.
Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
/// Stops an animation identified by its role.
Q_INVOKABLE void stopAnimationByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url);
void clearJointAnimationPriorities();
// get/set avatar data
void saveData();
void loadData();
@ -93,32 +83,27 @@ public:
// Set what driving keys are being pressed to control thrust levels
void clearDriveKeys();
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
void relayDriveKeysToCharacterController();
bool isMyAvatar() const { return true; }
eyeContactTarget getEyeContactTarget();
virtual int parseDataFromBuffer(const QByteArray& buffer);
static void sendKillAvatar();
Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; }
Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); }
Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); }
Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); }
Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); }
Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); }
Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setJointRotations(QVector<glm::quat> jointRotations);
virtual void setJointData(int index, const glm::quat& rotation);
virtual void clearJointData(int index);
@ -127,7 +112,8 @@ public:
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadURL(const QUrl& headURL, const QString& modelName = QString());
Q_INVOKABLE void useBodyURL(const QUrl& bodyURL, const QString& modelName = QString());
Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL, const QString& headName = QString(), const QString& bodyName = QString());
Q_INVOKABLE void useHeadAndBodyURLs(const QUrl& headURL, const QUrl& bodyURL,
const QString& headName = QString(), const QString& bodyName = QString());
Q_INVOKABLE bool getUseFullAvatar() const { return _useFullAvatar; }
Q_INVOKABLE const QUrl& getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
@ -142,48 +128,30 @@ public:
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
virtual glm::vec3 getSkeletonPosition() const;
void updateLocalAABox();
DynamicCharacterController* getCharacterController() { return &_characterController; }
void clearJointAnimationPriorities();
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
QString getScriptedMotorFrame() const;
void setScriptedMotorVelocity(const glm::vec3& velocity);
void setScriptedMotorTimescale(float timescale);
void setScriptedMotorFrame(QString frame);
const QString& getCollisionSoundURL() {return _collisionSoundURL; }
void setCollisionSoundURL(const QString& url);
void clearScriptableSettings();
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
/// Renders a laser pointer for UI picking
void renderLaserPointers(gpu::Batch& batch);
glm::vec3 getLaserPointerTipPosition(const PalmData* palm);
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
float getBoomLength() const { return _boomLength; }
void setBoomLength(float boomLength) { _boomLength = boomLength; }
static const float ZOOM_MIN;
static const float ZOOM_MAX;
static const float ZOOM_DEFAULT;
public slots:
void increaseSize();
void decreaseSize();
void resetSize();
void goToLocation(const glm::vec3& newPosition,
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
bool shouldFaceLocation = false);
@ -203,7 +171,7 @@ public slots:
void clearReferential();
bool setModelReferential(const QUuid& id);
bool setJointReferential(const QUuid& id, int jointIndex);
bool isRecording();
qint64 recorderElapsed();
void startRecording();
@ -212,7 +180,7 @@ public slots:
void loadLastRecording();
virtual void rebuildSkeletonBody();
signals:
void transformChanged();
void newCollisionSoundURL(const QUrl& url);
@ -220,6 +188,33 @@ signals:
private:
QByteArray toByteArray();
void simulate(float deltaTime);
void updateFromTrackers(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override;
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
bool getShouldRenderLocally() const { return _shouldRender; }
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
bool isMyAvatar() const { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer);
virtual glm::vec3 getSkeletonPosition() const;
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
QString getScriptedMotorFrame() const;
void setScriptedMotorVelocity(const glm::vec3& velocity);
void setScriptedMotorTimescale(float timescale);
void setScriptedMotorFrame(QString frame);
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
bool allowDuplicates = false, bool useSaved = true);
void renderLaserPointers(gpu::Batch& batch);
const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; }
bool cameraInsideHead() const;
// These are made private for MyAvatar so that you will use the "use" methods instead
@ -234,7 +229,7 @@ private:
bool _wasPushing;
bool _isPushing;
bool _isBraking;
float _boomLength;
float _trapDuration; // seconds that avatar has been trapped by collisions
@ -256,37 +251,36 @@ private:
bool _billboardValid;
float _oculusYawOffset;
QList<AnimationHandlePointer> _animationHandles;
eyeContactTarget _eyeContactTarget;
RecorderPointer _recorder;
glm::vec3 _trackedHeadPosition;
Setting::Handle<float> _realWorldFieldOfView;
// private methods
// private methods
void updateOrientation(float deltaTime);
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool isHovering);
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
void updatePosition(float deltaTime);
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void maybeUpdateBillboard();
void initHeadBones();
// Avatar Preferences
bool _useFullAvatar = false;
QUrl _fullAvatarURLFromPreferences;
QUrl _headURLFromPreferences;
QUrl _skeletonURLFromPreferences;
QString _headModelName;
QString _bodyModelName;
QString _fullAvatarModelName;
// used for rendering when in first person view or when in an HMD.
SkeletonModel _firstPersonSkeletonModel;
RigPointer _rig;
bool _prevShouldDrawHead;
std::unordered_set<int> _headBoneSet;
};
#endif // hifi_MyAvatar_h

View file

@ -12,9 +12,7 @@
#include <glm/gtx/transform.hpp>
#include <QMultiMap>
#include <CapsuleShape.h>
#include <DeferredLightingEffect.h>
#include <SphereShape.h>
#include "Application.h"
#include "Avatar.h"
@ -24,29 +22,31 @@
#include "Util.h"
#include "InterfaceLogging.h"
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
Model(parent),
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
Model(rig, parent),
_triangleFanID(DependencyManager::get<GeometryCache>()->allocateID()),
_owningAvatar(owningAvatar),
_boundingShape(),
_boundingShapeLocalOffset(0.0f),
_boundingCapsuleLocalOffset(0.0f),
_boundingCapsuleRadius(0.0f),
_boundingCapsuleHeight(0.0f),
_defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
_headClipDistance(DEFAULT_NEAR_CLIP),
_isFirstPerson(false)
_headClipDistance(DEFAULT_NEAR_CLIP)
{
assert(_rig);
assert(_owningAvatar);
_enableShapes = true;
}
SkeletonModel::~SkeletonModel() {
}
void SkeletonModel::initJointStates(QVector<JointState> states) {
Model::initJointStates(states);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_boundingRadius = _rig->initJointStates(states, parentTransform);
// Determine the default eye position for avatar scale = 1.0
int headJointIndex = _geometry->getFBXGeometry().headJointIndex;
if (0 <= headJointIndex && headJointIndex < _jointStates.size()) {
if (0 <= headJointIndex && headJointIndex < _rig->getJointStateCount()) {
glm::vec3 leftEyePosition, rightEyePosition;
getEyeModelPositions(leftEyePosition, rightEyePosition);
@ -66,14 +66,11 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
// the SkeletonModel override of updateJointState() will clear the translation part
// of its root joint and we need that done before we try to build shapes hence we
// recompute all joint transforms at this time.
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
for (int i = 0; i < _rig->getJointStateCount(); i++) {
_rig->updateJointState(i, parentTransform);
}
clearShapes();
if (_enableShapes) {
buildShapes();
}
buildShapes();
Extents meshExtents = getMeshExtents();
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
@ -86,6 +83,29 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
const float PALM_PRIORITY = DEFAULT_PRIORITY;
const float LEAN_PRIORITY = DEFAULT_PRIORITY;
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation());
Model::updateRig(deltaTime, parentTransform);
if (_owningAvatar->isMyAvatar()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
Rig::HeadParameters params;
params.leanSideways = _owningAvatar->getHead()->getFinalLeanSideways();
params.leanForward = _owningAvatar->getHead()->getFinalLeanSideways();
params.torsoTwist = _owningAvatar->getHead()->getTorsoTwist();
params.localHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInLocalFrame();
params.worldHeadOrientation = _owningAvatar->getHead()->getFinalOrientationInWorldFrame();
params.eyeLookAt = _owningAvatar->getHead()->getCorrectedLookAtPosition();
params.eyeSaccade = _owningAvatar->getHead()->getSaccade();
params.leanJointIndex = geometry.leanJointIndex;
params.neckJointIndex = geometry.neckJointIndex;
params.leftEyeJointIndex = geometry.leftEyeJointIndex;
params.rightEyeJointIndex = geometry.rightEyeJointIndex;
_rig->updateFromHeadParameters(params);
}
}
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setTranslation(_owningAvatar->getSkeletonPosition());
static const glm::quat refOrientation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
@ -141,49 +161,6 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY);
}
}
if (_isFirstPerson) {
cauterizeHead();
updateClusterMatrices();
}
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const {
if (jointIndex < 0 || jointIndex >= int(_shapes.size())) {
return;
}
if (jointIndex == getLeftHandJointIndex()
|| jointIndex == getRightHandJointIndex()) {
// get all shapes that have this hand as an ancestor in the skeleton heirarchy
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointStates.size(); i++) {
const FBXJoint& joint = geometry.joints[i];
int parentIndex = joint.parentIndex;
Shape* shape = _shapes[i];
if (i == jointIndex) {
// this shape is the hand
if (shape) {
shapes.push_back(shape);
}
if (parentIndex != -1 && _shapes[parentIndex]) {
// also add the forearm
shapes.push_back(_shapes[parentIndex]);
}
} else if (shape) {
while (parentIndex != -1) {
if (parentIndex == jointIndex) {
// this shape is a child of the hand
shapes.push_back(shape);
break;
}
parentIndex = geometry.joints[parentIndex].parentIndex;
}
}
}
}
}
void SkeletonModel::renderIKConstraints(gpu::Batch& batch) {
@ -202,7 +179,7 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) {
}
void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
// NOTE: 'position' is in model-frame
@ -217,16 +194,20 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
if (forearmLength < EPSILON) {
return;
}
JointState& state = _jointStates[jointIndex];
glm::quat handRotation = state.getRotation();
glm::quat handRotation;
if (!_rig->getJointStateRotation(jointIndex, handRotation)) {
return;
}
// align hand with forearm
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
state.applyRotationDelta(rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), true, PALM_PRIORITY);
_rig->applyJointRotationDelta(jointIndex,
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
true, PALM_PRIORITY);
}
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -252,68 +233,16 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
glm::vec3 forearm = palmRotation * glm::vec3(sign * forearmLength, 0.0f, 0.0f);
setJointPosition(parentJointIndex, palmPosition + forearm,
glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY);
JointState& parentState = _jointStates[parentJointIndex];
parentState.setRotationInBindFrame(palmRotation, PALM_PRIORITY);
_rig->setJointRotationInBindFrame(parentJointIndex, palmRotation, PALM_PRIORITY);
// lock hand to forearm by slamming its rotation (in parent-frame) to identity
_jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat(), PALM_PRIORITY);
_rig->setJointRotationInConstrainedFrame(jointIndex, glm::quat(), PALM_PRIORITY);
} else {
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
}
}
void SkeletonModel::updateJointState(int index) {
if (index < 0 && index >= _jointStates.size()) {
return; // bail
}
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(joint.parentIndex);
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (index == geometry.leanJointIndex) {
maybeUpdateLeanRotation(parentState, state);
} else if (index == geometry.neckJointIndex) {
maybeUpdateNeckRotation(parentState, joint, state);
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
maybeUpdateEyeRotation(parentState, joint, state);
}
}
Model::updateJointState(index);
if (index == _geometry->getFBXGeometry().rootJointIndex) {
state.clearTransformTranslation();
}
}
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, JointState& state) {
if (!_owningAvatar->isMyAvatar()) {
return;
}
// get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * state.getDefaultRotationInParentFrame());
state.setRotationInConstrainedFrame(
glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis)
* glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis)
* glm::angleAxis(RADIANS_PER_DEGREE * _owningAvatar->getHead()->getTorsoTwist(), inverse * yAxis)
* state.getFBXJoint().rotation, LEAN_PRIORITY);
}
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, state);
}
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state);
}
void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -322,9 +251,11 @@ void SkeletonModel::renderJointConstraints(gpu::Batch& batch, int jointIndex) {
batch._glLineWidth(3.0f);
do {
const FBXJoint& joint = geometry.joints.at(jointIndex);
const JointState& jointState = _jointStates.at(jointIndex);
const JointState& jointState = _rig->getJointState(jointIndex);
glm::vec3 position = _rotation * jointState.getPosition() + _translation;
glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _rotation * _jointStates.at(joint.parentIndex).getRotation();
glm::quat parentRotation = (joint.parentIndex == -1) ?
_rotation :
_rotation * _rig->getJointState(joint.parentIndex).getRotation();
float fanScale = directionSize * 0.75f;
Transform transform = Transform();
@ -457,14 +388,11 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c
glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f);
glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition);
JointState& shoulderState = _jointStates[shoulderJointIndex];
shoulderState.setRotationInBindFrame(shoulderRotation, PALM_PRIORITY);
JointState& elbowState = _jointStates[elbowJointIndex];
elbowState.setRotationInBindFrame(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY);
JointState& handState = _jointStates[jointIndex];
handState.setRotationInBindFrame(rotation, PALM_PRIORITY);
_rig->setJointRotationInBindFrame(shoulderJointIndex, shoulderRotation, PALM_PRIORITY);
_rig->setJointRotationInBindFrame(elbowJointIndex,
rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) *
shoulderRotation, PALM_PRIORITY);
_rig->setJointRotationInBindFrame(jointIndex, rotation, PALM_PRIORITY);
}
bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const {
@ -519,7 +447,7 @@ bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckP
glm::quat worldFrameRotation;
bool success = getJointRotationInWorldFrame(parentIndex, worldFrameRotation);
if (success) {
neckParentRotation = worldFrameRotation * _jointStates[parentIndex].getFBXJoint().inverseDefaultRotation;
neckParentRotation = worldFrameRotation * _rig->getJointState(parentIndex).getFBXJoint().inverseDefaultRotation;
}
return success;
}
@ -570,7 +498,7 @@ float VERY_BIG_MASS = 1.0e6f;
// virtual
void SkeletonModel::buildShapes() {
if (_geometry == NULL || _jointStates.isEmpty()) {
if (_geometry == NULL || _rig->jointStatesEmpty()) {
return;
}
@ -579,46 +507,12 @@ void SkeletonModel::buildShapes() {
// rootJointIndex == -1 if the avatar model has no skeleton
return;
}
float uniformScale = extractUniformScale(_scale);
const int numStates = _jointStates.size();
for (int i = 0; i < numStates; i++) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
float radius = uniformScale * joint.boneRadius;
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
Shape::Type type = joint.shapeType;
int parentIndex = joint.parentIndex;
if (parentIndex == -1 || radius < EPSILON) {
type = INVALID_SHAPE;
} else if (type == CAPSULE_SHAPE && halfHeight < EPSILON) {
// this shape is forced to be a sphere
type = SPHERE_SHAPE;
}
Shape* shape = NULL;
if (type == SPHERE_SHAPE) {
shape = new SphereShape(radius);
shape->setEntity(this);
} else if (type == CAPSULE_SHAPE) {
assert(parentIndex != -1);
shape = new CapsuleShape(radius, halfHeight);
shape->setEntity(this);
}
if (shape && parentIndex != -1) {
// always disable collisions between joint and its parent
disableCollisions(i, parentIndex);
}
_shapes.push_back(shape);
}
// This method moves the shapes to their default positions in Model frame.
computeBoundingShape(geometry);
}
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// compute default joint transforms
int numStates = _jointStates.size();
assert(numStates == _shapes.size());
int numStates = _rig->getJointStateCount();
QVector<glm::mat4> transforms;
transforms.fill(glm::mat4(), numStates);
@ -628,131 +522,63 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
totalExtents.addPoint(glm::vec3(0.0f));
for (int i = 0; i < numStates; i++) {
// compute the default transform of this joint
JointState& state = _jointStates[i];
const JointState& state = _rig->getJointState(i);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
transforms[i] = _jointStates[i].getTransform();
transforms[i] = _rig->getJointTransform(i);
} else {
glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation;
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
}
// Each joint contributes its point to the bounding box
// Each joint contributes a sphere at its position
glm::vec3 axis(joint.boneRadius);
glm::vec3 jointPosition = extractTranslation(transforms[i]);
totalExtents.addPoint(jointPosition);
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
// Each joint with a shape contributes to the totalExtents: a box
// that contains the sphere centered at the end of the joint with radius of the bone.
// TODO: skip hand and arm shapes for bounding box calculation
int type = shape->getType();
if (type == CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
float radius = capsule->getRadius();
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
} else if (type == SPHERE_SHAPE) {
float radius = shape->getBoundingRadius();
glm::vec3 axis(radius);
Extents shapeExtents;
shapeExtents.reset();
shapeExtents.addPoint(jointPosition + axis);
shapeExtents.addPoint(jointPosition - axis);
totalExtents.addExtents(shapeExtents);
}
totalExtents.addPoint(jointPosition + axis);
totalExtents.addPoint(jointPosition - axis);
}
// compute bounding shape parameters
// NOTE: we assume that the longest side of totalExtents is the yAxis...
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
// ... and assume the radius is half the RMS of the X and Z sides:
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingShape.setRadius(capsuleRadius);
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
_boundingCapsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
_boundingCapsuleHeight = diagonal.y - 2.0f * _boundingCapsuleRadius;
glm::vec3 rootPosition = _jointStates[geometry.rootJointIndex].getPosition();
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
_boundingRadius = 0.5f * glm::length(diagonal);
}
void SkeletonModel::resetShapePositionsToDefaultPose() {
// DEBUG method.
// Moves shapes to the joint default locations for debug visibility into
// how the bounding shape is computed.
if (!_geometry || _shapes.isEmpty()) {
// geometry or joints have not yet been created
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
if (geometry.joints.isEmpty()) {
return;
}
// The shapes are moved to their default positions in computeBoundingShape().
computeBoundingShape(geometry);
// Then we move them into world frame for rendering at the Model's location.
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (shape) {
shape->setTranslation(_translation + _rotation * shape->getTranslation());
shape->setRotation(_rotation * shape->getRotation());
}
}
_boundingShape.setTranslation(_translation + _rotation * _boundingShapeLocalOffset);
_boundingShape.setRotation(_rotation);
}
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
const int BALL_SUBDIVISIONS = 10;
if (_shapes.isEmpty()) {
// the bounding shape has not been properly computed
// so no need to render it
return;
}
auto geometryCache = DependencyManager::get<GeometryCache>();
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
Transform transform; // = Transform();
// draw a blue sphere at the capsule end point
glm::vec3 endPoint;
_boundingShape.getEndPoint(endPoint);
endPoint = endPoint + _translation;
transform.setTranslation(endPoint);
// draw a blue sphere at the capsule top point
glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f);
transform.setTranslation(topPoint);
batch.setModelTransform(transform);
deferredLighting->bindSimpleProgram(batch);
geometryCache->renderSphere(batch, _boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
// draw a yellow sphere at the capsule start point
glm::vec3 startPoint;
_boundingShape.getStartPoint(startPoint);
startPoint = startPoint + _translation;
glm::vec3 axis = endPoint - startPoint;
transform.setTranslation(startPoint);
// draw a yellow sphere at the capsule bottom point
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
glm::vec3 axis = topPoint - bottomPoint;
transform.setTranslation(bottomPoint);
batch.setModelTransform(transform);
deferredLighting->bindSimpleProgram(batch);
geometryCache->renderSphere(batch, _boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
// draw a green cylinder between the two points
glm::vec3 origin(0.0f);
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius(),
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingCapsuleRadius, _boundingCapsuleRadius,
glm::vec4(0.6f, 0.8f, 0.6f, alpha));
}
@ -760,56 +586,5 @@ bool SkeletonModel::hasSkeleton() {
return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false;
}
void SkeletonModel::initHeadBones() {
_headBones.clear();
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
const int neckJointIndex = fbxGeometry.neckJointIndex;
std::queue<int> q;
q.push(neckJointIndex);
_headBones.push_back(neckJointIndex);
// fbxJoints only hold links to parents not children, so we have to do a bit of extra work here.
while (q.size() > 0) {
int jointIndex = q.front();
for (int i = 0; i < fbxGeometry.joints.size(); i++) {
const FBXJoint& fbxJoint = fbxGeometry.joints[i];
if (jointIndex == fbxJoint.parentIndex) {
_headBones.push_back(i);
q.push(i);
}
}
q.pop();
}
}
void SkeletonModel::invalidateHeadBones() {
_headBones.clear();
}
void SkeletonModel::cauterizeHead() {
if (isActive()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const int neckJointIndex = geometry.neckJointIndex;
if (neckJointIndex > 0 && neckJointIndex < _jointStates.size()) {
// lazy init of headBones
if (_headBones.size() == 0) {
initHeadBones();
}
// preserve the translation for the neck
glm::vec4 trans = _jointStates[neckJointIndex].getTransform()[3];
glm::vec4 zero(0, 0, 0, 0);
for (const int &i : _headBones) {
JointState& joint = _jointStates[i];
glm::mat4 newXform(zero, zero, zero, trans);
joint.setTransform(newXform);
joint.setVisibleTransform(newXform);
}
}
}
}
void SkeletonModel::onInvalidate() {
invalidateHeadBones();
}

View file

@ -13,7 +13,6 @@
#define hifi_SkeletonModel_h
#include <CapsuleShape.h>
#include <Model.h>
class Avatar;
@ -22,72 +21,69 @@ class MuscleConstraint;
/// A skeleton loaded from a model.
class SkeletonModel : public Model {
Q_OBJECT
public:
SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
~SkeletonModel();
virtual void initJointStates(QVector<JointState> states);
void simulate(float deltaTime, bool fullUpdate = true);
/// \param jointIndex index of hand joint
/// \param shapes[out] list in which is stored pointers to hand shapes
void getHandShapes(int jointIndex, QVector<const Shape*>& shapes) const;
virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
void renderIKConstraints(gpu::Batch& batch);
/// Returns the index of the left hand joint, or -1 if not found.
int getLeftHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().leftHandJointIndex : -1; }
/// Returns the index of the right hand joint, or -1 if not found.
int getRightHandJointIndex() const { return isActive() ? _geometry->getFBXGeometry().rightHandJointIndex : -1; }
/// Retrieve the position of the left hand
/// \return true whether or not the position was found
bool getLeftHandPosition(glm::vec3& position) const;
/// Retrieve the position of the right hand
/// \return true whether or not the position was found
bool getRightHandPosition(glm::vec3& position) const;
/// Restores some fraction of the default position of the left hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the left hand joint was found
bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the left shoulder.
/// \return whether or not the left shoulder joint was found
bool getLeftShoulderPosition(glm::vec3& position) const;
/// Returns the extended length from the left hand to its last free ancestor.
float getLeftArmLength() const;
/// Restores some fraction of the default position of the right hand.
/// \param fraction the fraction of the default position to restore
/// \return whether or not the right hand joint was found
bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f);
/// Gets the position of the right shoulder.
/// \return whether or not the right shoulder joint was found
bool getRightShoulderPosition(glm::vec3& position) const;
/// Returns the extended length from the right hand to its first free ancestor.
float getRightArmLength() const;
/// Returns the position of the head joint.
/// \return whether or not the head was found
bool getHeadPosition(glm::vec3& headPosition) const;
/// Returns the position of the neck joint.
/// \return whether or not the neck was found
bool getNeckPosition(glm::vec3& neckPosition) const;
/// Returns the rotation of the neck joint's parent from default orientation
/// \return whether or not the neck was found
bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const;
/// Retrieve the positions of up to two eye meshes.
/// \return whether or not both eye meshes were found
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
@ -98,19 +94,14 @@ public:
void computeBoundingShape(const FBXGeometry& geometry);
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; }
void resetShapePositionsToDefaultPose(); // DEBUG method
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
const glm::vec3 getBoundingCapsuleOffset() const { return _boundingCapsuleLocalOffset; }
bool hasSkeleton();
float getHeadClipDistance() const { return _headClipDistance; }
void setIsFirstPerson(bool value) { _isFirstPerson = value; }
bool getIsFirstPerson() const { return _isFirstPerson; }
virtual void onInvalidate() override;
signals:
@ -124,26 +115,14 @@ protected:
/// \param jointIndex index of joint in model
/// \param position position of joint in model-frame
void applyHandPosition(int jointIndex, const glm::vec3& position);
void applyPalmData(int jointIndex, PalmData& palm);
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
void maybeUpdateLeanRotation(const JointState& parentState, JointState& state);
void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
void cauterizeHead();
void initHeadBones();
void invalidateHeadBones();
private:
void renderJointConstraints(gpu::Batch& batch, int jointIndex);
void renderOrientationDirections(gpu::Batch& batch, int jointIndex,
void renderOrientationDirections(gpu::Batch& batch, int jointIndex,
glm::vec3 position, const glm::quat& orientation, float size);
struct OrientationLineIDs {
int _up;
int _front;
@ -158,18 +137,16 @@ private:
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
Avatar* _owningAvatar;
CapsuleShape _boundingShape;
glm::vec3 _boundingShapeLocalOffset;
glm::vec3 _boundingCapsuleLocalOffset;
float _boundingCapsuleRadius;
float _boundingCapsuleHeight;
glm::vec3 _defaultEyeModelPosition;
float _headClipDistance; // Near clip distance to use if no separate head model
bool _isFirstPerson;
std::vector<int> _headBones;
};
#endif // hifi_SkeletonModel_h

View file

@ -47,7 +47,6 @@ public:
bool isMuted() const { return _isMuted; }
void setIsMuted(bool isMuted) { _isMuted = isMuted; }
void toggleMute();
static float getEyeDeflection() { return _eyeDeflection.get(); }
static void setEyeDeflection(float eyeDeflection);
@ -57,6 +56,8 @@ signals:
public slots:
virtual void setEnabled(bool enabled) = 0;
void toggleMute();
bool getMuted() { return _isMuted; }
protected:
virtual ~FaceTracker() {};

View file

@ -0,0 +1,26 @@
//
// DialogsManagerScriptingInterface.cpp
// interface/src/scripting
//
// Created by Zander Otavka on 7/17/15.
// 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
//
#include "DialogsManagerScriptingInterface.h"
#include <DependencyManager.h>
#include "ui/DialogsManager.h"
DialogsManagerScriptingInterface::DialogsManagerScriptingInterface() {
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled,
this, &DialogsManagerScriptingInterface::addressBarToggled);
}
void DialogsManagerScriptingInterface::toggleAddressBar() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"toggleAddressBar", Qt::QueuedConnection);
}

View file

@ -0,0 +1,29 @@
//
// DialogsManagerScriptingInterface.h
// interface/src/scripting
//
// Created by Zander Otavka on 7/17/15.
// 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
//
#ifndef hifi_DialogsManagerScriptInterface_h
#define hifi_DialogsManagerScriptInterface_h
#include <QObject>
class DialogsManagerScriptingInterface : public QObject {
Q_OBJECT
public:
DialogsManagerScriptingInterface();
public slots:
void toggleAddressBar();
signals:
void addressBarToggled();
};
#endif

View file

@ -156,7 +156,7 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo
buttons->addWidget(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle()));
_stop->connect(_handle.data(), SIGNAL(runningChanged(bool)), SLOT(setEnabled(bool)));
_stop->connect(_handle.get(), SIGNAL(runningChanged(bool)), SLOT(setEnabled(bool)));
_stop->setEnabled(_handle->isRunning());
}

View file

@ -36,6 +36,7 @@
void DialogsManager::toggleAddressBar() {
AddressBarDialog::toggle();
emit addressBarToggled();
}
void DialogsManager::toggleDiskCacheEditor() {

View file

@ -72,6 +72,9 @@ public slots:
// Application Update
void showUpdateDialog();
signals:
void addressBarToggled();
private slots:
void toggleToolWindow();
void hmdToolsClosed();

View file

@ -13,19 +13,25 @@
#include <QScriptValue>
#include <DeferredLightingEffect.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <gpu/Batch.h>
#include <GLMHelpers.h>
#include "Application.h"
#include "GeometryUtil.h"
QString const BillboardOverlay::TYPE = "billboard";
BillboardOverlay::BillboardOverlay() {
_isLoaded = false;
}
BillboardOverlay::BillboardOverlay(const BillboardOverlay* billboardOverlay) :
Planar3DOverlay(billboardOverlay),
PanelAttachable(billboardOverlay),
_url(billboardOverlay->_url),
_texture(billboardOverlay->_texture),
_fromImage(billboardOverlay->_fromImage),
@ -33,6 +39,19 @@ BillboardOverlay::BillboardOverlay(const BillboardOverlay* billboardOverlay) :
{
}
void BillboardOverlay::setTransforms(Transform& transform) {
PanelAttachable::setTransforms(transform);
if (_isFacingAvatar) {
glm::quat rotation = Application::getInstance()->getCamera()->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
setRotation(rotation);
}
}
void BillboardOverlay::update(float deltatime) {
setTransforms(_transform);
}
void BillboardOverlay::render(RenderArgs* args) {
if (!_texture) {
_isLoaded = true;
@ -43,15 +62,8 @@ void BillboardOverlay::render(RenderArgs* args) {
return;
}
glm::quat rotation;
if (_isFacingAvatar) {
// rotate about vertical to face the camera
rotation = args->_viewFrustum->getOrientation();
rotation *= glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
rotation *= getRotation();
} else {
rotation = getRotation();
}
Q_ASSERT(args->_batch);
auto batch = args->_batch;
float imageWidth = _texture->getWidth();
float imageHeight = _texture->getHeight();
@ -86,25 +98,25 @@ void BillboardOverlay::render(RenderArgs* args) {
xColor color = getColor();
float alpha = getAlpha();
auto batch = args->_batch;
setTransforms(_transform);
Transform transform = _transform;
transform.postScale(glm::vec3(getDimensions(), 1.0f));
if (batch) {
Transform transform = _transform;
transform.postScale(glm::vec3(getDimensions(), 1.0f));
transform.setRotation(rotation);
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
DependencyManager::get<GeometryCache>()->renderQuad(*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha));
batch->setModelTransform(transform);
batch->setResourceTexture(0, _texture->getGPUTexture());
batch->setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(*batch, true, true, false, true);
DependencyManager::get<GeometryCache>()->renderQuad(
*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha)
);
batch->setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
void BillboardOverlay::setProperties(const QScriptValue &properties) {
Planar3DOverlay::setProperties(properties);
PanelAttachable::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
@ -161,7 +173,14 @@ QScriptValue BillboardOverlay::getProperty(const QString& property) {
if (property == "isFacingAvatar") {
return _isFacingAvatar;
}
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
QScriptValue value = PanelAttachable::getProperty(_scriptEngine, property);
if (value.isValid()) {
return value;
}
return Planar3DOverlay::getProperty(property);
}
@ -175,15 +194,10 @@ void BillboardOverlay::setBillboardURL(const QString& url) {
}
bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) {
float& distance, BoxFace& face) {
if (_texture && _texture->isLoaded()) {
glm::quat rotation = getRotation();
if (_isFacingAvatar) {
// rotate about vertical to face the camera
rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
}
// Make sure position and rotation is updated.
setTransforms(_transform);
// Produce the dimensions of the billboard based on the image's aspect ratio and the overlay's scale.
bool isNull = _fromImage.isNull();
@ -192,7 +206,7 @@ bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::v
float maxSize = glm::max(width, height);
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
return findRayRectangleIntersection(origin, direction, rotation, getPosition(), dimensions, distance);
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), dimensions, distance);
}
return false;

View file

@ -15,15 +15,21 @@
#include <TextureCache.h>
#include "Planar3DOverlay.h"
#include "PanelAttachable.h"
class BillboardOverlay : public Planar3DOverlay {
class BillboardOverlay : public Planar3DOverlay, public PanelAttachable {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
BillboardOverlay();
BillboardOverlay(const BillboardOverlay* billboardOverlay);
virtual void render(RenderArgs* args);
virtual void update(float deltatime);
// setters
void setURL(const QString& url);
void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; }
@ -36,9 +42,12 @@ public:
virtual BillboardOverlay* createClone() const;
protected:
virtual void setTransforms(Transform& transform);
private:
void setBillboardURL(const QString& url);
QString _url;
NetworkTexturePointer _texture;

View file

@ -15,6 +15,8 @@
#include <RegisteredMetaTypes.h>
QString const Circle3DOverlay::TYPE = "circle3d";
Circle3DOverlay::Circle3DOverlay() :
_startAt(0.0f),
_endAt(360.0f),

View file

@ -18,6 +18,9 @@ class Circle3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Circle3DOverlay();
Circle3DOverlay(const Circle3DOverlay* circle3DOverlay);

View file

@ -19,6 +19,8 @@
#include <GeometryCache.h>
#include <DependencyManager.h>
QString const Cube3DOverlay::TYPE = "cube";
Cube3DOverlay::Cube3DOverlay(const Cube3DOverlay* cube3DOverlay) :
Volume3DOverlay(cube3DOverlay)
{

View file

@ -17,6 +17,9 @@ class Cube3DOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Cube3DOverlay() {}
Cube3DOverlay(const Cube3DOverlay* cube3DOverlay);

View file

@ -0,0 +1,197 @@
//
// FloatingUIPanel.cpp
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// Copyright 2014 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 "FloatingUIPanel.h"
#include <QVariant>
#include <RegisteredMetaTypes.h>
#include <DependencyManager.h>
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "Application.h"
#include "Base3DOverlay.h"
std::function<glm::vec3()> const FloatingUIPanel::AVATAR_POSITION = []() -> glm::vec3 {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition();
};
std::function<glm::quat()> const FloatingUIPanel::AVATAR_ORIENTATION = []() -> glm::quat {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getOrientation() *
glm::angleAxis(glm::pi<float>(), IDENTITY_UP);
};
glm::vec3 FloatingUIPanel::getPosition() const {
return getOffsetRotation() * getOffsetPosition() + getAnchorPosition();
}
glm::quat FloatingUIPanel::getRotation() const {
return getOffsetRotation() * getFacingRotation();
}
void FloatingUIPanel::setAnchorPosition(const glm::vec3& position) {
setAnchorPosition([position]() -> glm::vec3 {
return position;
});
}
void FloatingUIPanel::setOffsetRotation(const glm::quat& rotation) {
setOffsetRotation([rotation]() -> glm::quat {
return rotation;
});
}
void FloatingUIPanel::addChild(unsigned int childId) {
if (!_children.contains(childId)) {
_children.append(childId);
}
}
void FloatingUIPanel::removeChild(unsigned int childId) {
if (_children.contains(childId)) {
_children.removeOne(childId);
}
}
QScriptValue FloatingUIPanel::getProperty(const QString &property) {
if (property == "anchorPosition") {
return vec3toScriptValue(_scriptEngine, getAnchorPosition());
}
if (property == "offsetRotation") {
return quatToScriptValue(_scriptEngine, getOffsetRotation());
}
if (property == "offsetPosition") {
return vec3toScriptValue(_scriptEngine, getOffsetPosition());
}
if (property == "facingRotation") {
return quatToScriptValue(_scriptEngine, getFacingRotation());
}
return QScriptValue();
}
void FloatingUIPanel::setProperties(const QScriptValue &properties) {
QScriptValue anchor = properties.property("anchorPosition");
if (anchor.isValid()) {
QScriptValue bindType = anchor.property("bind");
QScriptValue value = anchor.property("value");
if (bindType.isValid()) {
QString bindTypeString = bindType.toVariant().toString();
if (bindTypeString == "myAvatar") {
setAnchorPosition(AVATAR_POSITION);
} else if (value.isValid()) {
if (bindTypeString == "overlay") {
Overlay::Pointer overlay = Application::getInstance()->getOverlays()
.getOverlay(value.toVariant().toUInt());
if (overlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
setAnchorPosition([&overlay3D]() -> glm::vec3 {
return overlay3D->getPosition();
});
}
} else if (bindTypeString == "panel") {
FloatingUIPanel::Pointer panel = Application::getInstance()->getOverlays()
.getPanel(value.toVariant().toUInt());
setAnchorPosition([panel]() -> glm::vec3 {
return panel->getPosition();
});
} else if (bindTypeString == "vec3") {
QScriptValue x = value.property("x");
QScriptValue y = value.property("y");
QScriptValue z = value.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setAnchorPosition(newPosition);
}
}
}
}
}
QScriptValue offsetRotation = properties.property("offsetRotation");
if (offsetRotation.isValid()) {
QScriptValue bindType = offsetRotation.property("bind");
QScriptValue value = offsetRotation.property("value");
if (bindType.isValid()) {
QString bindTypeString = bindType.toVariant().toString();
if (bindTypeString == "myAvatar") {
setOffsetRotation(AVATAR_ORIENTATION);
} else if (value.isValid()) {
if (bindTypeString == "overlay") {
Overlay::Pointer overlay = Application::getInstance()->getOverlays()
.getOverlay(value.toVariant().toUInt());
if (overlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
setOffsetRotation([&overlay3D]() -> glm::quat {
return overlay3D->getRotation();
});
}
} else if (bindTypeString == "panel") {
FloatingUIPanel::Pointer panel = Application::getInstance()->getOverlays()
.getPanel(value.toVariant().toUInt());
setOffsetRotation([panel]() -> glm::quat {
return panel->getRotation();
});
} else if (bindTypeString == "quat") {
QScriptValue x = value.property("x");
QScriptValue y = value.property("y");
QScriptValue z = value.property("z");
QScriptValue w = value.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setOffsetRotation(newRotation);
}
}
}
}
}
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
QScriptValue facingRotation = properties.property("facingRotation");
if (facingRotation.isValid()) {
QScriptValue x = facingRotation.property("x");
QScriptValue y = facingRotation.property("y");
QScriptValue z = facingRotation.property("z");
QScriptValue w = facingRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setFacingRotation(newRotation);
}
}
}

View file

@ -0,0 +1,63 @@
//
// FloatingUIPanel.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/2/15.
// Copyright 2014 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_FloatingUIPanel_h
#define hifi_FloatingUIPanel_h
#include <functional>
#include <memory>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QScriptValue>
class FloatingUIPanel : public QObject {
Q_OBJECT
public:
typedef std::shared_ptr<FloatingUIPanel> Pointer;
void init(QScriptEngine* scriptEngine) { _scriptEngine = scriptEngine; }
glm::vec3 getAnchorPosition() const { return _anchorPosition(); }
glm::quat getOffsetRotation() const { return _offsetRotation(); }
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
glm::quat getFacingRotation() const { return _facingRotation; }
glm::vec3 getPosition() const;
glm::quat getRotation() const;
void setAnchorPosition(const std::function<glm::vec3()>& func) { _anchorPosition = func; }
void setAnchorPosition(const glm::vec3& position);
void setOffsetRotation(const std::function<glm::quat()>& func) { _offsetRotation = func; }
void setOffsetRotation(const glm::quat& rotation);
void setOffsetPosition(const glm::vec3& position) { _offsetPosition = position; }
void setFacingRotation(const glm::quat& rotation) { _facingRotation = rotation; }
const QList<unsigned int>& getChildren() { return _children; }
void addChild(unsigned int childId);
void removeChild(unsigned int childId);
unsigned int popLastChild() { return _children.takeLast(); }
QScriptValue getProperty(const QString& property);
void setProperties(const QScriptValue& properties);
private:
static std::function<glm::vec3()> const AVATAR_POSITION;
static std::function<glm::quat()> const AVATAR_ORIENTATION;
std::function<glm::vec3()> _anchorPosition{AVATAR_POSITION};
std::function<glm::quat()> _offsetRotation{AVATAR_ORIENTATION};
glm::vec3 _offsetPosition{0, 0, 0};
glm::quat _facingRotation{1, 0, 0, 0};
QScriptEngine* _scriptEngine;
QList<unsigned int> _children;
};
#endif // hifi_FloatingUIPanel_h

View file

@ -18,6 +18,9 @@
#include <PathUtils.h>
#include <ViewFrustum.h>
QString const Grid3DOverlay::TYPE = "grid";
Grid3DOverlay::Grid3DOverlay() :
_minorGridWidth(1.0),
_majorGridEvery(5) {

View file

@ -18,6 +18,9 @@ class Grid3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Grid3DOverlay();
Grid3DOverlay(const Grid3DOverlay* grid3DOverlay);

View file

@ -16,6 +16,9 @@
#include <gpu/StandardShaderLib.h>
#include <RegisteredMetaTypes.h>
QString const ImageOverlay::TYPE = "image";
ImageOverlay::ImageOverlay() :
_imageURL(),
_renderImage(false),

View file

@ -24,6 +24,9 @@ class ImageOverlay : public Overlay2D {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
ImageOverlay();
ImageOverlay(const ImageOverlay* imageOverlay);

View file

@ -13,6 +13,9 @@
#include <GeometryCache.h>
#include <RegisteredMetaTypes.h>
QString const Line3DOverlay::TYPE = "line3d";
Line3DOverlay::Line3DOverlay() :
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
{

View file

@ -17,6 +17,9 @@ class Line3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Line3DOverlay();
Line3DOverlay(const Line3DOverlay* line3DOverlay);
~Line3DOverlay();

View file

@ -14,6 +14,9 @@
#include <EntityTreeRenderer.h>
#include <gpu/Batch.h>
QString const LocalModelsOverlay::TYPE = "localmodels";
LocalModelsOverlay::LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer) :
Volume3DOverlay(),
_entityTreeRenderer(entityTreeRenderer) {

View file

@ -19,6 +19,9 @@ class EntityTreeRenderer;
class LocalModelsOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
LocalModelsOverlay(EntityTreeRenderer* entityTreeRenderer);
LocalModelsOverlay(const LocalModelsOverlay* localModelsOverlay);

View file

@ -10,11 +10,15 @@
//
#include "ModelOverlay.h"
#include "EntityRig.h"
#include "Application.h"
QString const ModelOverlay::TYPE = "model";
ModelOverlay::ModelOverlay()
: _model(),
: _model(std::make_shared<EntityRig>()),
_modelTextures(QVariantMap()),
_updateModel(false)
{
@ -24,7 +28,7 @@ ModelOverlay::ModelOverlay()
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
Volume3DOverlay(modelOverlay),
_model(),
_model(std::make_shared<EntityRig>()),
_modelTextures(QVariantMap()),
_url(modelOverlay->_url),
_updateModel(false)

View file

@ -19,6 +19,9 @@
class ModelOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
ModelOverlay();
ModelOverlay(const ModelOverlay* modelOverlay);

View file

@ -110,7 +110,8 @@ void Overlay::setProperties(const QScriptValue& properties) {
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
bool visible = properties.property("visible").toVariant().toBool();
setVisible(visible);
}
if (properties.property("anchor").isValid()) {

View file

@ -44,6 +44,7 @@ public:
virtual void removeFromScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
// getters
virtual QString getType() const = 0;
virtual bool is3D() const = 0;
bool isLoaded() { return _isLoaded; }
bool getVisible() const { return _visible; }

View file

@ -48,6 +48,7 @@ Overlays::~Overlays() {
}
_overlaysHUD.clear();
_overlaysWorld.clear();
_panels.clear();
}
cleanupOverlaysToDelete();
@ -124,85 +125,84 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
}
}
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
unsigned int thisID = 0;
Overlay* thisOverlay = NULL;
bool created = true;
if (type == "image") {
thisOverlay = new ImageOverlay();
} else if (type == "text") {
thisOverlay = new TextOverlay();
} else if (type == "text3d") {
thisOverlay = new Text3DOverlay();
} else if (type == "cube") {
thisOverlay = new Cube3DOverlay();
} else if (type == "sphere") {
thisOverlay = new Sphere3DOverlay();
} else if (type == "circle3d") {
thisOverlay = new Circle3DOverlay();
} else if (type == "rectangle3d") {
thisOverlay = new Rectangle3DOverlay();
} else if (type == "line3d") {
thisOverlay = new Line3DOverlay();
} else if (type == "grid") {
thisOverlay = new Grid3DOverlay();
} else if (type == "localmodels") {
thisOverlay = new LocalModelsOverlay(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == "model") {
thisOverlay = new ModelOverlay();
} else if (type == "billboard") {
thisOverlay = new BillboardOverlay();
} else {
created = false;
Overlay::Pointer Overlays::getOverlay(unsigned int id) const {
if (_overlaysHUD.contains(id)) {
return _overlaysHUD[id];
}
if (created) {
thisOverlay->setProperties(properties);
thisID = addOverlay(thisOverlay);
if (_overlaysWorld.contains(id)) {
return _overlaysWorld[id];
}
return thisID;
return nullptr;
}
unsigned int Overlays::addOverlay(Overlay* overlay) {
Overlay::Pointer overlayPointer(overlay);
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
Overlay::Pointer thisOverlay = nullptr;
if (type == ImageOverlay::TYPE) {
thisOverlay = std::make_shared<ImageOverlay>();
} else if (type == TextOverlay::TYPE) {
thisOverlay = std::make_shared<TextOverlay>();
} else if (type == Text3DOverlay::TYPE) {
thisOverlay = std::make_shared<Text3DOverlay>();
} else if (type == Cube3DOverlay::TYPE) {
thisOverlay = std::make_shared<Cube3DOverlay>();
} else if (type == Sphere3DOverlay::TYPE) {
thisOverlay = std::make_shared<Sphere3DOverlay>();
} else if (type == Circle3DOverlay::TYPE) {
thisOverlay = std::make_shared<Circle3DOverlay>();
} else if (type == Rectangle3DOverlay::TYPE) {
thisOverlay = std::make_shared<Rectangle3DOverlay>();
} else if (type == Line3DOverlay::TYPE) {
thisOverlay = std::make_shared<Line3DOverlay>();
} else if (type == Grid3DOverlay::TYPE) {
thisOverlay = std::make_shared<Grid3DOverlay>();
} else if (type == LocalModelsOverlay::TYPE) {
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == ModelOverlay::TYPE) {
thisOverlay = std::make_shared<ModelOverlay>();
} else if (type == BillboardOverlay::TYPE) {
thisOverlay = std::make_shared<BillboardOverlay>();
}
if (thisOverlay) {
thisOverlay->setProperties(properties);
return addOverlay(thisOverlay);
}
return 0;
}
unsigned int Overlays::addOverlay(Overlay::Pointer overlay) {
overlay->init(_scriptEngine);
QWriteLocker lock(&_lock);
unsigned int thisID = _nextOverlayID;
_nextOverlayID++;
if (overlay->is3D()) {
Base3DOverlay* overlay3D = static_cast<Base3DOverlay*>(overlay);
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
if (overlay3D->getDrawOnHUD()) {
_overlaysHUD[thisID] = overlayPointer;
_overlaysHUD[thisID] = overlay;
} else {
_overlaysWorld[thisID] = overlayPointer;
_overlaysWorld[thisID] = overlay;
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
render::PendingChanges pendingChanges;
overlayPointer->addToScene(overlayPointer, scene, pendingChanges);
overlay->addToScene(overlay, scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges);
}
} else {
_overlaysHUD[thisID] = overlayPointer;
_overlaysHUD[thisID] = overlay;
}
return thisID;
}
unsigned int Overlays::cloneOverlay(unsigned int id) {
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
return addOverlay(thisOverlay->createClone());
return addOverlay(Overlay::Pointer(thisOverlay->createClone()));
}
return 0; // Not found
@ -210,14 +210,8 @@ unsigned int Overlays::cloneOverlay(unsigned int id) {
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
QWriteLocker lock(&_lock);
Overlay::Pointer thisOverlay;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
if (thisOverlay->is3D()) {
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(thisOverlay);
@ -258,8 +252,51 @@ void Overlays::deleteOverlay(unsigned int id) {
}
}
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlayToDelete);
if (attachable && attachable->getAttachedPanel()) {
attachable->getAttachedPanel()->removeChild(id);
attachable->setAttachedPanel(nullptr);
}
QWriteLocker lock(&_deleteLock);
_overlaysToDelete.push_back(overlayToDelete);
emit overlayDeleted(id);
}
QString Overlays::getOverlayType(unsigned int overlayId) const {
Overlay::Pointer overlay = getOverlay(overlayId);
if (overlay) {
return overlay->getType();
}
return "";
}
unsigned int Overlays::getAttachedPanel(unsigned int childId) const {
Overlay::Pointer overlay = getOverlay(childId);
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
if (attachable) {
return _panels.key(attachable->getAttachedPanel());
}
return 0;
}
void Overlays::setAttachedPanel(unsigned int childId, unsigned int panelId) {
Overlay::Pointer overlay = getOverlay(childId);
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
if (attachable) {
if (_panels.contains(panelId)) {
auto panel = _panels[panelId];
panel->addChild(childId);
attachable->setAttachedPanel(panel);
} else {
auto panel = attachable->getAttachedPanel();
if (panel) {
panel->removeChild(childId);
attachable->setAttachedPanel(nullptr);
}
}
}
}
unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
@ -302,13 +339,8 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
OverlayPropertyResult Overlays::getProperty(unsigned int id, const QString& property) {
OverlayPropertyResult result;
Overlay::Pointer thisOverlay;
Overlay::Pointer thisOverlay = getOverlay(id);
QReadLocker lock(&_lock);
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
if (thisOverlay) {
result.value = thisOverlay->getProperty(property);
}
@ -456,12 +488,8 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R
bool Overlays::isLoaded(unsigned int id) {
QReadLocker lock(&_lock);
Overlay::Pointer thisOverlay = NULL;
if (_overlaysHUD.contains(id)) {
thisOverlay = _overlaysHUD[id];
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
} else {
Overlay::Pointer thisOverlay = getOverlay(id);
if (!thisOverlay) {
return false; // not found
}
return thisOverlay->isLoaded();
@ -483,3 +511,56 @@ QSizeF Overlays::textSize(unsigned int id, const QString& text) const {
}
return QSizeF(0.0f, 0.0f);
}
unsigned int Overlays::addPanel(FloatingUIPanel::Pointer panel) {
QWriteLocker lock(&_lock);
unsigned int thisID = _nextOverlayID;
_nextOverlayID++;
_panels[thisID] = panel;
return thisID;
}
unsigned int Overlays::addPanel(const QScriptValue& properties) {
FloatingUIPanel::Pointer panel = std::make_shared<FloatingUIPanel>();
panel->init(_scriptEngine);
panel->setProperties(properties);
return addPanel(panel);
}
void Overlays::editPanel(unsigned int panelId, const QScriptValue& properties) {
if (_panels.contains(panelId)) {
_panels[panelId]->setProperties(properties);
}
}
OverlayPropertyResult Overlays::getPanelProperty(unsigned int panelId, const QString& property) {
OverlayPropertyResult result;
if (_panels.contains(panelId)) {
FloatingUIPanel::Pointer thisPanel = _panels[panelId];
QReadLocker lock(&_lock);
result.value = thisPanel->getProperty(property);
}
return result;
}
void Overlays::deletePanel(unsigned int panelId) {
FloatingUIPanel::Pointer panelToDelete;
{
QWriteLocker lock(&_lock);
if (_panels.contains(panelId)) {
panelToDelete = _panels.take(panelId);
} else {
return;
}
}
while (!panelToDelete->getChildren().isEmpty()) {
deleteOverlay(panelToDelete->popLastChild());
}
emit panelDeleted(panelId);
}

View file

@ -2,8 +2,14 @@
// Overlays.h
// interface/src/ui/overlays
//
// Modified by Zander Otavka on 7/15/15
// Copyright 2014 High Fidelity, Inc.
//
// Exposes methods for managing `Overlay`s and `FloatingUIPanel`s to scripts.
//
// YOU SHOULD NOT USE `Overlays` DIRECTLY, unless you like pain and deprecation. Instead, use the
// object oriented abstraction layer found in `examples/libraries/overlayUtils.js`.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
@ -16,6 +22,9 @@
#include "Overlay.h"
#include "FloatingUIPanel.h"
#include "PanelAttachable.h"
class PickRay;
class OverlayPropertyResult {
@ -57,12 +66,16 @@ public:
void update(float deltatime);
void renderHUD(RenderArgs* renderArgs);
Overlay::Pointer getOverlay(unsigned int id) const;
FloatingUIPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; }
public slots:
/// adds an overlay with the specific properties
unsigned int addOverlay(const QString& type, const QScriptValue& properties);
/// adds an overlay that's already been created
unsigned int addOverlay(Overlay* overlay);
unsigned int addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
unsigned int addOverlay(Overlay::Pointer overlay);
/// clones an existing overlay
unsigned int cloneOverlay(unsigned int id);
@ -74,6 +87,12 @@ public slots:
/// deletes a particle
void deleteOverlay(unsigned int id);
/// get the string type of the overlay used in addOverlay
QString getOverlayType(unsigned int overlayId) const;
unsigned int getAttachedPanel(unsigned int childId) const;
void setAttachedPanel(unsigned int childId, unsigned int panelId);
/// returns the top most 2D overlay at the screen point, or 0 if not overlay at that point
unsigned int getOverlayAtPoint(const glm::vec2& point);
@ -90,12 +109,35 @@ public slots:
/// overlay; in meters if it is a 3D text overlay
QSizeF textSize(unsigned int id, const QString& text) const;
/// adds a panel that has already been created
unsigned int addPanel(FloatingUIPanel::Pointer panel);
/// creates and adds a panel based on a set of properties
unsigned int addPanel(const QScriptValue& properties);
/// edit the properties of a panel
void editPanel(unsigned int panelId, const QScriptValue& properties);
/// get a property of a panel
OverlayPropertyResult getPanelProperty(unsigned int panelId, const QString& property);
/// deletes a panel and all child overlays
void deletePanel(unsigned int panelId);
signals:
void overlayDeleted(unsigned int id);
void panelDeleted(unsigned int id);
private:
void cleanupOverlaysToDelete();
QMap<unsigned int, Overlay::Pointer> _overlaysHUD;
QMap<unsigned int, Overlay::Pointer> _overlaysWorld;
QMap<unsigned int, FloatingUIPanel::Pointer> _panels;
QList<Overlay::Pointer> _overlaysToDelete;
unsigned int _nextOverlayID;
QReadWriteLock _lock;
QReadWriteLock _deleteLock;
QScriptEngine* _scriptEngine;

View file

@ -0,0 +1,80 @@
//
// PanelAttachable.cpp
// hifi
//
// Created by Zander Otavka on 7/15/15.
// 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
//
#include "PanelAttachable.h"
#include <RegisteredMetaTypes.h>
PanelAttachable::PanelAttachable() :
_attachedPanel(nullptr),
_facingRotation(1, 0, 0, 0)
{
}
PanelAttachable::PanelAttachable(const PanelAttachable* panelAttachable) :
_attachedPanel(panelAttachable->_attachedPanel),
_offsetPosition(panelAttachable->_offsetPosition),
_facingRotation(panelAttachable->_facingRotation)
{
}
void PanelAttachable::setTransforms(Transform& transform) {
if (getAttachedPanel()) {
transform.setTranslation(getAttachedPanel()->getAnchorPosition());
transform.setRotation(getAttachedPanel()->getOffsetRotation());
transform.postTranslate(getOffsetPosition() + getAttachedPanel()->getOffsetPosition());
transform.postRotate(getFacingRotation() * getAttachedPanel()->getFacingRotation());
}
}
QScriptValue PanelAttachable::getProperty(QScriptEngine* scriptEngine, const QString &property) {
if (property == "offsetPosition") {
return vec3toScriptValue(scriptEngine, getOffsetPosition());
}
if (property == "facingRotation") {
return quatToScriptValue(scriptEngine, getFacingRotation());
}
return QScriptValue();
}
void PanelAttachable::setProperties(const QScriptValue &properties) {
QScriptValue offsetPosition = properties.property("offsetPosition");
if (offsetPosition.isValid()) {
QScriptValue x = offsetPosition.property("x");
QScriptValue y = offsetPosition.property("y");
QScriptValue z = offsetPosition.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
setOffsetPosition(newPosition);
}
}
QScriptValue facingRotation = properties.property("facingRotation");
if (facingRotation.isValid()) {
QScriptValue x = facingRotation.property("x");
QScriptValue y = facingRotation.property("y");
QScriptValue z = facingRotation.property("z");
QScriptValue w = facingRotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setFacingRotation(newRotation);
}
}
}

View file

@ -0,0 +1,45 @@
//
// PanelAttachable.h
// interface/src/ui/overlays
//
// Created by Zander Otavka on 7/1/15.
// 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
//
#ifndef hifi_PanelAttachable_h
#define hifi_PanelAttachable_h
#include "FloatingUIPanel.h"
#include <glm/glm.hpp>
#include <Transform.h>
class PanelAttachable {
public:
PanelAttachable();
PanelAttachable(const PanelAttachable* panelAttachable);
FloatingUIPanel::Pointer getAttachedPanel() const { return _attachedPanel; }
glm::vec3 getOffsetPosition() const { return _offsetPosition; }
glm::quat getFacingRotation() const { return _facingRotation; }
void setAttachedPanel(FloatingUIPanel::Pointer panel) { _attachedPanel = panel; }
void setOffsetPosition(const glm::vec3& position) { _offsetPosition = position; }
void setFacingRotation(const glm::quat& rotation) { _facingRotation = rotation; }
QScriptValue getProperty(QScriptEngine* scriptEngine, const QString& property);
void setProperties(const QScriptValue& properties);
protected:
virtual void setTransforms(Transform& transform);
private:
FloatingUIPanel::Pointer _attachedPanel;
glm::vec3 _offsetPosition;
glm::quat _facingRotation;
};
#endif // hifi_PanelAttachable_h

View file

@ -14,8 +14,15 @@
#include <GeometryUtil.h>
#include <RegisteredMetaTypes.h>
Planar3DOverlay::Planar3DOverlay() :
Base3DOverlay(),
_dimensions{1.0f, 1.0f}
{
}
Planar3DOverlay::Planar3DOverlay(const Planar3DOverlay* planar3DOverlay) :
Base3DOverlay(planar3DOverlay)
Base3DOverlay(planar3DOverlay),
_dimensions(planar3DOverlay->_dimensions)
{
}

View file

@ -17,7 +17,7 @@ class Planar3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
Planar3DOverlay() {}
Planar3DOverlay();
Planar3DOverlay(const Planar3DOverlay* planar3DOverlay);
AABox getBounds() const;
@ -32,7 +32,7 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
protected:
glm::vec2 _dimensions{1.0f, 1.0f};
glm::vec2 _dimensions;
};

View file

@ -13,6 +13,9 @@
#include <GeometryCache.h>
#include <SharedUtil.h>
QString const Rectangle3DOverlay::TYPE = "rectangle3d";
Rectangle3DOverlay::Rectangle3DOverlay() :
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
{

View file

@ -17,6 +17,9 @@ class Rectangle3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Rectangle3DOverlay();
Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay);
~Rectangle3DOverlay();

View file

@ -15,6 +15,8 @@
#include <gpu/Batch.h>
#include <SharedUtil.h>
QString const Sphere3DOverlay::TYPE = "sphere";
Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
Volume3DOverlay(Sphere3DOverlay)
{

View file

@ -17,6 +17,9 @@ class Sphere3DOverlay : public Volume3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Sphere3DOverlay() {}
Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay);

View file

@ -23,6 +23,8 @@ const int FIXED_FONT_POINT_SIZE = 40;
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 80.0f; // this is a ratio determined through experimentation
const float LINE_SCALE_RATIO = 1.2f;
QString const Text3DOverlay::TYPE = "text3d";
Text3DOverlay::Text3DOverlay() :
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
_backgroundAlpha(DEFAULT_BACKGROUND_ALPHA),

View file

@ -21,6 +21,9 @@ class Text3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Text3DOverlay();
Text3DOverlay(const Text3DOverlay* text3DOverlay);
~Text3DOverlay();

View file

@ -78,6 +78,8 @@ QString toQmlColor(const glm::vec4& v) {
arg((int)(v.b * 255), 2, 16, QChar('0'));
}
QString const TextOverlay::TYPE = "text";
TextOverlay::TextOverlay() :
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
_backgroundAlpha(DEFAULT_BACKGROUND_ALPHA),

View file

@ -29,6 +29,9 @@ class TextOverlay : public Overlay2D {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
TextOverlay();
TextOverlay(const TextOverlay* textOverlay);
~TextOverlay();

View file

@ -1,6 +1,6 @@
//
// AnimationCache.cpp
// libraries/script-engine/src/
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 4/14/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -1,6 +1,6 @@
//
// AnimationCache.h
// libraries/script-engine/src/
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 4/14/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -1,6 +1,6 @@
//
// AnimationHandle.cpp
// interface/src/renderer
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 10/18/13.
// Copyright 2013 High Fidelity, Inc.
@ -10,7 +10,7 @@
//
#include "AnimationHandle.h"
#include "Model.h"
void AnimationHandle::setURL(const QUrl& url) {
if (_url != url) {
@ -20,28 +20,17 @@ void AnimationHandle::setURL(const QUrl& url) {
}
}
static void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
for (QList<AnimationHandlePointer>::iterator it = handles.begin(); it != handles.end(); it++) {
if (handle->getPriority() > (*it)->getPriority()) {
handles.insert(it, handle);
return;
}
}
handles.append(handle);
}
void AnimationHandle::setPriority(float priority) {
if (_priority == priority) {
return;
}
if (isRunning()) {
_model->_runningAnimations.removeOne(_self);
_rig->removeRunningAnimation(getAnimationHandlePointer());
if (priority < _priority) {
replaceMatchingPriorities(priority);
}
_priority = priority;
insertSorted(_model->_runningAnimations, _self);
_rig->addRunningAnimation(getAnimationHandlePointer());
} else {
_priority = priority;
}
@ -68,21 +57,21 @@ void AnimationHandle::setRunning(bool running) {
}
_animationLoop.setRunning(running);
if (isRunning()) {
if (!_model->_runningAnimations.contains(_self)) {
insertSorted(_model->_runningAnimations, _self);
if (!_rig->isRunningAnimation(getAnimationHandlePointer())) {
_rig->addRunningAnimation(getAnimationHandlePointer());
}
} else {
_model->_runningAnimations.removeOne(_self);
_rig->removeRunningAnimation(getAnimationHandlePointer());
restoreJoints();
replaceMatchingPriorities(0.0f);
}
emit runningChanged(isRunning());
}
AnimationHandle::AnimationHandle(Model* model) :
QObject(model),
_model(model),
_priority(1.0f)
AnimationHandle::AnimationHandle(RigPointer rig) :
QObject(rig.get()),
_rig(rig),
_priority(1.0f)
{
}
@ -110,42 +99,61 @@ void AnimationHandle::setAnimationDetails(const AnimationDetails& details) {
}
void AnimationHandle::setJointMappings(QVector<int> jointMappings) {
_jointMappings = jointMappings;
}
QVector<int> AnimationHandle::getJointMappings() {
if (_jointMappings.isEmpty()) {
QVector<FBXJoint> animationJoints = _animation->getGeometry().joints;
for (int i = 0; i < animationJoints.count(); i++) {
_jointMappings.append(_rig->indexOfJoint(animationJoints.at(i).name));
}
}
return _jointMappings;
}
void AnimationHandle::simulate(float deltaTime) {
if (!_animation || !_animation->isLoaded()) {
return;
}
_animationLoop.simulate(deltaTime);
// update the joint mappings if necessary/possible
if (_jointMappings.isEmpty()) {
if (_model && _model->isActive()) {
_jointMappings = _model->getGeometry()->getJointMappings(_animation);
}
if (_jointMappings.isEmpty()) {
return;
}
if (!_maskedJoints.isEmpty()) {
const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry();
for (int i = 0; i < _jointMappings.size(); i++) {
int& mapping = _jointMappings[i];
if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) {
mapping = -1;
}
}
}
if (getJointMappings().isEmpty()) {
qDebug() << "AnimationHandle::simulate -- _jointMappings.isEmpty()";
return;
}
// // update the joint mappings if necessary/possible
// if (_jointMappings.isEmpty()) {
// if (_model && _model->isActive()) {
// _jointMappings = _model->getGeometry()->getJointMappings(_animation);
// }
// if (_jointMappings.isEmpty()) {
// return;
// }
// if (!_maskedJoints.isEmpty()) {
// const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry();
// for (int i = 0; i < _jointMappings.size(); i++) {
// int& mapping = _jointMappings[i];
// if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) {
// mapping = -1;
// }
// }
// }
// }
const FBXGeometry& animationGeometry = _animation->getGeometry();
if (animationGeometry.animationFrames.isEmpty()) {
stop();
return;
}
if (_animationLoop.getMaxFrameIndexHint() != animationGeometry.animationFrames.size()) {
_animationLoop.setMaxFrameIndexHint(animationGeometry.animationFrames.size());
}
// blend between the closest two frames
applyFrame(getFrameIndex());
}
@ -154,17 +162,23 @@ void AnimationHandle::applyFrame(float frameIndex) {
if (!_animation || !_animation->isLoaded()) {
return;
}
const FBXGeometry& animationGeometry = _animation->getGeometry();
int frameCount = animationGeometry.animationFrames.size();
const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount);
const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount);
float frameFraction = glm::fract(frameIndex);
assert(_rig->getJointStateCount() >= _jointMappings.size());
for (int i = 0; i < _jointMappings.size(); i++) {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority);
_rig->setJointRotationInConstrainedFrame(mapping,
safeMix(floorFrame.rotations.at(i),
ceilFrame.rotations.at(i),
frameFraction),
_priority,
false,
_mix);
}
}
}
@ -173,9 +187,8 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) {
for (int i = 0; i < _jointMappings.size(); i++) {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
if (_priority == state._animationPriority) {
state._animationPriority = newPriority;
if (_priority == _rig->getJointAnimatinoPriority(mapping)) {
_rig->setJointAnimatinoPriority(mapping, newPriority);
}
}
}
@ -185,9 +198,7 @@ void AnimationHandle::restoreJoints() {
for (int i = 0; i < _jointMappings.size(); i++) {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
JointState& state = _model->_jointStates[mapping];
state.restoreRotation(1.0f, state._animationPriority);
_rig->restoreJointRotation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping));
}
}
}

View file

@ -1,6 +1,6 @@
//
// AnimationHandle.h
// interface/src/renderer
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 10/18/13.
// Copyright 2013 High Fidelity, Inc.
@ -18,22 +18,43 @@
#include <QUrl>
#include <QVector>
#include <AnimationCache.h>
#include <AnimationLoop.h>
#include "AnimationCache.h"
#include "AnimationLoop.h"
#include "Rig.h"
class AnimationHandle;
class Model;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
typedef std::weak_ptr<AnimationHandle> WeakAnimationHandlePointer;
inline uint qHash(const std::shared_ptr<AnimationHandle>& a, uint seed) {
// return qHash(a.get(), seed);
AnimationHandle* strongRef = a ? a.get() : nullptr;
return qHash(strongRef, seed);
}
inline uint qHash(const std::weak_ptr<AnimationHandle>& a, uint seed) {
AnimationHandlePointer strongPointer = a.lock();
AnimationHandle* strongRef = strongPointer ? strongPointer.get() : nullptr;
return qHash(strongRef, seed);
}
/// Represents a handle to a model animation.
class AnimationHandle : public QObject {
// inline uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
// return qHash(handle.data(), seed);
// }
/// Represents a handle to a model animation. I.e., an Animation in use by a given Rig.
class AnimationHandle : public QObject, public std::enable_shared_from_this<AnimationHandle> {
Q_OBJECT
public:
AnimationHandle(RigPointer rig);
AnimationHandlePointer getAnimationHandlePointer() { return shared_from_this(); }
void setRole(const QString& role) { _role = role; }
const QString& getRole() const { return _role; }
@ -42,29 +63,30 @@ public:
void setPriority(float priority);
float getPriority() const { return _priority; }
void setMix(float mix) { _mix = mix; }
void setMaskedJoints(const QStringList& maskedJoints);
const QStringList& getMaskedJoints() const { return _maskedJoints; }
void setFPS(float fps) { _animationLoop.setFPS(fps); }
float getFPS() const { return _animationLoop.getFPS(); }
void setLoop(bool loop) { _animationLoop.setLoop(loop); }
bool getLoop() const { return _animationLoop.getLoop(); }
void setHold(bool hold) { _animationLoop.setHold(hold); }
bool getHold() const { return _animationLoop.getHold(); }
void setStartAutomatically(bool startAutomatically);
bool getStartAutomatically() const { return _animationLoop.getStartAutomatically(); }
void setFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); }
float getFirstFrame() const { return _animationLoop.getFirstFrame(); }
void setLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); }
float getLastFrame() const { return _animationLoop.getLastFrame(); }
void setRunning(bool running);
bool isRunning() const { return _animationLoop.isRunning(); }
@ -74,39 +96,38 @@ public:
AnimationDetails getAnimationDetails() const;
void setAnimationDetails(const AnimationDetails& details);
void setJointMappings(QVector<int> jointMappings);
QVector<int> getJointMappings(); // computing if necessary
void simulate(float deltaTime);
void applyFrame(float frameIndex);
void replaceMatchingPriorities(float newPriority);
void restoreJoints();
void clearJoints() { _jointMappings.clear(); }
signals:
void runningChanged(bool running);
public slots:
void start() { setRunning(true); }
void stop() { setRunning(false); }
private:
friend class Model;
AnimationHandle(Model* model);
void simulate(float deltaTime);
void applyFrame(float frameIndex);
void replaceMatchingPriorities(float newPriority);
void restoreJoints();
void clearJoints() { _jointMappings.clear(); }
Model* _model;
WeakAnimationHandlePointer _self;
RigPointer _rig;
AnimationPointer _animation;
QString _role;
QUrl _url;
float _priority;
float _mix;
QStringList _maskedJoints;
QVector<int> _jointMappings;
AnimationLoop _animationLoop;
static QHash<QWeakPointer<Animation>, QVector<int>> _jointMappingsCache;
};

View file

@ -0,0 +1,14 @@
//
// AnimationLogging.cpp
// libraries/audio/src
//
// Created by Howard Stearns on 7/24/15.
// Copyright 2014 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 "AnimationLogging.h"
Q_LOGGING_CATEGORY(animation, "hifi.animation")

View file

@ -0,0 +1,18 @@
//
// AnimationLogging.h
// libraries/animation/src/
//
// Created by Howard Stearns on 7/24/15.
//
// 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_AnimationLogging_h
#define hifi_AnimationLogging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(animation)
#endif

View file

@ -1,6 +1,6 @@
//
// AnimationLoop.cpp
// libraries/animation
// libraries/animation/src/
//
// Created by Brad Hefta-Gaub on 11/12/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -1,6 +1,6 @@
//
// AnimationLoop.h
// libraries/script-engine/src/
// libraries/animation/src/
//
// Created by Brad Hefta-Gaub on 11/12/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -1,6 +1,6 @@
//
// AnimationObject.cpp
// libraries/script-engine/src/
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 4/17/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -1,6 +1,6 @@
//
// AnimationObject.h
// libraries/script-engine/src/
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 4/17/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.

View file

@ -0,0 +1,34 @@
//
// AvatarRig.cpp
// libraries/animation/src/
//
// Created by SethAlves on 2015-7-22.
// 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
//
#include "AvatarRig.h"
/// Updates the state of the joint at the specified index.
void AvatarRig::updateJointState(int index, glm::mat4 parentTransform) {
if (index < 0 && index >= _jointStates.size()) {
return; // bail
}
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
// compute model transforms
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
state.computeTransform(parentTransform);
clearJointTransformTranslation(index);
} else {
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
}
}
}

View file

@ -0,0 +1,27 @@
//
// AvatarRig.h
// libraries/animation/src/
//
// Created by SethAlves on 2015-7-22.
// 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
//
#ifndef hifi_AvatarRig_h
#define hifi_AvatarRig_h
#include <QObject>
#include "Rig.h"
class AvatarRig : public Rig {
Q_OBJECT
public:
~AvatarRig() {}
virtual void updateJointState(int index, glm::mat4 parentTransform);
};
#endif // hifi_AvatarRig_h

View file

@ -0,0 +1,30 @@
//
// EntityRig.cpp
// libraries/animation/src/
//
// Created by SethAlves on 2015-7-22.
// 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
//
#include "EntityRig.h"
/// Updates the state of the joint at the specified index.
void EntityRig::updateJointState(int index, glm::mat4 parentTransform) {
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
// compute model transforms
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
state.computeTransform(parentTransform);
} else {
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
}
}
}

View file

@ -0,0 +1,27 @@
//
// EntityRig.h
// libraries/animation/src/
//
// Created by SethAlves on 2015-7-22.
// 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
//
#ifndef hifi_EntityRig_h
#define hifi_EntityRig_h
#include <QObject>
#include "Rig.h"
class EntityRig : public Rig {
Q_OBJECT
public:
~EntityRig() {}
virtual void updateJointState(int index, glm::mat4 parentTransform);
};
#endif // hifi_EntityRig_h

View file

@ -1,6 +1,6 @@
//
// JointState.cpp
// interface/src/renderer
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 10/18/13.
// Copyright 2013 High Fidelity, Inc.
@ -232,12 +232,13 @@ glm::quat JointState::computeVisibleParentRotation() const {
return _visibleRotation * glm::inverse(_fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation);
}
void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain) {
void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain, float mix) {
if (priority >= _animationPriority || _animationPriority == 0.0f) {
if (constrain && _constraint) {
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
}
setRotationInConstrainedFrameInternal(targetRotation);
auto rotation = (mix == 1.0f) ? targetRotation : safeMix(getRotationInConstrainedFrame(), targetRotation, mix);
setRotationInConstrainedFrameInternal(rotation);
_animationPriority = priority;
}
}

View file

@ -1,6 +1,6 @@
//
// JointState.h
// interface/src/renderer
// libraries/animation/src/
//
// Created by Andrzej Kapolka on 10/18/13.
// Copyright 2013 High Fidelity, Inc.
@ -84,7 +84,7 @@ public:
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false);
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false, float mix = 1.0f);
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; }

View file

@ -0,0 +1,754 @@
//
// Rig.cpp
// libraries/animation/src/
//
// Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15.
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <glm/gtx/vector_angle.hpp>
#include <queue>
#include "AnimationHandle.h"
#include "AnimationLogging.h"
#include "Rig.h"
void Rig::HeadParameters::dump() const {
qCDebug(animation, "HeadParameters =");
qCDebug(animation, " leanSideways = %0.5f", leanSideways);
qCDebug(animation, " leanForward = %0.5f", leanForward);
qCDebug(animation, " torsoTwist = %0.5f", torsoTwist);
glm::vec3 axis = glm::axis(localHeadOrientation);
float theta = glm::angle(localHeadOrientation);
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
axis = glm::axis(worldHeadOrientation);
theta = glm::angle(worldHeadOrientation);
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", eyeLookAt.x, eyeLookAt.y, eyeLookAt.z);
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", eyeSaccade.x, eyeSaccade.y, eyeSaccade.z);
qCDebug(animation, " leanJointIndex = %.d", leanJointIndex);
qCDebug(animation, " neckJointIndex = %.d", neckJointIndex);
qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex);
qCDebug(animation, " rightEyeJointIndex = %.d", rightEyeJointIndex);
}
void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
for (QList<AnimationHandlePointer>::iterator it = handles.begin(); it != handles.end(); it++) {
if (handle->getPriority() > (*it)->getPriority()) {
handles.insert(it, handle);
return;
}
}
handles.append(handle);
}
AnimationHandlePointer Rig::createAnimationHandle() {
AnimationHandlePointer handle(new AnimationHandle(getRigPointer()));
_animationHandles.append(handle);
return handle;
}
void Rig::removeAnimationHandle(const AnimationHandlePointer& handle) {
handle->stop();
// FIXME? Do we need to also animationHandle->clearJoints()? deleteAnimations(), below, was first written to do so, but did not first stop it.
_animationHandles.removeOne(handle);
}
void Rig::startAnimation(const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
//qCDebug(animation) << "startAnimation" << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints;
foreach (const AnimationHandlePointer& candidate, _animationHandles) {
if (candidate->getURL() == url) {
candidate->start();
return;
}
}
AnimationHandlePointer handle = createAnimationHandle();
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
handle->start();
}
void Rig::addAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints, bool startAutomatically) {
// check for a configured animation for the role
//qCDebug(animation) << "addAnimationByRole" << role << url << fps << priority << loop << hold << firstFrame << lastFrame << maskedJoints << startAutomatically;
foreach (const AnimationHandlePointer& candidate, _animationHandles) {
if (candidate->getRole() == role) {
if (startAutomatically) {
candidate->start();
}
return;
}
}
AnimationHandlePointer handle = createAnimationHandle();
handle->setRole(role);
handle->setURL(url);
handle->setFPS(fps);
handle->setPriority(priority);
handle->setLoop(loop);
handle->setHold(hold);
handle->setFirstFrame(firstFrame);
handle->setLastFrame(lastFrame);
handle->setMaskedJoints(maskedJoints);
if (startAutomatically) {
handle->start();
}
}
void Rig::startAnimationByRole(const QString& role, const QString& url, float fps, float priority,
bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) {
addAnimationByRole(role, url, fps, priority, loop, hold, firstFrame, lastFrame, maskedJoints, true);
}
void Rig::stopAnimationByRole(const QString& role) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getRole() == role) {
handle->stop();
}
}
}
void Rig::stopAnimation(const QString& url) {
foreach (const AnimationHandlePointer& handle, getRunningAnimations()) {
if (handle->getURL() == url) {
handle->stop();
}
}
}
bool Rig::removeRunningAnimation(AnimationHandlePointer animationHandle) {
return _runningAnimations.removeOne(animationHandle);
}
void Rig::addRunningAnimation(AnimationHandlePointer animationHandle) {
insertSorted(_runningAnimations, animationHandle);
}
bool Rig::isRunningAnimation(AnimationHandlePointer animationHandle) {
return _runningAnimations.contains(animationHandle);
}
void Rig::deleteAnimations() {
for (auto animation : _animationHandles) {
removeAnimationHandle(animation);
}
_animationHandles.clear();
}
float Rig::initJointStates(QVector<JointState> states, glm::mat4 parentTransform) {
_jointStates = states;
initJointTransforms(parentTransform);
int numStates = _jointStates.size();
float radius = 0.0f;
for (int i = 0; i < numStates; ++i) {
float distance = glm::length(_jointStates[i].getPosition());
if (distance > radius) {
radius = distance;
}
_jointStates[i].buildConstraint();
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
return radius;
}
// We could build and cache a dictionary, too....
// Should we be using .fst mapping instead/also?
int Rig::indexOfJoint(const QString& jointName) {
for (int i = 0; i < _jointStates.count(); i++) {
if (_jointStates[i].getFBXJoint().name == jointName) {
return i;
}
}
return -1;
}
void Rig::initJointTransforms(glm::mat4 parentTransform) {
// compute model transforms
int numStates = _jointStates.size();
for (int i = 0; i < numStates; ++i) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
state.initTransform(parentTransform);
} else {
const JointState& parentState = _jointStates.at(parentIndex);
state.initTransform(parentState.getTransform());
}
}
}
void Rig::clearJointTransformTranslation(int jointIndex) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return;
}
_jointStates[jointIndex].clearTransformTranslation();
}
void Rig::reset(const QVector<FBXJoint>& fbxJoints) {
if (_jointStates.isEmpty()) {
return;
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(fbxJoints.at(i).rotation, 0.0f);
}
}
JointState Rig::getJointState(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return JointState();
}
return _jointStates[jointIndex];
}
bool Rig::getJointStateRotation(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
const JointState& state = _jointStates.at(index);
rotation = state.getRotationInConstrainedFrame();
return !state.rotationIsDefault(rotation);
}
bool Rig::getVisibleJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
const JointState& state = _jointStates.at(index);
rotation = state.getVisibleRotationInConstrainedFrame();
return !state.rotationIsDefault(rotation);
}
void Rig::clearJointState(int index) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
state.setRotationInConstrainedFrame(glm::quat(), 0.0f);
}
}
void Rig::clearJointStates() {
_jointStates.clear();
}
void Rig::clearJointAnimationPriority(int index) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index]._animationPriority = 0.0f;
}
}
float Rig::getJointAnimatinoPriority(int index) {
if (index != -1 && index < _jointStates.size()) {
return _jointStates[index]._animationPriority;
}
return 0.0f;
}
void Rig::setJointAnimatinoPriority(int index, float newPriority) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index]._animationPriority = newPriority;
}
}
void Rig::setJointState(int index, bool valid, const glm::quat& rotation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
state.setRotationInConstrainedFrame(rotation, priority);
} else {
state.restoreRotation(1.0f, priority);
}
}
}
void Rig::restoreJointRotation(int index, float fraction, float priority) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index].restoreRotation(fraction, priority);
}
}
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in world-frame
position = translation + rotation * _jointStates[jointIndex].getPosition();
return true;
}
bool Rig::getJointPosition(int jointIndex, glm::vec3& position) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in model-frame
position = extractTranslation(_jointStates[jointIndex].getTransform());
return true;
}
bool Rig::getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
result = rotation * _jointStates[jointIndex].getRotation();
return true;
}
bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _jointStates[jointIndex].getRotation();
return true;
}
bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
result = rotation * _jointStates[jointIndex].getRotation();
return true;
}
bool Rig::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in world-frame
position = translation + rotation * _jointStates[jointIndex].getVisiblePosition();
return true;
}
bool Rig::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& result, glm::quat rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
result = rotation * _jointStates[jointIndex].getVisibleRotation();
return true;
}
glm::mat4 Rig::getJointTransform(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return glm::mat4();
}
return _jointStates[jointIndex].getTransform();
}
glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return glm::mat4();
}
return _jointStates[jointIndex].getVisibleTransform();
}
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
if (_enableRig) {
glm::vec3 front = worldRotation * IDENTITY_FRONT;
float forwardSpeed = glm::dot(worldVelocity, front);
float rotationalSpeed = glm::angle(front, _lastFront) / deltaTime;
bool isWalking = std::abs(forwardSpeed) > 0.01f;
bool isTurning = std::abs(rotationalSpeed) > 0.5f;
// Crude, until we have blending:
isTurning = isTurning && !isWalking; // Only one of walk/turn, walk wins.
isTurning = false; // FIXME
bool isIdle = !isWalking && !isTurning;
auto singleRole = [](bool walking, bool turning, bool idling) {
return walking ? "walk" : (turning ? "turn" : (idling ? "idle" : ""));
};
QString toStop = singleRole(_isWalking && !isWalking, _isTurning && !isTurning, _isIdle && !isIdle);
if (!toStop.isEmpty()) {
//qCDebug(animation) << "isTurning" << isTurning << "fronts" << front << _lastFront << glm::angle(front, _lastFront) << rotationalSpeed;
stopAnimationByRole(toStop);
}
QString newRole = singleRole(isWalking && !_isWalking, isTurning && !_isTurning, isIdle && !_isIdle);
if (!newRole.isEmpty()) {
startAnimationByRole(newRole);
qCDebug(animation) << deltaTime << ":" << worldVelocity << "." << front << "=> " << forwardSpeed << newRole;
}
_lastPosition = worldPosition;
_lastFront = front;
_isWalking = isWalking;
_isTurning = isTurning;
_isIdle = isIdle;
}
}
void Rig::updateAnimations(float deltaTime, glm::mat4 parentTransform) {
int nAnimationsSoFar = 0;
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->setMix(1.0f / ++nAnimationsSoFar);
handle->setPriority(1.0);
handle->simulate(deltaTime);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i, parentTransform);
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].resetTransformChanged();
}
}
bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return false;
}
if (freeLineage.isEmpty()) {
return false;
}
if (lastFreeIndex == -1) {
lastFreeIndex = freeLineage.last();
}
// this is a cyclic coordinate descent algorithm: see
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
const int ITERATION_COUNT = 1;
glm::vec3 worldAlignment = alignment;
for (int i = 0; i < ITERATION_COUNT; i++) {
// first, try to rotate the end effector as close as possible to the target rotation, if any
glm::quat endRotation;
if (useRotation) {
JointState& state = _jointStates[jointIndex];
state.setRotationInBindFrame(rotation, priority);
endRotation = state.getRotationInBindFrame();
}
// then, we go from the joint upwards, rotating the end as close as possible to the target
glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].getTransform());
for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) {
int index = freeLineage.at(j);
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
if (!(joint.isFree || allIntermediatesFree)) {
continue;
}
glm::vec3 jointPosition = extractTranslation(state.getTransform());
glm::vec3 jointVector = endPosition - jointPosition;
glm::quat oldCombinedRotation = state.getRotation();
glm::quat combinedDelta;
float combinedWeight;
if (useRotation) {
combinedDelta = safeMix(rotation * glm::inverse(endRotation),
rotationBetween(jointVector, position - jointPosition), 0.5f);
combinedWeight = 2.0f;
} else {
combinedDelta = rotationBetween(jointVector, position - jointPosition);
combinedWeight = 1.0f;
}
if (alignment != glm::vec3() && j > 1) {
jointVector = endPosition - jointPosition;
glm::vec3 positionSum;
for (int k = j - 1; k > 0; k--) {
int index = freeLineage.at(k);
updateJointState(index, parentTransform);
positionSum += extractTranslation(_jointStates.at(index).getTransform());
}
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
glm::cross(positionSum / (j - 1.0f) - jointPosition, jointVector));
glm::vec3 projectedAlignment = glm::cross(jointVector, glm::cross(worldAlignment, jointVector));
const float LENGTH_EPSILON = 0.001f;
if (glm::length(projectedCenterOfMass) > LENGTH_EPSILON && glm::length(projectedAlignment) > LENGTH_EPSILON) {
combinedDelta = safeMix(combinedDelta, rotationBetween(projectedCenterOfMass, projectedAlignment),
1.0f / (combinedWeight + 1.0f));
}
}
state.applyRotationDelta(combinedDelta, true, priority);
glm::quat actualDelta = state.getRotation() * glm::inverse(oldCombinedRotation);
endPosition = actualDelta * jointVector + jointPosition;
if (useRotation) {
endRotation = actualDelta * endRotation;
}
}
}
// now update the joint states from the top
for (int j = freeLineage.size() - 1; j >= 0; j--) {
updateJointState(freeLineage.at(j), parentTransform);
}
return true;
}
void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
const QVector<int>& freeLineage, glm::mat4 parentTransform) {
// NOTE: targetRotation is from bind- to model-frame
if (endIndex == -1 || _jointStates.isEmpty()) {
return;
}
if (freeLineage.isEmpty()) {
return;
}
int numFree = freeLineage.size();
// store and remember topmost parent transform
glm::mat4 topParentTransform;
{
int index = freeLineage.last();
const JointState& state = _jointStates.at(index);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
topParentTransform = parentTransform;
} else {
topParentTransform = _jointStates[parentIndex].getTransform();
}
}
// this is a cyclic coordinate descent algorithm: see
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
// keep track of the position of the end-effector
JointState& endState = _jointStates[endIndex];
glm::vec3 endPosition = endState.getPosition();
float distanceToGo = glm::distance(targetPosition, endPosition);
const int MAX_ITERATION_COUNT = 2;
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
int numIterations = 0;
do {
++numIterations;
// moving up, rotate each free joint to get endPosition closer to target
for (int j = 1; j < numFree; j++) {
int nextIndex = freeLineage.at(j);
JointState& nextState = _jointStates[nextIndex];
FBXJoint nextJoint = nextState.getFBXJoint();
if (! nextJoint.isFree) {
continue;
}
glm::vec3 pivot = nextState.getPosition();
glm::vec3 leverArm = endPosition - pivot;
float leverLength = glm::length(leverArm);
if (leverLength < EPSILON) {
continue;
}
glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot);
// We want to mix the shortest rotation with one that will pull the system down with gravity
// so that limbs don't float unrealistically. To do this we compute a simplified center of mass
// where each joint has unit mass and we don't bother averaging it because we only need direction.
if (j > 1) {
glm::vec3 centerOfMass(0.0f);
for (int k = 0; k < j; ++k) {
int massIndex = freeLineage.at(k);
centerOfMass += _jointStates[massIndex].getPosition() - pivot;
}
// the gravitational effect is a rotation that tends to align the two cross products
const glm::vec3 worldAlignment = glm::vec3(0.0f, -1.0f, 0.0f);
glm::quat gravityDelta = rotationBetween(glm::cross(centerOfMass, leverArm),
glm::cross(worldAlignment, leverArm));
float gravityAngle = glm::angle(gravityDelta);
const float MIN_GRAVITY_ANGLE = 0.1f;
float mixFactor = 0.5f;
if (gravityAngle < MIN_GRAVITY_ANGLE) {
// the final rotation is a mix of the two
mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE;
}
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
}
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
// in the process. This provides stability to the IK solution for most models.
glm::quat oldNextRotation = nextState.getRotation();
float mixFactor = 0.03f;
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
// measure the result of the rotation which may have been modified by
// blending and constraints
glm::quat actualDelta = nextState.getRotation() * glm::inverse(oldNextRotation);
endPosition = pivot + actualDelta * leverArm;
}
// recompute transforms from the top down
glm::mat4 currentParentTransform = topParentTransform;
for (int j = numFree - 1; j >= 0; --j) {
JointState& freeState = _jointStates[freeLineage.at(j)];
freeState.computeTransform(currentParentTransform);
currentParentTransform = freeState.getTransform();
}
// measure our success
endPosition = endState.getPosition();
distanceToGo = glm::distance(targetPosition, endPosition);
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
// set final rotation of the end joint
endState.setRotationInBindFrame(targetRotation, priority, true);
}
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return false;
}
foreach (int index, freeLineage) {
JointState& state = _jointStates[index];
state.restoreRotation(fraction, priority);
}
return true;
}
float Rig::getLimbLength(int jointIndex, const QVector<int>& freeLineage,
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return 0.0f;
}
float length = 0.0f;
float lengthScale = (scale.x + scale.y + scale.z) / 3.0f;
for (int i = freeLineage.size() - 2; i >= 0; i--) {
length += fbxJoints.at(freeLineage.at(i)).distanceToParent * lengthScale;
}
return length;
}
glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority, bool constrain) {
glm::quat endRotation;
if (jointIndex == -1 || _jointStates.isEmpty()) {
return endRotation;
}
JointState& state = _jointStates[jointIndex];
state.setRotationInBindFrame(rotation, priority, constrain);
endRotation = state.getRotationInBindFrame();
return endRotation;
}
glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return glm::vec3();
}
return _jointStates[jointIndex].getDefaultTranslationInConstrainedFrame();
}
glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, bool constrain, float mix) {
glm::quat endRotation;
if (jointIndex == -1 || _jointStates.isEmpty()) {
return endRotation;
}
JointState& state = _jointStates[jointIndex];
state.setRotationInConstrainedFrame(targetRotation, priority, constrain, mix);
endRotation = state.getRotationInConstrainedFrame();
return endRotation;
}
void Rig::updateVisibleJointStates() {
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
}
void Rig::setJointTransform(int jointIndex, glm::mat4 newTransform) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return;
}
_jointStates[jointIndex].setTransform(newTransform);
}
void Rig::setJointVisibleTransform(int jointIndex, glm::mat4 newTransform) {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return;
}
_jointStates[jointIndex].setVisibleTransform(newTransform);
}
void Rig::applyJointRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return;
}
_jointStates[jointIndex].applyRotationDelta(delta, constrain, priority);
}
glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return glm::quat();
}
return _jointStates[jointIndex].getDefaultRotationInParentFrame();
}
void Rig::updateFromHeadParameters(const HeadParameters& params) {
updateLeanJoint(params.leanJointIndex, params.leanSideways, params.leanForward, params.torsoTwist);
updateNeckJoint(params.neckJointIndex, params.localHeadOrientation, params.leanSideways, params.leanForward, params.torsoTwist);
updateEyeJoint(params.leftEyeJointIndex, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
}
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
// get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index));
setJointRotationInConstrainedFrame(index,
glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) *
glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) *
getJointState(index).getFBXJoint().rotation, DEFAULT_PRIORITY);
}
}
void Rig::updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
auto joint = _jointStates[index].getFBXJoint();
// get the rotation axes in joint space and use them to adjust the rotation
glm::mat3 axes = glm::mat3_cast(glm::quat());
glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() *
glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation)));
glm::vec3 pitchYawRoll = safeEulerAngles(localHeadOrientation);
glm::vec3 lean = glm::radians(glm::vec3(leanForward, torsoTwist, leanSideways));
pitchYawRoll -= lean;
setJointRotationInConstrainedFrame(index,
glm::angleAxis(-pitchYawRoll.z, glm::normalize(inverse * axes[2])) *
glm::angleAxis(pitchYawRoll.y, glm::normalize(inverse * axes[1])) *
glm::angleAxis(-pitchYawRoll.x, glm::normalize(inverse * axes[0])) *
joint.rotation, DEFAULT_PRIORITY);
}
}
void Rig::updateEyeJoint(int index, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
auto joint = _jointStates[index].getFBXJoint();
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
glm::mat4 inverse = glm::inverse(parentState.getTransform() *
glm::translate(getJointDefaultTranslationInConstrainedFrame(index)) *
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
glm::vec3 front = glm::vec3(inverse * glm::vec4(worldHeadOrientation * IDENTITY_FRONT, 0.0f));
glm::vec3 lookAtDelta = lookAt;
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(lookAtDelta + glm::length(lookAtDelta) * saccade, 1.0f));
glm::quat between = rotationBetween(front, lookAt);
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
float angle = glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE);
glm::quat rot = glm::angleAxis(angle, glm::axis(between));
setJointRotationInConstrainedFrame(index, rot * joint.rotation, DEFAULT_PRIORITY);
}
}

View file

@ -0,0 +1,171 @@
//
// Rig.h
// libraries/animation/src/
//
// Produces animation data and hip placement for the current timestamp.
//
// Created by Howard Stearns, Seth Alves, Anthony Thibault, Andrew Meadows on 7/15/15.
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*
Things we want to be able to do, that I think we cannot do now:
* Stop an animation at a given time so that it can be examined visually or in a test harness. (I think we can already stop animation and set frame to a computed float? But does that move the bones?)
* Play two animations, blending between them. (Current structure just has one, under script control.)
* Fade in an animation over another.
* Apply IK, lean, head pointing or other overrides relative to previous position.
All of this depends on coordinated state.
TBD:
- What are responsibilities of Animation/AnimationPointer/AnimationCache/AnimationDetails/AnimationObject/AnimationLoop?
Is there common/copied code (e.g., ScriptableAvatar::update)?
- How do attachments interact with the physics of the attached entity? E.g., do hand joints need to reflect held object
physics?
- Is there any current need (i.e., for initial campatability) to have multiple animations per role (e.g., idle) with the
system choosing randomly?
- Distribute some doc from here to the right files if it turns out to be correct:
- AnimationDetails is a script-useable copy of animation state, analogous to EntityItemProperties, but without anything
equivalent to editEntity.
But what's the intended difference vs AnimationObjection? Maybe AnimationDetails is to Animation as AnimationObject
is to AnimationPointer?
*/
#ifndef __hifi__Rig__
#define __hifi__Rig__
#include <QObject>
#include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS
class AnimationHandle;
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
// typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
class Rig;
typedef std::shared_ptr<Rig> RigPointer;
class Rig : public QObject, public std::enable_shared_from_this<Rig> {
public:
struct HeadParameters {
float leanSideways = 0.0f; // degrees
float leanForward = 0.0f; // degrees
float torsoTwist = 0.0f; // degrees
glm::quat localHeadOrientation = glm::quat();
glm::quat worldHeadOrientation = glm::quat();
glm::vec3 eyeLookAt = glm::vec3(); // world space
glm::vec3 eyeSaccade = glm::vec3(); // world space
int leanJointIndex = -1;
int neckJointIndex = -1;
int leftEyeJointIndex = -1;
int rightEyeJointIndex = -1;
void dump() const;
};
virtual ~Rig() {}
RigPointer getRigPointer() { return shared_from_this(); }
AnimationHandlePointer createAnimationHandle();
void removeAnimationHandle(const AnimationHandlePointer& handle);
bool removeRunningAnimation(AnimationHandlePointer animationHandle);
void addRunningAnimation(AnimationHandlePointer animationHandle);
bool isRunningAnimation(AnimationHandlePointer animationHandle);
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
void deleteAnimations();
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
void stopAnimation(const QString& url);
void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
void stopAnimationByRole(const QString& role);
void addAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f,
float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f,
float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList(), bool startAutomatically = false);
float initJointStates(QVector<JointState> states, glm::mat4 parentTransform);
bool jointStatesEmpty() { return _jointStates.isEmpty(); };
int getJointStateCount() const { return _jointStates.size(); }
int indexOfJoint(const QString& jointName) ;
void initJointTransforms(glm::mat4 parentTransform);
void clearJointTransformTranslation(int jointIndex);
void reset(const QVector<FBXJoint>& fbxJoints);
bool getJointStateRotation(int index, glm::quat& rotation) const;
void applyJointRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority);
JointState getJointState(int jointIndex) const; // XXX
bool getVisibleJointState(int index, glm::quat& rotation) const;
void clearJointState(int index);
void clearJointStates();
void clearJointAnimationPriority(int index);
float getJointAnimatinoPriority(int index);
void setJointAnimatinoPriority(int index, float newPriority);
void setJointState(int index, bool valid, const glm::quat& rotation, float priority);
void restoreJointRotation(int index, float fraction, float priority);
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const;
bool getJointPosition(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const;
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const;
bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const;
bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& result, glm::quat rotation) const;
glm::mat4 getJointTransform(int jointIndex) const;
void setJointTransform(int jointIndex, glm::mat4 newTransform);
glm::mat4 getJointVisibleTransform(int jointIndex) const;
void setJointVisibleTransform(int jointIndex, glm::mat4 newTransform);
// Start or stop animations as needed.
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation);
// Regardless of who started the animations or how many, update the joints.
void updateAnimations(float deltaTime, glm::mat4 parentTransform);
bool setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority,
const QVector<int>& freeLineage, glm::mat4 parentTransform);
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
const QVector<int>& freeLineage, glm::mat4 parentTransform);
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
const glm::vec3 scale, const QVector<FBXJoint>& fbxJoints) const;
glm::quat setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority, bool constrain = false);
glm::vec3 getJointDefaultTranslationInConstrainedFrame(int jointIndex);
glm::quat setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation,
float priority, bool constrain = false, float mix = 1.0f);
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
void updateVisibleJointStates();
virtual void updateJointState(int index, glm::mat4 parentTransform) = 0;
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
void updateFromHeadParameters(const HeadParameters& params);
protected:
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
void updateNeckJoint(int index, const glm::quat& localHeadOrientation, float leanSideways, float leanForward, float torsoTwist);
void updateEyeJoint(int index, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade);
QVector<JointState> _jointStates;
QList<AnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations;
bool _enableRig;
bool _isWalking;
bool _isTurning;
bool _isIdle;
glm::vec3 _lastFront;
glm::vec3 _lastPosition;
};
#endif /* defined(__hifi__Rig__) */

View file

@ -1037,9 +1037,14 @@ bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) {
localOutput->moveToThread(injector->getLocalBuffer()->thread());
// have it be stopped when that local buffer is about to close
connect(localOutput, &QAudioOutput::stateChanged, this, &AudioClient::audioStateChanged);
connect(this, &AudioClient::audioFinished, localOutput, &QAudioOutput::stop);
connect(this, &AudioClient::audioFinished, injector, &AudioInjector::stop);
// We don't want to stop this localOutput and injector whenever this AudioClient singleton goes idle,
// only when the localOutput does. But the connection is to localOutput, so that it happens on the right thread.
connect(localOutput, &QAudioOutput::stateChanged, localOutput, [=](QAudio::State state) {
if (state == QAudio::IdleState) {
localOutput->stop();
injector->stop();
}
});
connect(injector->getLocalBuffer(), &QIODevice::aboutToClose, localOutput, &QAudioOutput::stop);
@ -1358,9 +1363,3 @@ void AudioClient::saveSettings() {
windowSecondsForDesiredReduction.set(_receivedAudioStream.getWindowSecondsForDesiredReduction());
repetitionWithFade.set(_receivedAudioStream.getRepetitionWithFade());
}
void AudioClient::audioStateChanged(QAudio::State state) {
if (state == QAudio::IdleState) {
emit audioFinished();
}
}

View file

@ -209,9 +209,6 @@ protected:
deleteLater();
}
private slots:
void audioStateChanged(QAudio::State state);
private:
void outputFormatChanged();

View file

@ -40,6 +40,7 @@
#include "RenderablePolyVoxEntityItem.h"
#include "EntitiesRendererLogging.h"
#include "AddressManager.h"
#include "EntityRig.h"
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
AbstractScriptingServicesInterface* scriptingServices) :
@ -695,7 +696,7 @@ Model* EntityTreeRenderer::allocateModel(const QString& url, const QString& coll
return model;
}
model = new Model();
model = new Model(std::make_shared<EntityRig>());
model->init();
model->setURL(QUrl(url));
model->setCollisionModelURL(QUrl(collisionUrl));
@ -728,7 +729,7 @@ Model* EntityTreeRenderer::updateModel(Model* original, const QString& newUrl, c
}
// create the model and correctly initialize it with the new url
model = new Model();
model = new Model(std::make_shared<EntityRig>());
model->init();
model->setURL(QUrl(newUrl));
model->setCollisionModelURL(QUrl(collisionUrl));

View file

@ -67,7 +67,7 @@ int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
}
Model* RenderableZoneEntityItem::getModel() {
Model* model = new Model();
Model* model = new Model(nullptr);
model->setIsWireframe(true);
model->init();
return model;

View file

@ -1248,7 +1248,7 @@ class JointShapeInfo {
public:
JointShapeInfo() : numVertices(0),
sumVertexWeights(0.0f), sumWeightedRadii(0.0f), numVertexWeights(0),
averageVertex(0.0f), boneBegin(0.0f), averageRadius(0.0f) {
boneBegin(0.0f), averageRadius(0.0f) {
}
// NOTE: the points here are in the "joint frame" which has the "jointEnd" at the origin
@ -1256,9 +1256,8 @@ public:
float sumVertexWeights; // sum of all vertex weights
float sumWeightedRadii; // sum of weighted vertices
int numVertexWeights; // num vertices that contributed to sums
glm::vec3 averageVertex;// average of all mesh vertices (in joint frame)
glm::vec3 boneBegin; // parent joint location (in joint frame)
float averageRadius; // average distance from mesh points to averageVertex
float averageRadius;
};
class AnimationCurve {
@ -2280,8 +2279,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
joint.boneRadius = 0.0f;
joint.inverseBindRotation = joint.inverseDefaultRotation;
joint.name = model.name;
joint.shapePosition = glm::vec3(0.0f);
joint.shapeType = INVALID_SHAPE;
foreach (const QString& childID, childMap.values(modelID)) {
QString type = typeFlags.value(childID);
@ -2551,7 +2548,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
int jointIndex = fbxCluster.jointIndex;
FBXJoint& joint = geometry.joints[jointIndex];
glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform;
glm::quat rotateMeshToJoint = glm::inverse(extractRotation(transformJointToMesh));
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
glm::vec3 boneBegin = boneEnd;
glm::vec3 boneDirection;
@ -2585,8 +2581,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj);
++jointShapeInfo.numVertexWeights;
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
jointShapeInfo.averageVertex += vertexInJointFrame;
++jointShapeInfo.numVertices;
}
@ -2632,7 +2626,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform;
glm::quat rotateMeshToJoint = glm::inverse(extractRotation(transformJointToMesh));
glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
glm::vec3 boneBegin = boneEnd;
@ -2655,9 +2648,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
jointShapeInfo.sumVertexWeights += radiusWeight;
jointShapeInfo.sumWeightedRadii += radiusWeight * radiusScale * glm::distance(vertex, boneEnd - boneDirection * proj);
++jointShapeInfo.numVertexWeights;
glm::vec3 vertexInJointFrame = rotateMeshToJoint * (radiusScale * (vertex - boneEnd));
jointShapeInfo.averageVertex += vertexInJointFrame;
averageVertex += vertex;
}
int numVertices = extracted.mesh.vertices.size();
@ -2690,7 +2680,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
meshIDsToMeshIndices.insert(it.key(), meshIndex);
}
// now that all joints have been scanned, compute a collision shape for each joint
// now that all joints have been scanned, compute a radius for each bone
glm::vec3 defaultCapsuleAxis(0.0f, 1.0f, 0.0f);
for (int i = 0; i < geometry.joints.size(); ++i) {
FBXJoint& joint = geometry.joints[i];
@ -2708,40 +2698,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
joint.boneRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights;
}
// we use a capsule if the joint had ANY mesh vertices successfully projected onto the bone
// the joint is "capsule-like" if it had ANY mesh vertices successfully projected onto the bone
// AND its boneRadius is not too close to zero
bool collideLikeCapsule = jointShapeInfo.numVertexWeights > 0
&& glm::length(jointShapeInfo.boneBegin) > EPSILON;
if (collideLikeCapsule) {
joint.shapeRotation = rotationBetween(defaultCapsuleAxis, jointShapeInfo.boneBegin);
joint.shapePosition = 0.5f * jointShapeInfo.boneBegin;
joint.shapeType = CAPSULE_SHAPE;
} else {
// collide the joint like a sphere
joint.shapeType = SPHERE_SHAPE;
if (jointShapeInfo.numVertices > 0) {
jointShapeInfo.averageVertex /= (float)jointShapeInfo.numVertices;
joint.shapePosition = jointShapeInfo.averageVertex;
} else {
joint.shapePosition = glm::vec3(0.0f);
}
if (!collideLikeCapsule) {
// this joint's mesh did not successfully project onto the bone axis
// so it isn't "capsule-like" and we need to estimate its radius a different way:
// the average radius to the average point.
if (jointShapeInfo.numVertexWeights == 0
&& jointShapeInfo.numVertices > 0) {
// the bone projection algorithm was not able to compute the joint radius
// so we use an alternative measure
jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices;
joint.boneRadius = jointShapeInfo.averageRadius;
}
float distanceFromEnd = glm::length(joint.shapePosition);
float distanceFromBegin = glm::distance(joint.shapePosition, jointShapeInfo.boneBegin);
if (distanceFromEnd > joint.distanceToParent && distanceFromBegin > joint.distanceToParent) {
// The shape is further from both joint endpoints than the endpoints are from each other
// which probably means the model has a bad transform somewhere. We disable this shape
// by setting its type to INVALID_SHAPE.
joint.shapeType = INVALID_SHAPE;
}
}
}
geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString());

View file

@ -25,7 +25,6 @@
#include <Extents.h>
#include <Transform.h>
#include <ShapeInfo.h>
#include <model/Geometry.h>
#include <model/Material.h>
@ -78,9 +77,6 @@ public:
glm::quat inverseBindRotation;
glm::mat4 bindTransform;
QString name;
glm::vec3 shapePosition; // in joint frame
glm::quat shapeRotation; // in joint frame
quint8 shapeType;
bool isSkeletonJoint;
};

View file

@ -433,8 +433,6 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
geometry.joints[0].rotationMin = glm::vec3(0, 0, 0);
geometry.joints[0].rotationMax = glm::vec3(0, 0, 0);
geometry.joints[0].name = "OBJ";
geometry.joints[0].shapePosition = glm::vec3(0, 0, 0);
geometry.joints[0].shapeType = SPHERE_SHAPE;
geometry.joints[0].isSkeletonJoint = true;
geometry.jointIndices["x"] = 1;
@ -617,9 +615,6 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) {
qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation;
qCDebug(modelformat) << " bindTransform" << joint.bindTransform;
qCDebug(modelformat) << " name" << joint.name;
qCDebug(modelformat) << " shapePosition" << joint.shapePosition;
qCDebug(modelformat) << " shapeRotation" << joint.shapeRotation;
qCDebug(modelformat) << " shapeType" << joint.shapeType;
qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint;
}

View file

@ -1700,7 +1700,7 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGe
// make the minimal amount of dummy geometry to satisfy Model
FBXJoint joint = { false, QVector<int>(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(),
glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(),
glm::mat4(), QString(""), glm::vec3(), glm::quat(), SHAPE_TYPE_NONE, false};
glm::mat4(), QString(""), false};
_geometry.joints.append(joint);
_geometry.leftEyeJointIndex = -1;
_geometry.rightEyeJointIndex = -1;

View file

@ -58,7 +58,7 @@ static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<QWeakPointer<Net
static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
Model::Model(QObject* parent) :
Model::Model(RigPointer rig, QObject* parent) :
QObject(parent),
_scale(1.0f, 1.0f, 1.0f),
_scaleToFit(false),
@ -67,6 +67,7 @@ Model::Model(QObject* parent) :
_snapModelToRegistrationPoint(false),
_snappedToRegistrationPoint(false),
_showTrueJointTransforms(true),
_cauterizeBones(false),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url("http://invalid.com"),
@ -79,13 +80,13 @@ Model::Model(QObject* parent) :
_calculatedMeshTrianglesValid(false),
_meshGroupsKnown(false),
_isWireframe(false),
_renderCollisionHull(false) {
_renderCollisionHull(false),
_rig(rig) {
// we may have been created in the network thread, but we live in the main thread
if (_viewState) {
moveToThread(_viewState->getMainThread());
}
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
}
@ -214,10 +215,6 @@ void Model::setScaleInternal(const glm::vec3& scale) {
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
_scale = scale;
initJointTransforms();
if (_shapes.size() > 0) {
clearShapes();
buildShapes();
}
}
}
@ -243,22 +240,12 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
};
void Model::initJointTransforms() {
// compute model transforms
int numStates = _jointStates.size();
for (int i = 0; i < numStates; ++i) {
JointState& state = _jointStates[i];
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
// NOTE: in practice geometry.offset has a non-unity scale (rather than a translation)
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.initTransform(parentTransform);
} else {
const JointState& parentState = _jointStates.at(parentIndex);
state.initTransform(parentState.getTransform());
}
if (!_geometry) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_rig->initJointTransforms(parentTransform);
}
void Model::init() {
@ -387,14 +374,8 @@ void Model::init() {
}
void Model::reset() {
if (_jointStates.isEmpty()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
}
_rig->reset(geometry.joints);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
@ -427,28 +408,33 @@ bool Model::updateGeometry() {
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
QVector<JointState> newJointStates = createJointStates(newGeometry);
if (! _jointStates.isEmpty()) {
if (! _rig->jointStatesEmpty()) {
// copy the existing joint states
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
it != oldGeometry.jointIndices.constEnd(); it++) {
it != oldGeometry.jointIndices.constEnd(); it++) {
int oldIndex = it.value() - 1;
int newIndex = newGeometry.getJointIndex(it.key());
if (newIndex != -1) {
newJointStates[newIndex].copyState(_jointStates[oldIndex]);
newJointStates[newIndex].copyState(_rig->getJointState(oldIndex));
}
}
}
}
deleteGeometry();
_dilatedTextures.clear();
if (!geometry) {
std::cout << "WARNING: no geometry in Model::updateGeometry\n";
}
setGeometry(geometry);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
initJointStates(newJointStates);
needToRebuild = true;
} else if (_jointStates.isEmpty()) {
} else if (_rig->jointStatesEmpty()) {
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
if (fbxGeometry.joints.size() > 0) {
initJointStates(createJointStates(fbxGeometry));
@ -466,6 +452,7 @@ bool Model::updateGeometry() {
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
MeshState state;
state.clusterMatrices.resize(mesh.clusters.size());
state.cauterizedClusterMatrices.resize(mesh.clusters.size());
_meshStates.append(state);
auto buffer = std::make_shared<gpu::Buffer>();
@ -484,22 +471,9 @@ bool Model::updateGeometry() {
// virtual
void Model::initJointStates(QVector<JointState> states) {
_jointStates = states;
initJointTransforms();
int numStates = _jointStates.size();
float radius = 0.0f;
for (int i = 0; i < numStates; ++i) {
float distance = glm::length(_jointStates[i].getPosition());
if (distance > radius) {
radius = distance;
}
_jointStates[i].buildConstraint();
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
_boundingRadius = radius;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_boundingRadius = _rig->initJointStates(states, parentTransform);
}
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
@ -1061,47 +1035,20 @@ glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const {
return translatedPoint;
}
bool Model::getJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
const JointState& state = _jointStates.at(index);
rotation = state.getRotationInConstrainedFrame();
return !state.rotationIsDefault(rotation);
return _rig->getJointStateRotation(index, rotation);
}
bool Model::getVisibleJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
const JointState& state = _jointStates.at(index);
rotation = state.getVisibleRotationInConstrainedFrame();
return !state.rotationIsDefault(rotation);
return _rig->getVisibleJointState(index, rotation);
}
void Model::clearJointState(int index) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
state.setRotationInConstrainedFrame(glm::quat(), 0.0f);
}
}
void Model::clearJointAnimationPriority(int index) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index]._animationPriority = 0.0f;
}
_rig->clearJointState(index);
}
void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
state.setRotationInConstrainedFrame(rotation, priority);
} else {
state.restoreRotation(1.0f, priority);
}
}
_rig->setJointState(index, valid, rotation, priority);
}
int Model::getParentJointIndex(int jointIndex) const {
@ -1176,62 +1123,31 @@ void Model::setCollisionModelURL(const QUrl& url) {
}
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in world-frame
position = _translation + _rotation * _jointStates[jointIndex].getPosition();
return true;
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
}
bool Model::getJointPosition(int jointIndex, glm::vec3& position) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in model-frame
position = extractTranslation(_jointStates[jointIndex].getTransform());
return true;
return _rig->getJointPosition(jointIndex, position);
}
bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _rotation * _jointStates[jointIndex].getRotation();
return true;
return _rig->getJointRotationInWorldFrame(jointIndex, rotation, _rotation);
}
bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _jointStates[jointIndex].getRotation();
return true;
return _rig->getJointRotation(jointIndex, rotation);
}
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _rotation * _jointStates[jointIndex].getRotation();
return true;
return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation);
}
bool Model::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
// position is in world-frame
position = _translation + _rotation * _jointStates[jointIndex].getVisiblePosition();
return true;
return _rig->getVisibleJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
}
bool Model::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
rotation = _rotation * _jointStates[jointIndex].getVisibleRotation();
return true;
return _rig->getVisibleJointRotationInWorldFrame(jointIndex, rotation, _rotation);
}
QStringList Model::getJointNames() const {
@ -1244,26 +1160,6 @@ QStringList Model::getJointNames() const {
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
}
uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
return qHash(handle.data(), seed);
}
AnimationHandlePointer Model::createAnimationHandle() {
AnimationHandlePointer handle(new AnimationHandle(this));
handle->_self = handle;
_animationHandles.insert(handle);
return handle;
}
// virtual override from PhysicsEntity
void Model::buildShapes() {
// TODO: figure out how to load/build collision shapes for general models
}
void Model::updateShapePositions() {
// TODO: implement this when we know how to build shapes for regular Models
}
class Blender : public QRunnable {
public:
@ -1427,44 +1323,23 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
}
}
void Model::updateClusterMatrices() {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 modelToWorld = glm::mat4_cast(_rotation);
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
if (_showTrueJointTransforms) {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix;
}
} else {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getVisibleTransform() * cluster.inverseBindMatrix;
}
}
}
//virtual
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->updateAnimations(deltaTime, parentTransform);
}
void Model::simulateInternal(float deltaTime) {
// update the world space transforms for all joints
// update animations
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
handle->simulate(deltaTime);
}
for (int i = 0; i < _jointStates.size(); i++) {
updateJointState(i);
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].resetTransformChanged();
}
_shapesAreDirty = !_shapes.isEmpty();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
updateRig(deltaTime, parentTransform);
glm::mat4 zeroScale(glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
glm::mat4 modelToWorld = glm::mat4_cast(_rotation);
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
@ -1472,16 +1347,34 @@ void Model::simulateInternal(float deltaTime) {
if (_showTrueJointTransforms) {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getTransform() * cluster.inverseBindMatrix;
auto jointMatrix =_rig->getJointTransform(cluster.jointIndex);
state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
state.cauterizedClusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
}
}
} else {
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
state.clusterMatrices[j] = modelToWorld * _jointStates[cluster.jointIndex].getVisibleTransform() * cluster.inverseBindMatrix;
auto jointMatrix = _rig->getJointVisibleTransform(cluster.jointIndex);
state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
if (!_cauterizeBoneSet.empty()) {
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
jointMatrix = cauterizeMatrix;
}
state.cauterizedClusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
}
}
}
}
// post the blender if we're not currently waiting for one to finish
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
@ -1489,266 +1382,35 @@ void Model::simulateInternal(float deltaTime) {
}
}
void Model::updateJointState(int index) {
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
// compute model transforms
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
state.computeTransform(parentTransform);
} else {
// guard against out-of-bounds access to _jointStates
if (joint.parentIndex >= 0 && joint.parentIndex < _jointStates.size()) {
const JointState& parentState = _jointStates.at(parentIndex);
state.computeTransform(parentState.getTransform(), parentState.getTransformChanged());
}
}
}
void Model::updateVisibleJointStates() {
if (_showTrueJointTransforms) {
// no need to update visible transforms
return;
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
}
}
bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation, bool useRotation,
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return false;
}
int lastFreeIndex, bool allIntermediatesFree, const glm::vec3& alignment, float priority) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
if (freeLineage.isEmpty()) {
return false;
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
if (_rig->setJointPosition(jointIndex, position, rotation, useRotation,
lastFreeIndex, allIntermediatesFree, alignment, priority, freeLineage, parentTransform)) {
return true;
}
if (lastFreeIndex == -1) {
lastFreeIndex = freeLineage.last();
}
// this is a cyclic coordinate descent algorithm: see
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
const int ITERATION_COUNT = 1;
glm::vec3 worldAlignment = alignment;
for (int i = 0; i < ITERATION_COUNT; i++) {
// first, try to rotate the end effector as close as possible to the target rotation, if any
glm::quat endRotation;
if (useRotation) {
JointState& state = _jointStates[jointIndex];
state.setRotationInBindFrame(rotation, priority);
endRotation = state.getRotationInBindFrame();
}
// then, we go from the joint upwards, rotating the end as close as possible to the target
glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].getTransform());
for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) {
int index = freeLineage.at(j);
JointState& state = _jointStates[index];
const FBXJoint& joint = state.getFBXJoint();
if (!(joint.isFree || allIntermediatesFree)) {
continue;
}
glm::vec3 jointPosition = extractTranslation(state.getTransform());
glm::vec3 jointVector = endPosition - jointPosition;
glm::quat oldCombinedRotation = state.getRotation();
glm::quat combinedDelta;
float combinedWeight;
if (useRotation) {
combinedDelta = safeMix(rotation * glm::inverse(endRotation),
rotationBetween(jointVector, position - jointPosition), 0.5f);
combinedWeight = 2.0f;
} else {
combinedDelta = rotationBetween(jointVector, position - jointPosition);
combinedWeight = 1.0f;
}
if (alignment != glm::vec3() && j > 1) {
jointVector = endPosition - jointPosition;
glm::vec3 positionSum;
for (int k = j - 1; k > 0; k--) {
int index = freeLineage.at(k);
updateJointState(index);
positionSum += extractTranslation(_jointStates.at(index).getTransform());
}
glm::vec3 projectedCenterOfMass = glm::cross(jointVector,
glm::cross(positionSum / (j - 1.0f) - jointPosition, jointVector));
glm::vec3 projectedAlignment = glm::cross(jointVector, glm::cross(worldAlignment, jointVector));
const float LENGTH_EPSILON = 0.001f;
if (glm::length(projectedCenterOfMass) > LENGTH_EPSILON && glm::length(projectedAlignment) > LENGTH_EPSILON) {
combinedDelta = safeMix(combinedDelta, rotationBetween(projectedCenterOfMass, projectedAlignment),
1.0f / (combinedWeight + 1.0f));
}
}
state.applyRotationDelta(combinedDelta, true, priority);
glm::quat actualDelta = state.getRotation() * glm::inverse(oldCombinedRotation);
endPosition = actualDelta * jointVector + jointPosition;
if (useRotation) {
endRotation = actualDelta * endRotation;
}
}
}
// now update the joint states from the top
for (int j = freeLineage.size() - 1; j >= 0; j--) {
updateJointState(freeLineage.at(j));
}
_shapesAreDirty = !_shapes.isEmpty();
return true;
return false;
}
void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) {
// NOTE: targetRotation is from bind- to model-frame
if (endIndex == -1 || _jointStates.isEmpty()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage;
if (freeLineage.isEmpty()) {
return;
}
int numFree = freeLineage.size();
// store and remember topmost parent transform
glm::mat4 topParentTransform;
{
int index = freeLineage.last();
const JointState& state = _jointStates.at(index);
const FBXJoint& joint = state.getFBXJoint();
int parentIndex = joint.parentIndex;
if (parentIndex == -1) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
topParentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
} else {
topParentTransform = _jointStates[parentIndex].getTransform();
}
}
// this is a cyclic coordinate descent algorithm: see
// http://www.ryanjuckett.com/programming/animation/21-cyclic-coordinate-descent-in-2d
// keep track of the position of the end-effector
JointState& endState = _jointStates[endIndex];
glm::vec3 endPosition = endState.getPosition();
float distanceToGo = glm::distance(targetPosition, endPosition);
const int MAX_ITERATION_COUNT = 2;
const float ACCEPTABLE_IK_ERROR = 0.005f; // 5mm
int numIterations = 0;
do {
++numIterations;
// moving up, rotate each free joint to get endPosition closer to target
for (int j = 1; j < numFree; j++) {
int nextIndex = freeLineage.at(j);
JointState& nextState = _jointStates[nextIndex];
FBXJoint nextJoint = nextState.getFBXJoint();
if (! nextJoint.isFree) {
continue;
}
glm::vec3 pivot = nextState.getPosition();
glm::vec3 leverArm = endPosition - pivot;
float leverLength = glm::length(leverArm);
if (leverLength < EPSILON) {
continue;
}
glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot);
// We want to mix the shortest rotation with one that will pull the system down with gravity
// so that limbs don't float unrealistically. To do this we compute a simplified center of mass
// where each joint has unit mass and we don't bother averaging it because we only need direction.
if (j > 1) {
glm::vec3 centerOfMass(0.0f);
for (int k = 0; k < j; ++k) {
int massIndex = freeLineage.at(k);
centerOfMass += _jointStates[massIndex].getPosition() - pivot;
}
// the gravitational effect is a rotation that tends to align the two cross products
const glm::vec3 worldAlignment = glm::vec3(0.0f, -1.0f, 0.0f);
glm::quat gravityDelta = rotationBetween(glm::cross(centerOfMass, leverArm),
glm::cross(worldAlignment, leverArm));
float gravityAngle = glm::angle(gravityDelta);
const float MIN_GRAVITY_ANGLE = 0.1f;
float mixFactor = 0.5f;
if (gravityAngle < MIN_GRAVITY_ANGLE) {
// the final rotation is a mix of the two
mixFactor = 0.5f * gravityAngle / MIN_GRAVITY_ANGLE;
}
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
}
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
// in the process. This provides stability to the IK solution for most models.
glm::quat oldNextRotation = nextState.getRotation();
float mixFactor = 0.03f;
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
// measure the result of the rotation which may have been modified by
// blending and constraints
glm::quat actualDelta = nextState.getRotation() * glm::inverse(oldNextRotation);
endPosition = pivot + actualDelta * leverArm;
}
// recompute transforms from the top down
glm::mat4 parentTransform = topParentTransform;
for (int j = numFree - 1; j >= 0; --j) {
JointState& freeState = _jointStates[freeLineage.at(j)];
freeState.computeTransform(parentTransform);
parentTransform = freeState.getTransform();
}
// measure our success
endPosition = endState.getPosition();
distanceToGo = glm::distance(targetPosition, endPosition);
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
// set final rotation of the end joint
endState.setRotationInBindFrame(targetRotation, priority, true);
_shapesAreDirty = !_shapes.isEmpty();
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
_rig->inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
}
bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return false;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
foreach (int index, freeLineage) {
JointState& state = _jointStates[index];
state.restoreRotation(fraction, priority);
}
return true;
return _rig->restoreJointPosition(jointIndex, fraction, priority, freeLineage);
}
float Model::getLimbLength(int jointIndex) const {
if (jointIndex == -1 || _jointStates.isEmpty()) {
return 0.0f;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
float length = 0.0f;
float lengthScale = (_scale.x + _scale.y + _scale.z) / 3.0f;
for (int i = freeLineage.size() - 2; i >= 0; i--) {
length += geometry.joints.at(freeLineage.at(i)).distanceToParent * lengthScale;
}
return length;
}
void Model::renderJointCollisionShapes(float alpha) {
// implement this when we have shapes for regular models
return _rig->getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
}
bool Model::maybeStartBlender() {
@ -1814,24 +1476,15 @@ void Model::applyNextGeometry() {
void Model::deleteGeometry() {
_blendedVertexBuffers.clear();
_jointStates.clear();
_rig->clearJointStates();
_meshStates.clear();
clearShapes();
for (QSet<WeakAnimationHandlePointer>::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) {
AnimationHandlePointer handle = it->toStrongRef();
if (handle) {
handle->_jointMappings.clear();
it++;
} else {
it = _animationHandles.erase(it);
}
}
_rig->deleteAnimations();
if (_geometry) {
_geometry->clearLoadPriority(this);
}
_blendedBlendshapeCoefficients.clear();
}
@ -1943,7 +1596,9 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
args, locations);
{
updateVisibleJointStates();
if (!_showTrueJointTransforms) {
_rig->updateVisibleJointStates();
} // else no need to update visible transforms
}
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
@ -1969,12 +1624,21 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
}
if (isSkinned) {
batch._glUniformMatrix4fv(locations->clusterMatrices, state.clusterMatrices.size(), false,
(const float*)state.clusterMatrices.constData());
const float* bones;
if (_cauterizeBones) {
bones = (const float*)state.cauterizedClusterMatrices.constData();
} else {
bones = (const float*)state.clusterMatrices.constData();
}
batch._glUniformMatrix4fv(locations->clusterMatrices, state.clusterMatrices.size(), false, bones);
_transforms[0] = Transform();
_transforms[0].preTranslate(_translation);
} else {
_transforms[0] = Transform(state.clusterMatrices[0]);
if (_cauterizeBones) {
_transforms[0] = Transform(state.cauterizedClusterMatrices[0]);
} else {
_transforms[0] = Transform(state.clusterMatrices[0]);
}
_transforms[0].preTranslate(_translation);
}
batch.setModelTransform(_transforms[0]);

View file

@ -18,10 +18,10 @@
#include <QMutex>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <AABox.h>
#include <AnimationCache.h>
#include <DependencyManager.h>
#include <GeometryUtil.h>
#include <gpu/Stream.h>
@ -39,7 +39,6 @@
class AbstractViewStateInterface;
class QScriptEngine;
class Shape;
#include "RenderArgs.h"
class ViewFrustum;
@ -57,67 +56,16 @@ inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) {
/// A generic 3D model displaying geometry loaded from a URL.
class Model : public QObject, public PhysicsEntity {
Q_OBJECT
public:
typedef RenderArgs::RenderMode RenderMode;
static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; }
Model(QObject* parent = NULL);
Model(RigPointer rig, QObject* parent = nullptr);
virtual ~Model();
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to
void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions);
void setSnapModelToCenter(bool snapModelToCenter) {
setSnapModelToRegistrationPoint(snapModelToCenter, glm::vec3(0.5f,0.5f,0.5f));
};
bool getSnapModelToCenter() {
return _snapModelToRegistrationPoint && _registrationPoint == glm::vec3(0.5f,0.5f,0.5f);
}
void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
void setScale(const glm::vec3& scale);
const glm::vec3& getScale() const { return _scale; }
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
float getPupilDilation() const { return _pupilDilation; }
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
bool isActive() const { return _geometry && _geometry->isLoaded(); }
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
bool isVisible() const { return _isVisible; }
bool isLoaded() const { return _geometry && _geometry->isLoaded(); }
bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); }
void init();
void reset();
virtual void simulate(float deltaTime, bool fullUpdate = true);
void renderSetup(RenderArgs* args);
// new Scene/Engine rendering support
bool needsFixupInScene() { return !_readyWhenAdded && readyToAddToScene(); }
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) { return !_needsReload && isRenderable() && isActive() && isLoaded(); }
bool addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
bool addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
/// Sets the URL of the model to render.
/// \param fallback the URL of a fallback model to render if the requested model fails to load
@ -127,22 +75,131 @@ public:
bool retainCurrent = false, bool delayLoad = false);
const QUrl& getURL() const { return _url; }
// new Scene/Engine rendering support
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
bool needsFixupInScene() { return !_readyWhenAdded && readyToAddToScene(); }
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) {
return !_needsReload && isRenderable() && isActive() && isLoaded();
}
bool initWhenReady(render::ScenePointer scene);
bool addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
void renderSetup(RenderArgs* args);
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
bool isVisible() const { return _isVisible; }
AABox getPartBounds(int meshIndex, int partIndex);
void renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent);
bool maybeStartBlender();
/// Sets blended vertices computed in a separate thread.
void setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
bool isLoaded() const { return _geometry && _geometry->isLoaded(); }
bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); }
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
bool isWireframe() const { return _isWireframe; }
void init();
void reset();
void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions);
void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
virtual void simulate(float deltaTime, bool fullUpdate = true);
/// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
bool isActive() const { return _geometry && _geometry->isLoaded(); }
Q_INVOKABLE void setTextureWithNameToURL(const QString& name, const QUrl& url)
{ _geometry->setTextureWithNameToURL(name, url); }
bool convexHullContains(glm::vec3 point);
QStringList getJointNames() const;
/// Sets the joint state at the specified index.
void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f);
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, QString& extraInfo, bool pickAgainstTriangles = false);
// Set the model to use for collisions
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
const QUrl& getCollisionURL() const { return _collisionUrl; }
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
bool isWireframe() const { return _isWireframe; }
/// Returns a reference to the shared collision geometry.
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }
/// Sets the distance parameter used for LOD computations.
void setLODDistance(float distance) { _lodDistance = distance; }
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
void setSnapModelToCenter(bool snapModelToCenter) {
setSnapModelToRegistrationPoint(snapModelToCenter, glm::vec3(0.5f,0.5f,0.5f));
};
bool getSnapModelToCenter() {
return _snapModelToRegistrationPoint && _registrationPoint == glm::vec3(0.5f,0.5f,0.5f);
}
/// Returns the number of joint states in the model.
int getJointStateCount() const { return _rig->getJointStateCount(); }
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
/// \param jointIndex index of joint in model structure
/// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
/// Returns the extents of the model in its bind pose.
Extents getBindExtents() const;
/// Returns the extents of the model's mesh
Extents getMeshExtents() const;
void setScale(const glm::vec3& scale);
const glm::vec3& getScale() const { return _scale; }
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to
void setCauterizeBones(bool flag) { _cauterizeBones = flag; }
bool getCauterizeBones() const { return _cauterizeBones; }
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
protected:
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
float getPupilDilation() const { return _pupilDilation; }
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
/// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const;
@ -155,15 +212,6 @@ public:
/// Returns the scaled equivalent of a point in model space.
glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const;
/// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
/// Returns a reference to the shared collision geometry.
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
/// Returns the number of joint states in the model.
int getJointStateCount() const { return _jointStates.size(); }
/// Fetches the joint state at the specified index.
/// \return whether or not the joint state is "valid" (that is, non-default)
bool getJointState(int index, glm::quat& rotation) const;
@ -171,25 +219,12 @@ public:
/// Fetches the visible joint state at the specified index.
/// \return whether or not the joint state is "valid" (that is, non-default)
bool getVisibleJointState(int index, glm::quat& rotation) const;
/// Clear the joint states
void clearJointState(int index);
/// Clear the joint animation priority
void clearJointAnimationPriority(int index);
/// Sets the joint state at the specified index.
void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f);
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
/// Returns the index of the last free ancestor of the indexed joint, or -1 if not found.
int getLastFreeJointIndex(int jointIndex) const;
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
@ -199,52 +234,11 @@ public:
/// \return true if joint exists
bool getJointPosition(int jointIndex, glm::vec3& position) const;
/// \param jointIndex index of joint in model structure
/// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
QStringList getJointNames() const;
AnimationHandlePointer createAnimationHandle();
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
// virtual overrides from PhysicsEntity
virtual void buildShapes();
virtual void updateShapePositions();
virtual void renderJointCollisionShapes(float alpha);
bool maybeStartBlender();
/// Sets blended vertices computed in a separate thread.
void setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
void setShowTrueJointTransforms(bool show) { _showTrueJointTransforms = show; }
QVector<JointState>& getJointStates() { return _jointStates; }
const QVector<JointState>& getJointStates() const { return _jointStates; }
void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
Q_INVOKABLE void setTextureWithNameToURL(const QString& name, const QUrl& url)
{ _geometry->setTextureWithNameToURL(name, url); }
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, QString& extraInfo, bool pickAgainstTriangles = false);
bool convexHullContains(glm::vec3 point);
AABox getPartBounds(int meshIndex, int partIndex);
void renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent);
bool initWhenReady(render::ScenePointer scene);
protected:
QSharedPointer<NetworkGeometry> _geometry;
void setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry);
glm::vec3 _scale;
glm::vec3 _offset;
@ -257,34 +251,31 @@ protected:
bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space
bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to
bool _showTrueJointTransforms;
QVector<JointState> _jointStates;
class MeshState {
public:
QVector<glm::mat4> clusterMatrices;
QVector<glm::mat4> cauterizedClusterMatrices;
};
QVector<MeshState> _meshStates;
std::unordered_set<int> _cauterizeBoneSet;
bool _cauterizeBones;
// returns 'true' if needs fullUpdate after geometry change
bool updateGeometry();
virtual void initJointStates(QVector<JointState> states);
void setScaleInternal(const glm::vec3& scale);
void scaleToFit();
void snapToRegistrationPoint();
void simulateInternal(float deltaTime);
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
/// Updates the state of the joint at the specified index.
virtual void updateJointState(int index);
virtual void updateVisibleJointStates();
/// \param jointIndex index of joint in model structure
/// \param position position of joint in model-frame
/// \param rotation rotation of joint in model-frame
@ -302,11 +293,11 @@ protected:
/// the original position
/// \return true if the joint was found
bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f);
/// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's
/// first free ancestor.
float getLimbLength(int jointIndex) const;
/// Allow sub classes to force invalidating the bboxes
void invalidCalculatedMeshBoxes() {
_calculatedMeshBoxesValid = false;
@ -314,34 +305,28 @@ protected:
_calculatedMeshTrianglesValid = false;
}
// rebuild the clusterMatrices from the current jointStates
void updateClusterMatrices();
// hook for derived classes to be notified when setUrl invalidates the current model.
virtual void onInvalidate() {};
protected slots:
void geometryRefreshed();
private:
friend class AnimationHandle;
void applyNextGeometry();
void deleteGeometry();
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
QSharedPointer<NetworkGeometry> _nextGeometry;
float _lodDistance;
float _lodHysteresis;
float _nextLODHysteresis;
QSharedPointer<NetworkGeometry> _collisionGeometry;
float _pupilDilation;
QVector<float> _blendshapeCoefficients;
QUrl _url;
QUrl _collisionUrl;
bool _isVisible;
@ -351,10 +336,6 @@ private:
gpu::Batch _renderBatch;
QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures;
QSet<WeakAnimationHandlePointer> _animationHandles;
QList<AnimationHandlePointer> _runningAnimations;
QVector<float> _blendedBlendshapeCoefficients;
int _blendNumber;
@ -379,12 +360,12 @@ private:
QHash<QPair<int,int>, AABox> _calculatedMeshPartBoxes; // world coordinate AABoxes for all sub mesh part boxes
QHash<QPair<int,int>, qint64> _calculatedMeshPartOffset;
bool _calculatedMeshPartOffsetValid;
bool _calculatedMeshPartBoxesValid;
QVector<AABox> _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes
bool _calculatedMeshBoxesValid;
QVector< QVector<Triangle> > _calculatedMeshTriangles; // world coordinate triangles for all sub meshes
bool _calculatedMeshTrianglesValid;
QMutex _mutex;
@ -424,10 +405,10 @@ private:
IS_SHADOW_FLAG,
IS_MIRROR_FLAG, //THis means that the mesh is rendered mirrored, not the same as "Rear view mirror"
IS_WIREFRAME_FLAG,
NUM_FLAGS,
};
enum Flag {
IS_TRANSLUCENT = (1 << IS_TRANSLUCENT_FLAG),
HAS_LIGHTMAP = (1 << HAS_LIGHTMAP_FLAG),
@ -493,7 +474,7 @@ private:
RenderKey(int bitmask) : _flags(bitmask) {}
};
class RenderPipeline {
public:
gpu::PipelinePointer _pipeline;
@ -507,7 +488,7 @@ private:
public:
typedef RenderKey Key;
void addRenderPipeline(Key key, gpu::ShaderPointer& vertexShader, gpu::ShaderPointer& pixelShader);
void initLocations(gpu::ShaderPointer& program, Locations& locations);
@ -515,13 +496,16 @@ private:
static RenderPipelineLib _renderPipelineLib;
bool _renderCollisionHull;
QSet<std::shared_ptr<MeshPartPayload>> _transparentRenderItems;
QSet<std::shared_ptr<MeshPartPayload>> _opaqueRenderItems;
QMap<render::ItemID, render::PayloadPointer> _renderItems;
bool _readyWhenAdded = false;
bool _needsReload = true;
protected:
RigPointer _rig;
};
Q_DECLARE_METATYPE(QPointer<Model>)

View file

@ -11,17 +11,10 @@
#include "PhysicsEntity.h"
#include "PlaneShape.h"
#include "Shape.h"
#include "ShapeCollider.h"
#include "SphereShape.h"
PhysicsEntity::PhysicsEntity() :
_translation(0.0f),
_rotation(),
_boundingRadius(0.0f),
_shapesAreDirty(true),
_enableShapes(false) {
_boundingRadius(0.0f) {
}
PhysicsEntity::~PhysicsEntity() {
@ -29,143 +22,13 @@ PhysicsEntity::~PhysicsEntity() {
void PhysicsEntity::setTranslation(const glm::vec3& translation) {
if (_translation != translation) {
_shapesAreDirty = !_shapes.isEmpty();
_translation = translation;
}
}
void PhysicsEntity::setRotation(const glm::quat& rotation) {
if (_rotation != rotation) {
_shapesAreDirty = !_shapes.isEmpty();
_rotation = rotation;
}
}
void PhysicsEntity::setShapeBackPointers() {
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (shape) {
shape->setEntity(this);
}
}
}
void PhysicsEntity::setEnableShapes(bool enable) {
if (enable != _enableShapes) {
clearShapes();
_enableShapes = enable;
if (_enableShapes) {
buildShapes();
}
}
}
void PhysicsEntity::clearShapes() {
for (int i = 0; i < _shapes.size(); ++i) {
delete _shapes[i];
}
_shapes.clear();
}
bool PhysicsEntity::findRayIntersection(RayIntersectionInfo& intersection) const {
return ShapeCollider::findRayIntersection(_shapes, intersection);
}
bool PhysicsEntity::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
bool collided = false;
int numTheirShapes = shapes.size();
for (int i = 0; i < numTheirShapes; ++i) {
const Shape* theirShape = shapes[i];
if (!theirShape) {
continue;
}
int numOurShapes = _shapes.size();
for (int j = 0; j < numOurShapes; ++j) {
const Shape* ourShape = _shapes.at(j);
if (ourShape && ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
collided = true;
}
}
}
return collided;
}
bool PhysicsEntity::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions) {
bool collided = false;
SphereShape sphere(sphereRadius, sphereCenter);
for (int i = 0; i < _shapes.size(); i++) {
Shape* shape = _shapes[i];
if (!shape) {
continue;
}
if (ShapeCollider::collideShapes(&sphere, shape, collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
}
return collided;
}
bool PhysicsEntity::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
bool collided = false;
PlaneShape planeShape(plane);
for (int i = 0; i < _shapes.size(); i++) {
if (_shapes.at(i) && ShapeCollider::collideShapes(&planeShape, _shapes.at(i), collisions)) {
CollisionInfo* collision = collisions.getLastCollision();
collision->_data = (void*)(this);
collision->_intData = i;
collided = true;
}
}
return collided;
}
// -----------------------------------------------------------
// TODO: enforce this maximum when shapes are actually built. The gotcha here is
// that the Model class (derived from PhysicsEntity) expects numShapes == numJoints,
// so we have to modify that code to be safe.
const int MAX_SHAPES_PER_ENTITY = 256;
// the first 256 prime numbers
const int primes[256] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
1597, 1601, 1607, 1609, 1613, 1619 };
void PhysicsEntity::disableCollisions(int shapeIndexA, int shapeIndexB) {
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
_disabledCollisions.insert(primes[shapeIndexA] * primes[shapeIndexB]);
}
}
bool PhysicsEntity::collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const {
if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
return !_disabledCollisions.contains(primes[shapeIndexA] * primes[shapeIndexB]);
}
return false;
}

View file

@ -21,8 +21,6 @@
#include <CollisionInfo.h>
#include <RayIntersectionInfo.h>
class Shape;
class PhysicsEntity {
public:
@ -38,30 +36,10 @@ public:
const glm::quat& getRotation() const { return _rotation; }
float getBoundingRadius() const { return _boundingRadius; }
void setShapeBackPointers();
void setEnableShapes(bool enable);
virtual void buildShapes() = 0;
virtual void clearShapes();
const QVector<Shape*> getShapes() const { return _shapes; }
bool findRayIntersection(RayIntersectionInfo& intersection) const;
bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
bool findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions);
bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
void disableCollisions(int shapeIndexA, int shapeIndexB);
bool collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const;
protected:
glm::vec3 _translation;
glm::quat _rotation;
float _boundingRadius;
bool _shapesAreDirty;
bool _enableShapes;
QVector<Shape*> _shapes;
QSet<int> _disabledCollisions;
};
#endif // hifi_PhysicsEntity_h

View file

@ -0,0 +1,9 @@
# Declare dependencies
macro (setup_testcase_dependencies)
# link in the shared libraries
link_hifi_libraries(shared animation gpu fbx model)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase()

View file

@ -0,0 +1,107 @@
//
// RigTests.cpp
// tests/rig/src
//
// Created by Howard Stearns on 6/16/15
// 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
//
/* FIXME/TBD:
The following lower level functionality might be separated out into a separate class, covered by a separate test case class:
- With no input, initial pose is standing, arms at side
- Some single animation produces correct results at a given keyframe time.
- Some single animation produces correct results at a given interpolated time.
- Blend between two animations, started at separate times, produces correct result at a given interpolated time.
- Head orientation can be overridden to produce change that doesn't come from the playing animation.
- Hand position/orientation can be overridden to produce change that doesn't come from the playing animation.
- Hand position/orientation can be overrridden to produce elbow change that doesn't come from the playing animation.
- Respect scaling? (e.g., so that MyAvatar.increase/decreaseSize can alter rig, such that anti-scating and footfalls-on-stairs works)
Higher level functionality:
- start/stopAnimation adds the animation to that which is playing, blending/fading as needed.
- thrust causes walk role animation to be used.
- turning causes turn role animation to be used. (two tests, correctly symmetric left & right)
- walk/turn do not skate (footfalls match over-ground velocity)
- (Later?) walk up stairs / hills have proper footfall for terrain
- absence of above causes return to idle role animation to be used
- (later?) The lower-level head/hand placements respect previous state. E.g., actual hand movement can be slower than requested.
- (later) The lower-level head/hand placements can move whole skeleton. E.g., turning head past a limit may turn whole body. Reaching up can move shoulders and hips.
Backward-compatability operations. We should think of this behavior as deprecated:
- clearJointData return to standing. TBD: presumably with idle and all other animations NOT playing, until explicitly reenabled with a new TBD method?
- setJointData applies the given data. Same TBD.
These can be defined true or false, but the tests document the behavior and tells us if something's changed:
- An external change to the original skeleton IS/ISN'T seen by the rig.
- An external change to the original skeleton's head orientation IS/ISN'T seen by the rig.
- An external change to the original skeleton's hand orientiation IS/ISN'T seen by the rig.
*/
#include <iostream>
#include "FBXReader.h"
#include "OBJReader.h"
#include "AvatarRig.h" // We might later test Rig vs AvatarRig separately, but for now, we're concentrating on the main use case.
#include "RigTests.h"
QTEST_MAIN(RigTests)
void RigTests::initTestCase() {
//#define FROM_FILE "/Users/howardstearns/howardHiFi/Zack.fbx"
#ifdef FROM_FILE
QFile file(FROM_FILE);
QCOMPARE(file.open(QIODevice::ReadOnly), true);
FBXGeometry geometry = readFBX(file.readAll(), QVariantHash());
#else
QUrl fbxUrl("https://s3.amazonaws.com/hifi-public/models/skeletons/Zack/Zack.fbx");
QNetworkReply* reply = OBJReader().request(fbxUrl, false); // Just a convenience hack for synchronoud http request
auto fbxHttpCode = !reply->isFinished() ? -1 : reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QCOMPARE(fbxHttpCode, 200);
FBXGeometry geometry = readFBX(reply->readAll(), QVariantHash());
#endif
QVector<JointState> jointStates;
for (int i = 0; i < geometry.joints.size(); ++i) {
// Note that if the geometry is stack allocated and goes away, so will the joints. Hence the heap copy here.
FBXJoint* joint = new FBXJoint(geometry.joints[i]);
JointState state;
state.setFBXJoint(joint);
jointStates.append(state);
}
_rig = std::make_shared<AvatarRig>();
_rig->initJointStates(jointStates, glm::mat4());
std::cout << "Rig is ready " << geometry.joints.count() << " joints " << std::endl;
}
static void reportJoint(int index, JointState joint) { // Handy for debugging
std::cout << "\n";
std::cout << index << " " << joint.getFBXJoint().name.toUtf8().data() << "\n";
std::cout << " pos:" << joint.getPosition() << "/" << joint.getPositionInParentFrame() << " from " << joint.getParentIndex() << "\n";
std::cout << " rot:" << safeEulerAngles(joint.getRotation()) << "/" << safeEulerAngles(joint.getRotationInParentFrame()) << "/" << safeEulerAngles(joint.getRotationInBindFrame()) << "\n";
std::cout << "\n";
}
static void reportByName(RigPointer rig, const QString& name) {
int jointIndex = rig->indexOfJoint(name);
reportJoint(jointIndex, rig->getJointState(jointIndex));
}
static void reportAll(RigPointer rig) {
for (int i = 0; i < rig->getJointStateCount(); i++) {
JointState joint = rig->getJointState(i);
reportJoint(i, joint);
}
}
static void reportSome(RigPointer rig) {
QString names[] = {"Head", "Neck", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "Spine2", "Spine1", "Spine", "Hips", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase", "RightToe_End"};
for (auto name : names) {
reportByName(rig, name);
}
}
void RigTests::initialPoseArmsDown() {
//reportAll(_rig);
reportSome(_rig);
}

View file

@ -0,0 +1,55 @@
//
// RigTests.h
// tests/rig/src
//
// Created by Howard Stearns on 6/16/15
// 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
//
#ifndef hifi_RigTests_h
#define hifi_RigTests_h
#include <QtTest/QtTest>
#include <Rig.h>
//#include "../QTestExtensions.h"
// The QTest terminology is not consistent with itself or with industry:
// The whole directory, and the rig-tests target, doesn't seem to be a QTest concept, an corresponds roughly to a toplevel suite of suites.
// The directory can contain any number of classes like this one. (Don't forget to wipe your build dir, and rerun cmake when you add one.):
// QTest doc (http://doc.qt.io/qt-5/qtest-overview.html) calls this a "test case".
// The output of QTest's 'ctest' runner calls this a "test" when run in the whole directory (e.g., when reporting success/failure counts).
// The test case (like this class) can contain any number of test slots:
// QTest doc calls these "test functions"
// When you run a single test case executable (e.g., "rig-RigTests"), the (unlabeled) count includes these test functions and also the before method, which is auto generated as initTestCase.
// To build and run via make:
// make help | grep tests # shows all test targets, including all-tests and rig-tests.
// make all-tests # will compile and then die as soon as any test case dies, even if its not in your directory
// make rig-tests # will compile and run `ctest .` in the tests/rig directory, running all the test cases found there.
// Alas, only summary output is shown on stdout. The real results, including any stdout that your code does, is in tests/rig/Testing/Temporary/LastTest.log, or...
// tests/rig/rig-RigTests (or the executable corresponding to any test case you define here) will run just that case and give output directly.
//
// To build and run via Xcode:
// On some machines, xcode can't find cmake on the path it uses. I did, effectively: sudo ln -s `which cmake` /usr/bin
// Note the above make instructions.
// all-tests, rig-tests, and rig-RigTests are all targets:
// The first two of these show no output at all, but if there's a failure you can see it by clicking on the red failure in the "issue navigator" (or by externally viewing the .log above).
// The last (or any other individual test case executable) does show output in the Xcode output display.
class RigTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void initialPoseArmsDown();
private:
RigPointer _rig;
};
#endif // hifi_RigTests_h

View file

@ -12,6 +12,8 @@
#include <QVector>
#include "VHACDUtil.h"
const float COLLISION_TETRAHEDRON_SCALE = 0.25f;
// FBXReader jumbles the order of the meshes by reading them back out of a hashtable. This will put
// them back in the order in which they appeared in the file.
@ -113,6 +115,8 @@ void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result,
int index1 = triangles[i * 3 + 1] + indexStartOffset;
int index2 = triangles[i * 3 + 2] + indexStartOffset;
// TODO: skip triangles with a normal that points more negative-y than positive-y
glm::vec3 p0 = result.vertices[index0];
glm::vec3 p1 = result.vertices[index1];
glm::vec3 p2 = result.vertices[index2];
@ -125,12 +129,14 @@ void vhacd::VHACDUtil::fattenMeshes(const FBXMesh& mesh, FBXMesh& result,
continue;
}
// from the middle of the triangle, pull a point down to form a tetrahedron.
float dropAmount = 0;
dropAmount = glm::max(glm::length(p1 - p0), dropAmount);
dropAmount = glm::max(glm::length(p2 - p1), dropAmount);
dropAmount = glm::max(glm::length(p0 - p2), dropAmount);
dropAmount *= COLLISION_TETRAHEDRON_SCALE;
glm::vec3 p3 = av - glm::vec3(0, dropAmount, 0); // a point 1 meter below the average of this triangle's points
glm::vec3 p3 = av - glm::vec3(0.0f, dropAmount, 0.0f);
int index3 = result.vertices.size();
result.vertices << p3; // add the new point to the result mesh