mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 09:24:00 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into hobbes
This commit is contained in:
commit
4478ca79ca
66 changed files with 1366 additions and 838 deletions
|
@ -46,6 +46,9 @@ if (WIN32)
|
|||
# Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables
|
||||
# TODO: Remove when building 64-bit.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||
# always produce symbols as PDB files
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter")
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
|
|
@ -233,8 +233,8 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
}
|
||||
|
||||
if (_avatarBillboardTimer) {
|
||||
_avatarIdentityTimer->stop();
|
||||
delete _avatarIdentityTimer;
|
||||
_avatarBillboardTimer->stop();
|
||||
delete _avatarBillboardTimer;
|
||||
_avatarBillboardTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -364,10 +364,15 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
|||
}
|
||||
|
||||
void Agent::aboutToFinish() {
|
||||
_scriptEngine->stop();
|
||||
setIsAvatar(false);// will stop timers for sending billboards and identity packets
|
||||
if (_scriptEngine) {
|
||||
_scriptEngine->stop();
|
||||
}
|
||||
|
||||
_pingTimer->stop();
|
||||
delete _pingTimer;
|
||||
if (_pingTimer) {
|
||||
_pingTimer->stop();
|
||||
delete _pingTimer;
|
||||
}
|
||||
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
|
||||
|
|
|
@ -238,7 +238,6 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType());
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
int packetsSent = 0;
|
||||
|
||||
|
@ -272,18 +271,19 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
|
||||
auto it = missingSequenceNumbers.constBegin();
|
||||
|
||||
while (it != missingSequenceNumbers.constEnd()) {
|
||||
unsigned short int sequenceNumber = *it;
|
||||
nackPacketList->writePrimitive(sequenceNumber);
|
||||
++it;
|
||||
}
|
||||
|
||||
|
||||
if (nackPacketList->getNumPackets()) {
|
||||
if (it != missingSequenceNumbers.constEnd()) {
|
||||
auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType());
|
||||
|
||||
while (it != missingSequenceNumbers.constEnd()) {
|
||||
unsigned short int sequenceNumber = *it;
|
||||
nackPacketList->writePrimitive(sequenceNumber);
|
||||
++it;
|
||||
}
|
||||
|
||||
qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
|
||||
|
||||
|
||||
packetsSent += nackPacketList->getNumPackets();
|
||||
|
||||
|
||||
// send the list of nack packets
|
||||
nodeList->sendPacketList(std::move(nackPacketList), *destinationNode);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE)
|
|||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<TARGET_FILE:${TARGET_NAME}>"
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
93
examples/acScripts/flickeringLight.js
Normal file
93
examples/acScripts/flickeringLight.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// flickeringLight.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/09/29.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Creates an ephemeral flickering light that will randomly flicker as long as the script is running.
|
||||
// After the script stops running, the light will eventually disappear (~10 seconds later). This script
|
||||
// can run in the interface or in an assignment client and it will work equally well.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
function randFloat(low, high) {
|
||||
return low + Math.random() * (high - low);
|
||||
}
|
||||
|
||||
var LIGHT_NAME = "flickering fire";
|
||||
|
||||
var LIGHT_POSITION = {
|
||||
x: 551.13,
|
||||
y: 494.77,
|
||||
z: 502.26
|
||||
};
|
||||
|
||||
var LIGHT_COLOR = {
|
||||
red: 255,
|
||||
green: 100,
|
||||
blue: 28
|
||||
};
|
||||
|
||||
var ZERO_VEC = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var totalTime = 0;
|
||||
var lastUpdate = 0;
|
||||
var UPDATE_INTERVAL = 1 / 30; // 30fps
|
||||
|
||||
var MINIMUM_LIGHT_INTENSITY = 0.75;
|
||||
var MAXIMUM_LIGHT_INTENSITY = 2.75;
|
||||
var LIGHT_INTENSITY_RANDOMNESS = 0.3;
|
||||
var EPHEMERAL_LIFETIME = 60; // ephemeral entities will live for 60 seconds after script stops running
|
||||
|
||||
var LightMaker = {
|
||||
light: null,
|
||||
spawnLight: function() {
|
||||
print('CREATING LIGHT')
|
||||
var _this = this;
|
||||
_this.light = Entities.addEntity({
|
||||
type: "Light",
|
||||
name: LIGHT_NAME,
|
||||
position: LIGHT_POSITION,
|
||||
lifetime: EPHEMERAL_LIFETIME,
|
||||
color: LIGHT_COLOR,
|
||||
dimensions: { x: 10, y: 10, z: 10 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var hasSpawned = false;
|
||||
|
||||
function update(deltaTime) {
|
||||
|
||||
if (!Entities.serversExist() || !Entities.canRez()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSpawned === false) {
|
||||
hasSpawned = true;
|
||||
LightMaker.spawnLight();
|
||||
} else {
|
||||
totalTime += deltaTime;
|
||||
|
||||
// We don't want to edit the entity EVERY update cycle, because that's just a lot
|
||||
// of wasted bandwidth and extra effort on the server for very little visual gain
|
||||
if (totalTime - lastUpdate > UPDATE_INTERVAL) {
|
||||
var intensity = (MINIMUM_LIGHT_INTENSITY + (MAXIMUM_LIGHT_INTENSITY + (Math.sin(totalTime) * MAXIMUM_LIGHT_INTENSITY)));
|
||||
intensity += randFloat(-LIGHT_INTENSITY_RANDOMNESS, LIGHT_INTENSITY_RANDOMNESS);
|
||||
var properties = Entities.getEntityProperties(LightMaker.light, "age");
|
||||
var newLifetime = properties.age + EPHEMERAL_LIFETIME;
|
||||
Entities.editEntity(LightMaker.light, { type: "Light", intensity: intensity, lifetime: newLifetime });
|
||||
lastUpdate = totalTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
146
examples/actionInspector.js
Normal file
146
examples/actionInspector.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// actionInspector.js
|
||||
// examples
|
||||
//
|
||||
// Created by Seth Alves on 2015-9-30.
|
||||
// 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/utils.js");
|
||||
|
||||
|
||||
var INSPECT_RADIUS = 10;
|
||||
var overlays = {};
|
||||
|
||||
|
||||
var toType = function(obj) {
|
||||
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
|
||||
}
|
||||
|
||||
|
||||
function actionArgumentsToString(actionArguments) {
|
||||
var result = "type: " + actionArguments["type"] + "\n";
|
||||
for (var argumentName in actionArguments) {
|
||||
if (actionArguments.hasOwnProperty(argumentName)) {
|
||||
if (argumentName == "type") {
|
||||
continue;
|
||||
}
|
||||
var arg = actionArguments[argumentName];
|
||||
var argType = toType(arg);
|
||||
var argString = arg;
|
||||
if (argType == "object") {
|
||||
if (Object.keys(arg).length == 3) {
|
||||
argString = vec3toStr(arg, 1);
|
||||
}
|
||||
} else if (argType == "number") {
|
||||
argString = arg.toFixed(2);
|
||||
}
|
||||
result += argumentName + ": "
|
||||
// + toType(arg) + " -- "
|
||||
+ argString + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function updateOverlay(entityID, actionText) {
|
||||
var properties = Entities.getEntityProperties(entityID, ["position", "dimensions"]);
|
||||
var position = Vec3.sum(properties.position, {x:0, y:properties.dimensions.y, z:0});
|
||||
// print("position: " + vec3toStr(position) + " " + actionText);
|
||||
if (entityID in overlays) {
|
||||
var overlay = overlays[entityID];
|
||||
Overlays.editOverlay(overlay, {
|
||||
text: actionText,
|
||||
position: position
|
||||
});
|
||||
} else {
|
||||
var lines = actionText.split(/\r\n|\r|\n/);
|
||||
|
||||
var maxLineLength = lines[0].length;
|
||||
for (var i = 1; i < lines.length; i++) {
|
||||
if (lines[i].length > maxLineLength) {
|
||||
maxLineLength = lines[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
var textWidth = maxLineLength * 0.034; // XXX how to know this?
|
||||
var textHeight = .5;
|
||||
var numberOfLines = lines.length;
|
||||
var textMargin = 0.05;
|
||||
var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines;
|
||||
|
||||
overlays[entityID] = Overlays.addOverlay("text3d", {
|
||||
position: position,
|
||||
dimensions: { x: textWidth, y: textHeight },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
topMargin: textMargin,
|
||||
leftMargin: textMargin,
|
||||
bottomMargin: textMargin,
|
||||
rightMargin: textMargin,
|
||||
text: actionText,
|
||||
lineHeight: lineHeight,
|
||||
alpha: 0.9,
|
||||
backgroundAlpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
isFacingAvatar: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cleanup() {
|
||||
for (var entityID in overlays) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Script.setInterval(function() {
|
||||
var nearbyEntities = Entities.findEntities(MyAvatar.position, INSPECT_RADIUS);
|
||||
for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) {
|
||||
var entityID = nearbyEntities[entityIndex];
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
var actionText = ""
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var actionArgumentText = actionArgumentsToString(actionArguments);
|
||||
if (actionArgumentText != "") {
|
||||
actionText += "-----------------\n";
|
||||
actionText += actionArgumentText;
|
||||
}
|
||||
}
|
||||
if (actionText != "") {
|
||||
updateOverlay(entityID, actionText);
|
||||
}
|
||||
|
||||
// if an entity no longer has an action, remove its overlay
|
||||
if (actionIDs.length == 0) {
|
||||
if (entityID in overlays) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
delete overlays[entityID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if an entity is too far away, remove its overlay
|
||||
for (var entityID in overlays) {
|
||||
var position = Entities.getEntityProperties(entityID, ["position"]).position;
|
||||
if (Vec3.distance(position, MyAvatar.position) > INSPECT_RADIUS) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
delete overlays[entityID];
|
||||
}
|
||||
}
|
||||
|
||||
}, 100);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -19,7 +19,8 @@ Script.include("../libraries/utils.js");
|
|||
//
|
||||
|
||||
var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value
|
||||
var TRIGGER_ON_VALUE = 0.2;
|
||||
var TRIGGER_ON_VALUE = 0.4;
|
||||
var TRIGGER_OFF_VALUE = 0.15;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@ -54,27 +55,54 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things
|
|||
var RIGHT_HAND = 1;
|
||||
var LEFT_HAND = 0;
|
||||
|
||||
var ZERO_VEC = { x: 0, y: 0, z: 0};
|
||||
var ZERO_VEC = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
|
||||
var MSEC_PER_SEC = 1000.0;
|
||||
|
||||
// these control how long an abandoned pointer line will hang around
|
||||
var startTime = Date.now();
|
||||
var LIFETIME = 10;
|
||||
var ACTION_LIFETIME = 10; // seconds
|
||||
|
||||
// states for the state machine
|
||||
var STATE_SEARCHING = 0;
|
||||
var STATE_DISTANCE_HOLDING = 1;
|
||||
var STATE_CONTINUE_DISTANCE_HOLDING = 2;
|
||||
var STATE_NEAR_GRABBING = 3;
|
||||
var STATE_CONTINUE_NEAR_GRABBING = 4;
|
||||
var STATE_NEAR_GRABBING_NON_COLLIDING = 5;
|
||||
var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 6;
|
||||
var STATE_RELEASE = 7;
|
||||
var STATE_OFF = 0;
|
||||
var STATE_SEARCHING = 1;
|
||||
var STATE_DISTANCE_HOLDING = 2;
|
||||
var STATE_CONTINUE_DISTANCE_HOLDING = 3;
|
||||
var STATE_NEAR_GRABBING = 4;
|
||||
var STATE_CONTINUE_NEAR_GRABBING = 5;
|
||||
var STATE_NEAR_GRABBING_NON_COLLIDING = 6;
|
||||
var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 7;
|
||||
var STATE_RELEASE = 8;
|
||||
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
var GRABBABLE_DATA_KEY = "grabbableKey";
|
||||
|
||||
function getTag() {
|
||||
return "grab-" + MyAvatar.sessionUUID;
|
||||
}
|
||||
|
||||
function entityIsGrabbedByOther(entityID) {
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var tag = actionArguments["tag"];
|
||||
if (tag == getTag()) {
|
||||
continue;
|
||||
}
|
||||
if (tag.slice(0, 5) == "grab-") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function MyController(hand, triggerAction) {
|
||||
this.hand = hand;
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
|
@ -85,23 +113,32 @@ function MyController(hand, triggerAction) {
|
|||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||
}
|
||||
|
||||
var SPATIAL_CONTROLLERS_PER_PALM = 2;
|
||||
var TIP_CONTROLLER_OFFSET = 1;
|
||||
this.triggerAction = triggerAction;
|
||||
this.palm = 2 * hand;
|
||||
// this.tip = 2 * hand + 1; // unused, but I'm leaving this here for fear it will be needed
|
||||
this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand;
|
||||
this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET;
|
||||
|
||||
this.actionID = null; // action this script created...
|
||||
this.grabbedEntity = null; // on this entity.
|
||||
this.grabbedVelocity = ZERO_VEC; // rolling average of held object's velocity
|
||||
this.state = 0;
|
||||
this.state = STATE_OFF;
|
||||
this.pointer = null; // entity-id of line object
|
||||
this.triggerValue = 0; // rolling average of trigger value
|
||||
|
||||
var _this = this;
|
||||
|
||||
this.update = function() {
|
||||
|
||||
this.updateSmoothedTrigger();
|
||||
|
||||
switch (this.state) {
|
||||
case STATE_OFF:
|
||||
this.off();
|
||||
this.touchTest();
|
||||
break;
|
||||
case STATE_SEARCHING:
|
||||
this.search();
|
||||
this.touchTest();
|
||||
break;
|
||||
case STATE_DISTANCE_HOLDING:
|
||||
this.distanceHolding();
|
||||
|
@ -158,21 +195,35 @@ function MyController(hand, triggerAction) {
|
|||
this.pointer = null;
|
||||
};
|
||||
|
||||
this.triggerSmoothedSqueezed = function() {
|
||||
this.updateSmoothedTrigger = function() {
|
||||
var triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
// smooth out trigger value
|
||||
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
||||
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
||||
}
|
||||
|
||||
this.triggerSmoothedSqueezed = function() {
|
||||
return this.triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.triggerSmoothedReleased = function() {
|
||||
return this.triggerValue < TRIGGER_OFF_VALUE;
|
||||
};
|
||||
|
||||
this.triggerSqueezed = function() {
|
||||
var triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
return triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
this.off = function() {
|
||||
if (this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_SEARCHING;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.search = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -206,6 +257,10 @@ function MyController(hand, triggerAction) {
|
|||
this.state = STATE_NEAR_GRABBING;
|
||||
|
||||
} else {
|
||||
if (entityIsGrabbedByOther(intersection.entityID)) {
|
||||
// don't allow two people to distance grab the same object
|
||||
return;
|
||||
}
|
||||
// the hand is far from the intersected object. go into distance-holding mode
|
||||
this.state = STATE_DISTANCE_HOLDING;
|
||||
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||
|
@ -244,6 +299,7 @@ function MyController(hand, triggerAction) {
|
|||
};
|
||||
|
||||
this.distanceHolding = function() {
|
||||
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
|
||||
|
@ -255,11 +311,14 @@ function MyController(hand, triggerAction) {
|
|||
this.handPreviousPosition = handControllerPosition;
|
||||
this.handPreviousRotation = handRotation;
|
||||
|
||||
this.actionID = NULL_ACTION_ID;
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
tag: getTag(),
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
|
@ -276,10 +335,13 @@ function MyController(hand, triggerAction) {
|
|||
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
|
||||
}
|
||||
|
||||
this.currentAvatarPosition = MyAvatar.position;
|
||||
this.currentAvatarOrientation = MyAvatar.orientation;
|
||||
|
||||
};
|
||||
|
||||
this.continueDistanceHolding = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -294,11 +356,46 @@ function MyController(hand, triggerAction) {
|
|||
// the action was set up on a previous call. update the targets.
|
||||
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
|
||||
|
||||
// how far did avatar move this timestep?
|
||||
var currentPosition = MyAvatar.position;
|
||||
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
|
||||
this.currentAvatarPosition = currentPosition;
|
||||
|
||||
// How far did the avatar turn this timestep?
|
||||
// Note: The following code is too long because we need a Quat.quatBetween() function
|
||||
// that returns the minimum quaternion between two quaternions.
|
||||
var currentOrientation = MyAvatar.orientation;
|
||||
if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) {
|
||||
var negativeCurrentOrientation = {
|
||||
x: -currentOrientation.x,
|
||||
y: -currentOrientation.y,
|
||||
z: -currentOrientation.z,
|
||||
w: -currentOrientation.w
|
||||
};
|
||||
var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation));
|
||||
} else {
|
||||
var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation));
|
||||
}
|
||||
var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition);
|
||||
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition);
|
||||
var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar);
|
||||
var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar);
|
||||
this.currentAvatarOrientation = currentOrientation;
|
||||
|
||||
// how far did hand move this timestep?
|
||||
var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition);
|
||||
this.handPreviousPosition = handControllerPosition;
|
||||
|
||||
// magnify the hand movement but not the change from avatar movement & rotation
|
||||
handMoved = Vec3.subtract(handMoved, avatarDeltaPosition);
|
||||
handMoved = Vec3.subtract(handMoved, handMovementFromTurning);
|
||||
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||
|
||||
// Move the object by the magnified amount and then by amount from avatar movement & rotation
|
||||
var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning);
|
||||
|
||||
var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters
|
||||
var now = Date.now();
|
||||
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
|
||||
|
@ -318,12 +415,14 @@ function MyController(hand, triggerAction) {
|
|||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
};
|
||||
|
||||
this.nearGrabbing = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -344,11 +443,13 @@ function MyController(hand, triggerAction) {
|
|||
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
|
||||
this.actionID = NULL_ACTION_ID;
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||
relativePosition: offsetPosition,
|
||||
relativeRotation: offsetRotation
|
||||
relativeRotation: offsetRotation,
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
|
@ -363,40 +464,54 @@ function MyController(hand, triggerAction) {
|
|||
|
||||
}
|
||||
|
||||
this.currentHandControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.currentHandControllerTipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
|
||||
this.currentObjectTime = Date.now();
|
||||
};
|
||||
|
||||
this.continueNearGrabbing = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
||||
// keep track of the measured velocity of the held object
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
// Keep track of the fingertip velocity to impart when we release the object
|
||||
// Note that the idea of using a constant 'tip' velocity regardless of the
|
||||
// object's actual held offset is an idea intended to make it easier to throw things:
|
||||
// Because we might catch something or transfer it between hands without a good idea
|
||||
// of it's actual offset, let's try imparting a velocity which is at a fixed radius
|
||||
// from the palm.
|
||||
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
var now = Date.now();
|
||||
|
||||
var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerPosition); // meters
|
||||
var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters
|
||||
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
|
||||
this.computeReleaseVelocity(deltaPosition, deltaTime, true);
|
||||
|
||||
this.currentHandControllerPosition = handControllerPosition;
|
||||
this.currentHandControllerTipPosition = handControllerPosition;
|
||||
this.currentObjectTime = now;
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
|
||||
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {lifetime: ACTION_LIFETIME});
|
||||
};
|
||||
|
||||
this.nearGrabbingNonColliding = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
|
||||
} else {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding");
|
||||
this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING;
|
||||
};
|
||||
|
||||
this.continueNearGrabbingNonColliding = function() {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -459,17 +574,14 @@ function MyController(hand, triggerAction) {
|
|||
};
|
||||
|
||||
this.startTouch = function(entityID) {
|
||||
// print('START TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "startTouch");
|
||||
};
|
||||
|
||||
this.continueTouch = function(entityID) {
|
||||
// print('CONTINUE TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "continueTouch");
|
||||
};
|
||||
|
||||
this.stopTouch = function(entityID) {
|
||||
// print('STOP TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "stopTouch");
|
||||
};
|
||||
|
||||
|
@ -491,10 +603,13 @@ function MyController(hand, triggerAction) {
|
|||
};
|
||||
|
||||
this.release = function() {
|
||||
|
||||
this.lineOff();
|
||||
|
||||
if (this.grabbedEntity !== null && this.actionID !== null) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
if (this.grabbedEntity !== null) {
|
||||
if(this.actionID !== null) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
||||
}
|
||||
|
||||
|
@ -508,7 +623,7 @@ function MyController(hand, triggerAction) {
|
|||
this.grabbedVelocity = ZERO_VEC;
|
||||
this.grabbedEntity = null;
|
||||
this.actionID = null;
|
||||
this.state = STATE_SEARCHING;
|
||||
this.state = STATE_OFF;
|
||||
};
|
||||
|
||||
this.cleanup = function() {
|
||||
|
|
10
examples/example/hmd/colorCube.fs
Normal file
10
examples/example/hmd/colorCube.fs
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
|
||||
|
||||
specular = _modelNormal.rgb;
|
||||
if (any(lessThan(specular, vec3(0.0)))) {
|
||||
specular = vec3(1.0) + specular;
|
||||
}
|
||||
diffuse = vec3(1.0, 1.0, 1.0);
|
||||
return 1.0;
|
||||
}
|
42
examples/example/hmd/colorCube.js
Normal file
42
examples/example/hmd/colorCube.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
function avatarRelativePosition(position) {
|
||||
return Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, position));
|
||||
}
|
||||
|
||||
ColorCube = function() {};
|
||||
ColorCube.prototype.NAME = "ColorCube";
|
||||
ColorCube.prototype.POSITION = { x: 0, y: 0.5, z: -0.5 };
|
||||
ColorCube.prototype.USER_DATA = { ProceduralEntity: {
|
||||
version: 2, shaderUrl: Script.resolvePath("colorCube.fs"),
|
||||
} };
|
||||
|
||||
// Clear any previous entities within 50 meters
|
||||
ColorCube.prototype.clear = function() {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
var that = this;
|
||||
ids.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == that.NAME) {
|
||||
Entities.deleteEntity(id);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
ColorCube.prototype.create = function() {
|
||||
var that = this;
|
||||
var size = HMD.ipd;
|
||||
var id = Entities.addEntity({
|
||||
type: "Box",
|
||||
position: avatarRelativePosition(that.POSITION),
|
||||
name: that.NAME,
|
||||
color: that.COLOR,
|
||||
ignoreCollisions: true,
|
||||
collisionsWillMove: false,
|
||||
dimensions: { x: size, y: size, z: size },
|
||||
lifetime: 3600,
|
||||
userData: JSON.stringify(that.USER_DATA)
|
||||
});
|
||||
}
|
||||
|
||||
var colorCube = new ColorCube();
|
||||
colorCube.clear();
|
||||
colorCube.create();
|
209
examples/grab.js
209
examples/grab.js
|
@ -9,11 +9,67 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*global print, Mouse, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
|
||||
|
||||
Script.include("libraries/utils.js");
|
||||
// objects that appear smaller than this can't be grabbed
|
||||
var MAX_SOLID_ANGLE = 0.01;
|
||||
|
||||
var ZERO_VEC3 = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var IDENTITY_QUAT = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 0
|
||||
};
|
||||
var GRABBABLE_DATA_KEY = "grabbableKey";
|
||||
|
||||
var defaultGrabbableData = {
|
||||
grabbable: true
|
||||
};
|
||||
|
||||
|
||||
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
|
||||
var ZERO_VEC3 = {x: 0, y: 0, z: 0};
|
||||
var IDENTITY_QUAT = {x: 0, y: 0, z: 0, w: 0};
|
||||
var ZERO_VEC3 = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var IDENTITY_QUAT = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 0
|
||||
};
|
||||
var ACTION_LIFETIME = 120; // 2 minutes
|
||||
|
||||
function getTag() {
|
||||
return "grab-" + MyAvatar.sessionUUID;
|
||||
}
|
||||
|
||||
function entityIsGrabbedByOther(entityID) {
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
var actionIndex;
|
||||
var actionID;
|
||||
var actionArguments;
|
||||
var tag;
|
||||
for (actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
actionID = actionIDs[actionIndex];
|
||||
actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
tag = actionArguments.tag;
|
||||
if (tag === getTag()) {
|
||||
continue;
|
||||
}
|
||||
if (tag.slice(0, 5) === "grab-") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// helper function
|
||||
function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistance) {
|
||||
|
@ -59,36 +115,72 @@ function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistanc
|
|||
|
||||
// Mouse class stores mouse click and drag info
|
||||
Mouse = function() {
|
||||
this.current = {x: 0, y: 0 };
|
||||
this.previous = {x: 0, y: 0 };
|
||||
this.rotateStart = {x: 0, y: 0 };
|
||||
this.cursorRestore = {x: 0, y: 0};
|
||||
this.current = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.previous = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.rotateStart = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this.cursorRestore = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
Mouse.prototype.startDrag = function(position) {
|
||||
this.current = {x: position.x, y: position.y};
|
||||
this.current = {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
};
|
||||
this.startRotateDrag();
|
||||
}
|
||||
|
||||
Mouse.prototype.updateDrag = function(position) {
|
||||
this.current = {x: position.x, y: position.y };
|
||||
this.current = {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
};
|
||||
}
|
||||
|
||||
Mouse.prototype.startRotateDrag = function() {
|
||||
this.previous = {x: this.current.x, y: this.current.y};
|
||||
this.rotateStart = {x: this.current.x, y: this.current.y};
|
||||
this.cursorRestore = { x: Window.getCursorPositionX(), y: Window.getCursorPositionY() };
|
||||
this.previous = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
this.rotateStart = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
this.cursorRestore = {
|
||||
x: Window.getCursorPositionX(),
|
||||
y: Window.getCursorPositionY()
|
||||
};
|
||||
}
|
||||
|
||||
Mouse.prototype.getDrag = function() {
|
||||
var delta = {x: this.current.x - this.previous.x, y: this.current.y - this.previous.y};
|
||||
this.previous = {x: this.current.x, y: this.current.y};
|
||||
var delta = {
|
||||
x: this.current.x - this.previous.x,
|
||||
y: this.current.y - this.previous.y
|
||||
};
|
||||
this.previous = {
|
||||
x: this.current.x,
|
||||
y: this.current.y
|
||||
};
|
||||
return delta;
|
||||
}
|
||||
|
||||
Mouse.prototype.restoreRotateCursor = function() {
|
||||
Window.setCursorPosition(this.cursorRestore.x, this.cursorRestore.y);
|
||||
this.current = {x: this.rotateStart.x, y: this.rotateStart.y};
|
||||
this.current = {
|
||||
x: this.rotateStart.x,
|
||||
y: this.rotateStart.y
|
||||
};
|
||||
}
|
||||
|
||||
var mouse = new Mouse();
|
||||
|
@ -98,19 +190,27 @@ var mouse = new Mouse();
|
|||
Beacon = function() {
|
||||
this.height = 0.10;
|
||||
this.overlayID = Overlays.addOverlay("line3d", {
|
||||
color: {red: 200, green: 200, blue: 200},
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: 2
|
||||
lineWidth: 2
|
||||
});
|
||||
}
|
||||
|
||||
Beacon.prototype.enable = function() {
|
||||
Overlays.editOverlay(this.overlayID, { visible: true });
|
||||
Overlays.editOverlay(this.overlayID, {
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
Beacon.prototype.disable = function() {
|
||||
Overlays.editOverlay(this.overlayID, { visible: false });
|
||||
Overlays.editOverlay(this.overlayID, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
Beacon.prototype.updatePosition = function(position) {
|
||||
|
@ -158,13 +258,17 @@ Grabber = function() {
|
|||
// verticalCylinder (SHIFT)
|
||||
// rotate (CONTROL)
|
||||
this.mode = "xzplane";
|
||||
|
||||
|
||||
// offset allows the user to grab an object off-center. It points from the object's center
|
||||
// to the point where the ray intersects the grab plane (at the moment the grab is initiated).
|
||||
// Future target positions of the ray intersection are on the same plane, and the offset is subtracted
|
||||
// to compute the target position of the object's center.
|
||||
this.offset = {x: 0, y: 0, z: 0 };
|
||||
|
||||
this.offset = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
this.targetPosition;
|
||||
this.targetRotation;
|
||||
|
||||
|
@ -179,7 +283,11 @@ Grabber.prototype.computeNewGrabPlane = function() {
|
|||
|
||||
var modeWasRotate = (this.mode == "rotate");
|
||||
this.mode = "xzPlane";
|
||||
this.planeNormal = {x: 0, y: 1, z: 0 };
|
||||
this.planeNormal = {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
};
|
||||
if (this.rotateKey) {
|
||||
this.mode = "rotate";
|
||||
mouse.startRotateDrag();
|
||||
|
@ -192,7 +300,7 @@ Grabber.prototype.computeNewGrabPlane = function() {
|
|||
this.mode = "verticalCylinder";
|
||||
// NOTE: during verticalCylinder mode a new planeNormal will be computed each move
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.pointOnPlane = Vec3.sum(this.currentPosition, this.offset);
|
||||
var xzOffset = Vec3.subtract(this.pointOnPlane, Camera.getPosition());
|
||||
|
@ -217,6 +325,12 @@ Grabber.prototype.pressEvent = function(event) {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, pickResults.entityID, defaultGrabbableData);
|
||||
if (grabbableData.grabbable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouse.startDrag(event);
|
||||
|
||||
var clickedEntity = pickResults.entityID;
|
||||
|
@ -233,13 +347,19 @@ Grabber.prototype.pressEvent = function(event) {
|
|||
return;
|
||||
}
|
||||
|
||||
Entities.editEntity(clickedEntity, { gravity: ZERO_VEC3 });
|
||||
Entities.editEntity(clickedEntity, {
|
||||
gravity: ZERO_VEC3
|
||||
});
|
||||
this.isGrabbing = true;
|
||||
|
||||
this.entityID = clickedEntity;
|
||||
this.currentPosition = entityProperties.position;
|
||||
this.originalGravity = entityProperties.gravity;
|
||||
this.targetPosition = {x: this.startPosition.x, y: this.startPosition.y, z: this.startPosition.z};
|
||||
this.targetPosition = {
|
||||
x: this.startPosition.x,
|
||||
y: this.startPosition.y,
|
||||
z: this.startPosition.z
|
||||
};
|
||||
|
||||
// compute the grab point
|
||||
var nearestPoint = Vec3.subtract(this.startPosition, cameraPosition);
|
||||
|
@ -261,7 +381,9 @@ Grabber.prototype.pressEvent = function(event) {
|
|||
Grabber.prototype.releaseEvent = function() {
|
||||
if (this.isGrabbing) {
|
||||
if (Vec3.length(this.originalGravity) != 0) {
|
||||
Entities.editEntity(this.entityID, { gravity: this.originalGravity});
|
||||
Entities.editEntity(this.entityID, {
|
||||
gravity: this.originalGravity
|
||||
});
|
||||
}
|
||||
|
||||
this.isGrabbing = false
|
||||
|
@ -288,7 +410,10 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
}
|
||||
this.currentPosition = entityProperties.position;
|
||||
|
||||
var actionArgs = {};
|
||||
var actionArgs = {
|
||||
tag: getTag(),
|
||||
lifetime: ACTION_LIFETIME
|
||||
};
|
||||
|
||||
if (this.mode === "rotate") {
|
||||
var drag = mouse.getDrag();
|
||||
|
@ -303,7 +428,14 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
// var qZero = entityProperties.rotation;
|
||||
//var qZero = this.lastRotation;
|
||||
this.lastRotation = Quat.multiply(deltaQ, this.lastRotation);
|
||||
actionArgs = {targetRotation: this.lastRotation, angularTimeScale: 0.1};
|
||||
|
||||
actionArgs = {
|
||||
targetRotation: this.lastRotation,
|
||||
angularTimeScale: 0.1,
|
||||
tag: getTag(),
|
||||
lifetime: ACTION_LIFETIME
|
||||
};
|
||||
|
||||
} else {
|
||||
var newPointOnPlane;
|
||||
if (this.mode === "verticalCylinder") {
|
||||
|
@ -314,7 +446,11 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
var pointOnCylinder = Vec3.multiply(planeNormal, this.xzDistanceToGrab);
|
||||
pointOnCylinder = Vec3.sum(Camera.getPosition(), pointOnCylinder);
|
||||
this.pointOnPlane = mouseIntersectionWithPlane(pointOnCylinder, planeNormal, mouse.current, this.maxDistance);
|
||||
newPointOnPlane = {x: this.pointOnPlane.x, y: this.pointOnPlane.y, z: this.pointOnPlane.z};
|
||||
newPointOnPlane = {
|
||||
x: this.pointOnPlane.x,
|
||||
y: this.pointOnPlane.y,
|
||||
z: this.pointOnPlane.z
|
||||
};
|
||||
} else {
|
||||
var cameraPosition = Camera.getPosition();
|
||||
newPointOnPlane = mouseIntersectionWithPlane(this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance);
|
||||
|
@ -327,13 +463,22 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
}
|
||||
}
|
||||
this.targetPosition = Vec3.subtract(newPointOnPlane, this.offset);
|
||||
actionArgs = {targetPosition: this.targetPosition, linearTimeScale: 0.1};
|
||||
|
||||
actionArgs = {
|
||||
targetPosition: this.targetPosition,
|
||||
linearTimeScale: 0.1,
|
||||
tag: getTag(),
|
||||
lifetime: ACTION_LIFETIME
|
||||
};
|
||||
|
||||
|
||||
beacon.updatePosition(this.targetPosition);
|
||||
}
|
||||
|
||||
if (!this.actionID) {
|
||||
this.actionID = Entities.addAction("spring", this.entityID, actionArgs);
|
||||
if (!entityIsGrabbedByOther(this.entityID)) {
|
||||
this.actionID = Entities.addAction("spring", this.entityID, actionArgs);
|
||||
}
|
||||
} else {
|
||||
Entities.updateAction(this.entityID, this.actionID, actionArgs);
|
||||
}
|
||||
|
@ -385,4 +530,4 @@ Controller.mousePressEvent.connect(pressEvent);
|
|||
Controller.mouseMoveEvent.connect(moveEvent);
|
||||
Controller.mouseReleaseEvent.connect(releaseEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
43
examples/toys/basketball_hoop/createHoop.js
Normal file
43
examples/toys/basketball_hoop/createHoop.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// createHoop.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by James B. Pollack on 9/29/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is a script that creates a persistent basketball hoop with a working collision hull. Feel free to move it.
|
||||
// Run basketball.js to make a basketball.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx";
|
||||
var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj";
|
||||
|
||||
var hoopStartPosition =
|
||||
Vec3.sum(MyAvatar.position,
|
||||
Vec3.multiplyQbyV(MyAvatar.orientation, {
|
||||
x: 0,
|
||||
y: 0.0,
|
||||
z: -2
|
||||
}));
|
||||
|
||||
var hoop = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: hoopURL,
|
||||
position: hoopStartPosition,
|
||||
shapeType: 'compound',
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
dimensions: {
|
||||
x: 1.89,
|
||||
y: 3.99,
|
||||
z: 3.79
|
||||
},
|
||||
compoundShapeURL: hoopCollisionHullURL
|
||||
});
|
||||
|
43
examples/toys/ping_pong_gun/createPingPongGun.js
Normal file
43
examples/toys/ping_pong_gun/createPingPongGun.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
// createPingPongGun.js
|
||||
//
|
||||
// Script Type: Entity Spawner
|
||||
// Created by James B. Pollack on 9/30/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates a gun that shoots ping pong balls when you pull the trigger on a hand controller.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
Script.include("../../utilities.js");
|
||||
|
||||
var scriptURL = Script.resolvePath('pingPongGun.js');
|
||||
|
||||
var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'
|
||||
var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj';
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var pingPongGun = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: MODEL_URL,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
script: scriptURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x:0.67,
|
||||
y: 0.14,
|
||||
z: 0.09
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
});
|
||||
|
||||
function cleanUp() {
|
||||
Entities.deleteEntity(pingPongGun);
|
||||
}
|
||||
Script.scriptEnding.connect(cleanUp);
|
160
examples/toys/ping_pong_gun/pingPongGun.js
Normal file
160
examples/toys/ping_pong_gun/pingPongGun.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
// pingPongGun.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
// Created by James B. Pollack @imgntn on 9/21/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script shoots a ping pong ball.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
(function() {
|
||||
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
var SHOOTING_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/ping_pong_gun/pong_sound.wav';
|
||||
|
||||
function PingPongGun() {
|
||||
return;
|
||||
}
|
||||
|
||||
//if the trigger value goes below this value, reload the gun.
|
||||
var RELOAD_THRESHOLD = 0.95;
|
||||
var GUN_TIP_FWD_OFFSET = 0.45;
|
||||
var GUN_TIP_UP_OFFSET = 0.040;
|
||||
var GUN_FORCE = 15;
|
||||
var BALL_RESTITUTION = 0.6;
|
||||
var BALL_LINEAR_DAMPING = 0.4;
|
||||
var BALL_GRAVITY = {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var BALL_DIMENSIONS = {
|
||||
x: 0.04,
|
||||
y: 0.04,
|
||||
z: 0.04
|
||||
}
|
||||
|
||||
|
||||
var BALL_COLOR = {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
}
|
||||
|
||||
PingPongGun.prototype = {
|
||||
hand: null,
|
||||
whichHand: null,
|
||||
gunTipPosition: null,
|
||||
canShoot: false,
|
||||
canShootTimeout: null,
|
||||
setRightHand: function() {
|
||||
this.hand = 'RIGHT';
|
||||
},
|
||||
|
||||
setLeftHand: function() {
|
||||
this.hand = 'LEFT';
|
||||
},
|
||||
|
||||
startNearGrab: function() {
|
||||
this.setWhichHand();
|
||||
},
|
||||
|
||||
setWhichHand: function() {
|
||||
this.whichHand = this.hand;
|
||||
},
|
||||
|
||||
continueNearGrab: function() {
|
||||
|
||||
if (this.whichHand === null) {
|
||||
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
|
||||
this.setWhichHand();
|
||||
} else {
|
||||
if (this.canShootTimeout !== null) {
|
||||
Script.clearTimeout(this.canShootTimeout);
|
||||
}
|
||||
this.checkTriggerPressure(this.whichHand);
|
||||
}
|
||||
},
|
||||
|
||||
releaseGrab: function() {
|
||||
var _t = this;
|
||||
this.canShootTimeout = Script.setTimeout(function() {
|
||||
_t.canShoot = false;
|
||||
}, 250)
|
||||
},
|
||||
|
||||
checkTriggerPressure: function(gunHand) {
|
||||
var handClickString = gunHand + "_HAND_CLICK";
|
||||
|
||||
var handClick = Controller.findAction(handClickString);
|
||||
|
||||
this.triggerValue = Controller.getActionValue(handClick);
|
||||
|
||||
if (this.triggerValue < RELOAD_THRESHOLD) {
|
||||
// print('RELOAD');
|
||||
this.canShoot = true;
|
||||
} else if (this.triggerValue >= RELOAD_THRESHOLD && this.canShoot === true) {
|
||||
var gunProperties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||
this.shootBall(gunProperties);
|
||||
this.canShoot = false;
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
||||
shootBall: function(gunProperties) {
|
||||
var forwardVec = Quat.getFront(Quat.multiply(gunProperties.rotation, Quat.fromPitchYawRollDegrees(0, -90, 0)));
|
||||
forwardVec = Vec3.normalize(forwardVec);
|
||||
forwardVec = Vec3.multiply(forwardVec, GUN_FORCE);
|
||||
var properties = {
|
||||
type: 'Sphere',
|
||||
color: BALL_COLOR,
|
||||
dimensions: BALL_DIMENSIONS,
|
||||
linearDamping: BALL_LINEAR_DAMPING,
|
||||
gravity: BALL_GRAVITY,
|
||||
restitution: BALL_RESTITUTION,
|
||||
collisionsWillMove: true,
|
||||
rotation: gunProperties.rotation,
|
||||
position: this.getGunTipPosition(gunProperties),
|
||||
velocity: forwardVec,
|
||||
lifetime: 10
|
||||
};
|
||||
|
||||
Entities.addEntity(properties);
|
||||
|
||||
this.playSoundAtCurrentPosition(gunProperties.position);
|
||||
},
|
||||
|
||||
playSoundAtCurrentPosition: function(position) {
|
||||
var audioProperties = {
|
||||
volume: 0.1,
|
||||
position: position
|
||||
};
|
||||
|
||||
Audio.playSound(this.SHOOTING_SOUND, audioProperties);
|
||||
},
|
||||
|
||||
getGunTipPosition: function(properties) {
|
||||
//the tip of the gun is going to be in a different place than the center, so we move in space relative to the model to find that position
|
||||
var frontVector = Quat.getRight(properties.rotation);
|
||||
var frontOffset = Vec3.multiply(frontVector, GUN_TIP_FWD_OFFSET);
|
||||
var upVector = Quat.getRight(properties.rotation);
|
||||
var upOffset = Vec3.multiply(upVector, GUN_TIP_UP_OFFSET);
|
||||
var gunTipPosition = Vec3.sum(properties.position, frontOffset);
|
||||
gunTipPosition = Vec3.sum(gunTipPosition, upOffset);
|
||||
return gunTipPosition;
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
this.SHOOTING_SOUND = SoundCache.getSound(SHOOTING_SOUND_URL);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new PingPongGun();
|
||||
});
|
|
@ -10,22 +10,21 @@
|
|||
//
|
||||
|
||||
var array = [];
|
||||
var buffer = "\n\n\n\n\n======= JS API list =======";
|
||||
function listKeys(string, object) {
|
||||
if (string == "listKeys" || string == "array" || string == "buffer" || string == "i") {
|
||||
if (string === "listKeys" || string === "array" || string === "buffer" || string === "i") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(object) != "object") {
|
||||
if (typeof(object) !== "object" || object === null) {
|
||||
array.push(string + " " + typeof(object));
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = Object.keys(object);
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (string == "") {
|
||||
if (string === "") {
|
||||
listKeys(keys[i], object[keys[i]]);
|
||||
} else {
|
||||
} else if (keys[i] !== "parent") {
|
||||
listKeys(string + "." + keys[i], object[keys[i]]);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +33,10 @@ function listKeys(string, object) {
|
|||
listKeys("", this);
|
||||
array.sort();
|
||||
|
||||
var buffer = "\n======= JS API list =======";
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
buffer = buffer + "\n" + array[i];
|
||||
buffer += "\n" + array[i];
|
||||
}
|
||||
buffer = buffer + "\n========= API END =========\n\n\n\n\n";
|
||||
buffer += "\n========= API END =========\n";
|
||||
|
||||
print(buffer);
|
||||
|
|
|
@ -26,13 +26,14 @@
|
|||
{
|
||||
"jointName": "Neck",
|
||||
"positionVar": "neckPosition",
|
||||
"rotationVar": "neckRotation"
|
||||
"rotationVar": "neckRotation",
|
||||
"typeVar": "headAndNeckType"
|
||||
},
|
||||
{
|
||||
"jointName": "Head",
|
||||
"positionVar": "headPosition",
|
||||
"rotationVar": "headRotation",
|
||||
"typeVar": "headType"
|
||||
"typeVar": "headAndNeckType"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -196,7 +196,6 @@ Hifi.VrMenu {
|
|||
|
||||
function insertItem(menu, beforeItem, newMenuItem) {
|
||||
for (var i = 0; i < menu.items.length; ++i) {
|
||||
console.log(menu.items[i]);
|
||||
if (menu.items[i] === beforeItem) {
|
||||
return menu.insertItem(i, newMenuItem);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,9 @@ EntityActionPointer InterfaceActionFactory::factory(EntityActionType type,
|
|||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
if (action->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
@ -63,5 +66,9 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt
|
|||
if (action) {
|
||||
action->deserialize(data);
|
||||
}
|
||||
if (action->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
|
|
@ -446,9 +446,9 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarUpdateThreading, 0, false,
|
||||
qApp, SLOT(setAvatarUpdateThreading(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, true,
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false,
|
||||
avatar, SLOT(setEnableRigAnimations(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false,
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, true,
|
||||
avatar, SLOT(setEnableAnimGraph(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBindPose, 0, false,
|
||||
avatar, SLOT(setEnableDebugDrawBindPose(bool)));
|
||||
|
|
|
@ -84,6 +84,9 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
|||
|
||||
|
||||
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
glm::vec3 relativePosition =
|
||||
EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
|
||||
|
@ -134,7 +137,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
|
||||
|
||||
QVariantMap AvatarActionHold::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&]{
|
||||
if (!_mine) {
|
||||
arguments = ObjectActionSpring::getArguments();
|
||||
|
|
|
@ -153,7 +153,8 @@ void MyAvatar::reset() {
|
|||
|
||||
// This should be simpler when we have only graph animations always on.
|
||||
bool isRig = _rig->getEnableRig();
|
||||
bool isGraph = _rig->getEnableAnimGraph();
|
||||
// seting rig animation to true, below, will clear the graph animation menu item, so grab it now.
|
||||
bool isGraph = _rig->getEnableAnimGraph() || Menu::getInstance()->isOptionChecked(MenuOption::EnableAnimGraph);
|
||||
qApp->setRawAvatarUpdateThreading(false);
|
||||
_rig->disableHands = true;
|
||||
setEnableRigAnimations(true);
|
||||
|
@ -270,6 +271,25 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
|
|||
return _sensorToWorldMatrix;
|
||||
}
|
||||
|
||||
// returns true if pos is OUTSIDE of the vertical capsule
|
||||
// where the middle cylinder length is defined by capsuleLen and the radius by capsuleRad.
|
||||
static bool capsuleCheck(const glm::vec3& pos, float capsuleLen, float capsuleRad) {
|
||||
const float halfCapsuleLen = capsuleLen / 2.0f;
|
||||
if (fabs(pos.y) <= halfCapsuleLen) {
|
||||
// cylinder check for middle capsule
|
||||
glm::vec2 horizPos(pos.x, pos.z);
|
||||
return glm::length(horizPos) > capsuleRad;
|
||||
} else if (pos.y > halfCapsuleLen) {
|
||||
glm::vec3 center(0.0f, halfCapsuleLen, 0.0f);
|
||||
return glm::length(center - pos) > capsuleRad;
|
||||
} else if (pos.y < halfCapsuleLen) {
|
||||
glm::vec3 center(0.0f, -halfCapsuleLen, 0.0f);
|
||||
return glm::length(center - pos) > capsuleRad;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass a recent sample of the HMD to the avatar.
|
||||
// This can also update the avatar's position to follow the HMD
|
||||
// as it moves through the world.
|
||||
|
@ -288,11 +308,14 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
|
|||
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
|
||||
|
||||
const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds
|
||||
const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters
|
||||
|
||||
// define a vertical capsule
|
||||
const float STRAIGHTING_LEAN_CAPSULE_RADIUS = 0.2f; // meters
|
||||
const float STRAIGHTING_LEAN_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters.
|
||||
|
||||
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
|
||||
glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
|
||||
if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) {
|
||||
if (!_straightingLean && capsuleCheck(diff, STRAIGHTING_LEAN_CAPSULE_LENGTH, STRAIGHTING_LEAN_CAPSULE_RADIUS)) {
|
||||
|
||||
// begin homing toward derived body position.
|
||||
_straightingLean = true;
|
||||
|
@ -1764,6 +1787,12 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
|||
}
|
||||
|
||||
void MyAvatar::updateMotionBehaviorFromMenu() {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu");
|
||||
return;
|
||||
}
|
||||
|
||||
Menu* menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
|
||||
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
|
||||
|
@ -1847,13 +1876,39 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
|||
const glm::quat hmdOrientation = getHMDSensorOrientation();
|
||||
const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
|
||||
|
||||
// In sensor space, figure out where the avatar body should be,
|
||||
// by applying offsets from the avatar's neck & head joints.
|
||||
vec3 localEyes = _skeletonModel.getDefaultEyeModelPosition();
|
||||
vec3 localNeck(0.0f, 0.48f, 0.0f); // start with some kind of guess if the skeletonModel is not loaded yet.
|
||||
_skeletonModel.getLocalNeckPosition(localNeck);
|
||||
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f);
|
||||
const glm::vec3 DEFAULT_HIPS_POS(0.0f, 1.0f, 0.0f);
|
||||
|
||||
vec3 localEyes, localNeck;
|
||||
if (!_debugDrawSkeleton) {
|
||||
const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
localEyes = rotY180 * (((DEFAULT_RIGHT_EYE_POS + DEFAULT_LEFT_EYE_POS) / 2.0f) - DEFAULT_HIPS_POS);
|
||||
localNeck = rotY180 * (DEFAULT_NECK_POS - DEFAULT_HIPS_POS);
|
||||
} else {
|
||||
// TODO: At the moment MyAvatar does not have access to the rig, which has the skeleton, which has the bind poses.
|
||||
// for now use the _debugDrawSkeleton, which is initialized with the same FBX model as the rig.
|
||||
|
||||
// TODO: cache these indices.
|
||||
int rightEyeIndex = _debugDrawSkeleton->nameToJointIndex("RightEye");
|
||||
int leftEyeIndex = _debugDrawSkeleton->nameToJointIndex("LeftEye");
|
||||
int neckIndex = _debugDrawSkeleton->nameToJointIndex("Neck");
|
||||
int hipsIndex = _debugDrawSkeleton->nameToJointIndex("Hips");
|
||||
|
||||
glm::vec3 absRightEyePos = rightEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS;
|
||||
glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS;
|
||||
glm::vec3 absNeckPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS;
|
||||
glm::vec3 absHipsPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(hipsIndex).trans : DEFAULT_HIPS_POS;
|
||||
|
||||
const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
localEyes = rotY180 * (((absRightEyePos + absLeftEyePos) / 2.0f) - absHipsPos);
|
||||
localNeck = rotY180 * (absNeckPos - absHipsPos);
|
||||
}
|
||||
|
||||
// apply simplistic head/neck model
|
||||
// figure out where the avatar body should be by applying offsets from the avatar's neck & head joints.
|
||||
|
||||
// eyeToNeck offset is relative full HMD orientation.
|
||||
// while neckToRoot offset is only relative to HMDs yaw.
|
||||
glm::vec3 eyeToNeck = hmdOrientation * (localNeck - localEyes);
|
||||
|
|
|
@ -193,7 +193,7 @@ public slots:
|
|||
glm::vec3 getThrust() { return _thrust; };
|
||||
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
|
||||
|
||||
void updateMotionBehaviorFromMenu();
|
||||
Q_INVOKABLE void updateMotionBehaviorFromMenu();
|
||||
|
||||
glm::vec3 getLeftPalmPosition();
|
||||
glm::vec3 getLeftPalmVelocity();
|
||||
|
|
|
@ -320,7 +320,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
|
|||
float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f;
|
||||
_rig->applyJointRotationDelta(jointIndex,
|
||||
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
|
||||
true, PALM_PRIORITY);
|
||||
PALM_PRIORITY);
|
||||
}
|
||||
|
||||
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//
|
||||
|
||||
#include "HMDScriptingInterface.h"
|
||||
|
||||
#include "display-plugins/DisplayPlugin.h"
|
||||
#include <avatar/AvatarManager.h>
|
||||
|
||||
HMDScriptingInterface& HMDScriptingInterface::getInstance() {
|
||||
|
@ -53,3 +53,7 @@ QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* conte
|
|||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
float HMDScriptingInterface::getIPD() const {
|
||||
return Application::getInstance()->getActiveDisplayPlugin()->getIPD();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class HMDScriptingInterface : public QObject {
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(bool magnifier READ getMagnifier)
|
||||
Q_PROPERTY(bool active READ isHMDMode)
|
||||
Q_PROPERTY(float ipd READ getIPD)
|
||||
public:
|
||||
static HMDScriptingInterface& getInstance();
|
||||
|
||||
|
@ -33,6 +34,7 @@ private:
|
|||
HMDScriptingInterface() {};
|
||||
bool getMagnifier() const { return Application::getInstance()->getApplicationCompositor().hasMagnifier(); };
|
||||
bool isHMDMode() const { return Application::getInstance()->isHMDMode(); }
|
||||
float getIPD() const;
|
||||
|
||||
bool getHUDLookAtPosition3D(glm::vec3& result) const;
|
||||
|
||||
|
|
|
@ -313,18 +313,21 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
|
|||
glm::mat4 overlayXfm;
|
||||
_modelTransform.getMatrix(overlayXfm);
|
||||
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) {
|
||||
PalmData& palm = myAvatar->getHand()->getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
glm::vec2 polar = getPolarCoordinates(palm);
|
||||
// Convert to quaternion
|
||||
mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
mat4 reticleXfm = overlayXfm * pointerXfm;
|
||||
reticleXfm = glm::scale(reticleXfm, reticleScale);
|
||||
batch.setModelTransform(reticleXfm);
|
||||
// Render reticle at location
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
|
||||
// Only render the hand pointers if the HandMouseInput is enabled
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) {
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) {
|
||||
PalmData& palm = myAvatar->getHand()->getPalms()[i];
|
||||
if (palm.isActive()) {
|
||||
glm::vec2 polar = getPolarCoordinates(palm);
|
||||
// Convert to quaternion
|
||||
mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
mat4 reticleXfm = overlayXfm * pointerXfm;
|
||||
reticleXfm = glm::scale(reticleXfm, reticleScale);
|
||||
batch.setModelTransform(reticleXfm);
|
||||
// Render reticle at location
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde
|
|||
return rootIndex;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets) {
|
||||
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses) {
|
||||
// build a list of valid targets from _targetVarVec and animVars
|
||||
_maxTargetIndex = -1;
|
||||
bool removeUnfoundJoints = false;
|
||||
|
@ -107,7 +107,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
|||
}
|
||||
} else {
|
||||
IKTarget target;
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses);
|
||||
target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
|
||||
target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
|
||||
target.setType(animVars.lookup(targetVar.typeVar, QString("")));
|
||||
|
@ -154,7 +154,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
do {
|
||||
int lowestMovedIndex = _relativePoses.size();
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
if (target.type == IKTarget::Type::RotationOnly) {
|
||||
// the final rotation will be enforced after the iterations
|
||||
continue;
|
||||
|
@ -162,6 +161,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
AnimPose targetPose = target.pose;
|
||||
|
||||
// cache tip absolute transform
|
||||
int tipIndex = target.index;
|
||||
glm::vec3 tipPosition = absolutePoses[tipIndex].trans;
|
||||
glm::quat tipRotation = absolutePoses[tipIndex].rot;
|
||||
|
||||
|
@ -288,6 +288,17 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
}
|
||||
} while (numLoops < MAX_IK_LOOPS);
|
||||
|
||||
/* KEEP: example code for measuring endeffector error of IK solution
|
||||
for (uint32_t i = 0; i < targets.size(); ++i) {
|
||||
auto& target = targets[i];
|
||||
if (target.type == IKTarget::Type::RotationOnly) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 tipPosition = absolutePoses[target.index].trans;
|
||||
std::cout << i << " IK error = " << glm::distance(tipPosition, target.pose.trans) << std::endl; // adebug
|
||||
}
|
||||
*/
|
||||
|
||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
|
@ -312,25 +323,8 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
|
||||
if (!_relativePoses.empty()) {
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
computeTargets(animVars, targets);
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
solveWithCyclicCoordinateDescent(targets);
|
||||
}
|
||||
}
|
||||
// don't call this function, call overlay() instead
|
||||
assert(false);
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
|
@ -356,7 +350,27 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
}
|
||||
}
|
||||
}
|
||||
return evaluate(animVars, dt, triggersOut);
|
||||
|
||||
if (!_relativePoses.empty()) {
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
computeTargets(animVars, targets, underPoses);
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
solveWithCyclicCoordinateDescent(targets);
|
||||
}
|
||||
}
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
RotationConstraint* AnimInverseKinematics::getConstraint(int index) {
|
||||
|
|
|
@ -50,7 +50,7 @@ protected:
|
|||
void setType(const QString& typeVar) { type = ((typeVar == "RotationOnly") ? Type::RotationOnly : Type::RotationAndPosition); }
|
||||
};
|
||||
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets);
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton);
|
||||
|
||||
|
|
|
@ -93,9 +93,9 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe
|
|||
const float dt = 0.0f;
|
||||
Triggers triggers;
|
||||
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
|
||||
|
||||
#if WANT_DEBUGa
|
||||
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget;
|
||||
|
||||
#endif
|
||||
_currentState = desiredState;
|
||||
}
|
||||
|
||||
|
|
|
@ -184,6 +184,8 @@ public:
|
|||
case AnimVariant::Type::String:
|
||||
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
|
||||
break;
|
||||
default:
|
||||
assert("AnimVariant::Type" == "valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,6 @@ void AnimationHandle::applyFrame(float frameIndex) {
|
|||
ceilFrame.rotations.at(i),
|
||||
frameFraction),
|
||||
_priority,
|
||||
false,
|
||||
_mix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,16 +13,11 @@
|
|||
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <AngularConstraint.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "JointState.h"
|
||||
|
||||
JointState::~JointState() {
|
||||
if (_constraint) {
|
||||
delete _constraint;
|
||||
_constraint = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void JointState::copyState(const JointState& other) {
|
||||
|
@ -35,18 +30,12 @@ void JointState::copyState(const JointState& other) {
|
|||
_distanceToParent = other._distanceToParent;
|
||||
_animationPriority = other._animationPriority;
|
||||
|
||||
_visibleTransform = other._visibleTransform;
|
||||
_visibleRotation = extractRotation(_visibleTransform);
|
||||
_visibleRotationInConstrainedFrame = other._visibleRotationInConstrainedFrame;
|
||||
// DO NOT copy _constraint
|
||||
_name = other._name;
|
||||
_isFree = other._isFree;
|
||||
_parentIndex = other._parentIndex;
|
||||
_defaultRotation = other._defaultRotation;
|
||||
_inverseDefaultRotation = other._inverseDefaultRotation;
|
||||
_translation = other._translation;
|
||||
_rotationMin = other._rotationMin;
|
||||
_rotationMax = other._rotationMax;
|
||||
_preRotation = other._preRotation;
|
||||
_postRotation = other._postRotation;
|
||||
_preTransform = other._preTransform;
|
||||
|
@ -61,8 +50,6 @@ JointState::JointState(const FBXJoint& joint) {
|
|||
_translation = joint.translation;
|
||||
_defaultRotation = joint.rotation;
|
||||
_inverseDefaultRotation = joint.inverseDefaultRotation;
|
||||
_rotationMin = joint.rotationMin;
|
||||
_rotationMax = joint.rotationMax;
|
||||
_preRotation = joint.preRotation;
|
||||
_postRotation = joint.postRotation;
|
||||
_preTransform = joint.preTransform;
|
||||
|
@ -71,15 +58,6 @@ JointState::JointState(const FBXJoint& joint) {
|
|||
}
|
||||
|
||||
void JointState::buildConstraint() {
|
||||
if (_constraint) {
|
||||
delete _constraint;
|
||||
_constraint = NULL;
|
||||
}
|
||||
if (glm::distance2(glm::vec3(-PI), _rotationMin) > EPSILON ||
|
||||
glm::distance2(glm::vec3(PI), _rotationMax) > EPSILON ) {
|
||||
// this joint has rotation constraints
|
||||
_constraint = AngularConstraint::newAngularConstraint(_rotationMin, _rotationMax);
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat JointState::getRotation() const {
|
||||
|
@ -113,13 +91,6 @@ void JointState::computeTransform(const glm::mat4& parentTransform, bool parentT
|
|||
}
|
||||
}
|
||||
|
||||
void JointState::computeVisibleTransform(const glm::mat4& parentTransform) {
|
||||
glm::quat rotationInParentFrame = _preRotation * _visibleRotationInConstrainedFrame * _postRotation;
|
||||
glm::mat4 transformInParentFrame = _preTransform * glm::mat4_cast(rotationInParentFrame) * _postTransform;
|
||||
_visibleTransform = parentTransform * glm::translate(_translation) * transformInParentFrame;
|
||||
_visibleRotation = extractRotation(_visibleTransform);
|
||||
}
|
||||
|
||||
glm::quat JointState::getRotationInBindFrame() const {
|
||||
return getRotation() * _inverseBindRotation;
|
||||
}
|
||||
|
@ -128,10 +99,6 @@ glm::quat JointState::getRotationInParentFrame() const {
|
|||
return _preRotation * _rotationInConstrainedFrame * _postRotation;
|
||||
}
|
||||
|
||||
glm::quat JointState::getVisibleRotationInParentFrame() const {
|
||||
return _preRotation * _visibleRotationInConstrainedFrame * _postRotation;
|
||||
}
|
||||
|
||||
void JointState::restoreRotation(float fraction, float priority) {
|
||||
if (priority == _animationPriority || _animationPriority == 0.0f) {
|
||||
setRotationInConstrainedFrameInternal(safeMix(_rotationInConstrainedFrame, _defaultRotation, fraction));
|
||||
|
@ -139,19 +106,16 @@ void JointState::restoreRotation(float fraction, float priority) {
|
|||
}
|
||||
}
|
||||
|
||||
void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain) {
|
||||
void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority) {
|
||||
// rotation is from bind- to model-frame
|
||||
if (priority >= _animationPriority) {
|
||||
glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(getRotation()) * rotation * glm::inverse(_inverseBindRotation);
|
||||
if (constrain && _constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
setRotationInConstrainedFrameInternal(targetRotation);
|
||||
_animationPriority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
void JointState::setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority, bool constrain) {
|
||||
void JointState::setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority) {
|
||||
// rotation is from bind- to model-frame
|
||||
if (priority >= _animationPriority) {
|
||||
glm::quat parentRotation = computeParentRotation();
|
||||
|
@ -160,9 +124,6 @@ void JointState::setRotationInModelFrame(const glm::quat& rotationInModelFrame,
|
|||
// R' = Rp * Rpre * r' * Rpost
|
||||
// r' = (Rp * Rpre)^ * R' * Rpost^
|
||||
glm::quat targetRotation = glm::inverse(parentRotation * _preRotation) * rotationInModelFrame * glm::inverse(_postRotation);
|
||||
if (constrain && _constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
_rotationInConstrainedFrame = glm::normalize(targetRotation);
|
||||
_transformChanged = true;
|
||||
_animationPriority = priority;
|
||||
|
@ -174,26 +135,15 @@ void JointState::clearTransformTranslation() {
|
|||
_transform[3][1] = 0.0f;
|
||||
_transform[3][2] = 0.0f;
|
||||
_transformChanged = true;
|
||||
_visibleTransform[3][0] = 0.0f;
|
||||
_visibleTransform[3][1] = 0.0f;
|
||||
_visibleTransform[3][2] = 0.0f;
|
||||
}
|
||||
|
||||
void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) {
|
||||
void JointState::applyRotationDelta(const glm::quat& delta, float priority) {
|
||||
// NOTE: delta is in model-frame
|
||||
if (priority < _animationPriority || delta == glm::quat()) {
|
||||
return;
|
||||
}
|
||||
_animationPriority = priority;
|
||||
glm::quat targetRotation = _rotationInConstrainedFrame * glm::inverse(getRotation()) * delta * getRotation();
|
||||
if (!constrain || _constraint == NULL) {
|
||||
// no constraints
|
||||
_rotationInConstrainedFrame = targetRotation;
|
||||
_transformChanged = true;
|
||||
|
||||
_rotation = delta * getRotation();
|
||||
return;
|
||||
}
|
||||
setRotationInConstrainedFrameInternal(targetRotation);
|
||||
}
|
||||
|
||||
|
@ -209,36 +159,17 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float
|
|||
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
|
||||
targetRotation = safeMix(targetRotation, _defaultRotation, mixFactor);
|
||||
}
|
||||
if (_constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
setRotationInConstrainedFrameInternal(targetRotation);
|
||||
}
|
||||
|
||||
void JointState::mixVisibleRotationDelta(const glm::quat& delta, float mixFactor) {
|
||||
// NOTE: delta is in model-frame
|
||||
glm::quat targetRotation = _visibleRotationInConstrainedFrame * glm::inverse(_visibleRotation) * delta * _visibleRotation;
|
||||
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
|
||||
targetRotation = safeMix(targetRotation, _rotationInConstrainedFrame, mixFactor);
|
||||
}
|
||||
setVisibleRotationInConstrainedFrame(targetRotation);
|
||||
}
|
||||
|
||||
glm::quat JointState::computeParentRotation() const {
|
||||
// R = Rp * Rpre * r * Rpost
|
||||
// Rp = R * (Rpre * r * Rpost)^
|
||||
return getRotation() * glm::inverse(_preRotation * _rotationInConstrainedFrame * _postRotation);
|
||||
}
|
||||
|
||||
glm::quat JointState::computeVisibleParentRotation() const {
|
||||
return _visibleRotation * glm::inverse(_preRotation * _visibleRotationInConstrainedFrame * _postRotation);
|
||||
}
|
||||
|
||||
void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain, float mix) {
|
||||
void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, float mix) {
|
||||
if (priority >= _animationPriority || _animationPriority == 0.0f) {
|
||||
if (constrain && _constraint) {
|
||||
_constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f);
|
||||
}
|
||||
auto rotation = (mix == 1.0f) ? targetRotation : safeMix(getRotationInConstrainedFrame(), targetRotation, mix);
|
||||
setRotationInConstrainedFrameInternal(rotation);
|
||||
_animationPriority = priority;
|
||||
|
@ -255,12 +186,6 @@ void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRo
|
|||
}
|
||||
}
|
||||
|
||||
void JointState::setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation) {
|
||||
glm::quat parentRotation = computeVisibleParentRotation();
|
||||
_visibleRotationInConstrainedFrame = targetRotation;
|
||||
_visibleRotation = parentRotation * _preRotation * _visibleRotationInConstrainedFrame * _postRotation;
|
||||
}
|
||||
|
||||
bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) const {
|
||||
glm::quat defaultRotation = _defaultRotation;
|
||||
return glm::abs(rotation.x - defaultRotation.x) < tolerance &&
|
||||
|
@ -277,9 +202,3 @@ glm::quat JointState::getDefaultRotationInParentFrame() const {
|
|||
const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const {
|
||||
return _translation;
|
||||
}
|
||||
|
||||
void JointState::slaveVisibleTransform() {
|
||||
_visibleTransform = _transform;
|
||||
_visibleRotation = getRotation();
|
||||
_visibleRotationInConstrainedFrame = _rotationInConstrainedFrame;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
|
||||
const float DEFAULT_PRIORITY = 3.0f;
|
||||
|
||||
class AngularConstraint;
|
||||
|
||||
class JointState {
|
||||
public:
|
||||
JointState() {}
|
||||
|
@ -39,11 +37,6 @@ public:
|
|||
// but _rotation will be asynchronously extracted
|
||||
void computeTransform(const glm::mat4& parentTransform, bool parentTransformChanged = true, bool synchronousRotationCompute = false);
|
||||
|
||||
void computeVisibleTransform(const glm::mat4& parentTransform);
|
||||
const glm::mat4& getVisibleTransform() const { return _visibleTransform; }
|
||||
glm::quat getVisibleRotation() const { return _visibleRotation; }
|
||||
glm::vec3 getVisiblePosition() const { return extractTranslation(_visibleTransform); }
|
||||
|
||||
const glm::mat4& getTransform() const { return _transform; }
|
||||
void resetTransformChanged() { _transformChanged = false; }
|
||||
bool getTransformChanged() const { return _transformChanged; }
|
||||
|
@ -55,14 +48,13 @@ public:
|
|||
glm::quat getRotationInBindFrame() const;
|
||||
|
||||
glm::quat getRotationInParentFrame() const;
|
||||
glm::quat getVisibleRotationInParentFrame() const;
|
||||
const glm::vec3& getPositionInParentFrame() const { return _positionInParentFrame; }
|
||||
float getDistanceToParent() const { return _distanceToParent; }
|
||||
|
||||
int getParentIndex() const { return _parentIndex; }
|
||||
|
||||
/// \param delta is in the model-frame
|
||||
void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f);
|
||||
void applyRotationDelta(const glm::quat& delta, float priority = 1.0f);
|
||||
|
||||
/// Applies delta rotation to joint but mixes a little bit of the default pose as well.
|
||||
/// This helps keep an IK solution stable.
|
||||
|
@ -70,7 +62,6 @@ public:
|
|||
/// \param mixFactor fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
|
||||
/// \param priority priority level of this animation blend
|
||||
void mixRotationDelta(const glm::quat& delta, float mixFactor, float priority = 1.0f);
|
||||
void mixVisibleRotationDelta(const glm::quat& delta, float mixFactor);
|
||||
|
||||
/// Blends a fraciton of default pose into joint rotation.
|
||||
/// \param fraction fraction in range [0,1] of how much default pose to blend in (0 is none, 1 is all)
|
||||
|
@ -80,17 +71,15 @@ public:
|
|||
/// \param rotation is from bind- to model-frame
|
||||
/// computes and sets new _rotationInConstrainedFrame
|
||||
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
||||
void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
|
||||
void setRotationInBindFrame(const glm::quat& rotation, float priority);
|
||||
|
||||
/// \param rotationInModelRame is in model-frame
|
||||
/// computes and sets new _rotationInConstrainedFrame to match rotationInModelFrame
|
||||
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
|
||||
void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority, bool constrain);
|
||||
void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority);
|
||||
|
||||
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false, float mix = 1.0f);
|
||||
void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation);
|
||||
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, float mix = 1.0f);
|
||||
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
|
||||
const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; }
|
||||
|
||||
bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const;
|
||||
|
||||
|
@ -100,15 +89,11 @@ public:
|
|||
|
||||
void clearTransformTranslation();
|
||||
|
||||
void slaveVisibleTransform();
|
||||
|
||||
/// \return parent model-frame rotation
|
||||
// (used to keep _rotation consistent when modifying _rotationInWorldFrame directly)
|
||||
glm::quat computeParentRotation() const;
|
||||
glm::quat computeVisibleParentRotation() const;
|
||||
|
||||
void setTransform(const glm::mat4& transform) { _transform = transform; }
|
||||
void setVisibleTransform(const glm::mat4& transform) { _visibleTransform = transform; }
|
||||
|
||||
const glm::vec3& getTranslation() const { return _translation; }
|
||||
const glm::mat4& getPreTransform() const { return _preTransform; }
|
||||
|
@ -132,24 +117,17 @@ private:
|
|||
glm::vec3 _positionInParentFrame {0.0f}; // only changes when the Model is scaled
|
||||
float _animationPriority {0.0f}; // the priority of the animation affecting this joint
|
||||
float _distanceToParent {0.0f};
|
||||
AngularConstraint* _constraint{nullptr}; // JointState owns its AngularConstraint
|
||||
|
||||
glm::mat4 _transform; // joint- to model-frame
|
||||
glm::quat _rotation; // joint- to model-frame
|
||||
glm::quat _rotationInConstrainedFrame; // rotation in frame where angular constraints would be applied
|
||||
|
||||
glm::mat4 _visibleTransform;
|
||||
glm::quat _visibleRotation;
|
||||
glm::quat _visibleRotationInConstrainedFrame;
|
||||
|
||||
glm::quat _defaultRotation; // Not necessarilly bind rotation. See FBXJoint transform/bindTransform
|
||||
glm::quat _inverseDefaultRotation;
|
||||
glm::vec3 _translation;
|
||||
QString _name;
|
||||
int _parentIndex;
|
||||
bool _isFree;
|
||||
glm::vec3 _rotationMin;
|
||||
glm::vec3 _rotationMax;
|
||||
glm::quat _preRotation;
|
||||
glm::quat _postRotation;
|
||||
glm::mat4 _preTransform;
|
||||
|
|
|
@ -224,14 +224,6 @@ void Rig::initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
|||
_rightShoulderJointIndex = rightShoulderJointIndex;
|
||||
|
||||
initJointTransforms(rootTransform);
|
||||
|
||||
int numStates = _jointStates.size();
|
||||
for (int i = 0; i < numStates; ++i) {
|
||||
_jointStates[i].buildConstraint();
|
||||
}
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
_jointStates[i].slaveVisibleTransform();
|
||||
}
|
||||
}
|
||||
|
||||
// We could build and cache a dictionary, too....
|
||||
|
@ -293,15 +285,6 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const {
|
|||
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];
|
||||
|
@ -392,25 +375,6 @@ bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm:
|
|||
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();
|
||||
|
@ -418,13 +382,6 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
|
|||
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) {
|
||||
|
||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||
|
@ -596,7 +553,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
|||
// copy poses into jointStates
|
||||
const float PRIORITY = 1.0f;
|
||||
for (size_t i = 0; i < poses.size(); i++) {
|
||||
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, false, 1.0f);
|
||||
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, 1.0f);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -719,7 +676,7 @@ bool Rig::setJointPosition(int jointIndex, const glm::vec3& position, const glm:
|
|||
1.0f / (combinedWeight + 1.0f));
|
||||
}
|
||||
}
|
||||
state.applyRotationDelta(combinedDelta, true, priority);
|
||||
state.applyRotationDelta(combinedDelta, priority);
|
||||
glm::quat actualDelta = state.getRotation() * glm::inverse(oldCombinedRotation);
|
||||
endPosition = actualDelta * jointVector + jointPosition;
|
||||
if (useRotation) {
|
||||
|
@ -838,11 +795,9 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
|||
|
||||
// Apply the rotation delta.
|
||||
glm::quat oldNextRotation = nextState.getRotation();
|
||||
float mixFactor = 0.05f;
|
||||
nextState.applyRotationDelta(deltaRotation, mixFactor, priority);
|
||||
nextState.applyRotationDelta(deltaRotation, priority);
|
||||
|
||||
// measure the result of the rotation which may have been modified by
|
||||
// blending and constraints
|
||||
// measure the result of the rotation which may have been modified by blending
|
||||
glm::quat actualDelta = nextState.getRotation() * glm::inverse(oldNextRotation);
|
||||
endPosition = pivot + actualDelta * leverArm;
|
||||
}
|
||||
|
@ -861,7 +816,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
|||
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo > ACCEPTABLE_IK_ERROR);
|
||||
|
||||
// set final rotation of the end joint
|
||||
endState.setRotationInModelFrame(targetRotation, priority, true);
|
||||
endState.setRotationInModelFrame(targetRotation, priority);
|
||||
}
|
||||
|
||||
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
|
||||
|
@ -889,13 +844,13 @@ float Rig::getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
|||
return length;
|
||||
}
|
||||
|
||||
glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority, bool constrain) {
|
||||
glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority) {
|
||||
glm::quat endRotation;
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return endRotation;
|
||||
}
|
||||
JointState& state = _jointStates[jointIndex];
|
||||
state.setRotationInBindFrame(rotation, priority, constrain);
|
||||
state.setRotationInBindFrame(rotation, priority);
|
||||
endRotation = state.getRotationInBindFrame();
|
||||
return endRotation;
|
||||
}
|
||||
|
@ -907,13 +862,13 @@ glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) {
|
|||
return _jointStates[jointIndex].getDefaultTranslationInConstrainedFrame();
|
||||
}
|
||||
|
||||
glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, bool constrain, float mix) {
|
||||
glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, float mix) {
|
||||
glm::quat endRotation;
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return endRotation;
|
||||
}
|
||||
JointState& state = _jointStates[jointIndex];
|
||||
state.setRotationInConstrainedFrame(targetRotation, priority, constrain, mix);
|
||||
state.setRotationInConstrainedFrame(targetRotation, priority, mix);
|
||||
endRotation = state.getRotationInConstrainedFrame();
|
||||
return endRotation;
|
||||
}
|
||||
|
@ -926,30 +881,17 @@ bool Rig::getJointRotationInConstrainedFrame(int jointIndex, glm::quat& quatOut)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Rig::updateVisibleJointStates() {
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
_jointStates[i].slaveVisibleTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::clearJointStatePriorities() {
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
_jointStates[i].setAnimationPriority(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
void Rig::applyJointRotationDelta(int jointIndex, const glm::quat& delta, float priority) {
|
||||
if (jointIndex == -1 || _jointStates.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_jointStates[jointIndex].applyRotationDelta(delta, constrain, priority);
|
||||
_jointStates[jointIndex].applyRotationDelta(delta, priority);
|
||||
}
|
||||
|
||||
glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
|
||||
|
@ -1026,12 +968,21 @@ static void computeHeadNeckAnimVars(AnimSkeleton::ConstPointer skeleton, const A
|
|||
const glm::quat hmdOrientation = hmdPose.rot * rotY180; // rotY180 will make z forward not -z
|
||||
|
||||
// TODO: cache jointIndices
|
||||
int rightEyeIndex = skeleton->nameToJointIndex("RightEye");
|
||||
int leftEyeIndex = skeleton->nameToJointIndex("LeftEye");
|
||||
int headIndex = skeleton->nameToJointIndex("Head");
|
||||
int neckIndex = skeleton->nameToJointIndex("Neck");
|
||||
|
||||
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_HEAD_POS(0.0f, 1.55f, 0.0f);
|
||||
const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f);
|
||||
|
||||
// Use absolute bindPose positions just in case the relBindPose have rotations we don't expect.
|
||||
glm::vec3 absRightEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("RightEye")).trans;
|
||||
glm::vec3 absLeftEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("LeftEye")).trans;
|
||||
glm::vec3 absHeadPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Head")).trans;
|
||||
glm::vec3 absNeckPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Neck")).trans;
|
||||
glm::vec3 absRightEyePos = rightEyeIndex != -1 ? skeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS;
|
||||
glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? skeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS;
|
||||
glm::vec3 absHeadPos = headIndex != -1 ? skeleton->getAbsoluteBindPose(headIndex).trans : DEFAULT_HEAD_POS;
|
||||
glm::vec3 absNeckPos = neckIndex != -1 ? skeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS;
|
||||
|
||||
glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f;
|
||||
glm::vec3 eyeOffset = absCenterEyePos - absHeadPos;
|
||||
|
@ -1079,7 +1030,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
|||
|
||||
_animVars.set("headPosition", headPos);
|
||||
_animVars.set("headRotation", headRot);
|
||||
_animVars.set("headType", QString("RotationAndPosition"));
|
||||
_animVars.set("headAndNeckType", QString("RotationAndPosition"));
|
||||
_animVars.set("neckPosition", neckPos);
|
||||
_animVars.set("neckRotation", neckRot);
|
||||
|
||||
|
@ -1092,7 +1043,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
|||
|
||||
_animVars.unset("headPosition");
|
||||
_animVars.set("headRotation", realLocalHeadOrientation);
|
||||
_animVars.set("headType", QString("RotationOnly"));
|
||||
_animVars.set("headAndNeckType", QString("RotationOnly"));
|
||||
_animVars.unset("neckPosition");
|
||||
_animVars.unset("neckRotation");
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ public:
|
|||
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);
|
||||
void applyJointRotationDelta(int jointIndex, const glm::quat& delta, float priority);
|
||||
JointState getJointState(int jointIndex) const; // XXX
|
||||
bool getVisibleJointState(int index, glm::quat& rotation) const;
|
||||
void clearJointState(int index);
|
||||
|
@ -165,10 +165,10 @@ public:
|
|||
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::quat setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority);
|
||||
glm::vec3 getJointDefaultTranslationInConstrainedFrame(int jointIndex);
|
||||
glm::quat setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation,
|
||||
float priority, bool constrain = false, float mix = 1.0f);
|
||||
float priority, float mix = 1.0f);
|
||||
bool getJointRotationInConstrainedFrame(int jointIndex, glm::quat& rotOut) const;
|
||||
glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
|
||||
void updateVisibleJointStates();
|
||||
|
|
|
@ -966,7 +966,8 @@ bool AvatarData::hasIdentityChangedAfterParsing(NLPacket& packet) {
|
|||
QByteArray AvatarData::identityByteArray() {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? QUrl("") : _skeletonModelURL;
|
||||
QUrl emptyURL("");
|
||||
const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? emptyURL : _skeletonModelURL;
|
||||
|
||||
identityStream << QUuid() << _faceModelURL << urlToSend << _attachmentData << _displayName;
|
||||
|
||||
|
@ -989,8 +990,11 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
|
|||
}
|
||||
|
||||
void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
_skeletonModelURL = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
||||
|
||||
const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL;
|
||||
if (expanded == _skeletonModelURL) {
|
||||
return;
|
||||
}
|
||||
_skeletonModelURL = expanded;
|
||||
qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString();
|
||||
|
||||
updateJointMappings();
|
||||
|
|
|
@ -121,6 +121,8 @@ public:
|
|||
static const glm::mat4 pose; return pose;
|
||||
}
|
||||
|
||||
virtual float getIPD() const { return 0.0f; }
|
||||
|
||||
virtual void abandonCalibration() {}
|
||||
virtual void resetSensors() {}
|
||||
virtual float devicePixelRatio() { return 1.0; }
|
||||
|
|
|
@ -149,3 +149,11 @@ void OculusBaseDisplayPlugin::deactivate() {
|
|||
void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
++_frameIndex;
|
||||
}
|
||||
|
||||
float OculusBaseDisplayPlugin::getIPD() const {
|
||||
float result = 0.0f;
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
result = ovr_GetFloat(_hmd, OVR_KEY_IPD, OVR_DEFAULT_IPD);
|
||||
#endif
|
||||
return result;
|
||||
}
|
|
@ -31,6 +31,7 @@ public:
|
|||
virtual void resetSensors() override final;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override final;
|
||||
virtual glm::mat4 getHeadPose() const override final;
|
||||
virtual float getIPD() const override final;
|
||||
|
||||
protected:
|
||||
virtual void preRender() override final;
|
||||
|
|
|
@ -43,7 +43,7 @@ void RenderablePolyLineEntityItem::createPipeline() {
|
|||
static const int NORMAL_OFFSET = 12;
|
||||
static const int COLOR_OFFSET = 24;
|
||||
static const int TEXTURE_OFFSET = 28;
|
||||
|
||||
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
QString path = PathUtils::resourcesPath() + "images/paintStroke.png";
|
||||
_texture = textureCache->getImageTexture(path);
|
||||
|
@ -52,22 +52,22 @@ void RenderablePolyLineEntityItem::createPipeline() {
|
|||
_format->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), NORMAL_OFFSET);
|
||||
_format->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, COLOR_OFFSET);
|
||||
_format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEXTURE_OFFSET);
|
||||
|
||||
|
||||
auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(paintStroke_vert)));
|
||||
auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(paintStroke_frag)));
|
||||
gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS));
|
||||
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
PAINTSTROKE_GPU_SLOT = 0;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("paintStrokeTextureBinding"), PAINTSTROKE_GPU_SLOT));
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
_pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
_pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
|
||||
}
|
||||
|
||||
void RenderablePolyLineEntityItem::updateGeometry() {
|
||||
|
@ -78,30 +78,30 @@ void RenderablePolyLineEntityItem::updateGeometry() {
|
|||
float tailStart = 0.0f;
|
||||
float tailEnd = 0.25f;
|
||||
float tailLength = tailEnd - tailStart;
|
||||
|
||||
|
||||
float headStart = 0.76f;
|
||||
float headEnd = 1.0f;
|
||||
float headLength = headEnd - headStart;
|
||||
float uCoord, vCoord;
|
||||
|
||||
int numTailStrips = 5;
|
||||
|
||||
int numTailStrips = 5;
|
||||
int numHeadStrips = 10;
|
||||
int startHeadIndex = _normals.size() - numHeadStrips;
|
||||
for (int i = 0; i < _normals.size(); i++) {
|
||||
int startHeadIndex = _vertices.size() / 2 - numHeadStrips;
|
||||
for (int i = 0; i < _vertices.size() / 2; i++) {
|
||||
uCoord = 0.26f;
|
||||
vCoord = 0.0f;
|
||||
//tail
|
||||
if(i < numTailStrips) {
|
||||
uCoord = float(i)/numTailStrips * tailLength + tailStart;
|
||||
if (i < numTailStrips) {
|
||||
uCoord = float(i) / numTailStrips * tailLength + tailStart;
|
||||
}
|
||||
|
||||
|
||||
//head
|
||||
if( i > startHeadIndex) {
|
||||
uCoord = float( (i+ 1) - startHeadIndex)/numHeadStrips * headLength + headStart;
|
||||
if (i > startHeadIndex) {
|
||||
uCoord = float((i + 1) - startHeadIndex) / numHeadStrips * headLength + headStart;
|
||||
}
|
||||
|
||||
uv = vec2(uCoord, vCoord);
|
||||
|
||||
|
||||
_verticesBuffer->append(sizeof(glm::vec3), (const gpu::Byte*)&_vertices.at(vertexIndex));
|
||||
_verticesBuffer->append(sizeof(glm::vec3), (const gpu::Byte*)&_normals.at(i));
|
||||
_verticesBuffer->append(sizeof(int), (gpu::Byte*)&_color);
|
||||
|
@ -114,32 +114,32 @@ void RenderablePolyLineEntityItem::updateGeometry() {
|
|||
_verticesBuffer->append(sizeof(int), (gpu::Byte*)_color);
|
||||
_verticesBuffer->append(sizeof(glm::vec2), (const gpu::Byte*)&uv);
|
||||
vertexIndex++;
|
||||
|
||||
_numVertices +=2;
|
||||
|
||||
_numVertices += 2;
|
||||
}
|
||||
_pointsChanged = false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyLineEntityItem::render(RenderArgs* args) {
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
if (_points.size() < 2 || _vertices.size() != _normals.size() * 2) {
|
||||
if (_points.size() < 2 || _normals.size () < 2 || _vertices.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!_pipeline) {
|
||||
createPipeline();
|
||||
}
|
||||
|
||||
|
||||
PerformanceTimer perfTimer("RenderablePolyLineEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::PolyLine);
|
||||
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
if (_pointsChanged) {
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
Transform transform = Transform();
|
||||
transform.setTranslation(getPosition());
|
||||
|
@ -151,8 +151,8 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) {
|
|||
|
||||
batch.setInputFormat(_format);
|
||||
batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride);
|
||||
|
||||
|
||||
batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0);
|
||||
|
||||
|
||||
RenderableDebugableEntityItem::render(this, args);
|
||||
};
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
static EntityActionType actionTypeFromString(QString actionTypeString);
|
||||
static QString actionTypeToString(EntityActionType actionType);
|
||||
|
||||
virtual bool lifetimeIsOver() { return false; }
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() = 0;
|
||||
virtual void setPosition(glm::vec3 position) = 0;
|
||||
|
|
|
@ -594,25 +594,22 @@ QString ParticleEffectEntityItem::getAnimationSettings() const {
|
|||
}
|
||||
|
||||
void ParticleEffectEntityItem::updateRadius(quint32 index, float age) {
|
||||
_particleRadiuses[index] = Interpolate::cubicInterpolate3Points(_radiusStarts[index], _radiusMiddles[index],
|
||||
_particleRadiuses[index] = Interpolate::interpolate3Points(_radiusStarts[index], _radiusMiddles[index],
|
||||
_radiusFinishes[index], age);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::updateColor(quint32 index, float age) {
|
||||
_particleColors[index].red =
|
||||
(int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].red, _colorMiddles[index].red,
|
||||
_colorFinishes[index].red, age), 0.0f, 255.0f);
|
||||
_particleColors[index].green =
|
||||
(int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].green, _colorMiddles[index].green,
|
||||
_colorFinishes[index].green, age), 0.0f, 255.0f);
|
||||
_particleColors[index].blue =
|
||||
(int)glm::clamp(Interpolate::cubicInterpolate3Points(_colorStarts[index].blue, _colorMiddles[index].blue,
|
||||
_colorFinishes[index].blue, age), 0.0f, 255.0f);
|
||||
_particleColors[index].red = (int)Interpolate::interpolate3Points(_colorStarts[index].red, _colorMiddles[index].red,
|
||||
_colorFinishes[index].red, age);
|
||||
_particleColors[index].green = (int)Interpolate::interpolate3Points(_colorStarts[index].green, _colorMiddles[index].green,
|
||||
_colorFinishes[index].green, age);
|
||||
_particleColors[index].blue = (int)Interpolate::interpolate3Points(_colorStarts[index].blue, _colorMiddles[index].blue,
|
||||
_colorFinishes[index].blue, age);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::updateAlpha(quint32 index, float age) {
|
||||
_particleAlphas[index] = glm::clamp(Interpolate::cubicInterpolate3Points(_alphaStarts[index], _alphaMiddles[index],
|
||||
_alphaFinishes[index], age), 0.0f, 1.0f);
|
||||
_particleAlphas[index] = Interpolate::interpolate3Points(_alphaStarts[index], _alphaMiddles[index],
|
||||
_alphaFinishes[index], age);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) {
|
||||
|
|
|
@ -27,12 +27,12 @@ const int PolyLineEntityItem::MAX_POINTS_PER_LINE = 70;
|
|||
|
||||
|
||||
EntityItemPointer PolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer result { new PolyLineEntityItem(entityID, properties) };
|
||||
EntityItemPointer result{ new PolyLineEntityItem(entityID, properties) };
|
||||
return result;
|
||||
}
|
||||
|
||||
PolyLineEntityItem::PolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
|
||||
EntityItem(entityItemID) ,
|
||||
EntityItem(entityItemID),
|
||||
_lineWidth(DEFAULT_LINE_WIDTH),
|
||||
_pointsChanged(true),
|
||||
_points(QVector<glm::vec3>(0.0f)),
|
||||
|
@ -48,16 +48,16 @@ _strokeWidths(QVector<float>(0.0f))
|
|||
EntityItemProperties PolyLineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
|
||||
|
||||
|
||||
|
||||
properties._color = getXColor();
|
||||
properties._colorChanged = false;
|
||||
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(normals, getNormals);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths);
|
||||
|
||||
|
||||
properties._glowLevel = getGlowLevel();
|
||||
properties._glowLevelChanged = false;
|
||||
return properties;
|
||||
|
@ -67,20 +67,20 @@ bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
bool somethingChanged = false;
|
||||
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineWidth, setLineWidth);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(linePoints, setLinePoints);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(normals, setNormals);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(strokeWidths, setStrokeWidths);
|
||||
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "PolyLineEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties._lastEdited);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) {
|
|||
return false;
|
||||
}
|
||||
glm::vec3 halfBox = getDimensions() * 0.5f;
|
||||
if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) {
|
||||
if ((point.x < -halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < -halfBox.z || point.z > halfBox.z)) {
|
||||
qDebug() << "Point is outside entity's bounding box";
|
||||
return false;
|
||||
}
|
||||
|
@ -102,17 +102,17 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PolyLineEntityItem::setStrokeWidths(const QVector<float>& strokeWidths ) {
|
||||
bool PolyLineEntityItem::setStrokeWidths(const QVector<float>& strokeWidths) {
|
||||
_strokeWidths = strokeWidths;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) {
|
||||
_normals = normals;
|
||||
if (_points.size () < 2 || _normals.size() < 2) {
|
||||
if (_points.size() < 2 || _normals.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int minVectorSize = _normals.size();
|
||||
if (_points.size() < minVectorSize) {
|
||||
minVectorSize = _points.size();
|
||||
|
@ -123,15 +123,15 @@ bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) {
|
|||
|
||||
_vertices.clear();
|
||||
glm::vec3 v1, v2, tangent, binormal, point;
|
||||
|
||||
for (int i = 0; i < minVectorSize-1; i++) {
|
||||
|
||||
for (int i = 0; i < minVectorSize - 1; i++) {
|
||||
float width = _strokeWidths.at(i);
|
||||
point = _points.at(i);
|
||||
|
||||
tangent = _points.at(i+1) - point;
|
||||
|
||||
tangent = _points.at(i + 1) - point;
|
||||
glm::vec3 normal = normals.at(i);
|
||||
binormal = glm::normalize(glm::cross(tangent, normal)) * width;
|
||||
|
||||
|
||||
//This checks to make sure binormal is not a NAN
|
||||
assert(binormal.x == binormal.x);
|
||||
v1 = point + binormal;
|
||||
|
@ -139,11 +139,11 @@ bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) {
|
|||
_vertices << v1 << v2;
|
||||
}
|
||||
//for last point we can just assume binormals are same since it represents last two vertices of quad
|
||||
point = _points.at(_points.size() - 1);
|
||||
point = _points.at(minVectorSize - 1);
|
||||
v1 = point + binormal;
|
||||
v2 = point - binormal;
|
||||
_vertices << v1 << v2;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,7 @@ bool PolyLineEntityItem::setLinePoints(const QVector<glm::vec3>& points) {
|
|||
//Check to see if points actually changed. If they haven't, return before doing anything else
|
||||
else if (points.size() == _points.size()) {
|
||||
//same number of points, so now compare every point
|
||||
for (int i = 0; i < points.size(); i++ ) {
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
if (points.at(i) != _points.at(i)){
|
||||
_pointsChanged = true;
|
||||
break;
|
||||
|
@ -171,9 +171,9 @@ bool PolyLineEntityItem::setLinePoints(const QVector<glm::vec3>& points) {
|
|||
for (int i = 0; i < points.size(); i++) {
|
||||
glm::vec3 point = points.at(i);
|
||||
glm::vec3 halfBox = getDimensions() * 0.5f;
|
||||
if ((point.x < - halfBox.x || point.x > halfBox.x) ||
|
||||
if ((point.x < -halfBox.x || point.x > halfBox.x) ||
|
||||
(point.y < -halfBox.y || point.y > halfBox.y) ||
|
||||
(point.z < - halfBox.z || point.z > halfBox.z)) {
|
||||
(point.z < -halfBox.z || point.z > halfBox.z)) {
|
||||
qDebug() << "Point is outside entity's bounding box";
|
||||
return false;
|
||||
}
|
||||
|
@ -183,18 +183,18 @@ bool PolyLineEntityItem::setLinePoints(const QVector<glm::vec3>& points) {
|
|||
}
|
||||
|
||||
int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);
|
||||
READ_ENTITY_PROPERTY(PROP_LINE_WIDTH, float, setLineWidth);
|
||||
READ_ENTITY_PROPERTY(PROP_LINE_POINTS, QVector<glm::vec3>, setLinePoints);
|
||||
READ_ENTITY_PROPERTY(PROP_NORMALS, QVector<glm::vec3>, setNormals);
|
||||
READ_ENTITY_PROPERTY(PROP_NORMALS, QVector<glm::vec3>, setNormals);
|
||||
READ_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths);
|
||||
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -211,16 +211,16 @@ EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParam
|
|||
}
|
||||
|
||||
void PolyLineEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
bool successPropertyFits = true;
|
||||
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, getLineWidth());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, getLinePoints());
|
||||
|
|
|
@ -144,13 +144,12 @@ void FBXReader::consolidateFBXMaterials() {
|
|||
// FIXME: Do not use the Specular Factor yet as some FBX models have it set to 0
|
||||
// metallic *= material.specularFactor;
|
||||
material._material->setMetallic(metallic);
|
||||
material._material->setGloss(material.shininess);
|
||||
material._material->setGloss(material.shininess);
|
||||
|
||||
if (material.opacity <= 0.0f) {
|
||||
material._material->setOpacity(1.0f);
|
||||
material._material->setOpacity(1.0f);
|
||||
} else {
|
||||
material._material->setOpacity(material.opacity);
|
||||
material._material->setOpacity(material.opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -539,14 +539,16 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
|
|||
|
||||
foreach (QString materialID, materials.keys()) {
|
||||
OBJMaterial& objMaterial = materials[materialID];
|
||||
geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, // glm::vec3(1.0f, 1.0f, 1.0f)
|
||||
objMaterial.specularColor, // glm::vec3(1.0f)
|
||||
glm::vec3(), // glm::vec3()
|
||||
glm::vec2(0.f, 1.0f), // glm::vec2(0.f, 1.0f)
|
||||
objMaterial.shininess, // 96.0f
|
||||
objMaterial.opacity); // 1.0f
|
||||
FBXMaterial& material = geometry.materials[materialID];
|
||||
material._material = std::make_shared<model::Material>();
|
||||
geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor,
|
||||
objMaterial.specularColor,
|
||||
glm::vec3(0.0f),
|
||||
glm::vec2(0.0f, 1.0f),
|
||||
objMaterial.shininess,
|
||||
objMaterial.opacity);
|
||||
FBXMaterial& fbxMaterial = geometry.materials[materialID];
|
||||
fbxMaterial.materialID = materialID;
|
||||
fbxMaterial._material = std::make_shared<model::Material>();
|
||||
model::MaterialPointer modelMaterial = fbxMaterial._material;
|
||||
|
||||
if (!objMaterial.diffuseTextureFilename.isEmpty()) {
|
||||
FBXTexture texture;
|
||||
|
@ -554,21 +556,16 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
|
|||
// TODO -- something to get textures working again
|
||||
}
|
||||
|
||||
material._material->setEmissive(material.emissiveColor);
|
||||
if (glm::all(glm::equal(material.diffuseColor, glm::vec3(0.0f)))) {
|
||||
material._material->setDiffuse(material.diffuseColor);
|
||||
} else {
|
||||
material._material->setDiffuse(material.diffuseColor);
|
||||
}
|
||||
material._material->setMetallic(glm::length(material.specularColor));
|
||||
material._material->setGloss(material.shininess);
|
||||
modelMaterial->setEmissive(fbxMaterial.emissiveColor);
|
||||
modelMaterial->setDiffuse(fbxMaterial.diffuseColor);
|
||||
modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor));
|
||||
modelMaterial->setGloss(fbxMaterial.shininess);
|
||||
|
||||
if (material.opacity <= 0.0f) {
|
||||
material._material->setOpacity(1.0f);
|
||||
if (fbxMaterial.opacity <= 0.0f) {
|
||||
modelMaterial->setOpacity(1.0f);
|
||||
} else {
|
||||
material._material->setOpacity(material.opacity);
|
||||
modelMaterial->setOpacity(fbxMaterial.opacity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return geometryPtr;
|
||||
|
|
|
@ -23,7 +23,8 @@ std::unique_ptr<NLPacketList> NLPacketList::create(PacketType packetType, QByteA
|
|||
}
|
||||
|
||||
std::unique_ptr<NLPacketList> NLPacketList::fromPacketList(std::unique_ptr<PacketList> packetList) {
|
||||
auto nlPacketList = std::unique_ptr<NLPacketList>(new NLPacketList(std::move(*packetList.release()))); nlPacketList->open(ReadOnly);
|
||||
auto nlPacketList = std::unique_ptr<NLPacketList>(new NLPacketList(std::move(*packetList.release())));
|
||||
nlPacketList->open(ReadOnly);
|
||||
return nlPacketList;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& pack
|
|||
}
|
||||
_newestSequenceNumber = sequenceNumber;
|
||||
|
||||
QWriteLocker locker(&_packetsLock);
|
||||
_sentPackets.insert(NLPacket::createCopy(packet));
|
||||
}
|
||||
|
||||
|
@ -48,6 +49,11 @@ const NLPacket* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
|
|||
if (seqDiff < 0) {
|
||||
seqDiff += UINT16_RANGE;
|
||||
}
|
||||
|
||||
return _sentPackets.get(seqDiff)->get();
|
||||
|
||||
QReadLocker locker(&_packetsLock);
|
||||
auto packet = _sentPackets.get(seqDiff);
|
||||
if (packet) {
|
||||
return packet->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#define hifi_SentPacketHistory_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <qbytearray.h>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
#include "NLPacket.h"
|
||||
#include "RingBufferHistory.h"
|
||||
|
@ -29,6 +31,7 @@ public:
|
|||
const NLPacket* getPacket(uint16_t sequenceNumber) const;
|
||||
|
||||
private:
|
||||
mutable QReadWriteLock _packetsLock { QReadWriteLock::Recursive };
|
||||
RingBufferHistory<std::unique_ptr<NLPacket>> _sentPackets; // circular buffer
|
||||
|
||||
uint16_t _newestSequenceNumber;
|
||||
|
|
|
@ -49,7 +49,8 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
|||
}
|
||||
|
||||
if (_statsTimer) {
|
||||
_statsTimer->stop();
|
||||
_statsTimer->deleteLater();
|
||||
_statsTimer = nullptr;
|
||||
}
|
||||
|
||||
// call our virtual aboutToFinish method - this gives the ThreadedAssignment subclass a chance to cleanup
|
||||
|
@ -105,7 +106,7 @@ void ThreadedAssignment::sendStatsPacket() {
|
|||
void ThreadedAssignment::startSendingStats() {
|
||||
// send the stats packet every 1s
|
||||
if (!_statsTimer) {
|
||||
_statsTimer = new QTimer();
|
||||
_statsTimer = new QTimer;
|
||||
connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket);
|
||||
}
|
||||
|
||||
|
|
|
@ -161,13 +161,16 @@ void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {
|
|||
|
||||
_lastDecreaseMaxSeq = _sendCurrSeqNum;
|
||||
|
||||
// avoid synchronous rate decrease across connections using randomization
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(1, _avgNAKNum);
|
||||
|
||||
_randomDecreaseThreshold = distribution(generator);
|
||||
|
||||
if (_avgNAKNum < 1) {
|
||||
_randomDecreaseThreshold = 1;
|
||||
} else {
|
||||
// avoid synchronous rate decrease across connections using randomization
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(1, std::max(1, _avgNAKNum));
|
||||
|
||||
_randomDecreaseThreshold = distribution(generator);
|
||||
}
|
||||
} else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((++_nakCount % _randomDecreaseThreshold) == 0)) {
|
||||
// there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we
|
||||
// decided we would decrease the packet send period
|
||||
|
|
|
@ -32,7 +32,7 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq
|
|||
_destination(destination),
|
||||
_congestionControl(move(congestionControl))
|
||||
{
|
||||
Q_ASSERT_X(socket, "Connection::Connection", "Must be called with a valid Socket*");
|
||||
Q_ASSERT_X(parentSocket, "Connection::Connection", "Must be called with a valid Socket*");
|
||||
|
||||
Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object");
|
||||
_congestionControl->init();
|
||||
|
|
|
@ -28,7 +28,8 @@ class Packet;
|
|||
class PacketList : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static std::unique_ptr<PacketList> create(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false);
|
||||
static std::unique_ptr<PacketList> create(PacketType packetType, QByteArray extendedHeader = QByteArray(),
|
||||
bool isReliable = false, bool isOrdered = false);
|
||||
static std::unique_ptr<PacketList> fromReceivedPackets(std::list<std::unique_ptr<Packet>>&& packets);
|
||||
|
||||
bool isReliable() const { return _isReliable; }
|
||||
|
|
|
@ -346,6 +346,7 @@ void OctreeEditPacketSender::processNackPacket(NLPacket& packet, SharedNodePoint
|
|||
if (_sentPacketHistories.count(sendingNode->getUUID()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SentPacketHistory& sentPacketHistory = _sentPacketHistories[sendingNode->getUUID()];
|
||||
|
||||
// read sequence numbers and queue packets for resend
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Constraint.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.07.24
|
||||
// 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_Constraint_h
|
||||
#define hifi_Constraint_h
|
||||
|
||||
class Constraint {
|
||||
public:
|
||||
Constraint() {}
|
||||
virtual ~Constraint() {}
|
||||
|
||||
/// Enforce contraint by moving relevant points.
|
||||
/// \return max distance of point movement
|
||||
virtual float enforce() = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_Constraint_h
|
|
@ -30,6 +30,18 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
|
|||
dynamicsWorld->removeAction(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_expires > 0) {
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now > _expires) {
|
||||
EntityItemPointer ownerEntity = _ownerEntity.lock();
|
||||
_active = false;
|
||||
if (ownerEntity) {
|
||||
ownerEntity->removeAction(nullptr, getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
|
@ -37,6 +49,40 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
|
|||
updateActionWorker(deltaTimeStep);
|
||||
}
|
||||
|
||||
bool ObjectAction::updateArguments(QVariantMap arguments) {
|
||||
bool lifetimeSet = true;
|
||||
float lifetime = EntityActionInterface::extractFloatArgument("action", arguments, "lifetime", lifetimeSet, false);
|
||||
if (lifetimeSet) {
|
||||
quint64 now = usecTimestampNow();
|
||||
_expires = now + (quint64)(lifetime * USECS_PER_SECOND);
|
||||
} else {
|
||||
_expires = 0;
|
||||
}
|
||||
|
||||
bool tagSet = true;
|
||||
QString tag = EntityActionInterface::extractStringArgument("action", arguments, "tag", tagSet, false);
|
||||
if (tagSet) {
|
||||
_tag = tag;
|
||||
} else {
|
||||
tag = "";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectAction::getArguments() {
|
||||
QVariantMap arguments;
|
||||
if (_expires == 0) {
|
||||
arguments["lifetime"] = 0.0f;
|
||||
} else {
|
||||
quint64 now = usecTimestampNow();
|
||||
arguments["lifetime"] = (float)(_expires - now) / (float)USECS_PER_SECOND;
|
||||
}
|
||||
arguments["tag"] = _tag;
|
||||
return arguments;
|
||||
}
|
||||
|
||||
|
||||
void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) {
|
||||
}
|
||||
|
||||
|
@ -136,3 +182,14 @@ void ObjectAction::activateBody() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ObjectAction::lifetimeIsOver() {
|
||||
if (_expires == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now >= _expires) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ public:
|
|||
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) = 0;
|
||||
virtual QVariantMap getArguments() = 0;
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
// this is called from updateAction and should be overridden by subclasses
|
||||
virtual void updateActionWorker(float deltaTimeStep) = 0;
|
||||
|
@ -46,6 +46,8 @@ public:
|
|||
virtual QByteArray serialize() const = 0;
|
||||
virtual void deserialize(QByteArray serializedArguments) = 0;
|
||||
|
||||
virtual bool lifetimeIsOver();
|
||||
|
||||
protected:
|
||||
|
||||
virtual btRigidBody* getRigidBody();
|
||||
|
@ -59,11 +61,11 @@ protected:
|
|||
virtual void setAngularVelocity(glm::vec3 angularVelocity);
|
||||
virtual void activateBody();
|
||||
|
||||
private:
|
||||
|
||||
protected:
|
||||
bool _active;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
|
||||
quint64 _expires; // in seconds since epoch
|
||||
QString _tag;
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectAction_h
|
||||
|
|
|
@ -80,6 +80,9 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
|
|||
|
||||
|
||||
bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
glm::vec3 pointToOffsetFrom =
|
||||
EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
|
||||
|
@ -90,7 +93,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
|||
ok = true;
|
||||
float linearTimeScale =
|
||||
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
|
||||
if (!ok) {
|
||||
if (!ok) {
|
||||
linearTimeScale = _linearTimeScale;
|
||||
}
|
||||
|
||||
|
@ -119,7 +122,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
|||
}
|
||||
|
||||
QVariantMap ObjectActionOffset::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&] {
|
||||
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
|
@ -140,6 +143,9 @@ QByteArray ObjectActionOffset::serialize() const {
|
|||
dataStream << _linearTimeScale;
|
||||
dataStream << _positionalTargetSet;
|
||||
|
||||
dataStream << _expires;
|
||||
dataStream << _tag;
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
|
@ -165,5 +171,8 @@ void ObjectActionOffset::deserialize(QByteArray serializedArguments) {
|
|||
dataStream >> _linearTimeScale;
|
||||
dataStream >> _positionalTargetSet;
|
||||
|
||||
dataStream >> _expires;
|
||||
dataStream >> _tag;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
|
@ -109,6 +109,9 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
const float MIN_TIMESCALE = 0.1f;
|
||||
|
||||
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
// targets are required, spring-constants are optional
|
||||
bool ok = true;
|
||||
glm::vec3 positionalTarget =
|
||||
|
@ -155,7 +158,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
}
|
||||
|
||||
QVariantMap ObjectActionSpring::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&] {
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
arguments["targetPosition"] = glmToQMap(_positionalTarget);
|
||||
|
@ -182,6 +185,9 @@ QByteArray ObjectActionSpring::serialize() const {
|
|||
dataStream << _angularTimeScale;
|
||||
dataStream << _rotationalTargetSet;
|
||||
|
||||
dataStream << _expires;
|
||||
dataStream << _tag;
|
||||
|
||||
return serializedActionArguments;
|
||||
}
|
||||
|
||||
|
@ -210,5 +216,8 @@ void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
|
|||
dataStream >> _angularTimeScale;
|
||||
dataStream >> _rotationalTargetSet;
|
||||
|
||||
dataStream >> _expires;
|
||||
dataStream >> _tag;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
|
@ -442,7 +442,7 @@ void DeferredLightingEffect::render(RenderArgs* args) {
|
|||
|
||||
deferredTransforms[i].projection = projMats[i];
|
||||
|
||||
auto sideViewMat = eyeViews[i] * monoViewMat;
|
||||
auto sideViewMat = monoViewMat * glm::inverse(eyeViews[i]);
|
||||
viewTransforms[i].evalFromRawMatrix(sideViewMat);
|
||||
deferredTransforms[i].viewInverse = sideViewMat;
|
||||
|
||||
|
@ -574,10 +574,8 @@ void DeferredLightingEffect::render(RenderArgs* args) {
|
|||
|
||||
for (auto lightID : _pointLights) {
|
||||
auto& light = _allocatedLights[lightID];
|
||||
// IN DEBUG: light->setShowContour(true);
|
||||
if (_pointLightLocations->lightBufferUnit >= 0) {
|
||||
batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer());
|
||||
}
|
||||
// IN DEBUG: light->setShowContour(true);
|
||||
batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer());
|
||||
|
||||
float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION);
|
||||
// TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume,
|
||||
|
@ -612,8 +610,7 @@ void DeferredLightingEffect::render(RenderArgs* args) {
|
|||
|
||||
for (auto lightID : _spotLights) {
|
||||
auto light = _allocatedLights[lightID];
|
||||
// IN DEBUG: light->setShowContour(true);
|
||||
|
||||
// IN DEBUG: light->setShowContour(true);
|
||||
batch.setUniformBuffer(_spotLightLocations->lightBufferUnit, light->getSchemaBuffer());
|
||||
|
||||
auto eyeLightPos = eyePoint - light->getPosition();
|
||||
|
|
|
@ -68,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
_scaledToFit(false),
|
||||
_snapModelToRegistrationPoint(false),
|
||||
_snappedToRegistrationPoint(false),
|
||||
_showTrueJointTransforms(true),
|
||||
_cauterizeBones(false),
|
||||
_pupilDilation(0.0f),
|
||||
_url(HTTP_INVALID_COM),
|
||||
|
@ -1008,10 +1007,6 @@ bool Model::getJointState(int index, glm::quat& rotation) const {
|
|||
return _rig->getJointStateRotation(index, rotation);
|
||||
}
|
||||
|
||||
bool Model::getVisibleJointState(int index, glm::quat& rotation) const {
|
||||
return _rig->getVisibleJointState(index, rotation);
|
||||
}
|
||||
|
||||
void Model::clearJointState(int index) {
|
||||
_rig->clearJointState(index);
|
||||
}
|
||||
|
@ -1093,14 +1088,6 @@ bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const
|
|||
return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation);
|
||||
}
|
||||
|
||||
bool Model::getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
return _rig->getVisibleJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
|
||||
}
|
||||
|
||||
bool Model::getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const {
|
||||
return _rig->getVisibleJointRotationInWorldFrame(jointIndex, rotation, _rotation);
|
||||
}
|
||||
|
||||
QStringList Model::getJointNames() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QStringList result;
|
||||
|
@ -1304,33 +1291,17 @@ void Model::updateClusterMatrices() {
|
|||
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);
|
||||
auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
|
||||
state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
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);
|
||||
auto jointMatrix = _rig->getJointVisibleTransform(cluster.jointIndex); // differs from above only in using get...VisibleTransform
|
||||
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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1546,13 +1517,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, int shape
|
|||
pickPrograms(batch, mode, translucentMesh, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, wireframe,
|
||||
args, locations);
|
||||
|
||||
{
|
||||
if (!_showTrueJointTransforms) {
|
||||
PerformanceTimer perfTimer("_rig->updateVisibleJointStates()");
|
||||
_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
|
||||
// to false to rebuild out mesh groups.
|
||||
if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) {
|
||||
|
|
|
@ -221,26 +221,17 @@ protected:
|
|||
/// \return whether or not the joint state is "valid" (that is, non-default)
|
||||
bool getJointState(int index, glm::quat& rotation) const;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Returns the index of the last free ancestor of the indexed joint, or -1 if not found.
|
||||
int getLastFreeJointIndex(int jointIndex) const;
|
||||
|
||||
bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
|
||||
bool getVisibleJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
|
||||
|
||||
/// \param jointIndex index of joint in model structure
|
||||
/// \param position[out] position of joint in model-frame
|
||||
/// \return true if joint exists
|
||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
|
||||
void setShowTrueJointTransforms(bool show) { _showTrueJointTransforms = show; }
|
||||
|
||||
QSharedPointer<NetworkGeometry> _geometry;
|
||||
void setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry);
|
||||
|
||||
|
@ -259,8 +250,6 @@ protected:
|
|||
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;
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
QVector<glm::mat4> clusterMatrices;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
// the interpolated normal
|
||||
in vec3 _normal;
|
||||
in vec3 _modelNormal;
|
||||
in vec3 _color;
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _position;
|
||||
|
|
|
@ -22,6 +22,7 @@ uniform bool Instanced = false;
|
|||
// the interpolated normal
|
||||
|
||||
out vec3 _normal;
|
||||
out vec3 _modelNormal;
|
||||
out vec3 _color;
|
||||
out vec2 _texCoord0;
|
||||
out vec4 _position;
|
||||
|
@ -30,6 +31,7 @@ void main(void) {
|
|||
_color = inColor.rgb;
|
||||
_texCoord0 = inTexCoord0.st;
|
||||
_position = inPosition;
|
||||
_modelNormal = inNormal.xyz;
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
//
|
||||
// AngularConstraint.cpp
|
||||
// interface/src/renderer
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// 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 <glm/gtx/norm.hpp>
|
||||
|
||||
#include "AngularConstraint.h"
|
||||
#include "GLMHelpers.h"
|
||||
#include "NumericalConstants.h"
|
||||
|
||||
// helper function
|
||||
/// \param angle radian angle to be clamped within angleMin and angleMax
|
||||
/// \param angleMin minimum value
|
||||
/// \param angleMax maximum value
|
||||
/// \return value between minAngle and maxAngle closest to angle
|
||||
float clampAngle(float angle, float angleMin, float angleMax) {
|
||||
float minDistance = angle - angleMin;
|
||||
float maxDistance = angle - angleMax;
|
||||
if (maxDistance > 0.0f) {
|
||||
minDistance = glm::min(minDistance, angleMin + TWO_PI - angle);
|
||||
angle = (minDistance < maxDistance) ? angleMin : angleMax;
|
||||
} else if (minDistance < 0.0f) {
|
||||
maxDistance = glm::max(maxDistance, angleMax - TWO_PI - angle);
|
||||
angle = (minDistance > maxDistance) ? angleMin : angleMax;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
// static
|
||||
AngularConstraint* AngularConstraint::newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles) {
|
||||
float minDistance2 = glm::distance2(minAngles, glm::vec3(-PI, -PI, -PI));
|
||||
float maxDistance2 = glm::distance2(maxAngles, glm::vec3(PI, PI, PI));
|
||||
if (minDistance2 < EPSILON && maxDistance2 < EPSILON) {
|
||||
// no constraint
|
||||
return NULL;
|
||||
}
|
||||
// count the zero length elements
|
||||
glm::vec3 rangeAngles = maxAngles - minAngles;
|
||||
int pivotIndex = -1;
|
||||
int numZeroes = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (rangeAngles[i] < EPSILON) {
|
||||
++numZeroes;
|
||||
} else {
|
||||
pivotIndex = i;
|
||||
}
|
||||
}
|
||||
if (numZeroes == 2) {
|
||||
// this is a hinge
|
||||
int forwardIndex = (pivotIndex + 1) % 3;
|
||||
glm::vec3 forwardAxis(0.0f);
|
||||
forwardAxis[forwardIndex] = 1.0f;
|
||||
glm::vec3 rotationAxis(0.0f);
|
||||
rotationAxis[pivotIndex] = 1.0f;
|
||||
return new HingeConstraint(forwardAxis, rotationAxis, minAngles[pivotIndex], maxAngles[pivotIndex]);
|
||||
} else if (numZeroes == 0) {
|
||||
// approximate the angular limits with a cone roller
|
||||
// we assume the roll is about z
|
||||
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
|
||||
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 coneAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
// the coneAngle is half the average range of the two non-roll rotations
|
||||
glm::vec3 range = maxAngles - minAngles;
|
||||
float coneAngle = 0.25f * (range[0] + range[1]);
|
||||
return new ConeRollerConstraint(coneAngle, coneAxis, minAngles.z, maxAngles.z);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool AngularConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) {
|
||||
glm::quat clampedTarget = targetRotation;
|
||||
bool clamped = clamp(clampedTarget);
|
||||
if (clamped) {
|
||||
// check if oldRotation is also clamped
|
||||
glm::quat clampedOld = oldRotation;
|
||||
bool clamped2 = clamp(clampedOld);
|
||||
if (clamped2) {
|
||||
// oldRotation is already beyond the constraint
|
||||
// we clamp again midway between targetRotation and clamped oldPosition
|
||||
clampedTarget = glm::shortMix(clampedOld, targetRotation, mixFraction);
|
||||
// and then clamp that
|
||||
clamp(clampedTarget);
|
||||
}
|
||||
// finally we mix targetRotation with the clampedTarget
|
||||
targetRotation = glm::shortMix(clampedTarget, targetRotation, mixFraction);
|
||||
}
|
||||
return clamped;
|
||||
}
|
||||
|
||||
HingeConstraint::HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle)
|
||||
: _minAngle(minAngle), _maxAngle(maxAngle) {
|
||||
assert(_minAngle < _maxAngle);
|
||||
// we accept the rotationAxis direction
|
||||
assert(glm::length(rotationAxis) > EPSILON);
|
||||
_rotationAxis = glm::normalize(rotationAxis);
|
||||
// but we compute the final _forwardAxis
|
||||
glm::vec3 otherAxis = glm::cross(_rotationAxis, forwardAxis);
|
||||
assert(glm::length(otherAxis) > EPSILON);
|
||||
_forwardAxis = glm::normalize(glm::cross(otherAxis, _rotationAxis));
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool HingeConstraint::clamp(glm::quat& rotation) const {
|
||||
glm::vec3 forward = rotation * _forwardAxis;
|
||||
forward -= glm::dot(forward, _rotationAxis) * _rotationAxis;
|
||||
float length = glm::length(forward);
|
||||
if (length < EPSILON) {
|
||||
// infinite number of solutions ==> choose the middle of the contrained range
|
||||
rotation = glm::angleAxis(0.5f * (_minAngle + _maxAngle), _rotationAxis);
|
||||
return true;
|
||||
}
|
||||
forward /= length;
|
||||
float sign = (glm::dot(glm::cross(_forwardAxis, forward), _rotationAxis) > 0.0f ? 1.0f : -1.0f);
|
||||
//float angle = sign * acos(glm::dot(forward, _forwardAxis) / length);
|
||||
float angle = sign * acosf(glm::dot(forward, _forwardAxis));
|
||||
glm::quat newRotation = glm::angleAxis(clampAngle(angle, _minAngle, _maxAngle), _rotationAxis);
|
||||
if (fabsf(1.0f - glm::dot(newRotation, rotation)) > EPSILON * EPSILON) {
|
||||
rotation = newRotation;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HingeConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) {
|
||||
// the hinge works best without a soft clamp
|
||||
return clamp(targetRotation);
|
||||
}
|
||||
|
||||
ConeRollerConstraint::ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll)
|
||||
: _coneAngle(coneAngle), _minRoll(minRoll), _maxRoll(maxRoll) {
|
||||
assert(_maxRoll >= _minRoll);
|
||||
float axisLength = glm::length(coneAxis);
|
||||
assert(axisLength > EPSILON);
|
||||
_coneAxis = coneAxis / axisLength;
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool ConeRollerConstraint::clamp(glm::quat& rotation) const {
|
||||
bool applied = false;
|
||||
glm::vec3 rotatedAxis = rotation * _coneAxis;
|
||||
glm::vec3 perpAxis = glm::cross(rotatedAxis, _coneAxis);
|
||||
float perpAxisLength = glm::length(perpAxis);
|
||||
if (perpAxisLength > EPSILON) {
|
||||
perpAxis /= perpAxisLength;
|
||||
// enforce the cone
|
||||
float angle = acosf(glm::dot(rotatedAxis, _coneAxis));
|
||||
if (angle > _coneAngle) {
|
||||
rotation = glm::angleAxis(angle - _coneAngle, perpAxis) * rotation;
|
||||
rotatedAxis = rotation * _coneAxis;
|
||||
applied = true;
|
||||
}
|
||||
} else {
|
||||
// the rotation is 100% roll
|
||||
// there is no obvious perp axis so we must pick one
|
||||
perpAxis = rotatedAxis;
|
||||
// find the first non-zero element:
|
||||
float iValue = 0.0f;
|
||||
int i = 0;
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (fabsf(perpAxis[i]) > EPSILON) {
|
||||
iValue = perpAxis[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(i != 3);
|
||||
// swap or negate the next element
|
||||
int j = (i + 1) % 3;
|
||||
float jValue = perpAxis[j];
|
||||
if (fabsf(jValue - iValue) > EPSILON) {
|
||||
perpAxis[i] = jValue;
|
||||
perpAxis[j] = iValue;
|
||||
} else {
|
||||
perpAxis[i] = -iValue;
|
||||
}
|
||||
perpAxis = glm::cross(perpAxis, rotatedAxis);
|
||||
perpAxisLength = glm::length(perpAxis);
|
||||
assert(perpAxisLength > EPSILON);
|
||||
perpAxis /= perpAxisLength;
|
||||
}
|
||||
// measure the roll
|
||||
// NOTE: perpAxis is perpendicular to both _coneAxis and rotatedConeAxis, so we can
|
||||
// rotate it again and we'll end up with an something that has only been rolled.
|
||||
glm::vec3 rolledPerpAxis = rotation * perpAxis;
|
||||
float sign = glm::dot(rotatedAxis, glm::cross(perpAxis, rolledPerpAxis)) > 0.0f ? 1.0f : -1.0f;
|
||||
float roll = sign * angleBetween(rolledPerpAxis, perpAxis);
|
||||
if (roll < _minRoll || roll > _maxRoll) {
|
||||
float clampedRoll = clampAngle(roll, _minRoll, _maxRoll);
|
||||
rotation = glm::normalize(glm::angleAxis(clampedRoll - roll, rotatedAxis) * rotation);
|
||||
applied = true;
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// AngularConstraint.h
|
||||
// interface/src/renderer
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AngularConstraint_h
|
||||
#define hifi_AngularConstraint_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class AngularConstraint {
|
||||
public:
|
||||
/// \param minAngles minumum euler angles for the constraint
|
||||
/// \param maxAngles minumum euler angles for the constraint
|
||||
/// \return pointer to new AngularConstraint of the right type or NULL if none could be made
|
||||
static AngularConstraint* newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles);
|
||||
|
||||
AngularConstraint() {}
|
||||
virtual ~AngularConstraint() {}
|
||||
virtual bool clamp(glm::quat& rotation) const = 0;
|
||||
virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction);
|
||||
protected:
|
||||
};
|
||||
|
||||
class HingeConstraint : public AngularConstraint {
|
||||
public:
|
||||
HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle);
|
||||
virtual bool clamp(glm::quat& rotation) const;
|
||||
virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction);
|
||||
protected:
|
||||
glm::vec3 _forwardAxis;
|
||||
glm::vec3 _rotationAxis;
|
||||
float _minAngle;
|
||||
float _maxAngle;
|
||||
};
|
||||
|
||||
class ConeRollerConstraint : public AngularConstraint {
|
||||
public:
|
||||
ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll);
|
||||
virtual bool clamp(glm::quat& rotation) const;
|
||||
private:
|
||||
float _coneAngle;
|
||||
glm::vec3 _coneAxis;
|
||||
float _minRoll;
|
||||
float _maxRoll;
|
||||
};
|
||||
|
||||
#endif // hifi_AngularConstraint_h
|
|
@ -11,41 +11,50 @@
|
|||
|
||||
#include "Interpolate.h"
|
||||
|
||||
float Interpolate::cubicInterpolate2Points(float y0, float y1, float y2, float y3, float u) {
|
||||
float a0, a1, a2, a3, uu, uuu;
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
a0 = y3 - y2 - y0 + y1;
|
||||
a1 = y0 - y1 - a0;
|
||||
a2 = y2 - y0;
|
||||
a3 = y1;
|
||||
|
||||
uu = u * u;
|
||||
uuu = uu * u;
|
||||
|
||||
return (a0 * uuu + a1 * uu + a2 * u + a3);
|
||||
float Interpolate::bezierInterpolate(float y1, float y2, float y3, float u) {
|
||||
// https://en.wikipedia.org/wiki/Bezier_curve
|
||||
assert(0.0f <= u && u <= 1.0f);
|
||||
return (1.0f - u) * (1.0f - u) * y1 + 2.0f * (1.0f - u) * u * y2 + u * u * y3;
|
||||
}
|
||||
|
||||
float Interpolate::cubicInterpolate3Points(float y1, float y2, float y3, float u) {
|
||||
float y0, y4;
|
||||
float Interpolate::interpolate3Points(float y1, float y2, float y3, float u) {
|
||||
assert(0.0f <= u && u <= 1.0f);
|
||||
|
||||
if (u <= 0.5f) {
|
||||
if (y1 == y2) {
|
||||
return y2;
|
||||
if (u <= 0.5f && y1 == y2 || u >= 0.5f && y2 == y3) {
|
||||
// Flat line.
|
||||
return y2;
|
||||
}
|
||||
|
||||
if (y2 >= y1 && y2 >= y3 || y2 <= y1 && y2 <= y3) {
|
||||
// U or inverted-U shape.
|
||||
// Make the slope at y2 = 0, which means that the control points half way between the value points have the value y2.
|
||||
if (u <= 0.5f) {
|
||||
return bezierInterpolate(y1, y2, y2, 2.0f * u);
|
||||
} else {
|
||||
return bezierInterpolate(y2, y2, y3, 2.0f * u - 1.0f);
|
||||
}
|
||||
|
||||
y0 = 2.0f * y1 - y2; // y0 is linear extension of line from y2 to y1.
|
||||
u = 2.0f * u;
|
||||
|
||||
return Interpolate::cubicInterpolate2Points(y0, y1, y2, y3, u);
|
||||
|
||||
} else {
|
||||
if (y2 == y3) {
|
||||
return y2;
|
||||
// L or inverted and/or mirrored L shape.
|
||||
// Make the slope at y2 be the slope between y1 and y3, up to a maximum of double the minimum of the slopes between y1
|
||||
// and y2, and y2 and y3. Use this slope to calculate the control points half way between the value points.
|
||||
// Note: The maximum ensures that the control points and therefore the interpolated values stay between y1 and y3.
|
||||
float slope = y3 - y1;
|
||||
float slope12 = y2 - y1;
|
||||
float slope23 = y3 - y2;
|
||||
if (fabsf(slope) > fabsf(2.0f * slope12)) {
|
||||
slope = 2.0f * slope12;
|
||||
} else if (fabsf(slope) > fabsf(2.0f * slope23)) {
|
||||
slope = 2.0f * slope23;
|
||||
}
|
||||
|
||||
y4 = 2.0f * y3 - y2; // y4 is linear extension of line from y2 to y3.
|
||||
u = 2.0f * u - 1.0f;
|
||||
|
||||
return Interpolate::cubicInterpolate2Points(y1, y2, y3, y4, u);
|
||||
if (u <= 0.5f) {
|
||||
return bezierInterpolate(y1, y2 - slope / 2.0f, y2, 2.0f * u);
|
||||
} else {
|
||||
return bezierInterpolate(y2, y2 + slope / 2.0f, y3, 2.0f * u - 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
class Interpolate {
|
||||
|
||||
public:
|
||||
// Cubic interpolation at position u [0.0 - 1.0] between values y1 and y2 with equidistant values y0 and y3 either side.
|
||||
static float cubicInterpolate2Points(float y0, float y1, float y2, float y3, float u);
|
||||
// Bezier interpolate at position u [0.0 - 1.0] between y values equally spaced along the x-axis. The interpolated values
|
||||
// pass through y1 and y3 but not y2; y2 is the Bezier control point.
|
||||
static float bezierInterpolate(float y1, float y2, float y3, float u);
|
||||
|
||||
// Cubic interpolation at position u [0.0 - 1.0] between values y1 and y3 with midpoint value y2.
|
||||
static float cubicInterpolate3Points(float y1, float y2, float y3, float u);
|
||||
// Interpolate at position u [0.0 - 1.0] between y values equally spaced along the x-axis such that the interpolated values
|
||||
// pass through all three y values. Return value lies wholly within the range of y values passed in.
|
||||
static float interpolate3Points(float y1, float y2, float y3, float u);
|
||||
};
|
||||
|
||||
#endif // hifi_Interpolate_h
|
||||
|
|
Loading…
Reference in a new issue