mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 07:04:05 +02:00
resolve conflicts on merge with upstream/master
This commit is contained in:
commit
ed19987b7d
135 changed files with 7522 additions and 1884 deletions
README.md
assignment-client/src/avatars
domain-server/src
examples
controllers
defaultScripts.jsentityScripts
html/magBalls
libraries
toys
voxels.jsinterface/src
Application.cppApplication.hFileLogger.cppFileLogger.hMenu.cppMenu.hPluginContainerProxy.cpp
avatar
ui/overlays
libraries
animation/src
AnimBlendLinear.cppAnimBlendLinear.hAnimClip.cppAnimClip.hAnimNode.hAnimNodeLoader.cppAnimNodeLoader.hAnimOverlay.cppAnimOverlay.hAnimSkeleton.cppAnimSkeleton.hAnimStateMachine.cppAnimStateMachine.hAnimUtil.cppAnimUtil.hAnimVariant.hAnimationHandle.cppRig.cppRig.h
audio/src
avatars/src
display-plugins/src/display-plugins
DisplayPlugin.hNullDisplayPlugin.cppNullDisplayPlugin.hOpenGLDisplayPlugin.cppOpenGLDisplayPlugin.h
oculus
stereo
entities-renderer/src
entities/src
EntityItemID.hEntityItemProperties.cppEntityItemProperties.hEntityItemPropertiesMacros.hEntityPropertyFlags.hEntityScriptingInterface.cppEntityScriptingInterface.hPolyVoxEntityItem.cppPolyVoxEntityItem.h
fbx/src
|
@ -1,5 +1,4 @@
|
|||
High Fidelity (hifi) is an early-stage technology
|
||||
lab experimenting with Virtual Worlds and VR.
|
||||
High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR.
|
||||
|
||||
In this repository you'll find the source to many of the components in our
|
||||
alpha-stage virtual world. The project embraces distributed development
|
||||
|
|
|
@ -295,7 +295,8 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
avatarPacketList.startSegment();
|
||||
|
||||
numAvatarDataBytes += avatarPacketList.write(otherNode->getUUID().toRfc4122());
|
||||
numAvatarDataBytes += avatarPacketList.write(otherAvatar.toByteArray(false));
|
||||
numAvatarDataBytes +=
|
||||
avatarPacketList.write(otherAvatar.toByteArray(false, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO));
|
||||
|
||||
avatarPacketList.endSegment();
|
||||
|
||||
|
@ -364,6 +365,31 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
}
|
||||
);
|
||||
|
||||
// We're done encoding this version of the otherAvatars. Update their "lastSent" joint-states so
|
||||
// that we can notice differences, next time around.
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& otherNode)->bool {
|
||||
if (!otherNode->getLinkedData()) {
|
||||
return false;
|
||||
}
|
||||
if (otherNode->getType() != NodeType::Agent) {
|
||||
return false;
|
||||
}
|
||||
if (!otherNode->getActiveSocket()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&](const SharedNodePointer& otherNode) {
|
||||
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
MutexTryLocker lock(otherNodeData->getMutex());
|
||||
if (!lock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
AvatarData& otherAvatar = otherNodeData->getAvatar();
|
||||
otherAvatar.doneEncoding(false);
|
||||
});
|
||||
|
||||
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr,
|
||||
bool isConnectRequest) {
|
||||
NodeConnectionData newHeader;
|
||||
|
|
|
@ -1,307 +1,346 @@
|
|||
//
|
||||
// hydraGrab.js
|
||||
// examples
|
||||
//
|
||||
// Created by Clément Brisset on 4/24/14.
|
||||
// Updated by Eric Levin on 5/14/15.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
// Created by Eric Levin on 9/2/15
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script allows you to grab and move/rotate physical objects with the hydra
|
||||
//
|
||||
// Using the hydras :
|
||||
// grab physical entities with the right trigger
|
||||
// Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity;
|
||||
var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance;
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
var LASER_WIDTH = 3;
|
||||
var LASER_COLOR = {
|
||||
red: 50,
|
||||
green: 150,
|
||||
blue: 200
|
||||
};
|
||||
var LASER_HOVER_COLOR = {
|
||||
red: 200,
|
||||
green: 50,
|
||||
blue: 50
|
||||
};
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
var DROP_DISTANCE = 5.0;
|
||||
var DROP_COLOR = {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
};
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var rightTriggerAction = RIGHT_HAND_CLICK;
|
||||
|
||||
var FULL_STRENGTH = 0.05;
|
||||
var LASER_LENGTH_FACTOR = 500;
|
||||
var CLOSE_ENOUGH = 0.001;
|
||||
var SPRING_RATE = 1.5;
|
||||
var DAMPING_RATE = 0.8;
|
||||
var SCREEN_TO_METERS = 0.001;
|
||||
var DISTANCE_SCALE_FACTOR = 1000
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
|
||||
var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav");
|
||||
var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav");
|
||||
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
|
||||
var leftTriggerAction = LEFT_HAND_CLICK;
|
||||
|
||||
function getRayIntersection(pickRay) {
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
function controller(side) {
|
||||
this.triggerHeld = false;
|
||||
this.triggerThreshold = 0.9;
|
||||
this.side = side;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.trigger = side;
|
||||
this.originalGravity = {
|
||||
var ZERO_VEC = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
}
|
||||
var LINE_LENGTH = 500;
|
||||
var THICK_LINE_WIDTH = 7;
|
||||
var THIN_LINE_WIDTH = 2;
|
||||
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
start: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
end: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
color: LASER_COLOR,
|
||||
alpha: 1,
|
||||
lineWidth: LASER_WIDTH,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
};
|
||||
var INTERSECT_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
};
|
||||
|
||||
this.dropLine = Overlays.addOverlay("line3d", {
|
||||
color: DROP_COLOR,
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: 2
|
||||
});
|
||||
var GRAB_RADIUS = 0.5;
|
||||
|
||||
var GRAB_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 250
|
||||
};
|
||||
var SHOW_LINE_THRESHOLD = 0.2;
|
||||
var DISTANCE_HOLD_THRESHOLD = 0.8;
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
this.moveLaser();
|
||||
this.checkTrigger();
|
||||
this.checkEntityIntersection();
|
||||
if (this.grabbing) {
|
||||
this.updateEntity(deltaTime);
|
||||
}
|
||||
var right4Action = 18;
|
||||
var left4Action = 17;
|
||||
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.oldTipPosition = this.tipPosition;
|
||||
}
|
||||
var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
|
||||
|
||||
this.updateEntity = function(deltaTime) {
|
||||
this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition);
|
||||
this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition);
|
||||
this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR));
|
||||
var RIGHT = 1;
|
||||
var LEFT = 0;
|
||||
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right")
|
||||
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left")
|
||||
|
||||
this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
|
||||
this.currentPosition = this.entityProps.position;
|
||||
this.currentVelocity = this.entityProps.velocity;
|
||||
|
||||
var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition);
|
||||
this.distanceToTarget = Vec3.length(dPosition);
|
||||
if (this.distanceToTarget > CLOSE_ENOUGH) {
|
||||
// compute current velocity in the direction we want to move
|
||||
this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition));
|
||||
this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget);
|
||||
// compute the speed we would like to be going toward the target position
|
||||
|
||||
this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
|
||||
// compute how much we want to add to the existing velocity
|
||||
this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget);
|
||||
//If target is to far, roll off force as inverse square of distance
|
||||
if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) {
|
||||
this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0));
|
||||
}
|
||||
this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity);
|
||||
this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE));
|
||||
function controller(side, triggerAction, pullAction, hand) {
|
||||
this.hand = hand;
|
||||
if (hand === "right") {
|
||||
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
||||
} else {
|
||||
this.newVelocity = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
}
|
||||
this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip);
|
||||
this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity);
|
||||
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
velocity: this.newVelocity,
|
||||
angularVelocity: this.transformedAngularVelocity
|
||||
this.getHandPosition = MyAvatar.getLeftPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||
}
|
||||
this.triggerAction = triggerAction;
|
||||
this.pullAction = pullAction;
|
||||
this.actionID = null;
|
||||
this.tractorBeamActive = false;
|
||||
this.distanceHolding = false;
|
||||
this.closeGrabbing = false;
|
||||
this.triggerValue = 0;
|
||||
this.prevTriggerValue = 0;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
color: NO_INTERSECT_COLOR,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.updateLine = function() {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, LINE_LENGTH)
|
||||
]
|
||||
});
|
||||
|
||||
this.updateDropLine(this.targetPosition);
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.updateControllerState = function() {
|
||||
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
this.triggerValue = Controller.getTriggerValue(this.trigger);
|
||||
}
|
||||
|
||||
this.checkTrigger = function() {
|
||||
if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) {
|
||||
this.triggerHeld = true;
|
||||
} else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) {
|
||||
this.triggerHeld = false;
|
||||
if (this.grabbing) {
|
||||
this.release();
|
||||
}
|
||||
//only check if we havent already grabbed an object
|
||||
if (this.distanceHolding) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//move origin a bit away from hand so nothing gets in way
|
||||
var origin = Vec3.sum(handPosition, direction);
|
||||
if (this.checkForIntersections(origin, direction)) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: INTERSECT_COLOR,
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: NO_INTERSECT_COLOR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.updateDropLine = function(position) {
|
||||
|
||||
Overlays.editOverlay(this.dropLine, {
|
||||
visible: true,
|
||||
start: {
|
||||
x: position.x,
|
||||
y: position.y + DROP_DISTANCE,
|
||||
z: position.z
|
||||
},
|
||||
end: {
|
||||
x: position.x,
|
||||
y: position.y - DROP_DISTANCE,
|
||||
z: position.z
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.checkEntityIntersection = function() {
|
||||
|
||||
controller.prototype.checkForIntersections = function(origin, direction) {
|
||||
var pickRay = {
|
||||
origin: this.palmPosition,
|
||||
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition))
|
||||
origin: origin,
|
||||
direction: direction
|
||||
};
|
||||
var intersection = getRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && intersection.properties.collisionsWillMove) {
|
||||
this.laserWasHovered = true;
|
||||
if (this.triggerHeld && !this.grabbing) {
|
||||
this.grab(intersection.entityID);
|
||||
}
|
||||
Overlays.editOverlay(this.laser, {
|
||||
color: LASER_HOVER_COLOR
|
||||
});
|
||||
} else if (this.laserWasHovered) {
|
||||
this.laserWasHovered = false;
|
||||
Overlays.editOverlay(this.laser, {
|
||||
color: LASER_COLOR
|
||||
});
|
||||
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && intersection.properties.collisionsWillMove === 1) {
|
||||
this.distanceToEntity = Vec3.distance(origin, intersection.properties.position);
|
||||
Entities.editEntity(this.pointer, {
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, this.distanceToEntity)
|
||||
]
|
||||
});
|
||||
this.grabbedEntity = intersection.entityID;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.grab = function(entityId) {
|
||||
this.grabbing = true;
|
||||
this.grabbedEntity = entityId;
|
||||
this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
|
||||
this.targetPosition = this.entityProps.position;
|
||||
this.currentPosition = this.targetPosition;
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.originalGravity = this.entityProps.gravity;
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
Overlays.editOverlay(this.laser, {
|
||||
visible: false
|
||||
});
|
||||
Audio.playSound(grabSound, {
|
||||
position: this.entityProps.position,
|
||||
volume: 0.25
|
||||
});
|
||||
}
|
||||
controller.prototype.attemptMove = function() {
|
||||
if (this.tractorBeamActive) {
|
||||
return;
|
||||
}
|
||||
if (this.grabbedEntity || this.distanceHolding) {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
|
||||
this.release = function() {
|
||||
this.grabbing = false;
|
||||
var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity))
|
||||
this.distanceHolding = true;
|
||||
if (this.actionID === null) {
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: newPosition,
|
||||
linearTimeScale: .1
|
||||
});
|
||||
} else {
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: newPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.showPointer = function() {
|
||||
Entities.editEntity(this.pointer, {
|
||||
visible: true
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.hidePointer = function() {
|
||||
Entities.editEntity(this.pointer, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.letGo = function() {
|
||||
if (this.grabbedEntity && this.actionID) {
|
||||
this.deactivateEntity(this.grabbedEntity);
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
}
|
||||
this.grabbedEntity = null;
|
||||
Overlays.editOverlay(this.laser, {
|
||||
visible: true
|
||||
});
|
||||
Overlays.editOverlay(this.dropLine, {
|
||||
visible: false
|
||||
});
|
||||
this.actionID = null;
|
||||
this.distanceHolding = false;
|
||||
this.tractorBeamActive = false;
|
||||
this.checkForEntityArrival = false;
|
||||
this.closeGrabbing = false;
|
||||
}
|
||||
|
||||
Audio.playSound(releaseSound, {
|
||||
position: this.entityProps.position,
|
||||
volume: 0.25
|
||||
});
|
||||
|
||||
// only restore the original gravity if it's not zero. This is to avoid...
|
||||
// 1. interface A grabs an entity and locally saves off its gravity
|
||||
// 2. interface A sets the entity's gravity to zero
|
||||
// 3. interface B grabs the entity and saves off its gravity (which is zero)
|
||||
// 4. interface A releases the entity and puts the original gravity back
|
||||
// 5. interface B releases the entity and puts the original gravity back (to zero)
|
||||
if(vectorIsZero(this.originalGravity)) {
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
gravity: this.originalGravity
|
||||
});
|
||||
controller.prototype.update = function() {
|
||||
if (this.tractorBeamActive && this.checkForEntityArrival) {
|
||||
var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity
|
||||
if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) {
|
||||
this.letGo();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
|
||||
//First check if an object is within close range and then run the close grabbing logic
|
||||
if (this.checkForInRangeObject()) {
|
||||
this.grabEntity();
|
||||
} else {
|
||||
this.showPointer();
|
||||
this.shouldDisplayLine = true;
|
||||
}
|
||||
} else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) {
|
||||
this.hidePointer();
|
||||
this.letGo();
|
||||
this.shouldDisplayLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.moveLaser = function() {
|
||||
var inverseRotation = Quat.inverse(MyAvatar.orientation);
|
||||
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
|
||||
// startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
|
||||
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
|
||||
var endPosition = Vec3.sum(startPosition, direction);
|
||||
if (this.shouldDisplayLine) {
|
||||
this.updateLine();
|
||||
}
|
||||
if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) {
|
||||
this.attemptMove();
|
||||
}
|
||||
|
||||
Overlays.editOverlay(this.laser, {
|
||||
start: startPosition,
|
||||
end: endPosition
|
||||
|
||||
this.prevTriggerValue = this.triggerValue;
|
||||
}
|
||||
|
||||
controller.prototype.grabEntity = function() {
|
||||
var handRotation = this.getHandRotation();
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
|
||||
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
||||
var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
|
||||
var offset = Vec3.subtract(objectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
this.closeGrabbing = true;
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
relativePosition: offsetPosition,
|
||||
relativeRotation: offsetRotation,
|
||||
hand: this.hand,
|
||||
timeScale: 0.05
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.cleanup = function() {
|
||||
Overlays.deleteOverlay(this.laser);
|
||||
Overlays.deleteOverlay(this.dropLine);
|
||||
}
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
rightController.update(deltaTime);
|
||||
leftController.update(deltaTime);
|
||||
|
||||
controller.prototype.checkForInRangeObject = function() {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
||||
var minDistance = GRAB_RADIUS;
|
||||
var grabbedEntity = null;
|
||||
//Get nearby entities and assign nearest
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
var props = Entities.getEntityProperties(entities[i]);
|
||||
var distance = Vec3.distance(props.position, handPosition);
|
||||
if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) {
|
||||
grabbedEntity = entities[i];
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
if (grabbedEntity === null) {
|
||||
return false;
|
||||
} else {
|
||||
//We are grabbing an entity, so let it know we've grabbed it
|
||||
this.grabbedEntity = grabbedEntity;
|
||||
this.activateEntity(this.grabbedEntity);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
controller.prototype.activateEntity = function(entity) {
|
||||
var data = {
|
||||
activated: true,
|
||||
avatarId: MyAvatar.sessionUUID
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
}
|
||||
|
||||
function vectorIsZero(v) {
|
||||
return v.x === 0 && v.y === 0 && v.z === 0;
|
||||
controller.prototype.deactivateEntity = function(entity) {
|
||||
var data = {
|
||||
activated: false,
|
||||
avatarId: null
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
}
|
||||
|
||||
var rightController = new controller(RIGHT);
|
||||
var leftController = new controller(LEFT);
|
||||
controller.prototype.onActionEvent = function(action, state) {
|
||||
if (this.pullAction === action && state === 1) {
|
||||
if (this.actionID !== null) {
|
||||
var self = this;
|
||||
this.tractorBeamActive = true;
|
||||
//We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some
|
||||
//low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving!
|
||||
Script.setTimeout(function() {
|
||||
self.checkForEntityArrival = true;
|
||||
}, 500);
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var direction = Controller.getSpatialControlNormal(this.tip);
|
||||
//move final destination along line a bit, so it doesnt hit avatar hand
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
controller.prototype.cleanup = function() {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
if (this.grabbedEntity) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
rightController.update();
|
||||
leftController.update();
|
||||
}
|
||||
|
||||
function onActionEvent(action, state) {
|
||||
rightController.onActionEvent(action, state);
|
||||
leftController.onActionEvent(action, state);
|
||||
|
||||
}
|
||||
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
function cleanup() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update)
|
||||
Controller.actionEvent.connect(onActionEvent);
|
|
@ -13,32 +13,7 @@ Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.j
|
|||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var nullActionID = "00000000-0000-0000-0000-000000000000";
|
||||
var controllerID;
|
||||
var controllerActive;
|
||||
var leftHandObjectID = null;
|
||||
var rightHandObjectID = null;
|
||||
var leftHandActionID = nullActionID;
|
||||
var rightHandActionID = nullActionID;
|
||||
|
||||
var TRIGGER_THRESHOLD = 0.2;
|
||||
var GRAB_RADIUS = 0.15;
|
||||
|
||||
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var ACTION1 = Controller.findAction("ACTION1");
|
||||
var ACTION2 = Controller.findAction("ACTION2");
|
||||
|
||||
var rightHandGrabAction = RIGHT_HAND_CLICK;
|
||||
var leftHandGrabAction = LEFT_HAND_CLICK;
|
||||
|
||||
var rightHandGrabValue = 0;
|
||||
var leftHandGrabValue = 0;
|
||||
var prevRightHandGrabValue = 0
|
||||
var prevLeftHandGrabValue = 0;
|
||||
|
||||
var grabColor = { red: 0, green: 255, blue: 0};
|
||||
var releaseColor = { red: 0, green: 0, blue: 255};
|
||||
|
||||
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() {
|
||||
return {
|
||||
|
@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", {
|
|||
alpha: 1
|
||||
});
|
||||
|
||||
var overlays = false;
|
||||
var leftHandOverlay;
|
||||
var rightHandOverlay;
|
||||
if (overlays) {
|
||||
leftHandOverlay = Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.getLeftPalmPosition(),
|
||||
size: GRAB_RADIUS,
|
||||
color: releaseColor,
|
||||
alpha: 0.5,
|
||||
solid: false
|
||||
});
|
||||
rightHandOverlay = Overlays.addOverlay("sphere", {
|
||||
position: MyAvatar.getRightPalmPosition(),
|
||||
size: GRAB_RADIUS,
|
||||
color: releaseColor,
|
||||
alpha: 0.5,
|
||||
solid: false
|
||||
});
|
||||
}
|
||||
|
||||
var OBJECT_HEIGHT_OFFSET = 0.5;
|
||||
var MIN_OBJECT_SIZE = 0.05;
|
||||
|
@ -98,8 +54,6 @@ var GRAVITY = {
|
|||
z: 0.0
|
||||
}
|
||||
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
|
||||
var tableCreated = false;
|
||||
|
||||
|
@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table
|
|||
|
||||
var VELOCITY_MAG = 0.3;
|
||||
|
||||
var entitiesToResize = [];
|
||||
|
||||
var MODELS = Array(
|
||||
{ modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" },
|
||||
|
@ -136,196 +89,15 @@ var COLLISION_SOUNDS = Array(
|
|||
var RESIZE_TIMER = 0.0;
|
||||
var RESIZE_WAIT = 0.05; // 50 milliseconds
|
||||
|
||||
var leftFist = Entities.addEntity( {
|
||||
type: "Sphere",
|
||||
shapeType: 'sphere',
|
||||
position: MyAvatar.getLeftPalmPosition(),
|
||||
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
|
||||
rotation: MyAvatar.getLeftPalmRotation(),
|
||||
visible: false,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
});
|
||||
var rightFist = Entities.addEntity( {
|
||||
type: "Sphere",
|
||||
shapeType: 'sphere',
|
||||
position: MyAvatar.getRightPalmPosition(),
|
||||
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
|
||||
rotation: MyAvatar.getRightPalmRotation(),
|
||||
visible: false,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true
|
||||
});
|
||||
|
||||
function letGo(hand) {
|
||||
var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID;
|
||||
var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
|
||||
var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity();
|
||||
var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() :
|
||||
MyAvatar.getRightPalmAngularVelocity();
|
||||
if (actionIDToRemove != nullActionID && entityIDToEdit != null) {
|
||||
Entities.deleteAction(entityIDToEdit, actionIDToRemove);
|
||||
// TODO: upon successful letGo, restore collision groups
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = null;
|
||||
leftHandActionID = nullActionID;
|
||||
} else {
|
||||
rightHandObjectID = null;
|
||||
rightHandActionID = nullActionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setGrabbedObject(hand) {
|
||||
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
|
||||
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
||||
var objectID = null;
|
||||
var minDistance = GRAB_RADIUS;
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
// Don't grab the object in your other hands, your fists, or the table
|
||||
if ((hand == LEFT && entities[i] == rightHandObjectID) ||
|
||||
(hand == RIGHT && entities[i] == leftHandObjectID) ||
|
||||
entities[i] == leftFist || entities[i] == rightFist ||
|
||||
(tableCreated && entities[i] == tableEntities[0])) {
|
||||
continue;
|
||||
} else {
|
||||
var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition);
|
||||
if (distance <= minDistance) {
|
||||
objectID = entities[i];
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (objectID == null) {
|
||||
return false;
|
||||
}
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = objectID;
|
||||
} else {
|
||||
rightHandObjectID = objectID;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function grab(hand) {
|
||||
if (!setGrabbedObject(hand)) {
|
||||
// If you don't grab an object, make a fist
|
||||
Entities.editEntity((hand == LEFT) ? leftFist : rightFist, { ignoreForCollisions: false } );
|
||||
return;
|
||||
}
|
||||
var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
|
||||
var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation();
|
||||
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
|
||||
|
||||
var objectRotation = Entities.getEntityProperties(objectID).rotation;
|
||||
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
||||
var objectPosition = Entities.getEntityProperties(objectID).position;
|
||||
var offset = Vec3.subtract(objectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
// print(JSON.stringify(offsetPosition));
|
||||
var actionID = Entities.addAction("hold", objectID, {
|
||||
relativePosition: { x: 0, y: 0, z: 0 },
|
||||
relativeRotation: offsetRotation,
|
||||
hand: (hand == LEFT) ? "left" : "right",
|
||||
timeScale: 0.05
|
||||
});
|
||||
if (actionID == nullActionID) {
|
||||
if (hand == LEFT) {
|
||||
leftHandObjectID = null;
|
||||
} else {
|
||||
rightHandObjectID = null;
|
||||
}
|
||||
} else {
|
||||
// TODO: upon successful grab, add to collision group so object doesn't collide with immovable entities
|
||||
if (hand == LEFT) {
|
||||
leftHandActionID = actionID;
|
||||
} else {
|
||||
rightHandActionID = actionID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeModels() {
|
||||
var newEntitiesToResize = [];
|
||||
for (var i = 0; i < entitiesToResize.length; i++) {
|
||||
var naturalDimensions = Entities.getEntityProperties(entitiesToResize[i]).naturalDimensions;
|
||||
if (naturalDimensions.x != 1.0 || naturalDimensions.y != 1.0 || naturalDimensions.z != 1.0) {
|
||||
// bigger range of sizes for models
|
||||
var dimensions = Vec3.multiply(randFloat(MIN_OBJECT_SIZE, 3.0*MAX_OBJECT_SIZE), Vec3.normalize(naturalDimensions));
|
||||
Entities.editEntity(entitiesToResize[i], {
|
||||
dimensions: dimensions,
|
||||
shapeType: "box"
|
||||
});
|
||||
} else {
|
||||
newEntitiesToResize.push(entitiesToResize[i]);
|
||||
}
|
||||
|
||||
}
|
||||
entitiesToResize = newEntitiesToResize;
|
||||
}
|
||||
|
||||
function update(deltaTime) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() });
|
||||
Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() });
|
||||
}
|
||||
|
||||
// if (tableCreated && RESIZE_TIMER < RESIZE_WAIT) {
|
||||
// RESIZE_TIMER += deltaTime;
|
||||
// } else if (tableCreated) {
|
||||
// resizeModels();
|
||||
// }
|
||||
|
||||
rightHandGrabValue = Controller.getActionValue(rightHandGrabAction);
|
||||
leftHandGrabValue = Controller.getActionValue(leftHandGrabAction);
|
||||
|
||||
Entities.editEntity(leftFist, { position: MyAvatar.getLeftPalmPosition() });
|
||||
Entities.editEntity(rightFist, { position: MyAvatar.getRightPalmPosition() });
|
||||
|
||||
if (rightHandGrabValue > TRIGGER_THRESHOLD &&
|
||||
prevRightHandGrabValue < TRIGGER_THRESHOLD) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(rightHandOverlay, { color: grabColor });
|
||||
}
|
||||
grab(RIGHT);
|
||||
} else if (rightHandGrabValue < TRIGGER_THRESHOLD &&
|
||||
prevRightHandGrabValue > TRIGGER_THRESHOLD) {
|
||||
Entities.editEntity(rightFist, { ignoreForCollisions: true } );
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(rightHandOverlay, { color: releaseColor });
|
||||
}
|
||||
letGo(RIGHT);
|
||||
}
|
||||
|
||||
if (leftHandGrabValue > TRIGGER_THRESHOLD &&
|
||||
prevLeftHandGrabValue < TRIGGER_THRESHOLD) {
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { color: grabColor });
|
||||
}
|
||||
grab(LEFT);
|
||||
} else if (leftHandGrabValue < TRIGGER_THRESHOLD &&
|
||||
prevLeftHandGrabValue > TRIGGER_THRESHOLD) {
|
||||
Entities.editEntity(leftFist, { ignoreForCollisions: true } );
|
||||
if (overlays) {
|
||||
Overlays.editOverlay(leftHandOverlay, { color: releaseColor });
|
||||
}
|
||||
letGo(LEFT);
|
||||
}
|
||||
|
||||
prevRightHandGrabValue = rightHandGrabValue;
|
||||
prevLeftHandGrabValue = leftHandGrabValue;
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
letGo(RIGHT);
|
||||
letGo(LEFT);
|
||||
print("CLEANUP!!!")
|
||||
if (overlays) {
|
||||
Overlays.deleteOverlay(leftHandOverlay);
|
||||
Overlays.deleteOverlay(rightHandOverlay);
|
||||
}
|
||||
Entities.deleteEntity(leftFist);
|
||||
Entities.deleteEntity(rightFist);
|
||||
removeTable();
|
||||
toolBar.cleanup();
|
||||
}
|
||||
|
@ -405,7 +177,6 @@ function createTable() {
|
|||
density: 0.5,
|
||||
collisionsWillMove: true,
|
||||
color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) },
|
||||
// collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)]
|
||||
});
|
||||
if (type == "Model") {
|
||||
var randModel = randInt(0, MODELS.length);
|
||||
|
@ -413,7 +184,6 @@ function createTable() {
|
|||
shapeType: "box",
|
||||
modelURL: MODELS[randModel].modelURL
|
||||
});
|
||||
entitiesToResize.push(tableEntities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -426,5 +196,4 @@ function removeTable() {
|
|||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanUp);
|
||||
Script.update.connect(update);
|
||||
Controller.mousePressEvent.connect(onClick);
|
|
@ -17,3 +17,4 @@ Script.load("users.js");
|
|||
Script.load("grab.js");
|
||||
Script.load("directory.js");
|
||||
Script.load("dialTone.js");
|
||||
Script.load("libraries/omniTool.js");
|
||||
|
|
88
examples/entityScripts/breakdanceEntity.js
Normal file
88
examples/entityScripts/breakdanceEntity.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// breakdanceEntity.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/3/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example of an entity script which when assigned to an entity, will start the breakdance game if you grab and hold the entity
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../toys/breakdanceCore.js");
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var _this;
|
||||
|
||||
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
|
||||
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
|
||||
BreakdanceEntity = function() {
|
||||
_this = this;
|
||||
print("BreakdanceEntity constructor");
|
||||
};
|
||||
|
||||
BreakdanceEntity.prototype = {
|
||||
|
||||
// update() will be called regulary, because we've hooked the update signal in our preload() function
|
||||
// we will check out userData for the grabData. In the case of the hydraGrab script, it will tell us
|
||||
// if we're currently being grabbed and if the person grabbing us is the current interfaces avatar.
|
||||
// we will watch this for state changes and print out if we're being grabbed or released when it changes.
|
||||
update: function() {
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
|
||||
// because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID
|
||||
var entityID = _this.entityID;
|
||||
|
||||
// we want to assume that if there is no grab data, then we are not being grabbed
|
||||
var defaultGrabData = { activated: false, avatarId: null };
|
||||
|
||||
// this handy function getEntityCustomData() is available in utils.js and it will return just the specific section
|
||||
// of user data we asked for. If it's not available it returns our default data.
|
||||
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, defaultGrabData);
|
||||
|
||||
// if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface
|
||||
if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) {
|
||||
|
||||
if (!_this.beingGrabbed) {
|
||||
// remember we're being grabbed so we can detect being released
|
||||
_this.beingGrabbed = true;
|
||||
breakdanceStart();
|
||||
print("I'm was grabbed...");
|
||||
} else {
|
||||
breakdanceUpdate();
|
||||
}
|
||||
|
||||
} else if (_this.beingGrabbed) {
|
||||
|
||||
// if we are not being grabbed, and we previously were, then we were just released, remember that
|
||||
// and print out a message
|
||||
_this.beingGrabbed = false;
|
||||
print("I'm was released...");
|
||||
breakdanceEnd();
|
||||
}
|
||||
},
|
||||
|
||||
// preload() will be called when the entity has become visible (or known) to the interface
|
||||
// it gives us a chance to set our local JavaScript object up. In this case it means:
|
||||
// * remembering our entityID, so we can access it in cases where we're called without an entityID
|
||||
// * connecting to the update signal so we can check our grabbed state
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
Script.update.connect(this.update);
|
||||
},
|
||||
|
||||
// unload() will be called when our entity is no longer available. It may be because we were deleted,
|
||||
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
|
||||
// to the update signal
|
||||
unload: function(entityID) {
|
||||
Script.update.disconnect(this.update);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new BreakdanceEntity();
|
||||
})
|
81
examples/entityScripts/detectGrabExample.js
Normal file
81
examples/entityScripts/detectGrabExample.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// detectGrabExample.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 9/3/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example of an entity script which when assigned to an entity, will detect when the entity is being grabbed by the hydraGrab script
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var _this;
|
||||
|
||||
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
|
||||
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
|
||||
DetectGrabbed = function() {
|
||||
_this = this;
|
||||
};
|
||||
|
||||
DetectGrabbed.prototype = {
|
||||
|
||||
// update() will be called regulary, because we've hooked the update signal in our preload() function
|
||||
// we will check out userData for the grabData. In the case of the hydraGrab script, it will tell us
|
||||
// if we're currently being grabbed and if the person grabbing us is the current interfaces avatar.
|
||||
// we will watch this for state changes and print out if we're being grabbed or released when it changes.
|
||||
update: function() {
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
|
||||
// because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID
|
||||
var entityID = _this.entityID;
|
||||
|
||||
// we want to assume that if there is no grab data, then we are not being grabbed
|
||||
var defaultGrabData = { activated: false, avatarId: null };
|
||||
|
||||
// this handy function getEntityCustomData() is available in utils.js and it will return just the specific section
|
||||
// of user data we asked for. If it's not available it returns our default data.
|
||||
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, defaultGrabData);
|
||||
|
||||
// if the grabData says we're being grabbed, and the owner ID is our session, then we are being grabbed by this interface
|
||||
if (grabData.activated && grabData.avatarId == MyAvatar.sessionUUID) {
|
||||
|
||||
// remember we're being grabbed so we can detect being released
|
||||
_this.beingGrabbed = true;
|
||||
|
||||
// print out that we're being grabbed
|
||||
print("I'm being grabbed...");
|
||||
|
||||
} else if (_this.beingGrabbed) {
|
||||
|
||||
// if we are not being grabbed, and we previously were, then we were just released, remember that
|
||||
// and print out a message
|
||||
_this.beingGrabbed = false;
|
||||
print("I'm was released...");
|
||||
}
|
||||
},
|
||||
|
||||
// preload() will be called when the entity has become visible (or known) to the interface
|
||||
// it gives us a chance to set our local JavaScript object up. In this case it means:
|
||||
// * remembering our entityID, so we can access it in cases where we're called without an entityID
|
||||
// * connecting to the update signal so we can check our grabbed state
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
Script.update.connect(this.update);
|
||||
},
|
||||
|
||||
// unload() will be called when our entity is no longer available. It may be because we were deleted,
|
||||
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
|
||||
// to the update signal
|
||||
unload: function(entityID) {
|
||||
Script.update.disconnect(this.update);
|
||||
},
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new DetectGrabbed();
|
||||
})
|
25
examples/html/magBalls/addMode.html
Normal file
25
examples/html/magBalls/addMode.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<style>
|
||||
.container {
|
||||
display:table;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 25vw;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container"><span>Add</span></div>
|
||||
</body>
|
||||
</html>
|
25
examples/html/magBalls/deleteMode.html
Normal file
25
examples/html/magBalls/deleteMode.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<style>
|
||||
.container {
|
||||
display:table;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 25vw;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container"><span>Delete</span></div>
|
||||
</body>
|
||||
</html>
|
14
examples/html/magBalls/magBalls.css
Normal file
14
examples/html/magBalls/magBalls.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
.container {
|
||||
display:table;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 25vw;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
25
examples/html/magBalls/moveMode.html
Normal file
25
examples/html/magBalls/moveMode.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<style>
|
||||
.container {
|
||||
display:table;
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 25vw;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
line-height: normal;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container"><span>Move</span></div>
|
||||
</body>
|
||||
</html>
|
55
examples/libraries/avatarRelativeOverlays.js
Normal file
55
examples/libraries/avatarRelativeOverlays.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
|
||||
AvatarRelativeOverlays = function() {
|
||||
// id -> position & rotation
|
||||
this.overlays = {};
|
||||
this.lastAvatarTransform = {
|
||||
position: ZERO_VECTOR,
|
||||
rotation: IDENTITY_QUATERNION,
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME judder in movement is annoying.... add an option to
|
||||
// automatically hide all overlays when the position or orientation change and then
|
||||
// restore the ones that were previously visible once the movement stops.
|
||||
AvatarRelativeOverlays.prototype.onUpdate = function(deltaTime) {
|
||||
// cache avatar position and orientation and only update on change
|
||||
if (Vec3.equal(this.lastAvatarTransform.position, MyAvatar.position) &&
|
||||
Quat.equal(this.lastAvatarTransform.rotation, MyAvatar.orientation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastAvatarTransform.position = MyAvatar.position;
|
||||
this.lastAvatarTransform.rotation = MyAvatar.orientation;
|
||||
for (var overlayId in this.overlays) {
|
||||
this.updateOverlayTransform(overlayId);
|
||||
}
|
||||
}
|
||||
|
||||
AvatarRelativeOverlays.prototype.updateOverlayTransform = function(overlayId) {
|
||||
Overlays.editOverlay(overlayId, {
|
||||
position: getEyeRelativePosition(this.overlays[overlayId].position),
|
||||
rotation: getAvatarRelativeRotation(this.overlays[overlayId].rotation),
|
||||
})
|
||||
}
|
||||
|
||||
AvatarRelativeOverlays.prototype.addOverlay = function(type, overlayDefinition) {
|
||||
var overlayId = Overlays.addOverlay(type, overlayDefinition);
|
||||
if (!overlayId) {
|
||||
logDebug("Failed to create overlay of type " + type);
|
||||
return;
|
||||
}
|
||||
this.overlays[overlayId] = {
|
||||
position: overlayDefinition.position || ZERO_VECTOR,
|
||||
rotation: overlayDefinition.rotation || IDENTITY_QUATERNION,
|
||||
};
|
||||
this.updateOverlayTransform(overlayId);
|
||||
return overlayId;
|
||||
}
|
||||
|
||||
AvatarRelativeOverlays.prototype.deleteAll = function() {
|
||||
for (var overlayId in this.overlays) {
|
||||
Overlays.deleteOverlay(overlayId);
|
||||
}
|
||||
this.overlays = {};
|
||||
}
|
||||
|
65
examples/libraries/constants.js
Normal file
65
examples/libraries/constants.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/27
|
||||
// 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
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
|
||||
|
||||
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||
|
||||
IDENTITY_QUATERNION = { w: 1, x: 0, y: 0, z: 0 };
|
||||
|
||||
COLORS = {
|
||||
WHITE: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
},
|
||||
BLACK: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
GREY: {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
},
|
||||
RED: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
BLUE: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
GREEN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
CYAN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
YELLOW: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
MAGENTA: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ var SELECTION_OVERLAY = {
|
|||
|
||||
Highlighter = function() {
|
||||
this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY);
|
||||
this.hightlighted = null;
|
||||
this.highlighted = null;
|
||||
var _this = this;
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
|
@ -40,9 +40,9 @@ Highlighter.prototype.onCleanup = function() {
|
|||
Overlays.deleteOverlay(this.highlightCube);
|
||||
}
|
||||
|
||||
Highlighter.prototype.highlight = function(entityId) {
|
||||
if (entityId != this.hightlighted) {
|
||||
this.hightlighted = entityId;
|
||||
Highlighter.prototype.highlight = function(entityIdOrPosition) {
|
||||
if (entityIdOrPosition != this.highlighted) {
|
||||
this.highlighted = entityIdOrPosition;
|
||||
this.updateHighlight();
|
||||
}
|
||||
}
|
||||
|
@ -53,12 +53,29 @@ Highlighter.prototype.setSize = function(newSize) {
|
|||
});
|
||||
}
|
||||
|
||||
Highlighter.prototype.setColor = function(color) {
|
||||
Overlays.editOverlay(this.highlightCube, {
|
||||
color: color
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Highlighter.prototype.setRotation = function(newRotation) {
|
||||
Overlays.editOverlay(this.highlightCube, {
|
||||
rotation: newRotation
|
||||
});
|
||||
}
|
||||
|
||||
Highlighter.prototype.updateHighlight = function() {
|
||||
if (this.hightlighted) {
|
||||
var properties = Entities.getEntityProperties(this.hightlighted);
|
||||
if (this.highlighted) {
|
||||
var position = this.highlighted;
|
||||
if (typeof this.highlighted === "string") {
|
||||
var properties = Entities.getEntityProperties(this.highlighted);
|
||||
position = properties.position;
|
||||
}
|
||||
// logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position));
|
||||
Overlays.editOverlay(this.highlightCube, {
|
||||
position: properties.position,
|
||||
position: position,
|
||||
visible: true
|
||||
});
|
||||
} else {
|
145
examples/libraries/htmlColors.js
Normal file
145
examples/libraries/htmlColors.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
|
||||
HTML_COLORS = {
|
||||
AliceBlue: "#F0F8FF",
|
||||
AntiqueWhite: "#FAEBD7",
|
||||
Aqua: "#00FFFF",
|
||||
Aquamarine: "#7FFFD4",
|
||||
Azure: "#F0FFFF",
|
||||
Beige: "#F5F5DC",
|
||||
Bisque: "#FFE4C4",
|
||||
Black: "#000000",
|
||||
BlanchedAlmond: "#FFEBCD",
|
||||
Blue: "#0000FF",
|
||||
BlueViolet: "#8A2BE2",
|
||||
Brown: "#A52A2A",
|
||||
BurlyWood: "#DEB887",
|
||||
CadetBlue: "#5F9EA0",
|
||||
Chartreuse: "#7FFF00",
|
||||
Chocolate: "#D2691E",
|
||||
Coral: "#FF7F50",
|
||||
CornflowerBlue: "#6495ED",
|
||||
Cornsilk: "#FFF8DC",
|
||||
Crimson: "#DC143C",
|
||||
Cyan: "#00FFFF",
|
||||
DarkBlue: "#00008B",
|
||||
DarkCyan: "#008B8B",
|
||||
DarkGoldenRod: "#B8860B",
|
||||
DarkGray: "#A9A9A9",
|
||||
DarkGreen: "#006400",
|
||||
DarkKhaki: "#BDB76B",
|
||||
DarkMagenta: "#8B008B",
|
||||
DarkOliveGreen: "#556B2F",
|
||||
DarkOrange: "#FF8C00",
|
||||
DarkOrchid: "#9932CC",
|
||||
DarkRed: "#8B0000",
|
||||
DarkSalmon: "#E9967A",
|
||||
DarkSeaGreen: "#8FBC8F",
|
||||
DarkSlateBlue: "#483D8B",
|
||||
DarkSlateGray: "#2F4F4F",
|
||||
DarkTurquoise: "#00CED1",
|
||||
DarkViolet: "#9400D3",
|
||||
DeepPink: "#FF1493",
|
||||
DeepSkyBlue: "#00BFFF",
|
||||
DimGray: "#696969",
|
||||
DodgerBlue: "#1E90FF",
|
||||
FireBrick: "#B22222",
|
||||
FloralWhite: "#FFFAF0",
|
||||
ForestGreen: "#228B22",
|
||||
Fuchsia: "#FF00FF",
|
||||
Gainsboro: "#DCDCDC",
|
||||
GhostWhite: "#F8F8FF",
|
||||
Gold: "#FFD700",
|
||||
GoldenRod: "#DAA520",
|
||||
Gray: "#808080",
|
||||
Green: "#008000",
|
||||
GreenYellow: "#ADFF2F",
|
||||
HoneyDew: "#F0FFF0",
|
||||
HotPink: "#FF69B4",
|
||||
IndianRed: "#CD5C5C",
|
||||
Indigo: "#4B0082",
|
||||
Ivory: "#FFFFF0",
|
||||
Khaki: "#F0E68C",
|
||||
Lavender: "#E6E6FA",
|
||||
LavenderBlush: "#FFF0F5",
|
||||
LawnGreen: "#7CFC00",
|
||||
LemonChiffon: "#FFFACD",
|
||||
LightBlue: "#ADD8E6",
|
||||
LightCoral: "#F08080",
|
||||
LightCyan: "#E0FFFF",
|
||||
LightGoldenRodYellow: "#FAFAD2",
|
||||
LightGray: "#D3D3D3",
|
||||
LightGreen: "#90EE90",
|
||||
LightPink: "#FFB6C1",
|
||||
LightSalmon: "#FFA07A",
|
||||
LightSeaGreen: "#20B2AA",
|
||||
LightSkyBlue: "#87CEFA",
|
||||
LightSlateGray: "#778899",
|
||||
LightSteelBlue: "#B0C4DE",
|
||||
LightYellow: "#FFFFE0",
|
||||
Lime: "#00FF00",
|
||||
LimeGreen: "#32CD32",
|
||||
Linen: "#FAF0E6",
|
||||
Magenta: "#FF00FF",
|
||||
Maroon: "#800000",
|
||||
MediumAquaMarine: "#66CDAA",
|
||||
MediumBlue: "#0000CD",
|
||||
MediumOrchid: "#BA55D3",
|
||||
MediumPurple: "#9370DB",
|
||||
MediumSeaGreen: "#3CB371",
|
||||
MediumSlateBlue: "#7B68EE",
|
||||
MediumSpringGreen: "#00FA9A",
|
||||
MediumTurquoise: "#48D1CC",
|
||||
MediumVioletRed: "#C71585",
|
||||
MidnightBlue: "#191970",
|
||||
MintCream: "#F5FFFA",
|
||||
MistyRose: "#FFE4E1",
|
||||
Moccasin: "#FFE4B5",
|
||||
NavajoWhite: "#FFDEAD",
|
||||
Navy: "#000080",
|
||||
OldLace: "#FDF5E6",
|
||||
Olive: "#808000",
|
||||
OliveDrab: "#6B8E23",
|
||||
Orange: "#FFA500",
|
||||
OrangeRed: "#FF4500",
|
||||
Orchid: "#DA70D6",
|
||||
PaleGoldenRod: "#EEE8AA",
|
||||
PaleGreen: "#98FB98",
|
||||
PaleTurquoise: "#AFEEEE",
|
||||
PaleVioletRed: "#DB7093",
|
||||
PapayaWhip: "#FFEFD5",
|
||||
PeachPuff: "#FFDAB9",
|
||||
Peru: "#CD853F",
|
||||
Pink: "#FFC0CB",
|
||||
Plum: "#DDA0DD",
|
||||
PowderBlue: "#B0E0E6",
|
||||
Purple: "#800080",
|
||||
RebeccaPurple: "#663399",
|
||||
Red: "#FF0000",
|
||||
RosyBrown: "#BC8F8F",
|
||||
RoyalBlue: "#4169E1",
|
||||
SaddleBrown: "#8B4513",
|
||||
Salmon: "#FA8072",
|
||||
SandyBrown: "#F4A460",
|
||||
SeaGreen: "#2E8B57",
|
||||
SeaShell: "#FFF5EE",
|
||||
Sienna: "#A0522D",
|
||||
Silver: "#C0C0C0",
|
||||
SkyBlue: "#87CEEB",
|
||||
SlateBlue: "#6A5ACD",
|
||||
SlateGray: "#708090",
|
||||
Snow: "#FFFAFA",
|
||||
SpringGreen: "#00FF7F",
|
||||
SteelBlue: "#4682B4",
|
||||
Tan: "#D2B48C",
|
||||
Teal: "#008080",
|
||||
Thistle: "#D8BFD8",
|
||||
Tomato: "#FF6347",
|
||||
Turquoise: "#40E0D0",
|
||||
Violet: "#EE82EE",
|
||||
Wheat: "#F5DEB3",
|
||||
White: "#FFFFFF",
|
||||
WhiteSmoke: "#F5F5F5",
|
||||
Yellow: "#FFFF00",
|
||||
YellowGreen: "#9ACD32",
|
||||
}
|
||||
|
351
examples/libraries/omniTool.js
Normal file
351
examples/libraries/omniTool.js
Normal file
|
@ -0,0 +1,351 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/09/01
|
||||
// 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("constants.js");
|
||||
Script.include("utils.js");
|
||||
Script.include("highlighter.js");
|
||||
Script.include("omniTool/models/modelBase.js");
|
||||
Script.include("omniTool/models/wand.js");
|
||||
Script.include("omniTool/models/invisibleWand.js");
|
||||
|
||||
OmniToolModules = {};
|
||||
OmniToolModuleType = null;
|
||||
|
||||
OmniTool = function(side) {
|
||||
this.OMNI_KEY = "OmniTool";
|
||||
this.MAX_FRAMERATE = 60;
|
||||
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
|
||||
this.SIDE = side;
|
||||
this.PALM = 2 * side;
|
||||
this.ACTION = findAction(side ? "ACTION2" : "ACTION1");
|
||||
this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2");
|
||||
|
||||
this.highlighter = new Highlighter();
|
||||
this.ignoreEntities = {};
|
||||
this.nearestOmniEntity = {
|
||||
id: null,
|
||||
|
||||
inside: false,
|
||||
position: null,
|
||||
distance: Infinity,
|
||||
radius: 0,
|
||||
omniProperties: {},
|
||||
boundingBox: null,
|
||||
};
|
||||
|
||||
this.activeOmniEntityId = null;
|
||||
this.lastUpdateInterval = 0;
|
||||
this.active = false;
|
||||
this.module = null;
|
||||
this.moduleEntityId = null;
|
||||
this.lastScanPosition = ZERO_VECTOR;
|
||||
this.showWand(false);
|
||||
|
||||
// Connect to desired events
|
||||
var _this = this;
|
||||
Controller.actionEvent.connect(function(action, state) {
|
||||
_this.onActionEvent(action, state);
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.lastUpdateInterval += deltaTime;
|
||||
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
|
||||
_this.onUpdate(_this.lastUpdateInterval);
|
||||
_this.lastUpdateInterval = 0;
|
||||
}
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
OmniTool.prototype.showWand = function(show) {
|
||||
if (this.model && this.model.onCleanup) {
|
||||
this.model.onCleanup();
|
||||
}
|
||||
logDebug("Showing wand: " + show);
|
||||
if (show) {
|
||||
this.model = new Wand();
|
||||
this.model.setLength(0.4);
|
||||
this.model.setVisible(true);
|
||||
} else {
|
||||
this.model = new InvisibleWand();
|
||||
this.model.setLength(0.1);
|
||||
this.model.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OmniTool.prototype.onCleanup = function(action) {
|
||||
this.unloadModule();
|
||||
}
|
||||
|
||||
OmniTool.prototype.onActionEvent = function(action, state) {
|
||||
// FIXME figure out the issues when only one spatial controller is active
|
||||
// logDebug("Action: " + action + " " + state);
|
||||
|
||||
if (this.module && this.module.onActionEvent) {
|
||||
this.module.onActionEvent(action, state);
|
||||
}
|
||||
|
||||
if (action == this.ACTION) {
|
||||
if (state) {
|
||||
this.onClick();
|
||||
} else {
|
||||
this.onRelease();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME Does not work
|
||||
//// with only one controller active (listed as 2 here because 'tip' + 'palm')
|
||||
//// then treat the alt action button as the action button
|
||||
}
|
||||
|
||||
OmniTool.prototype.getOmniToolData = function(entityId) {
|
||||
return getEntityCustomData(this.OMNI_KEY, entityId, null);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setOmniToolData = function(entityId, data) {
|
||||
setEntityCustomData(this.OMNI_KEY, entityId, data);
|
||||
}
|
||||
|
||||
OmniTool.prototype.updateOmniToolData = function(entityId, data) {
|
||||
var currentData = this.getOmniToolData(entityId) || {};
|
||||
for (var key in data) {
|
||||
currentData[key] = data[key];
|
||||
}
|
||||
setEntityCustomData(this.OMNI_KEY, entityId, currentData);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setActive = function(active) {
|
||||
if (active === this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("OmniTool changing active state: " + active);
|
||||
this.active = active;
|
||||
this.model.setVisible(this.active);
|
||||
if (this.module && this.module.onActiveChanged) {
|
||||
this.module.onActiveChanged(this.side);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OmniTool.prototype.onUpdate = function(deltaTime) {
|
||||
// FIXME this returns data if either the left or right controller is not on the base
|
||||
this.position = Controller.getSpatialControlPosition(this.PALM);
|
||||
// When on the base, hydras report a position of 0
|
||||
this.setActive(Vec3.length(this.position) > 0.001);
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.model) {
|
||||
// Update the wand
|
||||
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
|
||||
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
|
||||
this.model.setTransform({
|
||||
rotation: this.rotation,
|
||||
position: this.position,
|
||||
});
|
||||
|
||||
if (this.model.onUpdate) {
|
||||
this.model.onUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.scan();
|
||||
|
||||
if (this.module && this.module.onUpdate) {
|
||||
this.module.onUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.onClick = function() {
|
||||
// First check to see if the user is switching to a new omni module
|
||||
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
|
||||
|
||||
// If this is already the active entity, turn it off
|
||||
// FIXME add a flag to allow omni modules to cause this entity to be
|
||||
// ignored in order to support items that will be picked up.
|
||||
if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
|
||||
this.showWand(false);
|
||||
this.unloadModule();
|
||||
this.highlighter.setColor("White");
|
||||
return;
|
||||
}
|
||||
|
||||
this.showWand(true);
|
||||
this.highlighter.setColor("Red");
|
||||
this.activateNewOmniModule();
|
||||
return;
|
||||
}
|
||||
|
||||
// Next check if there is an active module and if so propagate the click
|
||||
// FIXME how to I switch to a new module?
|
||||
if (this.module && this.module.onClick) {
|
||||
this.module.onClick();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.onRelease = function() {
|
||||
if (this.module && this.module.onRelease) {
|
||||
this.module.onRelease();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME resturn a structure of all nearby entities to distances
|
||||
OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) {
|
||||
if (!maxDistance) {
|
||||
maxDistance = 2.0;
|
||||
}
|
||||
var resultDistance = Infinity;
|
||||
var resultId = null;
|
||||
var resultProperties = null;
|
||||
var resultOmniData = null;
|
||||
var ids = Entities.findEntities(this.model.tipPosition, maxDistance);
|
||||
for (var i in ids) {
|
||||
var entityId = ids[i];
|
||||
if (this.ignoreEntities[entityId]) {
|
||||
continue;
|
||||
}
|
||||
var omniData = this.getOmniToolData(entityId);
|
||||
if (!omniData) {
|
||||
// FIXME find a place to flush this information
|
||||
this.ignoreEntities[entityId] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let searchers query specifically
|
||||
if (selector && !selector(entityId, omniData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
var distance = Vec3.distance(this.model.tipPosition, properties.position);
|
||||
if (distance < resultDistance) {
|
||||
resultDistance = distance;
|
||||
resultId = entityId;
|
||||
}
|
||||
}
|
||||
|
||||
return resultId;
|
||||
}
|
||||
|
||||
OmniTool.prototype.getPosition = function() {
|
||||
return this.model.tipPosition;
|
||||
}
|
||||
|
||||
OmniTool.prototype.onEnterNearestOmniEntity = function() {
|
||||
this.nearestOmniEntity.inside = true;
|
||||
this.highlighter.highlight(this.nearestOmniEntity.id);
|
||||
if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
|
||||
this.highlighter.setColor("Red");
|
||||
} else {
|
||||
this.highlighter.setColor("White");
|
||||
}
|
||||
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
|
||||
}
|
||||
|
||||
OmniTool.prototype.onLeaveNearestOmniEntity = function() {
|
||||
this.nearestOmniEntity.inside = false;
|
||||
this.highlighter.highlight(null);
|
||||
logDebug("On leave omniEntity " + this.nearestOmniEntity.id);
|
||||
}
|
||||
|
||||
OmniTool.prototype.setNearestOmniEntity = function(entityId) {
|
||||
if (entityId && entityId !== this.nearestOmniEntity.id) {
|
||||
if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) {
|
||||
this.onLeaveNearestOmniEntity();
|
||||
}
|
||||
this.nearestOmniEntity.id = entityId;
|
||||
this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId);
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
this.nearestOmniEntity.position = properties.position;
|
||||
// FIXME use a real bounding box, not a sphere
|
||||
var bbox = properties.boundingBox;
|
||||
this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn));
|
||||
this.highlighter.setRotation(properties.rotation);
|
||||
this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions));
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.scan = function() {
|
||||
var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition);
|
||||
|
||||
if (scanDistance < 0.005) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastScanPosition = this.model.tipPosition;
|
||||
|
||||
this.setNearestOmniEntity(this.findNearestOmniEntity());
|
||||
if (this.nearestOmniEntity.id) {
|
||||
var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position);
|
||||
// track distance on a half centimeter basis
|
||||
if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) {
|
||||
this.nearestOmniEntity.distance = distance;
|
||||
if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) {
|
||||
this.onEnterNearestOmniEntity();
|
||||
}
|
||||
|
||||
if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) {
|
||||
this.onLeaveNearestOmniEntity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OmniTool.prototype.unloadModule = function() {
|
||||
if (this.module && this.module.onUnload) {
|
||||
this.module.onUnload();
|
||||
}
|
||||
this.module = null;
|
||||
this.moduleEntityId = null;
|
||||
}
|
||||
|
||||
OmniTool.prototype.activateNewOmniModule = function() {
|
||||
// Support the ability for scripts to just run without replacing the current module
|
||||
var script = this.nearestOmniEntity.omniProperties.script;
|
||||
if (script.indexOf("/") < 0) {
|
||||
script = "omniTool/modules/" + script;
|
||||
}
|
||||
|
||||
// Reset the tool type
|
||||
OmniToolModuleType = null;
|
||||
logDebug("Including script path: " + script);
|
||||
try {
|
||||
Script.include(script);
|
||||
} catch(err) {
|
||||
logWarn("Failed to include script: " + script + "\n" + err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're building a new module, unload the old one
|
||||
if (OmniToolModuleType) {
|
||||
logDebug("New OmniToolModule: " + OmniToolModuleType);
|
||||
this.unloadModule();
|
||||
|
||||
try {
|
||||
this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id);
|
||||
this.moduleEntityId = this.nearestOmniEntity.id;
|
||||
if (this.module.onLoad) {
|
||||
this.module.onLoad();
|
||||
}
|
||||
} catch(err) {
|
||||
logWarn("Failed to instantiate new module: " + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME find a good way to sync the two omni tools
|
||||
OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ];
|
6
examples/libraries/omniTool/models/invisibleWand.js
Normal file
6
examples/libraries/omniTool/models/invisibleWand.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
InvisibleWand = function() {
|
||||
}
|
||||
|
||||
InvisibleWand.prototype = Object.create( ModelBase.prototype );
|
||||
|
25
examples/libraries/omniTool/models/modelBase.js
Normal file
25
examples/libraries/omniTool/models/modelBase.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
ModelBase = function() {
|
||||
this.length = 0.2;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setVisible = function(visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setLength = function(length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
ModelBase.prototype.setTransform = function(transform) {
|
||||
this.rotation = transform.rotation;
|
||||
this.position = transform.position;
|
||||
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
|
||||
this.tipPosition = Vec3.sum(this.position, this.tipVector);
|
||||
}
|
||||
|
||||
ModelBase.prototype.setTipColors = function(color1, color2) {
|
||||
}
|
||||
|
||||
ModelBase.prototype.onCleanup = function() {
|
||||
}
|
111
examples/libraries/omniTool/models/wand.js
Normal file
111
examples/libraries/omniTool/models/wand.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
|
||||
Wand = function() {
|
||||
// Max updates fps
|
||||
this.DEFAULT_TIP_COLORS = [ {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
}, {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
}];
|
||||
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
|
||||
|
||||
|
||||
this.pointers = [
|
||||
Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: this.DEFAULT_TIP_COLORS[0],
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
}),
|
||||
Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: this.DEFAULT_TIP_COLORS[1],
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
})
|
||||
];
|
||||
|
||||
this.wand = Overlays.addOverlay("cube", {
|
||||
position: ZERO_VECTOR,
|
||||
color: COLORS.WHITE,
|
||||
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var _this = this;
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype = Object.create( ModelBase.prototype );
|
||||
|
||||
Wand.prototype.setVisible = function(visible) {
|
||||
ModelBase.prototype.setVisible.call(this, visible);
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
visible: this.visible
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
visible: this.visible
|
||||
});
|
||||
Overlays.editOverlay(this.wand, {
|
||||
visible: this.visible
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.setTransform = function(transform) {
|
||||
ModelBase.prototype.setTransform.call(this, transform);
|
||||
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
position: this.tipPosition,
|
||||
rotation: this.rotation,
|
||||
visible: true,
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
position: this.tipPosition,
|
||||
rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation),
|
||||
visible: true,
|
||||
});
|
||||
Overlays.editOverlay(this.wand, {
|
||||
dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 },
|
||||
position: wandPosition,
|
||||
rotation: this.rotation,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.setTipColors = function(color1, color2) {
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
color: color1 || this.DEFAULT_TIP_COLORS[0],
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
color: color2 || this.DEFAULT_TIP_COLORS[1],
|
||||
});
|
||||
}
|
||||
|
||||
Wand.prototype.onUpdate = function(deltaTime) {
|
||||
if (this.visible) {
|
||||
var time = new Date().getTime() / 250;
|
||||
var scale1 = Math.abs(Math.sin(time));
|
||||
var scale2 = Math.abs(Math.cos(time));
|
||||
Overlays.editOverlay(this.pointers[0], {
|
||||
scale: scale1 * 0.01,
|
||||
});
|
||||
Overlays.editOverlay(this.pointers[1], {
|
||||
scale: scale2 * 0.01,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Wand.prototype.onCleanup = function() {
|
||||
Overlays.deleteOverlay(this.pointers[0]);
|
||||
Overlays.deleteOverlay(this.pointers[1]);
|
||||
Overlays.deleteOverlay(this.wand);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// breakdanceOmniToolModule.js
|
||||
// examples/libraries/omniTool/modules
|
||||
//
|
||||
// This is an omniTool module version of the breakdance game
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on Sept 3, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("../toys/breakdanceCore.js");
|
||||
|
||||
OmniToolModules.Breakdance = function() {
|
||||
print("OmniToolModules.Breakdance...");
|
||||
}
|
||||
|
||||
OmniToolModules.Breakdance.prototype.onLoad = function(deltaTime) {
|
||||
print("OmniToolModules.Breakdance.prototype.onLoad()...");
|
||||
breakdanceStart();
|
||||
}
|
||||
|
||||
OmniToolModules.Breakdance.prototype.onUpdate = function(deltaTime) {
|
||||
print("OmniToolModules.Breakdance.prototype.onUpdate()...");
|
||||
breakdanceUpdate();
|
||||
}
|
||||
|
||||
OmniToolModules.Breakdance.prototype.onUnload = function() {
|
||||
print("OmniToolModules.Breakdance.prototype.onUnload()...");
|
||||
breakdanceEnd();
|
||||
}
|
||||
|
||||
OmniToolModuleType = "Breakdance";
|
36
examples/libraries/omniTool/modules/test.js
Normal file
36
examples/libraries/omniTool/modules/test.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
|
||||
Script.include("avatarRelativeOverlays.js");
|
||||
|
||||
OmniToolModules.Test = function(omniTool, activeEntityId) {
|
||||
this.omniTool = omniTool;
|
||||
this.activeEntityId = activeEntityId;
|
||||
this.avatarOverlays = new AvatarRelativeOverlays();
|
||||
}
|
||||
|
||||
OmniToolModules.Test.prototype.onUnload = function() {
|
||||
if (this.testOverlay) {
|
||||
Overlays.deleteOverlay(this.testOverlay);
|
||||
this.testOverlay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var CUBE_POSITION = {
|
||||
x: 0.1,
|
||||
y: -0.1,
|
||||
z: -0.4
|
||||
};
|
||||
|
||||
OmniToolModules.Test.prototype.onClick = function() {
|
||||
if (this.testOverlay) {
|
||||
Overlays.deleteOverlay(this.testOverlay);
|
||||
this.testOverlay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OmniToolModules.Test.prototype.onUpdate = function(deltaTime) {
|
||||
this.avatarOverlays.onUpdate(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
OmniToolModuleType = "Test"
|
|
@ -11,6 +11,15 @@ vec3toStr = function (v, digits) {
|
|||
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
|
||||
}
|
||||
|
||||
|
||||
colorMix = function(colorA, colorB, mix) {
|
||||
var result = {};
|
||||
for (var key in colorA) {
|
||||
result[key] = (colorA[key] * (1 - mix)) + (colorB[key] * mix);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
scaleLine = function (start, end, scale) {
|
||||
var v = Vec3.subtract(end, start);
|
||||
var length = Vec3.length(v);
|
||||
|
@ -53,11 +62,17 @@ getEntityUserData = function(id) {
|
|||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.userData) {
|
||||
results = JSON.parse(properties.userData);
|
||||
try {
|
||||
results = JSON.parse(properties.userData);
|
||||
} catch(err) {
|
||||
logDebug(err);
|
||||
logDebug(properties.userData);
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
}
|
||||
|
||||
|
||||
// Non-destructively modify the user data of an entity.
|
||||
setEntityCustomData = function(customKey, id, data) {
|
||||
var userData = getEntityUserData(id);
|
||||
|
@ -70,14 +85,6 @@ getEntityCustomData = function(customKey, id, defaultValue) {
|
|||
return userData[customKey] ? userData[customKey] : defaultValue;
|
||||
}
|
||||
|
||||
getMagBallsData = function(id) {
|
||||
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
|
||||
}
|
||||
|
||||
setMagBallsData = function(id, value) {
|
||||
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
|
||||
}
|
||||
|
||||
mergeObjects = function(proto, custom) {
|
||||
var result = {};
|
||||
for (var attrname in proto) {
|
||||
|
@ -103,4 +110,39 @@ logInfo = function(str) {
|
|||
|
||||
logDebug = function(str) {
|
||||
print(str);
|
||||
}
|
||||
}
|
||||
|
||||
// Computes the penetration between a point and a sphere (centered at the origin)
|
||||
// if point is inside sphere: returns true and stores the result in 'penetration'
|
||||
// (the vector that would move the point outside the sphere)
|
||||
// otherwise returns false
|
||||
findSphereHit = function(point, sphereRadius) {
|
||||
var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations
|
||||
var vectorLength = Vec3.length(point);
|
||||
if (vectorLength < EPSILON) {
|
||||
return true;
|
||||
}
|
||||
var distance = vectorLength - sphereRadius;
|
||||
if (distance < 0.0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
findSpherePointHit = function(sphereCenter, sphereRadius, point) {
|
||||
return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius);
|
||||
}
|
||||
|
||||
findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) {
|
||||
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
|
||||
}
|
||||
|
||||
// Given a vec3 v, return a vec3 that is the same vector relative to the avatars
|
||||
// DEFAULT eye position, rotated into the avatars reference frame.
|
||||
getEyeRelativePosition = function(v) {
|
||||
return Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(MyAvatar.orientation, v));
|
||||
}
|
||||
|
||||
getAvatarRelativeRotation = function(q) {
|
||||
return Quat.multiply(MyAvatar.orientation, q);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
//
|
||||
// breakdanceToy.js
|
||||
// examples
|
||||
// breakdanceCore.js
|
||||
// examples/toys
|
||||
//
|
||||
// This is the core breakdance game library, it can be used as part of an entity script, or an omniTool module, or bootstapped on it's own
|
||||
// Created by Brad Hefta-Gaub on August 24, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
|
@ -9,34 +10,6 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
// helpers
|
||||
// Computes the penetration between a point and a sphere (centered at the origin)
|
||||
// if point is inside sphere: returns true and stores the result in 'penetration'
|
||||
// (the vector that would move the point outside the sphere)
|
||||
// otherwise returns false
|
||||
function findSphereHit(point, sphereRadius) {
|
||||
var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations
|
||||
var vectorLength = Vec3.length(point);
|
||||
if (vectorLength < EPSILON) {
|
||||
return true;
|
||||
}
|
||||
var distance = vectorLength - sphereRadius;
|
||||
if (distance < 0.0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function findSpherePointHit(sphereCenter, sphereRadius, point) {
|
||||
return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius);
|
||||
}
|
||||
|
||||
function findSphereSphereHit(firstCenter, firstRadius, secondCenter, secondRadius) {
|
||||
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
|
||||
}
|
||||
|
||||
|
||||
function getPositionPuppet() {
|
||||
var DISTANCE_IN_FRONT = 2;
|
||||
|
@ -245,13 +218,18 @@ function getPositionRightOnBase() {
|
|||
}
|
||||
|
||||
|
||||
// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines
|
||||
// our 3D cube that moves around...
|
||||
var handSize = 0.25;
|
||||
var leftCubePosition = MyAvatar.getLeftPalmPosition();
|
||||
var rightCubePosition = MyAvatar.getRightPalmPosition();
|
||||
// some globals we will need access to
|
||||
var HAND_SIZE = 0.25;
|
||||
var TARGET_SIZE = 0.3;
|
||||
var TARGET_COLOR = { red: 128, green: 128, blue: 128};
|
||||
var TARGET_COLOR_HIT = { red: 0, green: 255, blue: 0};
|
||||
|
||||
var text = Overlays.addOverlay("text", {
|
||||
var textOverlay, leftHandOverlay, rightHandOverlay,
|
||||
leftOnBaseOverlay, leftLoweredOverlay, leftOverheadOverlay, leftSideOverlay, leftFrontOverlay,
|
||||
rightOnBaseOverlay, rightLoweredOverlay, rightOverheadOverlay, rightSideOverlay, rightFrontOverlay;
|
||||
|
||||
function createOverlays() {
|
||||
textOverlay = Overlays.addOverlay("text", {
|
||||
x: 100,
|
||||
y: 300,
|
||||
width: 900,
|
||||
|
@ -265,31 +243,110 @@ var text = Overlays.addOverlay("text", {
|
|||
backgroundAlpha: 0.5
|
||||
});
|
||||
|
||||
var leftHand= Overlays.addOverlay("cube", {
|
||||
position: leftCubePosition,
|
||||
size: handSize,
|
||||
leftHandOverlay = Overlays.addOverlay("cube", {
|
||||
position: MyAvatar.getLeftPalmPosition(),
|
||||
size: HAND_SIZE,
|
||||
color: { red: 0, green: 0, blue: 255},
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var rightHand= Overlays.addOverlay("cube", {
|
||||
position: rightCubePosition,
|
||||
size: handSize,
|
||||
rightHandOverlay = Overlays.addOverlay("cube", {
|
||||
position: MyAvatar.getRightPalmPosition(),
|
||||
size: HAND_SIZE,
|
||||
color: { red: 255, green: 0, blue: 0},
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
leftOnBaseOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftOnBase(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var targetSize = 0.3;
|
||||
var targetColor = { red: 128, green: 128, blue: 128};
|
||||
var targetColorHit = { red: 0, green: 255, blue: 0};
|
||||
var moveCycleColor = { red: 255, green: 255, blue: 0};
|
||||
|
||||
leftLoweredOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftLowered(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
leftOverheadOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftOverhead(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
leftSideOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftSide(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
leftFrontOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftFront(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
rightOnBaseOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightOnBase(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
rightLoweredOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightLowered(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
rightOverheadOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightOverhead(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
rightSideOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightSide(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
rightFrontOverlay = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightFront(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
}
|
||||
|
||||
var TEMPORARY_LIFETIME = 60;
|
||||
|
||||
var animationSettings = JSON.stringify({
|
||||
var ANIMATION_SETTINGS = JSON.stringify({
|
||||
fps: 30,
|
||||
running: true,
|
||||
loop: true,
|
||||
|
@ -297,107 +354,22 @@ var animationSettings = JSON.stringify({
|
|||
lastFrame: 10000
|
||||
});
|
||||
|
||||
var naturalDimensions = { x: 1.63, y: 1.67, z: 0.31 };
|
||||
var dimensions = Vec3.multiply(naturalDimensions, 0.3);
|
||||
var NATURAL_DIMENSIONS = { x: 1.63, y: 1.67, z: 0.31 };
|
||||
var DIMENSIONS = Vec3.multiply(NATURAL_DIMENSIONS, 0.3);
|
||||
var puppetEntityID;
|
||||
|
||||
var puppetEntityID = Entities.addEntity({
|
||||
function createPuppet() {
|
||||
puppetEntityID = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/bboy1/bboy1.fbx",
|
||||
animationURL: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx",
|
||||
animationSettings: animationSettings,
|
||||
animationSettings: ANIMATION_SETTINGS,
|
||||
position: getPositionPuppet(),
|
||||
ignoreForCollisions: true,
|
||||
dimensions: dimensions,
|
||||
dimensions: DIMENSIONS,
|
||||
lifetime: TEMPORARY_LIFETIME
|
||||
});
|
||||
|
||||
var leftOnBase = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftOnBase(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
var leftLowered = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftLowered(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
var leftOverhead = Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftOverhead(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var leftSide= Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftSide(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
var leftFront= Overlays.addOverlay("cube", {
|
||||
position: getPositionLeftFront(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var rightOnBase = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightOnBase(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var rightLowered = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightLowered(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
var rightOverhead = Overlays.addOverlay("cube", {
|
||||
position: getPositionRightOverhead(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var rightSide= Overlays.addOverlay("cube", {
|
||||
position: getPositionRightSide(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
var rightFront= Overlays.addOverlay("cube", {
|
||||
position: getPositionRightFront(),
|
||||
size: targetSize,
|
||||
color: targetColor,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var startDate = new Date();
|
||||
var lastTime = startDate.getTime();
|
||||
}
|
||||
|
||||
var NO_POSE = 0;
|
||||
var LEFT_ON_BASE = 1;
|
||||
|
@ -411,8 +383,6 @@ var RIGHT_LOWERED = 128;
|
|||
var RIGHT_SIDE = 256;
|
||||
var RIGHT_FRONT = 512;
|
||||
|
||||
var lastPoseValue = NO_POSE;
|
||||
|
||||
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx
|
||||
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_pose_to_idle.fbx
|
||||
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx
|
||||
|
@ -461,8 +431,6 @@ poses[LEFT_ON_BASE + RIGHT_LOWERED ] = { name: "Left On Base + Right Lowered"
|
|||
poses[LEFT_ON_BASE + RIGHT_SIDE ] = { name: "Left On Base + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" };
|
||||
poses[LEFT_ON_BASE + RIGHT_FRONT ] = { name: "Left On Base + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" };
|
||||
|
||||
|
||||
|
||||
poses[LEFT_OVERHEAD + RIGHT_OVERHEAD ] = { name: "Left Overhead + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx" };
|
||||
poses[LEFT_LOWERED + RIGHT_OVERHEAD ] = { name: "Left Lowered + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_1.fbx" };
|
||||
poses[LEFT_SIDE + RIGHT_OVERHEAD ] = { name: "Left Side + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_2.fbx" };
|
||||
|
@ -484,43 +452,46 @@ poses[LEFT_SIDE + RIGHT_FRONT ] = { name: "Left Side + Right Front",
|
|||
poses[LEFT_FRONT + RIGHT_FRONT ] = { name: "Left Front + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_end.fbx" };
|
||||
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
var date= new Date();
|
||||
var now= date.getTime();
|
||||
var elapsed = now - lastTime;
|
||||
var inMoveCycle = false;
|
||||
breakdanceStart = function() {
|
||||
print("breakdanceStart...");
|
||||
createOverlays();
|
||||
createPuppet();
|
||||
}
|
||||
|
||||
breakdanceUpdate = function(deltaTime) {
|
||||
//print("breakdanceUpdate...");
|
||||
|
||||
var leftHandPos = MyAvatar.getLeftPalmPosition();
|
||||
var rightHandPos = MyAvatar.getRightPalmPosition();
|
||||
|
||||
Overlays.editOverlay(leftHand, { position: leftHandPos } );
|
||||
Overlays.editOverlay(rightHand, { position: rightHandPos } );
|
||||
Overlays.editOverlay(leftHandOverlay, { position: leftHandPos } );
|
||||
Overlays.editOverlay(rightHandOverlay, { position: rightHandPos } );
|
||||
|
||||
var hitTargetLeftOnBase = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOnBase(), targetSize/2);
|
||||
var hitTargetLeftOverhead = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOverhead(), targetSize/2);
|
||||
var hitTargetLeftLowered = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftLowered(), targetSize/2);
|
||||
var hitTargetLeftSide = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftSide(), targetSize/2);
|
||||
var hitTargetLeftFront = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftFront(), targetSize/2);
|
||||
var hitTargetLeftOnBase = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftOnBase(), TARGET_SIZE/2);
|
||||
var hitTargetLeftOverhead = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftOverhead(), TARGET_SIZE/2);
|
||||
var hitTargetLeftLowered = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftLowered(), TARGET_SIZE/2);
|
||||
var hitTargetLeftSide = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftSide(), TARGET_SIZE/2);
|
||||
var hitTargetLeftFront = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftFront(), TARGET_SIZE/2);
|
||||
|
||||
var hitTargetRightOnBase = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOnBase(), targetSize/2);
|
||||
var hitTargetRightOverhead = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOverhead(), targetSize/2);
|
||||
var hitTargetRightLowered = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightLowered(), targetSize/2);
|
||||
var hitTargetRightSide = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightSide(), targetSize/2);
|
||||
var hitTargetRightFront = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightFront(), targetSize/2);
|
||||
var hitTargetRightOnBase = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightOnBase(), TARGET_SIZE/2);
|
||||
var hitTargetRightOverhead = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightOverhead(), TARGET_SIZE/2);
|
||||
var hitTargetRightLowered = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightLowered(), TARGET_SIZE/2);
|
||||
var hitTargetRightSide = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightSide(), TARGET_SIZE/2);
|
||||
var hitTargetRightFront = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightFront(), TARGET_SIZE/2);
|
||||
|
||||
|
||||
// determine target colors
|
||||
var targetColorLeftOnBase = hitTargetLeftOnBase ? targetColorHit : targetColor;
|
||||
var targetColorLeftOverhead = hitTargetLeftOverhead ? targetColorHit : targetColor;
|
||||
var targetColorLeftLowered = hitTargetLeftLowered ? targetColorHit : targetColor;
|
||||
var targetColorLeftSide = hitTargetLeftSide ? targetColorHit : targetColor;
|
||||
var targetColorLeftFront = hitTargetLeftFront ? targetColorHit : targetColor;
|
||||
var targetColorLeftOnBase = hitTargetLeftOnBase ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorLeftOverhead = hitTargetLeftOverhead ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorLeftLowered = hitTargetLeftLowered ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorLeftSide = hitTargetLeftSide ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorLeftFront = hitTargetLeftFront ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
|
||||
var targetColorRightOnBase = hitTargetRightOnBase ? targetColorHit : targetColor;
|
||||
var targetColorRightOverhead = hitTargetRightOverhead ? targetColorHit : targetColor;
|
||||
var targetColorRightLowered = hitTargetRightLowered ? targetColorHit : targetColor;
|
||||
var targetColorRightSide = hitTargetRightSide ? targetColorHit : targetColor;
|
||||
var targetColorRightFront = hitTargetRightFront ? targetColorHit : targetColor;
|
||||
var targetColorRightOnBase = hitTargetRightOnBase ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorRightOverhead = hitTargetRightOverhead ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorRightLowered = hitTargetRightLowered ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorRightSide = hitTargetRightSide ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
var targetColorRightFront = hitTargetRightFront ? TARGET_COLOR_HIT : TARGET_COLOR;
|
||||
|
||||
// calculate a combined arm pose based on left and right hits
|
||||
var poseValue = NO_POSE;
|
||||
|
@ -536,47 +507,48 @@ Script.update.connect(function(deltaTime) {
|
|||
poseValue += hitTargetRightFront ? RIGHT_FRONT : 0;
|
||||
|
||||
if (poses[poseValue] == undefined) {
|
||||
Overlays.editOverlay(text, { text: "no pose -- value:" + poseValue });
|
||||
Overlays.editOverlay(textOverlay, { text: "no pose -- value:" + poseValue });
|
||||
} else {
|
||||
Overlays.editOverlay(text, { text: "pose:" + poses[poseValue].name + "\n" + "animation:" + poses[poseValue].animation });
|
||||
Overlays.editOverlay(textOverlay, { text: "pose:" + poses[poseValue].name + "\n" + "animation:" + poses[poseValue].animation });
|
||||
var props = Entities.getEntityProperties(puppetEntityID);
|
||||
//print("puppetEntityID:" + puppetEntityID + "age:"+props.age);
|
||||
Entities.editEntity(puppetEntityID, {
|
||||
animationURL: poses[poseValue].animation,
|
||||
lifetime: TEMPORARY_LIFETIME + props.age // renew lifetime
|
||||
});
|
||||
}
|
||||
|
||||
lastPoseValue = poseValue;
|
||||
|
||||
Overlays.editOverlay(leftOnBase, { position: getPositionLeftOnBase(), color: targetColorLeftOnBase } );
|
||||
Overlays.editOverlay(leftOverhead, { position: getPositionLeftOverhead(), color: targetColorLeftOverhead } );
|
||||
Overlays.editOverlay(leftLowered, { position: getPositionLeftLowered(), color: targetColorLeftLowered } );
|
||||
Overlays.editOverlay(leftSide, { position: getPositionLeftSide() , color: targetColorLeftSide } );
|
||||
Overlays.editOverlay(leftFront, { position: getPositionLeftFront() , color: targetColorLeftFront } );
|
||||
Overlays.editOverlay(leftOnBaseOverlay, { position: getPositionLeftOnBase(), color: targetColorLeftOnBase } );
|
||||
Overlays.editOverlay(leftOverheadOverlay, { position: getPositionLeftOverhead(), color: targetColorLeftOverhead } );
|
||||
Overlays.editOverlay(leftLoweredOverlay, { position: getPositionLeftLowered(), color: targetColorLeftLowered } );
|
||||
Overlays.editOverlay(leftSideOverlay, { position: getPositionLeftSide() , color: targetColorLeftSide } );
|
||||
Overlays.editOverlay(leftFrontOverlay, { position: getPositionLeftFront() , color: targetColorLeftFront } );
|
||||
|
||||
Overlays.editOverlay(rightOnBase, { position: getPositionRightOnBase(), color: targetColorRightOnBase } );
|
||||
Overlays.editOverlay(rightOverhead, { position: getPositionRightOverhead(), color: targetColorRightOverhead } );
|
||||
Overlays.editOverlay(rightLowered, { position: getPositionRightLowered(), color: targetColorRightLowered } );
|
||||
Overlays.editOverlay(rightSide, { position: getPositionRightSide() , color: targetColorRightSide } );
|
||||
Overlays.editOverlay(rightFront, { position: getPositionRightFront() , color: targetColorRightFront } );
|
||||
});
|
||||
Overlays.editOverlay(rightOnBaseOverlay, { position: getPositionRightOnBase(), color: targetColorRightOnBase } );
|
||||
Overlays.editOverlay(rightOverheadOverlay, { position: getPositionRightOverhead(), color: targetColorRightOverhead } );
|
||||
Overlays.editOverlay(rightLoweredOverlay, { position: getPositionRightLowered(), color: targetColorRightLowered } );
|
||||
Overlays.editOverlay(rightSideOverlay, { position: getPositionRightSide() , color: targetColorRightSide } );
|
||||
Overlays.editOverlay(rightFrontOverlay, { position: getPositionRightFront() , color: targetColorRightFront } );
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
Overlays.deleteOverlay(leftHand);
|
||||
Overlays.deleteOverlay(rightHand);
|
||||
|
||||
Overlays.deleteOverlay(text);
|
||||
Overlays.deleteOverlay(leftOnBase);
|
||||
Overlays.deleteOverlay(leftOverhead);
|
||||
Overlays.deleteOverlay(leftLowered);
|
||||
Overlays.deleteOverlay(leftSide);
|
||||
Overlays.deleteOverlay(leftFront);
|
||||
Overlays.deleteOverlay(rightOnBase);
|
||||
Overlays.deleteOverlay(rightOverhead);
|
||||
Overlays.deleteOverlay(rightLowered);
|
||||
Overlays.deleteOverlay(rightSide);
|
||||
Overlays.deleteOverlay(rightFront);
|
||||
breakdanceEnd= function() {
|
||||
print("breakdanceEnd...");
|
||||
|
||||
Overlays.deleteOverlay(leftHandOverlay);
|
||||
Overlays.deleteOverlay(rightHandOverlay);
|
||||
|
||||
Overlays.deleteOverlay(textOverlay);
|
||||
Overlays.deleteOverlay(leftOnBaseOverlay);
|
||||
Overlays.deleteOverlay(leftOverheadOverlay);
|
||||
Overlays.deleteOverlay(leftLoweredOverlay);
|
||||
Overlays.deleteOverlay(leftSideOverlay);
|
||||
Overlays.deleteOverlay(leftFrontOverlay);
|
||||
Overlays.deleteOverlay(rightOnBaseOverlay);
|
||||
Overlays.deleteOverlay(rightOverheadOverlay);
|
||||
Overlays.deleteOverlay(rightLoweredOverlay);
|
||||
Overlays.deleteOverlay(rightSideOverlay);
|
||||
Overlays.deleteOverlay(rightFrontOverlay);
|
||||
|
||||
print("puppetEntityID:"+puppetEntityID);
|
||||
Entities.deleteEntity(puppetEntityID);
|
||||
});
|
||||
}
|
18
examples/toys/breakdanceToy.js
Normal file
18
examples/toys/breakdanceToy.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// breakdanceToy.js
|
||||
// examples/toys
|
||||
//
|
||||
// This is an local script version of the breakdance game
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on Sept 3, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("../libraries/utils.js");
|
||||
Script.include("breakdanceCore.js");
|
||||
breakdanceStart();
|
||||
Script.update.connect(breakdanceUpdate);
|
||||
Script.scriptEnding.connect(breakdanceEnd);
|
311
examples/toys/magBalls.js
Normal file
311
examples/toys/magBalls.js
Normal file
|
@ -0,0 +1,311 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/25
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
|
||||
Script.include("../toys/magBalls/constants.js");
|
||||
Script.include("../toys/magBalls/graph.js");
|
||||
Script.include("../toys/magBalls/edgeSpring.js");
|
||||
Script.include("../toys/magBalls/magBalls.js");
|
||||
Script.include("avatarRelativeOverlays.js");
|
||||
|
||||
OmniToolModuleType = "MagBallsController"
|
||||
|
||||
getMagBallsData = function(id) {
|
||||
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
|
||||
}
|
||||
|
||||
setMagBallsData = function(id, value) {
|
||||
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
|
||||
}
|
||||
|
||||
var UI_BALL_RADIUS = 0.01;
|
||||
var MODE_INFO = { };
|
||||
|
||||
MODE_INFO[BALL_EDIT_MODE_ADD] = {
|
||||
uiPosition: {
|
||||
x: 0.15,
|
||||
y: -0.08,
|
||||
z: -0.35,
|
||||
},
|
||||
colors: [ COLORS.GREEN, COLORS.BLUE ],
|
||||
// FIXME use an http path or find a way to get the relative path to the file
|
||||
url: Script.resolvePath('../html/magBalls/addMode.html'),
|
||||
};
|
||||
|
||||
MODE_INFO[BALL_EDIT_MODE_DELETE] = {
|
||||
uiPosition: {
|
||||
x: 0.20,
|
||||
y: -0.08,
|
||||
z: -0.32,
|
||||
},
|
||||
colors: [ COLORS.RED, COLORS.BLUE ],
|
||||
// FIXME use an http path or find a way to get the relative path to the file
|
||||
url: Script.resolvePath('../html/magBalls/deleteMode.html'),
|
||||
};
|
||||
|
||||
var UI_POSITION_MODE_LABEL = Vec3.multiply(0.5,
|
||||
Vec3.sum(MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
|
||||
MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition));
|
||||
|
||||
UI_POSITION_MODE_LABEL.y = -0.02;
|
||||
|
||||
var UI_BALL_PROTOTYPE = {
|
||||
size: UI_BALL_RADIUS * 2.0,
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: true,
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController = function(omniTool, entityId) {
|
||||
this.omniTool = omniTool;
|
||||
this.entityId = entityId;
|
||||
|
||||
// In hold mode, holding a ball requires that you keep the action
|
||||
// button pressed, while if this is false, clicking on a ball selects
|
||||
// it and clicking again will drop it.
|
||||
this.holdMode = true;
|
||||
|
||||
this.highlighter = new Highlighter();
|
||||
this.magBalls = new MagBalls();
|
||||
this.highlighter.setSize(BALL_SIZE);
|
||||
this.ghostEdges = {};
|
||||
this.selectionRadiusMultipler = 1.5;
|
||||
this.uiOverlays = new AvatarRelativeOverlays();
|
||||
|
||||
|
||||
// create the overlay relative to the avatar
|
||||
this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
|
||||
color: MODE_INFO[BALL_EDIT_MODE_ADD].colors[0],
|
||||
position: MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
|
||||
}));
|
||||
this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
|
||||
color: MODE_INFO[BALL_EDIT_MODE_DELETE].colors[0],
|
||||
position: MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition,
|
||||
}));
|
||||
|
||||
// FIXME find the proper URLs to use
|
||||
this.modeLabel = this.uiOverlays.addOverlay("web3d", {
|
||||
isFacingAvatar: true,
|
||||
alpha: 1.0,
|
||||
dimensions: { x: 0.16, y: 0.12, z: 0.001},
|
||||
color: "White",
|
||||
position: UI_POSITION_MODE_LABEL,
|
||||
});
|
||||
|
||||
this.setMode(BALL_EDIT_MODE_ADD);
|
||||
|
||||
// DEBUGGING ONLY - Fix old, bad edge bounding boxes
|
||||
//for (var edgeId in this.magBalls.edges) {
|
||||
// Entities.editEntity(edgeId, {
|
||||
// dimensions: LINE_DIMENSIONS,
|
||||
// });
|
||||
//}
|
||||
// DEBUGGING ONLY - Clear any previous balls
|
||||
// this.magBalls.clear();
|
||||
// DEBUGGING ONLY - Attempt to fix connections between balls
|
||||
// and delete bad connections. Warning... if you haven't looked around
|
||||
// and caused the domain server to send you all the nearby balls as well as the connections,
|
||||
// this can break your structures
|
||||
// this.magBalls.repair();
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onUnload = function() {
|
||||
this.clearGhostEdges();
|
||||
this.uiOverlays.deleteAll();
|
||||
}
|
||||
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.setMode = function(mode) {
|
||||
if (mode === this.mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug("Changing mode to '" + mode + "'");
|
||||
Overlays.editOverlay(this.modeLabel, {
|
||||
url: MODE_INFO[mode].url
|
||||
});
|
||||
|
||||
this.mode = mode;
|
||||
var color1;
|
||||
var color2;
|
||||
switch (this.mode) {
|
||||
case BALL_EDIT_MODE_ADD:
|
||||
color1 = COLORS.BLUE;
|
||||
color2 = COLORS.GREEN;
|
||||
break;
|
||||
|
||||
case BALL_EDIT_MODE_MOVE:
|
||||
color1 = COLORS.GREEN;
|
||||
color2 = COLORS.LIGHT_GREEN;
|
||||
break;
|
||||
|
||||
case BALL_EDIT_MODE_DELETE:
|
||||
color1 = COLORS.RED;
|
||||
color2 = COLORS.BLUE;
|
||||
break;
|
||||
|
||||
case BALL_EDIT_MODE_DELETE_SHAPE:
|
||||
color1 = COLORS.RED;
|
||||
color2 = COLORS.YELLOW;
|
||||
break;
|
||||
}
|
||||
this.omniTool.model.setTipColors(color1, color2);
|
||||
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.findUiBallHit = function() {
|
||||
var result = null;
|
||||
for (var mode in MODE_INFO) {
|
||||
var modeInfo = MODE_INFO[mode];
|
||||
var spherePoint = getEyeRelativePosition(modeInfo.uiPosition);
|
||||
if (findSpherePointHit(spherePoint, UI_BALL_RADIUS * 2, this.tipPosition)) {
|
||||
this.highlighter.highlight(spherePoint);
|
||||
this.highlighter.setColor("White");
|
||||
// FIXME why doesn't this work?
|
||||
this.highlighter.setSize(UI_BALL_RADIUS * 4);
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onUpdateSelected = function(deltaTime) {
|
||||
if (!this.selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
var targetPosition = this.magBalls.getNodePosition(ballId);
|
||||
var distance = Vec3.distance(targetPosition, this.tipPosition);
|
||||
var variance = this.magBalls.getVariance(distance);
|
||||
var mix = Math.abs(variance) / this.magBalls.MAX_VARIANCE;
|
||||
var color = colorMix(COLORS.YELLOW, COLORS.RED, mix);
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: color,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
color: color,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
|
||||
this.tipPosition = this.omniTool.getPosition();
|
||||
this.uiOverlays.onUpdate(deltaTime);
|
||||
|
||||
this.onUpdateSelected();
|
||||
|
||||
if (this.findUiBallHit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_RADIUS * this.selectionRadiusMultipler);
|
||||
this.highlighter.highlight(target);
|
||||
this.highlighter.setColor(MODE_INFO[this.mode].colors[0]);
|
||||
if (!target) {
|
||||
this.magBalls.onUpdate(deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.deselect = function() {
|
||||
if (!this.selected) {
|
||||
return false
|
||||
}
|
||||
this.clearGhostEdges();
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onClick = function() {
|
||||
var newMode = this.findUiBallHit();
|
||||
if (newMode) {
|
||||
if (this.selected) {
|
||||
this.magBalls.destroyNode(highlighted);
|
||||
this.selected = null;
|
||||
}
|
||||
this.setMode(newMode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.deselect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
|
||||
|
||||
// TODO add checking against UI shapes for adding or deleting balls.
|
||||
var highlighted = this.highlighter.highlighted;
|
||||
if (this.mode == BALL_EDIT_MODE_ADD && !highlighted) {
|
||||
highlighted = this.magBalls.createBall(this.tipPosition);
|
||||
}
|
||||
|
||||
// Nothing to select or create means we're done here.
|
||||
if (!highlighted) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.mode) {
|
||||
case BALL_EDIT_MODE_ADD:
|
||||
case BALL_EDIT_MODE_MOVE:
|
||||
this.magBalls.selectBall(highlighted);
|
||||
this.selected = highlighted;
|
||||
logDebug("Selected " + this.selected);
|
||||
break;
|
||||
|
||||
case BALL_EDIT_MODE_DELETE:
|
||||
this.magBalls.destroyNode(highlighted);
|
||||
break;
|
||||
|
||||
case BALL_EDIT_MODE_DELETE_SHAPE:
|
||||
logDebug("Not implemented yet");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.selected) {
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.onRelease = function() {
|
||||
if (this.holdMode) {
|
||||
this.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
Script.include("handController.js");
|
||||
Script.include("highlighter.js");
|
||||
|
||||
BallController = function(side, magBalls) {
|
||||
HandController.call(this, side);
|
||||
this.magBalls = magBalls;
|
||||
this.highlighter = new Highlighter();
|
||||
this.highlighter.setSize(BALL_SIZE);
|
||||
this.ghostEdges = {};
|
||||
}
|
||||
|
||||
BallController.prototype = Object.create( HandController.prototype );
|
||||
|
||||
BallController.prototype.onUpdate = function(deltaTime) {
|
||||
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(target);
|
||||
return;
|
||||
}
|
||||
this.highlighter.highlight(null);
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BallController.prototype.onClick = function() {
|
||||
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
|
||||
BallController.prototype.onRelease = function() {
|
||||
this.clearGhostEdges();
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
BallController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
BallController.prototype.onCleanup = function() {
|
||||
HandController.prototype.onCleanup.call(this);
|
||||
this.clearGhostEdges();
|
||||
}
|
||||
|
||||
BallController.prototype.onAltClick = function() {
|
||||
return;
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
if (!target) {
|
||||
logDebug(target);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME move to delete shape
|
||||
var toDelete = {};
|
||||
var deleteQueue = [ target ];
|
||||
while (deleteQueue.length) {
|
||||
var curNode = deleteQueue.shift();
|
||||
if (toDelete[curNode]) {
|
||||
continue;
|
||||
}
|
||||
toDelete[curNode] = true;
|
||||
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
|
||||
deleteQueue.push(nodeId);
|
||||
}
|
||||
}
|
||||
for (var nodeId in toDelete) {
|
||||
this.magBalls.destroyNode(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
BallController.prototype.onAltRelease = function() {
|
||||
}
|
|
@ -1,79 +1,18 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/27
|
||||
// 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
|
||||
//
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
|
||||
MAG_BALLS_DATA_NAME = "magBalls";
|
||||
|
||||
// FIXME make this editable through some script UI, so the user can customize the size of the structure built
|
||||
SCALE = 0.5;
|
||||
BALL_SIZE = 0.08 * SCALE;
|
||||
STICK_LENGTH = 0.24 * SCALE;
|
||||
MAG_BALLS_SCALE = 0.5;
|
||||
BALL_SIZE = 0.08 * MAG_BALLS_SCALE;
|
||||
STICK_LENGTH = 0.24 * MAG_BALLS_SCALE;
|
||||
|
||||
DEBUG_MAGSTICKS = true;
|
||||
|
||||
CUSTOM_DATA_NAME = "magBalls";
|
||||
BALL_NAME = "MagBall";
|
||||
EDGE_NAME = "MagStick";
|
||||
|
||||
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
|
||||
|
||||
COLORS = {
|
||||
WHITE: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
},
|
||||
BLACK: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
GREY: {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128,
|
||||
},
|
||||
RED: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
BLUE: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
GREEN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
CYAN: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
YELLOW: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
MAGENTA: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 255
|
||||
}
|
||||
}
|
||||
|
||||
BALL_RADIUS = BALL_SIZE / 2.0;
|
||||
|
||||
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
|
||||
|
||||
BALL_DIMENSIONS = {
|
||||
x: BALL_SIZE,
|
||||
y: BALL_SIZE,
|
||||
|
@ -106,10 +45,13 @@ BALL_PROTOTYPE = {
|
|||
// 2 millimeters
|
||||
BALL_EPSILON = (.002) / BALL_DISTANCE;
|
||||
|
||||
// FIXME better handling of the line bounding box would require putting the
|
||||
// origin in the middle of the line, not at one end
|
||||
LINE_DIAGONAL = Math.sqrt((STICK_LENGTH * STICK_LENGTH * 2) * 3) * 1.1;
|
||||
LINE_DIMENSIONS = {
|
||||
x: 5,
|
||||
y: 5,
|
||||
z: 5
|
||||
x: LINE_DIAGONAL,
|
||||
y: LINE_DIAGONAL,
|
||||
z: LINE_DIAGONAL
|
||||
}
|
||||
|
||||
LINE_PROTOTYPE = {
|
||||
|
@ -138,3 +80,7 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
|
|||
// }
|
||||
|
||||
|
||||
BALL_EDIT_MODE_ADD = "add";
|
||||
BALL_EDIT_MODE_MOVE = "move";
|
||||
BALL_EDIT_MODE_DELETE = "delete";
|
||||
BALL_EDIT_MODE_DELETE_SHAPE = "deleteShape";
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
findMatchingNode = function(position, nodePositions) {
|
||||
for (var nodeId in nodePositions) {
|
||||
var nodePos = nodePositions[nodeId];
|
||||
var distance = Vec3.distance(position, nodePos);
|
||||
if (distance < 0.03) {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repairConnections = function() {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
|
||||
// Find all the balls and record their positions
|
||||
var nodePositions = {};
|
||||
for (var i in ids) {
|
||||
var id = ids[i];
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == BALL_NAME) {
|
||||
nodePositions[id] = properties.position;
|
||||
}
|
||||
}
|
||||
|
||||
// Now check all the edges to see if they're valid (point to balls)
|
||||
// and ensure that the balls point back to them
|
||||
var ballsToEdges = {};
|
||||
for (var i in ids) {
|
||||
var id = ids[i];
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == EDGE_NAME) {
|
||||
var startPos = properties.position;
|
||||
var endPos = Vec3.sum(startPos, properties.linePoints[1]);
|
||||
var magBallData = getMagBallsData(id);
|
||||
var update = false;
|
||||
if (!magBallData.start) {
|
||||
var startNode = findMatchingNode(startPos, nodePositions);
|
||||
if (startNode) {
|
||||
logDebug("Found start node " + startNode)
|
||||
magBallData.start = startNode;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
if (!magBallData.end) {
|
||||
var endNode = findMatchingNode(endPos, nodePositions);
|
||||
if (endNode) {
|
||||
logDebug("Found end node " + endNode)
|
||||
magBallData.end = endNode;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
if (!magBallData.start || !magBallData.end) {
|
||||
logDebug("Didn't find both ends");
|
||||
Entities.deleteEntity(id);
|
||||
continue;
|
||||
}
|
||||
if (!ballsToEdges[magBallData.start]) {
|
||||
ballsToEdges[magBallData.start] = [ id ];
|
||||
} else {
|
||||
ballsToEdges[magBallData.start].push(id);
|
||||
}
|
||||
if (!ballsToEdges[magBallData.end]) {
|
||||
ballsToEdges[magBallData.end] = [ id ];
|
||||
} else {
|
||||
ballsToEdges[magBallData.end].push(id);
|
||||
}
|
||||
if (update) {
|
||||
logDebug("Updating incomplete edge " + id);
|
||||
magBallData.length = BALL_DISTANCE;
|
||||
setMagBallsData(id, magBallData);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var nodeId in ballsToEdges) {
|
||||
var magBallData = getMagBallsData(nodeId);
|
||||
var edges = magBallData.edges || [];
|
||||
var edgeHash = {};
|
||||
for (var i in edges) {
|
||||
edgeHash[edges[i]] = true;
|
||||
}
|
||||
var update = false;
|
||||
for (var i in ballsToEdges[nodeId]) {
|
||||
var edgeId = ballsToEdges[nodeId][i];
|
||||
if (!edgeHash[edgeId]) {
|
||||
update = true;
|
||||
edgeHash[edgeId] = true;
|
||||
edges.push(edgeId);
|
||||
}
|
||||
}
|
||||
if (update) {
|
||||
logDebug("Fixing node with missing edge data");
|
||||
magBallData.edges = edges;
|
||||
setMagBallsData(nodeId, magBallData);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,10 @@ EdgeSpring = function(edgeId, graph) {
|
|||
this.desiredLength = magBallsData.length || BALL_DISTANCE;
|
||||
}
|
||||
|
||||
EdgeSpring.prototype.adjust = function(results) {
|
||||
// FIXME as iterations increase, start introducing some randomness
|
||||
// to the adjustment so that we avoid false equilibriums
|
||||
// Alternatively, larger iterations could increase the acceptable variance
|
||||
EdgeSpring.prototype.adjust = function(results, iterations) {
|
||||
var startPos = this.getAdjustedPosition(this.start, results);
|
||||
var endPos = this.getAdjustedPosition(this.end, results);
|
||||
var vector = Vec3.subtract(endPos, startPos);
|
||||
|
|
|
@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Graph.prototype.getNodesForEdge = function(edgeId) {
|
||||
return Object.keys(this.edges[edgeId]);
|
||||
}
|
||||
|
||||
Graph.prototype.getEdgeLength = function(edgeId) {
|
||||
var nodesInEdge = Object.keys(this.edges[edgeId]);
|
||||
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/29
|
||||
// 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
|
||||
//
|
||||
LEFT_CONTROLLER = 0;
|
||||
RIGHT_CONTROLLER = 1;
|
||||
|
||||
// FIXME add a customizable wand model and a mechanism to switch between wands
|
||||
HandController = function(side) {
|
||||
this.side = side;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.action = findAction(side ? "ACTION2" : "ACTION1");
|
||||
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
|
||||
this.active = false;
|
||||
this.tipScale = 1.4;
|
||||
this.pointer = Overlays.addOverlay("sphere", {
|
||||
position: ZERO_VECTOR,
|
||||
size: 0.01,
|
||||
color: COLORS.YELLOW,
|
||||
alpha: 1.0,
|
||||
solid: true,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
// Connect to desired events
|
||||
var _this = this;
|
||||
Controller.actionEvent.connect(function(action, state) {
|
||||
_this.onActionEvent(action, state);
|
||||
});
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.onUpdate(deltaTime);
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
}
|
||||
|
||||
HandController.prototype.onActionEvent = function(action, state) {
|
||||
var spatialControlCount = Controller.getNumberOfSpatialControls();
|
||||
// If only 2 spacial controls, then we only have one controller active, so use either button
|
||||
// otherwise, only use the specified action
|
||||
|
||||
if (action == this.action) {
|
||||
if (state) {
|
||||
this.onClick();
|
||||
} else {
|
||||
this.onRelease();
|
||||
}
|
||||
}
|
||||
|
||||
if (action == this.altAction) {
|
||||
if (state) {
|
||||
this.onAltClick();
|
||||
} else {
|
||||
this.onAltRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandController.prototype.setActive = function(active) {
|
||||
if (active == this.active) {
|
||||
return;
|
||||
}
|
||||
logDebug("Hand controller changing active state: " + active);
|
||||
this.active = active;
|
||||
Overlays.editOverlay(this.pointer, {
|
||||
visible: this.active
|
||||
});
|
||||
Entities.editEntity(this.wand, {
|
||||
visible: this.active
|
||||
});
|
||||
}
|
||||
|
||||
HandController.prototype.updateControllerState = function() {
|
||||
// FIXME this returns data if either the left or right controller is not on the base
|
||||
this.palmPos = Controller.getSpatialControlPosition(this.palm);
|
||||
var tipPos = Controller.getSpatialControlPosition(this.tip);
|
||||
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
|
||||
// When on the base, hydras report a position of 0
|
||||
this.setActive(Vec3.length(this.palmPos) > 0.001);
|
||||
|
||||
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
|
||||
|
||||
//if (this.active) {
|
||||
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
|
||||
//}
|
||||
}
|
||||
|
||||
HandController.prototype.onCleanup = function() {
|
||||
Overlays.deleteOverlay(this.pointer);
|
||||
}
|
||||
|
||||
HandController.prototype.onUpdate = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
if (this.active) {
|
||||
Overlays.editOverlay(this.pointer, {
|
||||
position: this.tipPosition
|
||||
});
|
||||
Entities.editEntity(this.wand, {
|
||||
position: this.tipPosition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HandController.prototype.onClick = function() {
|
||||
logDebug("Base hand controller does nothing on click");
|
||||
}
|
||||
|
||||
HandController.prototype.onRelease = function() {
|
||||
logDebug("Base hand controller does nothing on release");
|
||||
}
|
||||
|
||||
HandController.prototype.onAltClick = function() {
|
||||
logDebug("Base hand controller does nothing on alt click");
|
||||
}
|
||||
|
||||
HandController.prototype.onAltRelease = function() {
|
||||
logDebug("Base hand controller does nothing on alt click");
|
||||
}
|
||||
|
||||
|
|
@ -8,30 +8,30 @@
|
|||
|
||||
var UPDATE_INTERVAL = 0.1;
|
||||
|
||||
Script.include("graph.js");
|
||||
Script.include("edgeSpring.js");
|
||||
|
||||
// A collection of balls and edges connecting them.
|
||||
MagBalls = function() {
|
||||
Graph.call(this);
|
||||
|
||||
this.MAX_ADJUST_ITERATIONS = 100;
|
||||
this.REFRESH_WAIT_TICKS = 10;
|
||||
this.MAX_VARIANCE = 0.25;
|
||||
this.lastUpdateAge = 0;
|
||||
this.stable = false;
|
||||
this.stable = true;
|
||||
this.adjustIterations = 0;
|
||||
this.selectedNodes = {};
|
||||
this.edgeObjects = {};
|
||||
this.unstableEdges = {};
|
||||
|
||||
this.refresh();
|
||||
|
||||
var _this = this;
|
||||
Script.update.connect(function(deltaTime) {
|
||||
_this.onUpdate(deltaTime);
|
||||
});
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
_this.onCleanup();
|
||||
});
|
||||
|
||||
Entities.addingEntity.connect(function(entityId) {
|
||||
_this.onEntityAdded(entityId);
|
||||
});
|
||||
}
|
||||
|
||||
MagBalls.prototype = Object.create( Graph.prototype );
|
||||
|
@ -40,16 +40,30 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
|
|||
this.lastUpdateAge += deltaTime;
|
||||
if (this.lastUpdateAge > UPDATE_INTERVAL) {
|
||||
this.lastUpdateAge = 0;
|
||||
if (!this.stable) {
|
||||
if (this.refreshNeeded) {
|
||||
if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) {
|
||||
logDebug("Refreshing");
|
||||
this.refresh();
|
||||
this.refreshNeeded = 0;
|
||||
}
|
||||
}
|
||||
if (!this.stable && !Object.keys(this.selectedNodes).length) {
|
||||
this.adjustIterations += 1;
|
||||
// logDebug("Update");
|
||||
var adjusted = false;
|
||||
var nodeAdjustResults = {};
|
||||
var fixupEdges = {};
|
||||
|
||||
for(var edgeId in this.edges) {
|
||||
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
|
||||
if (!this.unstableEdges[edgeId]) {
|
||||
continue;
|
||||
}
|
||||
// FIXME need to add some randomness to this so that objects don't hit a
|
||||
// false equilibrium
|
||||
// FIXME should this be done node-wise, to more easily account for the number of edge
|
||||
// connections for a node?
|
||||
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults, this.adjustIterations);
|
||||
}
|
||||
|
||||
for (var nodeId in nodeAdjustResults) {
|
||||
var curPos = this.getNodePosition(nodeId);
|
||||
var newPos = nodeAdjustResults[nodeId];
|
||||
|
@ -58,22 +72,32 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
|
|||
fixupEdges[edgeId] = true;
|
||||
}
|
||||
// logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm");
|
||||
Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED });
|
||||
Entities.editEntity(nodeId, {
|
||||
position: newPos,
|
||||
// DEBUGGING, flashes moved balls
|
||||
// color: COLORS.RED
|
||||
});
|
||||
}
|
||||
|
||||
// DEBUGGING, flashes moved balls
|
||||
//Script.setTimeout(function(){
|
||||
// for (var nodeId in nodeAdjustResults) {
|
||||
// Entities.editEntity(nodeId, { color: BALL_COLOR });
|
||||
// }
|
||||
//}, ((UPDATE_INTERVAL * 1000) / 2));
|
||||
|
||||
for (var edgeId in fixupEdges) {
|
||||
this.fixupEdge(edgeId);
|
||||
}
|
||||
|
||||
Script.setTimeout(function(){
|
||||
for (var nodeId in nodeAdjustResults) {
|
||||
Entities.editEntity(nodeId, { color: BALL_COLOR });
|
||||
}
|
||||
}, ((UPDATE_INTERVAL * 1000) / 2));
|
||||
|
||||
|
||||
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
|
||||
if (adjusted) {
|
||||
logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning");
|
||||
}
|
||||
this.adjustIterations = 0;
|
||||
this.stable = true;
|
||||
this.unstableEdges = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +142,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
|||
// Check distance to attempt
|
||||
var distance = this.getNodeDistance(nodeId, otherNodeId);
|
||||
var variance = this.getVariance(distance);
|
||||
if (Math.abs(variance) > 0.25) {
|
||||
if (Math.abs(variance) > this.MAX_VARIANCE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -127,26 +151,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
|||
return variances;
|
||||
}
|
||||
|
||||
MagBalls.prototype.grabBall = function(position, maxDist) {
|
||||
var selected = this.findNearestNode(position, maxDist);
|
||||
if (!selected) {
|
||||
selected = this.createNode({ position: position });
|
||||
}
|
||||
if (selected) {
|
||||
this.stable = true;
|
||||
this.breakEdges(selected);
|
||||
this.selectedNodes[selected] = true;
|
||||
}
|
||||
return selected;
|
||||
MagBalls.prototype.breakEdges = function(nodeId) {
|
||||
//var unstableNodes = this.findShape(Object.keys.target);
|
||||
//for (var node in unstableNodes) {
|
||||
// this.unstableNodes[node] = true;
|
||||
//}
|
||||
Graph.prototype.breakEdges.call(this, nodeId);
|
||||
}
|
||||
|
||||
MagBalls.prototype.createBall = function(position) {
|
||||
var created = this.createNode({ position: position });
|
||||
this.selectBall(created);
|
||||
return created;
|
||||
}
|
||||
|
||||
MagBalls.prototype.selectBall = function(selected) {
|
||||
if (!selected) {
|
||||
return;
|
||||
}
|
||||
// stop updating shapes while manipulating
|
||||
this.stable = true;
|
||||
this.selectedNodes[selected] = true;
|
||||
this.breakEdges(selected);
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.releaseBall = function(releasedBall) {
|
||||
delete this.selectedNodes[releasedBall];
|
||||
logDebug("Released ball: " + releasedBall);
|
||||
|
||||
var releasePosition = this.getNodePosition(releasedBall);
|
||||
this.stable = false;
|
||||
|
||||
var releasePosition = this.getNodePosition(releasedBall);
|
||||
|
||||
// iterate through the other balls and ensure we don't intersect with
|
||||
// any of them. If we do, just delete this ball and return.
|
||||
|
@ -169,10 +205,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) {
|
|||
for (var otherBallId in targets) {
|
||||
this.createEdge(otherBallId, releasedBall);
|
||||
}
|
||||
|
||||
var unstableNodes = this.findShape(releasedBall);
|
||||
for (var nodeId in unstableNodes) {
|
||||
for (var edgeId in this.nodes[nodeId]) {
|
||||
this.unstableEdges[edgeId] = true;
|
||||
}
|
||||
}
|
||||
this.validate();
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.findShape = function(nodeId) {
|
||||
var result = {};
|
||||
var queue = [ nodeId ];
|
||||
while (queue.length) {
|
||||
var curNode = queue.shift();
|
||||
if (result[curNode]) {
|
||||
continue;
|
||||
}
|
||||
result[curNode] = true;
|
||||
for (var otherNodeId in this.getConnectedNodes(curNode)) {
|
||||
queue.push(otherNodeId);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.getVariance = function(distance) {
|
||||
// FIXME different balls or edges might have different ideas of variance...
|
||||
// let something else handle this
|
||||
|
@ -263,8 +323,11 @@ MagBalls.prototype.refresh = function() {
|
|||
Script.setTimeout(function() {
|
||||
for (var i in deleteEdges) {
|
||||
var edgeId = deleteEdges[i];
|
||||
logDebug("deleting invalid edge " + edgeId);
|
||||
Entities.deleteEntity(edgeId);
|
||||
//logDebug("deleting invalid edge " + edgeId);
|
||||
//Entities.deleteEntity(edgeId);
|
||||
Entities.editEntity(edgeId, {
|
||||
color: COLORS.RED
|
||||
})
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
@ -291,3 +354,106 @@ MagBalls.prototype.fixupEdge = function(edgeId) {
|
|||
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
|
||||
}
|
||||
|
||||
MagBalls.prototype.onEntityAdded = function(entityId) {
|
||||
// We already have it
|
||||
if (this.nodes[entityId] || this.edges[entityId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
|
||||
this.refreshNeeded = 1;
|
||||
}
|
||||
}
|
||||
|
||||
findMatchingNode = function(position, nodePositions) {
|
||||
for (var nodeId in nodePositions) {
|
||||
var nodePos = nodePositions[nodeId];
|
||||
var distance = Vec3.distance(position, nodePos);
|
||||
if (distance < 0.03) {
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MagBalls.prototype.repair = function() {
|
||||
// Find all the balls and record their positions
|
||||
var nodePositions = {};
|
||||
for (var nodeId in this.nodes) {
|
||||
nodePositions[nodeId] = this.getNodePosition(nodeId);
|
||||
}
|
||||
|
||||
// Now check all the edges to see if they're valid (point to balls)
|
||||
// and ensure that the balls point back to them
|
||||
var ballsToEdges = {};
|
||||
|
||||
// WARNING O(n^2) algorithm, every edge that is broken does
|
||||
// an O(N) search against the nodes
|
||||
for (var edgeId in this.edges) {
|
||||
var properties = Entities.getEntityProperties(edgeId);
|
||||
var startPos = properties.position;
|
||||
var endPos = Vec3.sum(startPos, properties.linePoints[1]);
|
||||
var magBallData = getMagBallsData(edgeId);
|
||||
var update = false;
|
||||
if (!magBallData.start) {
|
||||
var startNode = findMatchingNode(startPos, nodePositions);
|
||||
if (startNode) {
|
||||
logDebug("Found start node " + startNode)
|
||||
magBallData.start = startNode;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
if (!magBallData.end) {
|
||||
var endNode = findMatchingNode(endPos, nodePositions);
|
||||
if (endNode) {
|
||||
logDebug("Found end node " + endNode)
|
||||
magBallData.end = endNode;
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
if (!magBallData.start || !magBallData.end) {
|
||||
logDebug("Didn't find both ends");
|
||||
this.destroyEdge(edgeId);
|
||||
continue;
|
||||
}
|
||||
if (!ballsToEdges[magBallData.start]) {
|
||||
ballsToEdges[magBallData.start] = [ edgeId ];
|
||||
} else {
|
||||
ballsToEdges[magBallData.start].push(edgeId);
|
||||
}
|
||||
if (!ballsToEdges[magBallData.end]) {
|
||||
ballsToEdges[magBallData.end] = [ edgeId ];
|
||||
} else {
|
||||
ballsToEdges[magBallData.end].push(edgeId);
|
||||
}
|
||||
if (update) {
|
||||
logDebug("Updating incomplete edge " + edgeId);
|
||||
magBallData.length = BALL_DISTANCE;
|
||||
setMagBallsData(edgeId, magBallData);
|
||||
}
|
||||
}
|
||||
for (var nodeId in ballsToEdges) {
|
||||
var magBallData = getMagBallsData(nodeId);
|
||||
var edges = magBallData.edges || [];
|
||||
var edgeHash = {};
|
||||
for (var i in edges) {
|
||||
edgeHash[edges[i]] = true;
|
||||
}
|
||||
var update = false;
|
||||
for (var i in ballsToEdges[nodeId]) {
|
||||
var edgeId = ballsToEdges[nodeId][i];
|
||||
if (!edgeHash[edgeId]) {
|
||||
update = true;
|
||||
edgeHash[edgeId] = true;
|
||||
edges.push(edgeId);
|
||||
}
|
||||
}
|
||||
if (update) {
|
||||
logDebug("Fixing node with missing edge data");
|
||||
magBallData.edges = edges;
|
||||
setMagBallsData(nodeId, magBallData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/25
|
||||
// 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("constants.js");
|
||||
Script.include("utils.js");
|
||||
Script.include("magBalls.js");
|
||||
|
||||
Script.include("ballController.js");
|
||||
|
||||
var magBalls = new MagBalls();
|
||||
|
||||
// Clear any previous balls
|
||||
// magBalls.clear();
|
||||
|
||||
MenuController = function(side) {
|
||||
HandController.call(this, side);
|
||||
}
|
||||
|
||||
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
|
||||
var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ];
|
|
@ -1,66 +0,0 @@
|
|||
Script.include("handController.js");
|
||||
|
||||
MenuController = function(side, magBalls) {
|
||||
HandController.call(this, side);
|
||||
}
|
||||
|
||||
MenuController.prototype = Object.create( HandController.prototype );
|
||||
|
||||
MenuController.prototype.onUpdate = function(deltaTime) {
|
||||
HandController.prototype.onUpdate.call(this, deltaTime);
|
||||
if (!this.selected) {
|
||||
// Find the highlight target and set it.
|
||||
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(target);
|
||||
return;
|
||||
}
|
||||
this.highlighter.highlight(null);
|
||||
Entities.editEntity(this.selected, { position: this.tipPosition });
|
||||
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
|
||||
for (var ballId in targetBalls) {
|
||||
if (!this.ghostEdges[ballId]) {
|
||||
// create the ovleray
|
||||
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
|
||||
start: this.magBalls.getNodePosition(ballId),
|
||||
end: this.tipPosition,
|
||||
color: COLORS.RED,
|
||||
alpha: 1,
|
||||
lineWidth: 5,
|
||||
visible: true,
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(this.ghostEdges[ballId], {
|
||||
end: this.tipPosition,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (var ballId in this.ghostEdges) {
|
||||
if (!targetBalls[ballId]) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuController.prototype.onClick = function() {
|
||||
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
|
||||
this.highlighter.highlight(null);
|
||||
}
|
||||
|
||||
MenuController.prototype.onRelease = function() {
|
||||
this.clearGhostEdges();
|
||||
this.magBalls.releaseBall(this.selected);
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
MenuController.prototype.clearGhostEdges = function() {
|
||||
for(var ballId in this.ghostEdges) {
|
||||
Overlays.deleteOverlay(this.ghostEdges[ballId]);
|
||||
delete this.ghostEdges[ballId];
|
||||
}
|
||||
}
|
||||
|
||||
MenuController.prototype.onCleanup = function() {
|
||||
HandController.prototype.onCleanup.call(this);
|
||||
this.clearGhostEdges();
|
||||
}
|
|
@ -3,6 +3,7 @@ var shiftHeld = false;
|
|||
|
||||
Script.include([
|
||||
"libraries/toolBars.js",
|
||||
"libraries/utils.js",
|
||||
]);
|
||||
|
||||
var isActive = false;
|
||||
|
@ -12,24 +13,25 @@ var toolWidth = 50;
|
|||
|
||||
var addingVoxels = false;
|
||||
var deletingVoxels = false;
|
||||
var addingSpheres = false;
|
||||
var deletingSpheres = false;
|
||||
|
||||
offAlpha = 0.5;
|
||||
onAlpha = 0.9;
|
||||
var offAlpha = 0.5;
|
||||
var onAlpha = 0.9;
|
||||
var editSphereRadius = 4;
|
||||
|
||||
function floorVector(v) {
|
||||
return {x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z)};
|
||||
}
|
||||
|
||||
function vectorToString(v){
|
||||
return "{" + v.x + ", " + v.x + ", " + v.x + "}";
|
||||
}
|
||||
|
||||
var toolBar = (function () {
|
||||
var that = {},
|
||||
toolBar,
|
||||
activeButton,
|
||||
addVoxelButton,
|
||||
deleteVoxelButton,
|
||||
addSphereButton,
|
||||
deleteSphereButton,
|
||||
addTerrainButton;
|
||||
|
||||
function initialize() {
|
||||
|
@ -66,6 +68,24 @@ var toolBar = (function () {
|
|||
visible: false
|
||||
});
|
||||
|
||||
addSphereButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "sphere-add.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: offAlpha,
|
||||
visible: false
|
||||
});
|
||||
|
||||
deleteSphereButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "sphere-delete.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: toolWidth,
|
||||
height: toolHeight,
|
||||
alpha: offAlpha,
|
||||
visible: false
|
||||
});
|
||||
|
||||
addTerrainButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "voxel-terrain.svg",
|
||||
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
|
@ -78,6 +98,22 @@ var toolBar = (function () {
|
|||
that.setActive(false);
|
||||
}
|
||||
|
||||
function disableAllButtons() {
|
||||
addingVoxels = false;
|
||||
deletingVoxels = false;
|
||||
addingSpheres = false;
|
||||
deletingSpheres = false;
|
||||
|
||||
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||
toolBar.setAlpha(offAlpha, addSphereButton);
|
||||
toolBar.setAlpha(offAlpha, deleteSphereButton);
|
||||
|
||||
toolBar.selectTool(addVoxelButton, false);
|
||||
toolBar.selectTool(deleteVoxelButton, false);
|
||||
toolBar.selectTool(addSphereButton, false);
|
||||
toolBar.selectTool(deleteSphereButton, false);
|
||||
}
|
||||
|
||||
that.setActive = function(active) {
|
||||
if (active != isActive) {
|
||||
|
@ -91,6 +127,8 @@ var toolBar = (function () {
|
|||
that.showTools = function(doShow) {
|
||||
toolBar.showTool(addVoxelButton, doShow);
|
||||
toolBar.showTool(deleteVoxelButton, doShow);
|
||||
toolBar.showTool(addSphereButton, doShow);
|
||||
toolBar.showTool(deleteSphereButton, doShow);
|
||||
toolBar.showTool(addTerrainButton, doShow);
|
||||
};
|
||||
|
||||
|
@ -103,37 +141,46 @@ var toolBar = (function () {
|
|||
}
|
||||
|
||||
if (addVoxelButton === toolBar.clicked(clickedOverlay)) {
|
||||
if (addingVoxels) {
|
||||
addingVoxels = false;
|
||||
deletingVoxels = false;
|
||||
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||
toolBar.selectTool(addVoxelButton, false);
|
||||
toolBar.selectTool(deleteVoxelButton, false);
|
||||
} else {
|
||||
var wasAddingVoxels = addingVoxels;
|
||||
disableAllButtons()
|
||||
if (!wasAddingVoxels) {
|
||||
addingVoxels = true;
|
||||
deletingVoxels = false;
|
||||
toolBar.setAlpha(onAlpha, addVoxelButton);
|
||||
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (deleteVoxelButton === toolBar.clicked(clickedOverlay)) {
|
||||
if (deletingVoxels) {
|
||||
deletingVoxels = false;
|
||||
addingVoxels = false;
|
||||
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||
toolBar.setAlpha(offAlpha, deleteVoxelButton);
|
||||
} else {
|
||||
var wasDeletingVoxels = deletingVoxels;
|
||||
disableAllButtons()
|
||||
if (!wasDeletingVoxels) {
|
||||
deletingVoxels = true;
|
||||
addingVoxels = false;
|
||||
toolBar.setAlpha(offAlpha, addVoxelButton);
|
||||
toolBar.setAlpha(onAlpha, deleteVoxelButton);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addSphereButton === toolBar.clicked(clickedOverlay)) {
|
||||
var wasAddingSpheres = addingSpheres
|
||||
disableAllButtons()
|
||||
if (!wasAddingSpheres) {
|
||||
addingSpheres = true;
|
||||
toolBar.setAlpha(onAlpha, addSphereButton);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (deleteSphereButton === toolBar.clicked(clickedOverlay)) {
|
||||
var wasDeletingSpheres = deletingSpheres;
|
||||
disableAllButtons()
|
||||
if (!wasDeletingSpheres) {
|
||||
deletingSpheres = true;
|
||||
toolBar.setAlpha(onAlpha, deleteSphereButton);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (addTerrainButton === toolBar.clicked(clickedOverlay)) {
|
||||
addTerrainBlock();
|
||||
return true;
|
||||
|
@ -155,85 +202,212 @@ var toolBar = (function () {
|
|||
}());
|
||||
|
||||
|
||||
|
||||
function getTerrainAlignedLocation(pos) {
|
||||
var posDiv16 = Vec3.multiply(pos, 1.0 / 16.0);
|
||||
var posDiv16Floored = floorVector(posDiv16);
|
||||
return Vec3.multiply(posDiv16Floored, 16.0);
|
||||
}
|
||||
|
||||
|
||||
function lookupTerrainForLocation(pos) {
|
||||
var baseLocation = getTerrainAlignedLocation(pos);
|
||||
entitiesAtLoc = Entities.findEntities(baseLocation, 1.0);
|
||||
for (var i = 0; i < entitiesAtLoc.length; i++) {
|
||||
var id = entitiesAtLoc[i];
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == "terrain") {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function grabLowestJointY() {
|
||||
var jointNames = MyAvatar.getJointNames();
|
||||
var floorY = MyAvatar.position.y;
|
||||
for (var jointName in jointNames) {
|
||||
if (MyAvatar.getJointPosition(jointNames[jointName]).y < floorY) {
|
||||
floorY = MyAvatar.getJointPosition(jointNames[jointName]).y;
|
||||
}
|
||||
}
|
||||
return floorY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function addTerrainBlock() {
|
||||
|
||||
var myPosDiv16 = Vec3.multiply(Vec3.sum(MyAvatar.position, {x:8, x:8, z:8}), 1.0 / 16.0);
|
||||
var myPosDiv16Floored = floorVector(myPosDiv16);
|
||||
var baseLocation = Vec3.multiply(myPosDiv16Floored, 16.0);
|
||||
|
||||
if (baseLocation.y + 8 > MyAvatar.position.y) {
|
||||
var baseLocation = getTerrainAlignedLocation(Vec3.sum(MyAvatar.position, {x:8, y:8, z:8}));
|
||||
if (baseLocation.y > MyAvatar.position.y) {
|
||||
baseLocation.y -= 16;
|
||||
}
|
||||
|
||||
print("myPosDiv16 is " + vectorToString(myPosDiv16));
|
||||
print("MyPosDiv16Floored is " + vectorToString(myPosDiv16Floored));
|
||||
print("baseLocation is " + vectorToString(baseLocation));
|
||||
|
||||
alreadyThere = Entities.findEntities(baseLocation, 1.0);
|
||||
for (var i = 0; i < alreadyThere.length; i++) {
|
||||
var id = alreadyThere[i];
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == "terrain") {
|
||||
print("already terrain there");
|
||||
var alreadyThere = lookupTerrainForLocation(baseLocation);
|
||||
if (alreadyThere) {
|
||||
// there is already a terrain block under MyAvatar.
|
||||
// try in front of the avatar.
|
||||
facingPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(8.0, Quat.getFront(Camera.getOrientation())));
|
||||
facingPosition = Vec3.sum(facingPosition, {x:8, y:8, z:8});
|
||||
baseLocation = getTerrainAlignedLocation(facingPosition);
|
||||
alreadyThere = lookupTerrainForLocation(baseLocation);
|
||||
if (alreadyThere) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var polyVoxId = Entities.addEntity({
|
||||
var polyVoxID = Entities.addEntity({
|
||||
type: "PolyVox",
|
||||
name: "terrain",
|
||||
position: baseLocation,
|
||||
dimensions: { x: 16, y: 16, z: 16 },
|
||||
voxelVolumeSize: {x:16, y:16, z:16},
|
||||
voxelSurfaceStyle: 2
|
||||
dimensions: { x:16, y:16, z:16 },
|
||||
voxelVolumeSize: {x:16, y:64, z:16},
|
||||
voxelSurfaceStyle: 0,
|
||||
xTextureURL: "http://headache.hungry.com/~seth/hifi/dirt.jpeg",
|
||||
yTextureURL: "http://headache.hungry.com/~seth/hifi/grass.png",
|
||||
zTextureURL: "http://headache.hungry.com/~seth/hifi/dirt.jpeg"
|
||||
});
|
||||
Entities.setAllVoxels(polyVoxId, 255);
|
||||
|
||||
for (var y = 8; y < 16; y++) {
|
||||
for (var x = 0; x < 16; x++) {
|
||||
for (var z = 0; z < 16; z++) {
|
||||
Entities.setVoxel(polyVoxId, {x: x, y: y, z: z}, 0);
|
||||
}
|
||||
}
|
||||
var AvatarPositionInVoxelCoords = Entities.worldCoordsToVoxelCoords(polyVoxID, MyAvatar.position);
|
||||
// TODO -- how to find the avatar's feet?
|
||||
var topY = Math.round(AvatarPositionInVoxelCoords.y) - 4;
|
||||
Entities.setVoxelsInCuboid(polyVoxID, {x:0, y:0, z:0}, {x:16, y:topY, z:16}, 255);
|
||||
|
||||
|
||||
//////////
|
||||
// stitch together the terrain with x/y/z NeighorID properties
|
||||
//////////
|
||||
|
||||
// link neighbors to this plot
|
||||
imXNNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:16, y:0, z:0}));
|
||||
imYNNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:16, z:0}));
|
||||
imZNNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:0, z:16}));
|
||||
imXPNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:-16, y:0, z:0}));
|
||||
imYPNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:-16, z:0}));
|
||||
imZPNeighborFor = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:0, z:-16}));
|
||||
|
||||
if (imXNNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imXNNeighborFor);
|
||||
properties.xNNeighborID = polyVoxID;
|
||||
Entities.editEntity(imXNNeighborFor, properties);
|
||||
}
|
||||
if (imYNNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imYNNeighborFor);
|
||||
properties.yNNeighborID = polyVoxID;
|
||||
Entities.editEntity(imYNNeighborFor, properties);
|
||||
}
|
||||
if (imZNNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imZNNeighborFor);
|
||||
properties.zNNeighborID = polyVoxID;
|
||||
Entities.editEntity(imZNNeighborFor, properties);
|
||||
}
|
||||
|
||||
if (imXPNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imXPNeighborFor);
|
||||
properties.xPNeighborID = polyVoxID;
|
||||
Entities.editEntity(imXPNeighborFor, properties);
|
||||
}
|
||||
if (imYPNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imYPNeighborFor);
|
||||
properties.yPNeighborID = polyVoxID;
|
||||
Entities.editEntity(imYPNeighborFor, properties);
|
||||
}
|
||||
if (imZPNeighborFor) {
|
||||
var properties = Entities.getEntityProperties(imZPNeighborFor);
|
||||
properties.zPNeighborID = polyVoxID;
|
||||
Entities.editEntity(imZPNeighborFor, properties);
|
||||
}
|
||||
|
||||
|
||||
// link this plot to its neighbors
|
||||
var properties = Entities.getEntityProperties(polyVoxID);
|
||||
properties.xNNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:-16, y:0, z:0}));
|
||||
properties.yNNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:-16, z:0}));
|
||||
properties.zNNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:0, z:-16}));
|
||||
properties.xPNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:16, y:0, z:0}));
|
||||
properties.yPNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:16, z:0}));
|
||||
properties.zPNeighborID = lookupTerrainForLocation(Vec3.sum(baseLocation, {x:0, y:0, z:16}));
|
||||
Entities.editEntity(polyVoxID, properties);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function attemptVoxelChange(pickRayDir, intersection) {
|
||||
function attemptVoxelChangeForEntity(entityID, pickRayDir, intersectionLocation) {
|
||||
|
||||
var properties = Entities.getEntityProperties(intersection.entityID);
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
if (properties.type != "PolyVox") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (addingVoxels == false && deletingVoxels == false) {
|
||||
if (addingVoxels == false && deletingVoxels == false && addingSpheres == false && deletingSpheres == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var voxelPosition = Entities.worldCoordsToVoxelCoords(intersection.entityID, intersection.intersection);
|
||||
var pickRayDirInVoxelSpace = Entities.localCoordsToVoxelCoords(intersection.entityID, pickRayDir);
|
||||
var voxelOrigin = Entities.worldCoordsToVoxelCoords(entityID, Vec3.subtract(intersectionLocation, pickRayDir));
|
||||
var voxelPosition = Entities.worldCoordsToVoxelCoords(entityID, intersectionLocation);
|
||||
var pickRayDirInVoxelSpace = Vec3.subtract(voxelPosition, voxelOrigin);
|
||||
pickRayDirInVoxelSpace = Vec3.normalize(pickRayDirInVoxelSpace);
|
||||
|
||||
var doAdd = addingVoxels;
|
||||
var doDelete = deletingVoxels;
|
||||
var doAddSphere = addingSpheres;
|
||||
var doDeleteSphere = deletingSpheres;
|
||||
|
||||
if (controlHeld) {
|
||||
doAdd = deletingVoxels;
|
||||
doDelete = addingVoxels;
|
||||
if (doAdd) {
|
||||
doAdd = false;
|
||||
doDelete = true;
|
||||
} else if (doDelete) {
|
||||
doDelete = false;
|
||||
doAdd = true;
|
||||
} else if (doAddSphere) {
|
||||
doAddSphere = false;
|
||||
doDeleteSphere = true;
|
||||
} else if (doDeleteSphere) {
|
||||
doDeleteSphere = false;
|
||||
doAddSphere = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (doDelete) {
|
||||
var toErasePosition = Vec3.sum(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
||||
return Entities.setVoxel(intersection.entityID, floorVector(toErasePosition), 0);
|
||||
return Entities.setVoxel(entityID, floorVector(toErasePosition), 0);
|
||||
}
|
||||
if (doAdd) {
|
||||
var toDrawPosition = Vec3.subtract(voxelPosition, Vec3.multiply(pickRayDirInVoxelSpace, 0.1));
|
||||
return Entities.setVoxel(intersection.entityID, floorVector(toDrawPosition), 255);
|
||||
return Entities.setVoxel(entityID, floorVector(toDrawPosition), 255);
|
||||
}
|
||||
if (doDeleteSphere) {
|
||||
var toErasePosition = intersectionLocation;
|
||||
return Entities.setVoxelSphere(entityID, floorVector(toErasePosition), editSphereRadius, 0);
|
||||
}
|
||||
if (doAddSphere) {
|
||||
var toDrawPosition = intersectionLocation;
|
||||
return Entities.setVoxelSphere(entityID, floorVector(toDrawPosition), editSphereRadius, 255);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function attemptVoxelChange(pickRayDir, intersection) {
|
||||
|
||||
var ids;
|
||||
|
||||
ids = Entities.findEntities(intersection.intersection, editSphereRadius + 1.0);
|
||||
if (ids.indexOf(intersection.entityID) < 0) {
|
||||
ids.push(intersection.entityID);
|
||||
}
|
||||
|
||||
var success = false;
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var entityID = ids[i];
|
||||
success |= attemptVoxelChangeForEntity(entityID, pickRayDir, intersection.intersection)
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
if (!event.isLeftButton) {
|
||||
return;
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <ErrorDialog.h>
|
||||
#include <Finally.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Context.h>
|
||||
|
@ -150,10 +151,10 @@
|
|||
#include "ui/Stats.h"
|
||||
#include "ui/AddressBarDialog.h"
|
||||
#include "ui/UpdateDialog.h"
|
||||
|
||||
#include "ui/overlays/Cube3DOverlay.h"
|
||||
|
||||
#include "PluginContainerProxy.h"
|
||||
#include "AnimDebugDraw.h"
|
||||
|
||||
// ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
|
@ -1025,6 +1026,16 @@ void Application::paintGL() {
|
|||
if (nullptr == _displayPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some plugins process message events, potentially leading to
|
||||
// re-entering a paint event. don't allow further processing if this
|
||||
// happens
|
||||
if (_inPaint) {
|
||||
return;
|
||||
}
|
||||
_inPaint = true;
|
||||
Finally clearFlagLambda([this] { _inPaint = false; });
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
displayPlugin->preRender();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
@ -1219,7 +1230,7 @@ void Application::paintGL() {
|
|||
// Ensure all operations from the previous context are complete before we try to read the fbo
|
||||
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(sync);
|
||||
|
||||
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
|
||||
displayPlugin->display(finalTexture, toGlm(size));
|
||||
|
@ -1235,7 +1246,6 @@ void Application::paintGL() {
|
|||
_frameCount++;
|
||||
Stats::getInstance()->setRenderDetails(renderArgs._details);
|
||||
|
||||
|
||||
// Reset the gpu::Context Stages
|
||||
// Back to the default framebuffer;
|
||||
gpu::Batch batch;
|
||||
|
@ -2896,6 +2906,11 @@ void Application::update(float deltaTime) {
|
|||
loadViewFrustum(_myCamera, _viewFrustum);
|
||||
}
|
||||
|
||||
// Update animation debug draw renderer
|
||||
{
|
||||
AnimDebugDraw::getInstance().update();
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// Update my voxel servers with my current voxel query...
|
||||
|
@ -3560,6 +3575,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
renderContext._drawHitEffect = sceneInterface->doEngineDisplayHitEffect();
|
||||
|
||||
renderContext._occlusionStatus = Menu::getInstance()->isOptionChecked(MenuOption::DebugAmbientOcclusion);
|
||||
renderContext._fxaaStatus = Menu::getInstance()->isOptionChecked(MenuOption::Antialiasing);
|
||||
|
||||
renderArgs->_shouldRender = LODManager::shouldRender;
|
||||
|
||||
|
@ -4727,53 +4743,68 @@ void Application::updateDisplayMode() {
|
|||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
DisplayPluginPointer oldDisplayPlugin = _displayPlugin;
|
||||
if (oldDisplayPlugin != newDisplayPlugin) {
|
||||
if (!_currentDisplayPluginActions.isEmpty()) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(auto itemInfo, _currentDisplayPluginActions) {
|
||||
menu->removeMenuItem(itemInfo.first, itemInfo.second);
|
||||
}
|
||||
_currentDisplayPluginActions.clear();
|
||||
}
|
||||
|
||||
if (newDisplayPlugin) {
|
||||
_offscreenContext->makeCurrent();
|
||||
_activatingDisplayPlugin = true;
|
||||
newDisplayPlugin->activate();
|
||||
_activatingDisplayPlugin = false;
|
||||
_offscreenContext->makeCurrent();
|
||||
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
||||
_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
oldDisplayPlugin = _displayPlugin;
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
|
||||
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
|
||||
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
|
||||
bool newPluginWantsHMDTools = newDisplayPlugin ?
|
||||
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
|
||||
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
|
||||
// Only show the hmd tools after the correct plugin has
|
||||
// been activated so that it's UI is setup correctly
|
||||
if (newPluginWantsHMDTools) {
|
||||
_pluginContainer->showDisplayPluginsTools();
|
||||
}
|
||||
|
||||
if (oldDisplayPlugin) {
|
||||
oldDisplayPlugin->deactivate();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
|
||||
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
|
||||
DependencyManager::get<DialogsManager>()->hmdTools(false);
|
||||
}
|
||||
}
|
||||
emit activeDisplayPluginChanged();
|
||||
resetSensors();
|
||||
if (newDisplayPlugin == oldDisplayPlugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some plugins *cough* Oculus *cough* process message events from inside their
|
||||
// display function, and we don't want to change the display plugin underneath
|
||||
// the paintGL call, so we need to guard against that
|
||||
if (_inPaint) {
|
||||
qDebug() << "Deferring plugin switch until out of painting";
|
||||
// Have the old plugin stop requesting renders
|
||||
oldDisplayPlugin->stop();
|
||||
QCoreApplication::postEvent(this, new LambdaEvent([this] {
|
||||
updateDisplayMode();
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_currentDisplayPluginActions.isEmpty()) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(auto itemInfo, _currentDisplayPluginActions) {
|
||||
menu->removeMenuItem(itemInfo.first, itemInfo.second);
|
||||
}
|
||||
_currentDisplayPluginActions.clear();
|
||||
}
|
||||
|
||||
if (newDisplayPlugin) {
|
||||
_offscreenContext->makeCurrent();
|
||||
_activatingDisplayPlugin = true;
|
||||
newDisplayPlugin->activate();
|
||||
_activatingDisplayPlugin = false;
|
||||
_offscreenContext->makeCurrent();
|
||||
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
||||
_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
oldDisplayPlugin = _displayPlugin;
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
|
||||
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
|
||||
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
|
||||
bool newPluginWantsHMDTools = newDisplayPlugin ?
|
||||
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
|
||||
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
|
||||
|
||||
// Only show the hmd tools after the correct plugin has
|
||||
// been activated so that it's UI is setup correctly
|
||||
if (newPluginWantsHMDTools) {
|
||||
_pluginContainer->showDisplayPluginsTools();
|
||||
}
|
||||
|
||||
if (oldDisplayPlugin) {
|
||||
oldDisplayPlugin->deactivate();
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
|
||||
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
|
||||
DependencyManager::get<DialogsManager>()->hmdTools(false);
|
||||
}
|
||||
}
|
||||
emit activeDisplayPluginChanged();
|
||||
resetSensors();
|
||||
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
|
||||
}
|
||||
|
||||
|
@ -4943,7 +4974,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float
|
|||
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
const float FINGER_LENGTH = 0.3f; // meters
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, FINGER_LENGTH, 0.0f);
|
||||
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
|
||||
glm::vec3 oldTipPosition = palm->getTipRawPosition();
|
||||
if (deltaTime > 0.0f) {
|
||||
|
@ -5076,3 +5107,15 @@ void Application::crashApplication() {
|
|||
|
||||
qCDebug(interfaceapp) << "Intentionally crashed Interface";
|
||||
}
|
||||
|
||||
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
|
||||
QString name = displayPlugin->getName();
|
||||
QAction* action = menu->getActionForOption(name);
|
||||
if (pluginName == name) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
updateDisplayMode();
|
||||
}
|
||||
|
|
|
@ -673,6 +673,7 @@ private:
|
|||
int _simsPerSecondReport = 0;
|
||||
quint64 _lastSimsPerSecondUpdate = 0;
|
||||
bool _isForeground = true; // starts out assumed to be in foreground
|
||||
bool _inPaint = false;
|
||||
|
||||
friend class PluginContainerProxy;
|
||||
};
|
||||
|
|
|
@ -10,16 +10,39 @@
|
|||
//
|
||||
|
||||
#include "FileLogger.h"
|
||||
#include "HifiSockAddr.h"
|
||||
#include <FileUtils.h>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
|
||||
const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
|
||||
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
const QString LOGS_DIRECTORY = "Logs";
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <FileUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "HifiSockAddr.h"
|
||||
|
||||
static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
|
||||
static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
|
||||
static const QString LOGS_DIRECTORY = "Logs";
|
||||
// Max log size is 1 MB
|
||||
static const uint64_t MAX_LOG_SIZE = 1024 * 1024;
|
||||
// Max log age is 1 hour
|
||||
static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
|
||||
|
||||
QString getLogRollerFilename() {
|
||||
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = getLocalAddress();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
return result;
|
||||
}
|
||||
|
||||
const QString& getLogFilename() {
|
||||
static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt";
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
class FilePersistThread : public GenericQueueThread < QString > {
|
||||
public:
|
||||
|
@ -28,8 +51,22 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
void rollFileIfNecessary(QFile& file) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) {
|
||||
QString newFileName = getLogRollerFilename();
|
||||
if (file.copy(newFileName)) {
|
||||
_lastRollTime = now;
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.close();
|
||||
qDebug() << "Rolled log file: " << newFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool processQueueItems(const Queue& messages) {
|
||||
QFile file(_logger._fileName);
|
||||
rollFileIfNecessary(file);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
QTextStream out(&file);
|
||||
foreach(const QString& message, messages) {
|
||||
|
@ -40,20 +77,17 @@ protected:
|
|||
}
|
||||
private:
|
||||
const FileLogger& _logger;
|
||||
uint64_t _lastRollTime = 0;
|
||||
};
|
||||
|
||||
static FilePersistThread* _persistThreadInstance;
|
||||
|
||||
FileLogger::FileLogger(QObject* parent) :
|
||||
AbstractLoggerInterface(parent)
|
||||
AbstractLoggerInterface(parent), _fileName(getLogFilename())
|
||||
{
|
||||
_persistThreadInstance = new FilePersistThread(*this);
|
||||
_persistThreadInstance->initialize(true, QThread::LowestPriority);
|
||||
|
||||
_fileName = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = getLocalAddress();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
_fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
}
|
||||
|
||||
FileLogger::~FileLogger() {
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
virtual void locateLog() override;
|
||||
|
||||
private:
|
||||
QString _fileName;
|
||||
const QString _fileName;
|
||||
friend class FilePersistThread;
|
||||
};
|
||||
|
||||
|
|
|
@ -318,6 +318,7 @@ Menu::Menu() {
|
|||
0, // QML Qt::SHIFT | Qt::Key_A,
|
||||
true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing);
|
||||
|
||||
MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight);
|
||||
QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu);
|
||||
|
@ -448,6 +449,14 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false,
|
||||
avatar, SLOT(setEnableRigAnimations(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false,
|
||||
avatar, SLOT(setEnableAnimGraph(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBindPose, 0, false,
|
||||
avatar, SLOT(setEnableDebugDrawBindPose(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false,
|
||||
avatar, SLOT(setEnableDebugDrawAnimPose(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true,
|
||||
avatar, SLOT(setEnableMeshVisible(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
|
||||
MenuOption::Connexion,
|
||||
|
|
|
@ -132,6 +132,9 @@ namespace MenuOption {
|
|||
const QString AddRemoveFriends = "Add/Remove Friends...";
|
||||
const QString AddressBar = "Show Address Bar";
|
||||
const QString Animations = "Animations...";
|
||||
const QString AnimDebugDrawAnimPose = "Debug Draw Animation";
|
||||
const QString AnimDebugDrawBindPose = "Debug Draw Bind Pose";
|
||||
const QString Antialiasing = "Antialiasing";
|
||||
const QString Atmosphere = "Atmosphere";
|
||||
const QString Attachments = "Attachments...";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
|
@ -185,6 +188,7 @@ namespace MenuOption {
|
|||
const QString EchoServerAudio = "Echo Server Audio";
|
||||
const QString EditEntitiesHelp = "Edit Entities Help...";
|
||||
const QString Enable3DTVMode = "Enable 3DTV Mode";
|
||||
const QString EnableAnimGraph = "Enable Anim Graph";
|
||||
const QString EnableCharacterController = "Enable avatar collisions";
|
||||
const QString EnableRigAnimations = "Enable Rig Animations";
|
||||
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
||||
|
@ -214,6 +218,7 @@ namespace MenuOption {
|
|||
const QString Log = "Log";
|
||||
const QString LogExtraTimings = "Log Extra Timing Details";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
const QString MeshVisible = "Draw Mesh";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
const QString MuteEnvironment = "Mute Environment";
|
||||
|
|
|
@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() {
|
|||
QGLWidget* PluginContainerProxy::getPrimarySurface() {
|
||||
return qApp->_glWidget;
|
||||
}
|
||||
|
||||
void Application::setActiveDisplayPlugin(const QString& pluginName) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
|
||||
QString name = displayPlugin->getName();
|
||||
QAction* action = menu->getActionForOption(name);
|
||||
if (pluginName == name) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
}
|
||||
updateDisplayMode();
|
||||
}
|
||||
|
|
|
@ -782,8 +782,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
|
|||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const {
|
||||
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
|
||||
|
||||
// If we have nothing to draw, or it's tottaly transparent, return
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) {
|
||||
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
|
||||
const float CLIP_DISTANCE = 0.2f;
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|
||||
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|
||||
return;
|
||||
}
|
||||
auto renderer = textRenderer(DISPLAYNAME);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
|
||||
#include "devices/Faceshift.h"
|
||||
|
||||
|
@ -68,7 +69,7 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
|
|||
const int SCRIPTED_MOTOR_CAMERA_FRAME = 0;
|
||||
const int SCRIPTED_MOTOR_AVATAR_FRAME = 1;
|
||||
const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
|
||||
const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://s3.amazonaws.com/hifi-public/sounds/Collisions-hitsandslaps/airhockey_hit1.wav";
|
||||
const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav";
|
||||
|
||||
const float MyAvatar::ZOOM_MIN = 0.5f;
|
||||
const float MyAvatar::ZOOM_MAX = 25.0f;
|
||||
|
@ -123,18 +124,18 @@ MyAvatar::~MyAvatar() {
|
|||
_lookAtTargetAvatar.reset();
|
||||
}
|
||||
|
||||
QByteArray MyAvatar::toByteArray(bool cullSmallChanges) {
|
||||
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
|
||||
CameraMode mode = Application::getInstance()->getCamera()->getMode();
|
||||
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
|
||||
// fake the avatar position that is sent up to the AvatarMixer
|
||||
glm::vec3 oldPosition = _position;
|
||||
_position = getSkeletonPosition();
|
||||
QByteArray array = AvatarData::toByteArray(cullSmallChanges);
|
||||
QByteArray array = AvatarData::toByteArray(cullSmallChanges, sendAll);
|
||||
// copy the correct position back
|
||||
_position = oldPosition;
|
||||
return array;
|
||||
}
|
||||
return AvatarData::toByteArray(cullSmallChanges);
|
||||
return AvatarData::toByteArray(cullSmallChanges, sendAll);
|
||||
}
|
||||
|
||||
void MyAvatar::reset() {
|
||||
|
@ -711,6 +712,38 @@ void MyAvatar::setEnableRigAnimations(bool isEnabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableAnimGraph(bool isEnabled) {
|
||||
_rig->setEnableAnimGraph(isEnabled);
|
||||
if (isEnabled) {
|
||||
if (_skeletonModel.readyToAddToScene()) {
|
||||
initAnimGraph();
|
||||
}
|
||||
} else {
|
||||
destroyAnimGraph();
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawBindPose(bool isEnabled) {
|
||||
_enableDebugDrawBindPose = isEnabled;
|
||||
|
||||
if (!isEnabled) {
|
||||
AnimDebugDraw::getInstance().removeSkeleton("myAvatar");
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawAnimPose(bool isEnabled) {
|
||||
_enableDebugDrawAnimPose = isEnabled;
|
||||
|
||||
if (!isEnabled) {
|
||||
AnimDebugDraw::getInstance().removeAnimNode("myAvatar");
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
||||
_skeletonModel.setVisibleInScene(isEnabled, scene);
|
||||
}
|
||||
|
||||
void MyAvatar::loadData() {
|
||||
Settings settings;
|
||||
settings.beginGroup("Avatar");
|
||||
|
@ -1205,6 +1238,20 @@ void MyAvatar::initHeadBones() {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::initAnimGraph() {
|
||||
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
|
||||
// python2 -m SimpleHTTPServer&
|
||||
//auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
||||
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json");
|
||||
_skeletonModel.initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
||||
}
|
||||
|
||||
void MyAvatar::destroyAnimGraph() {
|
||||
_rig->destroyAnimGraph();
|
||||
AnimDebugDraw::getInstance().removeSkeleton("myAvatar");
|
||||
AnimDebugDraw::getInstance().removeAnimNode("myAvatar");
|
||||
}
|
||||
|
||||
void MyAvatar::preRender(RenderArgs* renderArgs) {
|
||||
|
||||
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
|
||||
|
@ -1213,6 +1260,28 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
|
|||
if (_skeletonModel.initWhenReady(scene)) {
|
||||
initHeadBones();
|
||||
_skeletonModel.setCauterizeBoneSet(_headBoneSet);
|
||||
initAnimGraph();
|
||||
}
|
||||
|
||||
if (_enableDebugDrawBindPose || _enableDebugDrawAnimPose) {
|
||||
|
||||
AnimSkeleton::ConstPointer animSkeleton = _rig->getAnimSkeleton();
|
||||
AnimNode::ConstPointer animNode = _rig->getAnimNode();
|
||||
|
||||
// bones space is rotated
|
||||
glm::quat rotY180 = glm::angleAxis((float)M_PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
AnimPose xform(glm::vec3(1), rotY180 * getOrientation(), getPosition());
|
||||
|
||||
if (animSkeleton && _enableDebugDrawBindPose) {
|
||||
glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f);
|
||||
AnimDebugDraw::getInstance().addSkeleton("myAvatar", animSkeleton, xform, gray);
|
||||
}
|
||||
|
||||
// This only works for when new anim system is enabled, at the moment.
|
||||
if (animNode && animSkeleton && _enableDebugDrawAnimPose && _rig->getEnableAnimGraph()) {
|
||||
glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f);
|
||||
AnimDebugDraw::getInstance().addAnimNode("myAvatar", animNode, xform, cyan);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDrawHead != _prevShouldDrawHead) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "Avatar.h"
|
||||
|
||||
class ModelItemID;
|
||||
class AnimNode;
|
||||
|
||||
enum eyeContactTarget {
|
||||
LEFT_EYE,
|
||||
|
@ -189,7 +190,12 @@ public slots:
|
|||
void loadLastRecording();
|
||||
|
||||
virtual void rebuildSkeletonBody();
|
||||
|
||||
void setEnableRigAnimations(bool isEnabled);
|
||||
void setEnableAnimGraph(bool isEnabled);
|
||||
void setEnableDebugDrawBindPose(bool isEnabled);
|
||||
void setEnableDebugDrawAnimPose(bool isEnabled);
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
|
||||
signals:
|
||||
void transformChanged();
|
||||
|
@ -200,7 +206,7 @@ private:
|
|||
|
||||
glm::vec3 getWorldBodyPosition() const;
|
||||
glm::quat getWorldBodyOrientation() const;
|
||||
QByteArray toByteArray(bool cullSmallChanges);
|
||||
QByteArray toByteArray(bool cullSmallChanges, bool sendAll);
|
||||
void simulate(float deltaTime);
|
||||
void updateFromTrackers(float deltaTime);
|
||||
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;
|
||||
|
@ -283,6 +289,8 @@ private:
|
|||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void maybeUpdateBillboard();
|
||||
void initHeadBones();
|
||||
void initAnimGraph();
|
||||
void destroyAnimGraph();
|
||||
|
||||
// Avatar Preferences
|
||||
QUrl _fullAvatarURLFromPreferences;
|
||||
|
@ -310,6 +318,9 @@ private:
|
|||
std::unordered_set<int> _headBoneSet;
|
||||
RigPointer _rig;
|
||||
bool _prevShouldDrawHead;
|
||||
|
||||
bool _enableDebugDrawBindPose = false;
|
||||
bool _enableDebugDrawAnimPose = false;
|
||||
};
|
||||
|
||||
#endif // hifi_MyAvatar_h
|
||||
|
|
|
@ -86,7 +86,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
|
|||
_rig->updateJointState(i, rootTransform);
|
||||
}
|
||||
|
||||
buildShapes();
|
||||
computeBoundingShape();
|
||||
|
||||
Extents meshExtents = getMeshExtents();
|
||||
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
|
||||
|
@ -248,6 +248,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
|
|||
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
|
||||
true, PALM_PRIORITY);
|
||||
}
|
||||
|
||||
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
||||
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
|
||||
return;
|
||||
|
@ -261,9 +262,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
|||
// the palm's position must be transformed into the model-frame
|
||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation);
|
||||
|
||||
// the palm's "raw" rotation is already in the model-frame
|
||||
glm::quat palmRotation = palm.getRawRotation();
|
||||
glm::quat palmRotation = inverseRotation * palm.getRotation();
|
||||
|
||||
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
|
||||
}
|
||||
|
@ -346,9 +345,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde
|
|||
}
|
||||
OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex];
|
||||
|
||||
glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size;
|
||||
glm::vec3 pUp = position + orientation * IDENTITY_UP * size;
|
||||
glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size;
|
||||
glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size;
|
||||
glm::vec3 pUp = position + orientation * IDENTITY_UP * size;
|
||||
glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size;
|
||||
|
||||
glm::vec3 red(1.0f, 0.0f, 0.0f);
|
||||
geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right);
|
||||
|
@ -466,7 +465,7 @@ float MIN_JOINT_MASS = 1.0f;
|
|||
float VERY_BIG_MASS = 1.0e6f;
|
||||
|
||||
// virtual
|
||||
void SkeletonModel::buildShapes() {
|
||||
void SkeletonModel::computeBoundingShape() {
|
||||
if (_geometry == NULL || _rig->jointStatesEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -476,34 +475,81 @@ void SkeletonModel::buildShapes() {
|
|||
// rootJointIndex == -1 if the avatar model has no skeleton
|
||||
return;
|
||||
}
|
||||
computeBoundingShape(geometry);
|
||||
}
|
||||
|
||||
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms
|
||||
int numStates = _rig->getJointStateCount();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numStates);
|
||||
// BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the
|
||||
// hands and feet into positions that are more correct than the default pose.
|
||||
|
||||
// Measure limb lengths so we can specify IK targets that will pull hands and feet tight to body
|
||||
QVector<QString> endEffectors;
|
||||
endEffectors.push_back("RightHand");
|
||||
endEffectors.push_back("LeftHand");
|
||||
endEffectors.push_back("RightFoot");
|
||||
endEffectors.push_back("LeftFoot");
|
||||
|
||||
QVector<QString> baseJoints;
|
||||
baseJoints.push_back("RightArm");
|
||||
baseJoints.push_back("LeftArm");
|
||||
baseJoints.push_back("RightUpLeg");
|
||||
baseJoints.push_back("LeftUpLeg");
|
||||
|
||||
for (int i = 0; i < endEffectors.size(); ++i) {
|
||||
QString tipName = endEffectors[i];
|
||||
QString baseName = baseJoints[i];
|
||||
float limbLength = 0.0f;
|
||||
int tipIndex = _rig->indexOfJoint(tipName);
|
||||
if (tipIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
// save tip's relative rotation for later
|
||||
glm::quat tipRotation = _rig->getJointState(tipIndex).getRotationInConstrainedFrame();
|
||||
|
||||
// IK on each endpoint
|
||||
int jointIndex = tipIndex;
|
||||
QVector<int> freeLineage;
|
||||
float priority = 1.0f;
|
||||
while (jointIndex > -1) {
|
||||
JointState limbJoint = _rig->getJointState(jointIndex);
|
||||
freeLineage.push_back(jointIndex);
|
||||
if (limbJoint.getName() == baseName) {
|
||||
glm::vec3 targetPosition = limbJoint.getPosition() - glm::vec3(0.0f, 1.5f * limbLength, 0.0f);
|
||||
// do IK a few times to make sure the endpoint gets close to its target
|
||||
for (int j = 0; j < 5; ++j) {
|
||||
_rig->inverseKinematics(tipIndex,
|
||||
targetPosition,
|
||||
glm::quat(),
|
||||
priority,
|
||||
freeLineage,
|
||||
glm::mat4());
|
||||
}
|
||||
break;
|
||||
}
|
||||
limbLength += limbJoint.getDistanceToParent();
|
||||
jointIndex = limbJoint.getParentIndex();
|
||||
}
|
||||
|
||||
// since this IK target is totally bogus we restore the tip's relative rotation
|
||||
_rig->setJointRotationInConstrainedFrame(tipIndex, tipRotation, priority);
|
||||
}
|
||||
|
||||
// recompute all joint model-frame transforms
|
||||
glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
|
||||
for (int i = 0; i < _rig->getJointStateCount(); i++) {
|
||||
_rig->updateJointState(i, rootTransform);
|
||||
}
|
||||
// END BOUNDING SHAPE HACK
|
||||
|
||||
// compute bounding box that encloses all shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
totalExtents.addPoint(glm::vec3(0.0f));
|
||||
int numStates = _rig->getJointStateCount();
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
// compute the default transform of this joint
|
||||
const JointState& state = _rig->getJointState(i);
|
||||
int parentIndex = state.getParentIndex();
|
||||
if (parentIndex == -1) {
|
||||
transforms[i] = _rig->getJointTransform(i);
|
||||
} else {
|
||||
glm::quat modifiedRotation = state.getPreRotation() * state.getDefaultRotation() * state.getPostRotation();
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(state.getTranslation())
|
||||
* state.getPreTransform() * glm::mat4_cast(modifiedRotation) * state.getPostTransform();
|
||||
}
|
||||
|
||||
// Each joint contributes a sphere at its position
|
||||
glm::vec3 axis(state.getBoneRadius());
|
||||
glm::vec3 jointPosition = extractTranslation(transforms[i]);
|
||||
glm::vec3 jointPosition = state.getPosition();
|
||||
totalExtents.addPoint(jointPosition + axis);
|
||||
totalExtents.addPoint(jointPosition - axis);
|
||||
}
|
||||
|
@ -517,6 +563,11 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
|
|||
|
||||
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
|
||||
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
|
||||
|
||||
// RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
_rig->restoreJointRotation(i, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
||||
|
@ -535,7 +586,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
|
|||
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
// draw a yellow sphere at the capsule bottom point
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 axis = topPoint - bottomPoint;
|
||||
transform.setTranslation(bottomPoint);
|
||||
batch.setModelTransform(transform);
|
||||
|
@ -555,3 +606,7 @@ bool SkeletonModel::hasSkeleton() {
|
|||
|
||||
void SkeletonModel::onInvalidate() {
|
||||
}
|
||||
|
||||
void SkeletonModel::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
||||
_rig->initAnimGraph(url, fbxGeometry);
|
||||
}
|
||||
|
|
|
@ -94,7 +94,6 @@ public:
|
|||
/// \return whether or not the head was found.
|
||||
glm::vec3 getDefaultEyeModelPosition() const;
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
|
||||
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
|
||||
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
|
||||
|
@ -106,13 +105,15 @@ public:
|
|||
|
||||
virtual void onInvalidate() override;
|
||||
|
||||
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
|
||||
|
||||
signals:
|
||||
|
||||
void skeletonLoaded();
|
||||
|
||||
protected:
|
||||
|
||||
void buildShapes();
|
||||
void computeBoundingShape();
|
||||
|
||||
/// \param jointIndex index of joint in model
|
||||
/// \param position position of joint in model-frame
|
||||
|
|
|
@ -66,16 +66,7 @@ Overlay::~Overlay() {
|
|||
|
||||
void Overlay::setProperties(const QScriptValue& properties) {
|
||||
QScriptValue color = properties.property("color");
|
||||
if (color.isValid()) {
|
||||
QScriptValue red = color.property("red");
|
||||
QScriptValue green = color.property("green");
|
||||
QScriptValue blue = color.property("blue");
|
||||
if (red.isValid() && green.isValid() && blue.isValid()) {
|
||||
_color.red = red.toVariant().toInt();
|
||||
_color.green = green.toVariant().toInt();
|
||||
_color.blue = blue.toVariant().toInt();
|
||||
}
|
||||
}
|
||||
xColorFromScriptValue(properties.property("color"), _color);
|
||||
|
||||
if (properties.property("alpha").isValid()) {
|
||||
setAlpha(properties.property("alpha").toVariant().toFloat());
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Grid3DOverlay.h"
|
||||
#include "TextOverlay.h"
|
||||
#include "Text3DOverlay.h"
|
||||
#include "Web3DOverlay.h"
|
||||
|
||||
|
||||
Overlays::Overlays() : _nextOverlayID(1) {
|
||||
|
@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
|
|||
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
|
||||
} else if (type == ModelOverlay::TYPE) {
|
||||
thisOverlay = std::make_shared<ModelOverlay>();
|
||||
} else if (type == Web3DOverlay::TYPE) {
|
||||
thisOverlay = std::make_shared<Web3DOverlay>();
|
||||
}
|
||||
|
||||
if (thisOverlay) {
|
||||
|
|
168
interface/src/ui/overlays/Web3DOverlay.cpp
Normal file
168
interface/src/ui/overlays/Web3DOverlay.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// Web3DOverlay.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 7/1/14.
|
||||
// Modified and renamed by Zander Otavka on 8/4/15
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Web3DOverlay.h"
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <TextureCache.h>
|
||||
#include <PathUtils.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
|
||||
#include <OffscreenQmlSurface.h>
|
||||
|
||||
// #include "Application.h"
|
||||
// #include "GeometryUtil.h"
|
||||
|
||||
static const float DPI = 30.47f;
|
||||
static const float METERS_TO_INCHES = 39.3701f;
|
||||
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
|
||||
QString const Web3DOverlay::TYPE = "web3d";
|
||||
|
||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) { }
|
||||
|
||||
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
||||
Billboard3DOverlay(Web3DOverlay),
|
||||
_url(Web3DOverlay->_url),
|
||||
_dpi(Web3DOverlay->_dpi),
|
||||
_resolution(Web3DOverlay->_resolution)
|
||||
{
|
||||
}
|
||||
|
||||
Web3DOverlay::~Web3DOverlay() {
|
||||
if (_webSurface) {
|
||||
_webSurface->pause();
|
||||
_webSurface->disconnect(_connection);
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
auto webSurface = _webSurface;
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Web3DOverlay::update(float deltatime) {
|
||||
applyTransformTo(_transform);
|
||||
}
|
||||
|
||||
void Web3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
if (!_webSurface) {
|
||||
_webSurface = new OffscreenQmlSurface();
|
||||
_webSurface->create(currentContext);
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
_webSurface->load("WebEntity.qml");
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
}
|
||||
|
||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS;
|
||||
vec2 halfSize = size / 2.0f;
|
||||
vec4 color(toGlm(getColor()), getAlpha());
|
||||
|
||||
applyTransformTo(_transform, true);
|
||||
Transform transform = _transform;
|
||||
if (glm::length2(getDimensions()) != 1.0f) {
|
||||
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
} else {
|
||||
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
}
|
||||
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true, false, false, true);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
|
||||
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
|
||||
}
|
||||
|
||||
void Web3DOverlay::setProperties(const QScriptValue &properties) {
|
||||
Billboard3DOverlay::setProperties(properties);
|
||||
|
||||
QScriptValue urlValue = properties.property("url");
|
||||
if (urlValue.isValid()) {
|
||||
QString newURL = urlValue.toVariant().toString();
|
||||
if (newURL != _url) {
|
||||
setURL(newURL);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue resolution = properties.property("resolution");
|
||||
if (resolution.isValid()) {
|
||||
vec2FromScriptValue(resolution, _resolution);
|
||||
}
|
||||
|
||||
|
||||
QScriptValue dpi = properties.property("dpi");
|
||||
if (dpi.isValid()) {
|
||||
_dpi = dpi.toVariant().toFloat();
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue Web3DOverlay::getProperty(const QString& property) {
|
||||
if (property == "url") {
|
||||
return _url;
|
||||
}
|
||||
if (property == "dpi") {
|
||||
return _dpi;
|
||||
}
|
||||
return Billboard3DOverlay::getProperty(property);
|
||||
}
|
||||
|
||||
void Web3DOverlay::setURL(const QString& url) {
|
||||
_url = url;
|
||||
if (_webSurface) {
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
|
||||
_webSurface->getRootItem()->setProperty("url", url);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
|
||||
//// Make sure position and rotation is updated.
|
||||
applyTransformTo(_transform, true);
|
||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
|
||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance);
|
||||
}
|
||||
|
||||
Web3DOverlay* Web3DOverlay::createClone() const {
|
||||
return new Web3DOverlay(this);
|
||||
}
|
50
interface/src/ui/overlays/Web3DOverlay.h
Normal file
50
interface/src/ui/overlays/Web3DOverlay.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/08/31
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Web3DOverlay_h
|
||||
#define hifi_Web3DOverlay_h
|
||||
|
||||
#include "Billboard3DOverlay.h"
|
||||
|
||||
class OffscreenQmlSurface;
|
||||
|
||||
class Web3DOverlay : public Billboard3DOverlay {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QString const TYPE;
|
||||
virtual QString getType() const { return TYPE; }
|
||||
|
||||
Web3DOverlay();
|
||||
Web3DOverlay(const Web3DOverlay* Web3DOverlay);
|
||||
virtual ~Web3DOverlay();
|
||||
|
||||
virtual void render(RenderArgs* args);
|
||||
|
||||
virtual void update(float deltatime);
|
||||
|
||||
// setters
|
||||
void setURL(const QString& url);
|
||||
|
||||
virtual void setProperties(const QScriptValue& properties);
|
||||
virtual QScriptValue getProperty(const QString& property);
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
|
||||
|
||||
virtual Web3DOverlay* createClone() const;
|
||||
|
||||
private:
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
QString _url;
|
||||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
};
|
||||
|
||||
#endif // hifi_Web3DOverlay_h
|
62
libraries/animation/src/AnimBlendLinear.cpp
Normal file
62
libraries/animation/src/AnimBlendLinear.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// AnimBlendLinear.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimBlendLinear.h"
|
||||
#include "GLMHelpers.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) :
|
||||
AnimNode(AnimNode::Type::BlendLinear, id),
|
||||
_alpha(alpha) {
|
||||
|
||||
}
|
||||
|
||||
AnimBlendLinear::~AnimBlendLinear() {
|
||||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||
|
||||
if (_children.size() == 0) {
|
||||
for (auto&& pose : _poses) {
|
||||
pose = AnimPose::identity;
|
||||
}
|
||||
} else if (_children.size() == 1) {
|
||||
_poses = _children[0]->evaluate(animVars, dt, triggersOut);
|
||||
} else {
|
||||
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
|
||||
size_t prevPoseIndex = glm::floor(clampedAlpha);
|
||||
size_t nextPoseIndex = glm::ceil(clampedAlpha);
|
||||
float alpha = glm::fract(clampedAlpha);
|
||||
if (prevPoseIndex == nextPoseIndex) {
|
||||
// this can happen if alpha is on an integer boundary
|
||||
_poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
} else {
|
||||
// need to eval and blend between two children.
|
||||
auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
|
||||
if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) {
|
||||
_poses.resize(prevPoses.size());
|
||||
|
||||
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
52
libraries/animation/src/AnimBlendLinear.h
Normal file
52
libraries/animation/src/AnimBlendLinear.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// AnimBlendLinear.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimBlendLinear_h
|
||||
#define hifi_AnimBlendLinear_h
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
// Linear blend between two AnimNodes.
|
||||
// the amount of blending is determined by the alpha parameter.
|
||||
// If the number of children is 2, then the alpha parameters should be between
|
||||
// 0 and 1. The first animation will have a (1 - alpha) factor, and the second
|
||||
// will have factor of alpha.
|
||||
// This node supports more then 2 children. In this case the alpha should be
|
||||
// between 0 and n - 1. This alpha can be used to linearly interpolate between
|
||||
// the closest two children poses. This can be used to sweep through a series
|
||||
// of animation poses.
|
||||
|
||||
class AnimBlendLinear : public AnimNode {
|
||||
public:
|
||||
friend class AnimTests;
|
||||
|
||||
AnimBlendLinear(const std::string& id, float alpha);
|
||||
virtual ~AnimBlendLinear() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
protected:
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
AnimPoseVec _poses;
|
||||
|
||||
float _alpha;
|
||||
|
||||
std::string _alphaVar;
|
||||
|
||||
// no copies
|
||||
AnimBlendLinear(const AnimBlendLinear&) = delete;
|
||||
AnimBlendLinear& operator=(const AnimBlendLinear&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimBlendLinear_h
|
161
libraries/animation/src/AnimClip.cpp
Normal file
161
libraries/animation/src/AnimClip.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// AnimClip.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
#include "AnimClip.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) :
|
||||
AnimNode(AnimNode::Type::Clip, id),
|
||||
_startFrame(startFrame),
|
||||
_endFrame(endFrame),
|
||||
_timeScale(timeScale),
|
||||
_loopFlag(loopFlag),
|
||||
_frame(startFrame)
|
||||
{
|
||||
loadURL(url);
|
||||
}
|
||||
|
||||
AnimClip::~AnimClip() {
|
||||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
// lookup parameters from animVars, using current instance variables as defaults.
|
||||
_startFrame = animVars.lookup(_startFrameVar, _startFrame);
|
||||
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
|
||||
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
||||
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
|
||||
_frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt, triggersOut);
|
||||
|
||||
// poll network anim to see if it's finished loading yet.
|
||||
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
|
||||
// loading is complete, copy animation frames from network animation, then throw it away.
|
||||
copyFromNetworkAnim();
|
||||
_networkAnim.reset();
|
||||
}
|
||||
|
||||
if (_anim.size()) {
|
||||
int frameCount = _anim.size();
|
||||
|
||||
int prevIndex = (int)glm::floor(_frame);
|
||||
int nextIndex = (int)glm::ceil(_frame);
|
||||
if (_loopFlag && nextIndex >= frameCount) {
|
||||
nextIndex = 0;
|
||||
}
|
||||
|
||||
// It can be quite possible for the user to set _startFrame and _endFrame to
|
||||
// values before or past valid ranges. We clamp the frames here.
|
||||
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
|
||||
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
|
||||
|
||||
const AnimPoseVec& prevFrame = _anim[prevIndex];
|
||||
const AnimPoseVec& nextFrame = _anim[nextIndex];
|
||||
float alpha = glm::fract(_frame);
|
||||
|
||||
::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
|
||||
}
|
||||
|
||||
return _poses;
|
||||
}
|
||||
|
||||
void AnimClip::loadURL(const std::string& url) {
|
||||
auto animCache = DependencyManager::get<AnimationCache>();
|
||||
_networkAnim = animCache->getAnimation(QString::fromStdString(url));
|
||||
_url = url;
|
||||
}
|
||||
|
||||
void AnimClip::setCurrentFrameInternal(float frame) {
|
||||
// because dt is 0, we should not encounter any triggers
|
||||
const float dt = 0.0f;
|
||||
Triggers triggers;
|
||||
_frame = accumulateTime(frame * _timeScale, dt, triggers);
|
||||
}
|
||||
|
||||
float AnimClip::accumulateTime(float frame, float dt, Triggers& triggersOut) const {
|
||||
const float startFrame = std::min(_startFrame, _endFrame);
|
||||
if (startFrame == _endFrame) {
|
||||
// when startFrame >= endFrame
|
||||
frame = _endFrame;
|
||||
} else if (_timeScale > 0.0f) {
|
||||
// accumulate time, keeping track of loops and end of animation events.
|
||||
const float FRAMES_PER_SECOND = 30.0f;
|
||||
float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND;
|
||||
while (framesRemaining > 0.0f) {
|
||||
float framesTillEnd = _endFrame - _frame;
|
||||
if (framesRemaining >= framesTillEnd) {
|
||||
if (_loopFlag) {
|
||||
// anim loop
|
||||
triggersOut.push_back(_id + "OnLoop");
|
||||
framesRemaining -= framesTillEnd;
|
||||
frame = startFrame;
|
||||
} else {
|
||||
// anim end
|
||||
triggersOut.push_back(_id + "OnDone");
|
||||
frame = _endFrame;
|
||||
framesRemaining = 0.0f;
|
||||
}
|
||||
} else {
|
||||
frame += framesRemaining;
|
||||
framesRemaining = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
void AnimClip::copyFromNetworkAnim() {
|
||||
assert(_networkAnim && _networkAnim->isLoaded() && _skeleton);
|
||||
_anim.clear();
|
||||
|
||||
// build a mapping from animation joint indices to skeleton joint indices.
|
||||
// by matching joints with the same name.
|
||||
const FBXGeometry& geom = _networkAnim->getGeometry();
|
||||
const QVector<FBXJoint>& animJoints = geom.joints;
|
||||
std::vector<int> jointMap;
|
||||
const int animJointCount = animJoints.count();
|
||||
jointMap.reserve(animJointCount);
|
||||
for (int i = 0; i < animJointCount; i++) {
|
||||
int skeletonJoint = _skeleton->nameToJointIndex(animJoints.at(i).name);
|
||||
if (skeletonJoint == -1) {
|
||||
qCWarning(animation) << "animation contains joint =" << animJoints.at(i).name << " which is not in the skeleton, url =" << _url.c_str();
|
||||
}
|
||||
jointMap.push_back(skeletonJoint);
|
||||
}
|
||||
|
||||
const int frameCount = geom.animationFrames.size();
|
||||
const int skeletonJointCount = _skeleton->getNumJoints();
|
||||
_anim.resize(frameCount);
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
|
||||
// init all joints in animation to bind pose
|
||||
_anim[i].reserve(skeletonJointCount);
|
||||
for (int j = 0; j < skeletonJointCount; j++) {
|
||||
_anim[i].push_back(_skeleton->getRelativeBindPose(j));
|
||||
}
|
||||
|
||||
// init over all joint animations
|
||||
for (int j = 0; j < animJointCount; j++) {
|
||||
int k = jointMap[j];
|
||||
if (k >= 0 && k < skeletonJointCount) {
|
||||
// currently FBX animations only have rotation.
|
||||
_anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
_poses.resize(skeletonJointCount);
|
||||
}
|
||||
|
||||
|
||||
const AnimPoseVec& AnimClip::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
74
libraries/animation/src/AnimClip.h
Normal file
74
libraries/animation/src/AnimClip.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// AnimClip.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimClip_h
|
||||
#define hifi_AnimClip_h
|
||||
|
||||
#include <string>
|
||||
#include "AnimationCache.h"
|
||||
#include "AnimNode.h"
|
||||
|
||||
// Playback a single animation timeline.
|
||||
// url determines the location of the fbx file to use within this clip.
|
||||
// startFrame and endFrame are in frames 1/30th of a second.
|
||||
// timescale can be used to speed-up or slow-down the animation.
|
||||
// loop flag, when true, will loop the animation as it reaches the end frame.
|
||||
|
||||
class AnimClip : public AnimNode {
|
||||
public:
|
||||
friend class AnimTests;
|
||||
|
||||
AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag);
|
||||
virtual ~AnimClip() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setStartFrameVar(const std::string& startFrameVar) { _startFrameVar = startFrameVar; }
|
||||
void setEndFrameVar(const std::string& endFrameVar) { _endFrameVar = endFrameVar; }
|
||||
void setTimeScaleVar(const std::string& timeScaleVar) { _timeScaleVar = timeScaleVar; }
|
||||
void setLoopFlagVar(const std::string& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
||||
void setFrameVar(const std::string& frameVar) { _frameVar = frameVar; }
|
||||
|
||||
protected:
|
||||
void loadURL(const std::string& url);
|
||||
|
||||
virtual void setCurrentFrameInternal(float frame) override;
|
||||
|
||||
float accumulateTime(float frame, float dt, Triggers& triggersOut) const;
|
||||
void copyFromNetworkAnim();
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
AnimationPointer _networkAnim;
|
||||
AnimPoseVec _poses;
|
||||
|
||||
// _anim[frame][joint]
|
||||
std::vector<AnimPoseVec> _anim;
|
||||
|
||||
std::string _url;
|
||||
float _startFrame;
|
||||
float _endFrame;
|
||||
float _timeScale;
|
||||
bool _loopFlag;
|
||||
float _frame;
|
||||
|
||||
std::string _startFrameVar;
|
||||
std::string _endFrameVar;
|
||||
std::string _timeScaleVar;
|
||||
std::string _loopFlagVar;
|
||||
std::string _frameVar;
|
||||
|
||||
// no copies
|
||||
AnimClip(const AnimClip&) = delete;
|
||||
AnimClip& operator=(const AnimClip&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimClip_h
|
115
libraries/animation/src/AnimNode.h
Normal file
115
libraries/animation/src/AnimNode.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// AnimNode.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimNode_h
|
||||
#define hifi_AnimNode_h
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimVariant.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
// Base class for all elements in the animation blend tree.
|
||||
// It provides the following categories of functions:
|
||||
//
|
||||
// * id getter, id is used to identify a node, useful for debugging and node searching.
|
||||
// * type getter, helpful for determining the derived type of this node.
|
||||
// * hierarchy accessors, for adding, removing and iterating over child AnimNodes
|
||||
// * skeleton accessors, the skeleton is from the model whose bones we are going to manipulate
|
||||
// * evaluate method, perform actual joint manipulations here and return result by reference.
|
||||
// Also, append any triggers that are detected during evaluation.
|
||||
|
||||
class AnimNode {
|
||||
public:
|
||||
enum class Type {
|
||||
Clip = 0,
|
||||
BlendLinear,
|
||||
Overlay,
|
||||
StateMachine,
|
||||
NumTypes
|
||||
};
|
||||
using Pointer = std::shared_ptr<AnimNode>;
|
||||
using ConstPointer = std::shared_ptr<const AnimNode>;
|
||||
using Triggers = std::vector<std::string>;
|
||||
|
||||
friend class AnimDebugDraw;
|
||||
friend void buildChildMap(std::map<std::string, Pointer>& map, Pointer node);
|
||||
friend class AnimStateMachine;
|
||||
|
||||
AnimNode(Type type, const std::string& id) : _type(type), _id(id) {}
|
||||
virtual ~AnimNode() {}
|
||||
|
||||
const std::string& getID() const { return _id; }
|
||||
Type getType() const { return _type; }
|
||||
|
||||
// hierarchy accessors
|
||||
void addChild(Pointer child) { _children.push_back(child); }
|
||||
void removeChild(Pointer child) {
|
||||
auto iter = std::find(_children.begin(), _children.end(), child);
|
||||
if (iter != _children.end()) {
|
||||
_children.erase(iter);
|
||||
}
|
||||
}
|
||||
Pointer getChild(int i) const {
|
||||
assert(i >= 0 && i < (int)_children.size());
|
||||
return _children[i];
|
||||
}
|
||||
int getChildCount() const { return (int)_children.size(); }
|
||||
|
||||
// pair this AnimNode graph with a skeleton.
|
||||
void setSkeleton(const AnimSkeleton::Pointer skeleton) {
|
||||
setSkeletonInternal(skeleton);
|
||||
for (auto&& child : _children) {
|
||||
child->setSkeleton(skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; }
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) = 0;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
return evaluate(animVars, dt, triggersOut);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void setCurrentFrame(float frame) {
|
||||
setCurrentFrameInternal(frame);
|
||||
for (auto&& child : _children) {
|
||||
child->setCurrentFrameInternal(frame);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void setCurrentFrameInternal(float frame) {}
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||
_skeleton = skeleton;
|
||||
}
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const = 0;
|
||||
|
||||
Type _type;
|
||||
std::string _id;
|
||||
std::vector<AnimNode::Pointer> _children;
|
||||
AnimSkeleton::ConstPointer _skeleton;
|
||||
|
||||
// no copies
|
||||
AnimNode(const AnimNode&) = delete;
|
||||
AnimNode& operator=(const AnimNode&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimNode_h
|
434
libraries/animation/src/AnimNodeLoader.cpp
Normal file
434
libraries/animation/src/AnimNodeLoader.cpp
Normal file
|
@ -0,0 +1,434 @@
|
|||
//
|
||||
// AnimNodeLoader.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFile>
|
||||
|
||||
#include "AnimNode.h"
|
||||
#include "AnimClip.h"
|
||||
#include "AnimBlendLinear.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "AnimOverlay.h"
|
||||
#include "AnimNodeLoader.h"
|
||||
#include "AnimStateMachine.h"
|
||||
|
||||
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
|
||||
// factory functions
|
||||
static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
|
||||
// called after children have been loaded
|
||||
static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
|
||||
static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
|
||||
static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
|
||||
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
|
||||
|
||||
static const char* animNodeTypeToString(AnimNode::Type type) {
|
||||
switch (type) {
|
||||
case AnimNode::Type::Clip: return "clip";
|
||||
case AnimNode::Type::BlendLinear: return "blendLinear";
|
||||
case AnimNode::Type::Overlay: return "overlay";
|
||||
case AnimNode::Type::StateMachine: return "stateMachine";
|
||||
case AnimNode::Type::NumTypes: return nullptr;
|
||||
};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
|
||||
switch (type) {
|
||||
case AnimNode::Type::Clip: return loadClipNode;
|
||||
case AnimNode::Type::BlendLinear: return loadBlendLinearNode;
|
||||
case AnimNode::Type::Overlay: return loadOverlayNode;
|
||||
case AnimNode::Type::StateMachine: return loadStateMachineNode;
|
||||
case AnimNode::Type::NumTypes: return nullptr;
|
||||
};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
|
||||
switch (type) {
|
||||
case AnimNode::Type::Clip: return processClipNode;
|
||||
case AnimNode::Type::BlendLinear: return processBlendLinearNode;
|
||||
case AnimNode::Type::Overlay: return processOverlayNode;
|
||||
case AnimNode::Type::StateMachine: return processStateMachineNode;
|
||||
case AnimNode::Type::NumTypes: return nullptr;
|
||||
};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#define READ_STRING(NAME, JSON_OBJ, ID, URL) \
|
||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||
if (!NAME##_VAL.isString()) { \
|
||||
qCCritical(animation) << "AnimNodeLoader, error reading string" \
|
||||
<< #NAME << ", id =" << ID \
|
||||
<< ", url =" << URL.toDisplayString(); \
|
||||
return nullptr; \
|
||||
} \
|
||||
QString NAME = NAME##_VAL.toString()
|
||||
|
||||
#define READ_OPTIONAL_STRING(NAME, JSON_OBJ) \
|
||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||
QString NAME; \
|
||||
if (NAME##_VAL.isString()) { \
|
||||
NAME = NAME##_VAL.toString(); \
|
||||
}
|
||||
|
||||
#define READ_BOOL(NAME, JSON_OBJ, ID, URL) \
|
||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||
if (!NAME##_VAL.isBool()) { \
|
||||
qCCritical(animation) << "AnimNodeLoader, error reading bool" \
|
||||
<< #NAME << ", id =" << ID \
|
||||
<< ", url =" << URL.toDisplayString(); \
|
||||
return nullptr; \
|
||||
} \
|
||||
bool NAME = NAME##_VAL.toBool()
|
||||
|
||||
#define READ_FLOAT(NAME, JSON_OBJ, ID, URL) \
|
||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||
if (!NAME##_VAL.isDouble()) { \
|
||||
qCCritical(animation) << "AnimNodeLoader, error reading double" \
|
||||
<< #NAME << "id =" << ID \
|
||||
<< ", url =" << URL.toDisplayString(); \
|
||||
return nullptr; \
|
||||
} \
|
||||
float NAME = (float)NAME##_VAL.toDouble()
|
||||
|
||||
static AnimNode::Type stringToEnum(const QString& str) {
|
||||
// O(n), move to map when number of types becomes large.
|
||||
const int NUM_TYPES = static_cast<int>(AnimNode::Type::NumTypes);
|
||||
for (int i = 0; i < NUM_TYPES; i++ ) {
|
||||
AnimNode::Type type = static_cast<AnimNode::Type>(i);
|
||||
if (str == animNodeTypeToString(type)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return AnimNode::Type::NumTypes;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) {
|
||||
auto idVal = jsonObj.value("id");
|
||||
if (!idVal.isString()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad string \"id\", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
QString id = idVal.toString();
|
||||
|
||||
auto typeVal = jsonObj.value("type");
|
||||
if (!typeVal.isString()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad object \"type\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
QString typeStr = typeVal.toString();
|
||||
AnimNode::Type type = stringToEnum(typeStr);
|
||||
if (type == AnimNode::Type::NumTypes) {
|
||||
qCCritical(animation) << "AnimNodeLoader, unknown node type" << typeStr << ", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto dataValue = jsonObj.value("data");
|
||||
if (!dataValue.isObject()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad string \"data\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
auto dataObj = dataValue.toObject();
|
||||
|
||||
assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
|
||||
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
|
||||
|
||||
auto childrenValue = jsonObj.value("children");
|
||||
if (!childrenValue.isArray()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad array \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
auto childrenArray = childrenValue.toArray();
|
||||
for (const auto& childValue : childrenArray) {
|
||||
if (!childValue.isObject()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad object in \"children\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
node->addChild(loadNode(childValue.toObject(), jsonUrl));
|
||||
}
|
||||
|
||||
if ((animNodeTypeToProcessFunc(type))(node, dataObj, id, jsonUrl)) {
|
||||
return node;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_STRING(url, jsonObj, id, jsonUrl);
|
||||
READ_FLOAT(startFrame, jsonObj, id, jsonUrl);
|
||||
READ_FLOAT(endFrame, jsonObj, id, jsonUrl);
|
||||
READ_FLOAT(timeScale, jsonObj, id, jsonUrl);
|
||||
READ_BOOL(loopFlag, jsonObj, id, jsonUrl);
|
||||
|
||||
READ_OPTIONAL_STRING(startFrameVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(endFrameVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(timeScaleVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(loopFlagVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimClip>(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag);
|
||||
|
||||
if (!startFrameVar.isEmpty()) {
|
||||
node->setStartFrameVar(startFrameVar.toStdString());
|
||||
}
|
||||
if (!endFrameVar.isEmpty()) {
|
||||
node->setEndFrameVar(endFrameVar.toStdString());
|
||||
}
|
||||
if (!timeScaleVar.isEmpty()) {
|
||||
node->setTimeScaleVar(timeScaleVar.toStdString());
|
||||
}
|
||||
if (!loopFlagVar.isEmpty()) {
|
||||
node->setLoopFlagVar(loopFlagVar.toStdString());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_FLOAT(alpha, jsonObj, id, jsonUrl);
|
||||
|
||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimBlendLinear>(id.toStdString(), alpha);
|
||||
|
||||
if (!alphaVar.isEmpty()) {
|
||||
node->setAlphaVar(alphaVar.toStdString());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static const char* boneSetStrings[AnimOverlay::NumBoneSets] = {
|
||||
"fullBody",
|
||||
"upperBody",
|
||||
"lowerBody",
|
||||
"rightArm",
|
||||
"leftArm",
|
||||
"aboveTheHead",
|
||||
"belowTheHead",
|
||||
"headOnly",
|
||||
"spineOnly",
|
||||
"empty"
|
||||
};
|
||||
|
||||
static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
|
||||
for (int i = 0; i < (int)AnimOverlay::NumBoneSets; i++) {
|
||||
if (str == boneSetStrings[i]) {
|
||||
return (AnimOverlay::BoneSet)i;
|
||||
}
|
||||
}
|
||||
return AnimOverlay::NumBoneSets;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_STRING(boneSet, jsonObj, id, jsonUrl);
|
||||
READ_FLOAT(alpha, jsonObj, id, jsonUrl);
|
||||
|
||||
auto boneSetEnum = stringToBoneSetEnum(boneSet);
|
||||
if (boneSetEnum == AnimOverlay::NumBoneSets) {
|
||||
qCCritical(animation) << "AnimNodeLoader, unknown bone set =" << boneSet << ", defaulting to \"fullBody\", url =" << jsonUrl.toDisplayString();
|
||||
boneSetEnum = AnimOverlay::FullBodyBoneSet;
|
||||
}
|
||||
|
||||
READ_OPTIONAL_STRING(boneSetVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimOverlay>(id.toStdString(), boneSetEnum, alpha);
|
||||
|
||||
if (!boneSetVar.isEmpty()) {
|
||||
node->setBoneSetVar(boneSetVar.toStdString());
|
||||
}
|
||||
if (!alphaVar.isEmpty()) {
|
||||
node->setAlphaVar(alphaVar.toStdString());
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
auto node = std::make_shared<AnimStateMachine>(id.toStdString());
|
||||
return node;
|
||||
}
|
||||
|
||||
void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) {
|
||||
for ( auto child : node->_children ) {
|
||||
map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child));
|
||||
}
|
||||
}
|
||||
|
||||
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl) {
|
||||
auto smNode = std::static_pointer_cast<AnimStateMachine>(node);
|
||||
assert(smNode);
|
||||
|
||||
READ_STRING(currentState, jsonObj, nodeId, jsonUrl);
|
||||
|
||||
auto statesValue = jsonObj.value("states");
|
||||
if (!statesValue.isArray()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// build a map for all children by name.
|
||||
std::map<std::string, AnimNode::Pointer> childMap;
|
||||
buildChildMap(childMap, node);
|
||||
|
||||
// first pass parse all the states and build up the state and transition map.
|
||||
using StringPair = std::pair<std::string, std::string>;
|
||||
using TransitionMap = std::multimap<AnimStateMachine::State::Pointer, StringPair>;
|
||||
TransitionMap transitionMap;
|
||||
|
||||
using StateMap = std::map<std::string, AnimStateMachine::State::Pointer>;
|
||||
StateMap stateMap;
|
||||
|
||||
auto statesArray = statesValue.toArray();
|
||||
for (const auto& stateValue : statesArray) {
|
||||
if (!stateValue.isObject()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
auto stateObj = stateValue.toObject();
|
||||
|
||||
READ_STRING(id, stateObj, nodeId, jsonUrl);
|
||||
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl);
|
||||
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl);
|
||||
|
||||
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
|
||||
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
|
||||
|
||||
auto stdId = id.toStdString();
|
||||
|
||||
auto iter = childMap.find(stdId);
|
||||
if (iter == childMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto statePtr = std::make_shared<AnimStateMachine::State>(stdId, iter->second, interpTarget, interpDuration);
|
||||
assert(statePtr);
|
||||
|
||||
if (!interpTargetVar.isEmpty()) {
|
||||
statePtr->setInterpTargetVar(interpTargetVar.toStdString());
|
||||
}
|
||||
if (!interpDurationVar.isEmpty()) {
|
||||
statePtr->setInterpDurationVar(interpDurationVar.toStdString());
|
||||
}
|
||||
|
||||
smNode->addState(statePtr);
|
||||
stateMap.insert(StateMap::value_type(statePtr->getID(), statePtr));
|
||||
|
||||
auto transitionsValue = stateObj.value("transitions");
|
||||
if (!transitionsValue.isArray()) {
|
||||
qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto transitionsArray = transitionsValue.toArray();
|
||||
for (const auto& transitionValue : transitionsArray) {
|
||||
if (!transitionValue.isObject()) {
|
||||
qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
auto transitionObj = transitionValue.toObject();
|
||||
|
||||
READ_STRING(var, transitionObj, nodeId, jsonUrl);
|
||||
READ_STRING(state, transitionObj, nodeId, jsonUrl);
|
||||
|
||||
transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString())));
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: now iterate thru all transitions and add them to the appropriate states.
|
||||
for (auto& transition : transitionMap) {
|
||||
AnimStateMachine::State::Pointer srcState = transition.first;
|
||||
auto iter = stateMap.find(transition.second.second);
|
||||
if (iter != stateMap.end()) {
|
||||
srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second));
|
||||
} else {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = stateMap.find(currentState.toStdString());
|
||||
if (iter == stateMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId << "url = " << jsonUrl.toDisplayString();
|
||||
}
|
||||
smNode->setCurrentState(iter->second);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AnimNodeLoader::AnimNodeLoader(const QUrl& url) :
|
||||
_url(url),
|
||||
_resource(nullptr) {
|
||||
|
||||
_resource = new Resource(url);
|
||||
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(onRequestDone(QNetworkReply&)));
|
||||
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
}
|
||||
|
||||
AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& jsonUrl) {
|
||||
|
||||
// convert string into a json doc
|
||||
QJsonParseError error;
|
||||
auto doc = QJsonDocument::fromJson(contents, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCCritical(animation) << "AnimNodeLoader, failed to parse json, error =" << error.errorString() << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
QJsonObject obj = doc.object();
|
||||
|
||||
// version
|
||||
QJsonValue versionVal = obj.value("version");
|
||||
if (!versionVal.isString()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad string \"version\", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
QString version = versionVal.toString();
|
||||
|
||||
// check version
|
||||
if (version != "1.0") {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad version number" << version << "expected \"1.0\", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// root
|
||||
QJsonValue rootVal = obj.value("root");
|
||||
if (!rootVal.isObject()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad object \"root\", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return loadNode(rootVal.toObject(), jsonUrl);
|
||||
}
|
||||
|
||||
void AnimNodeLoader::onRequestDone(QNetworkReply& request) {
|
||||
auto node = load(request.readAll(), _url);
|
||||
if (node) {
|
||||
emit success(node);
|
||||
} else {
|
||||
emit error(0, "json parse error");
|
||||
}
|
||||
}
|
||||
|
||||
void AnimNodeLoader::onRequestError(QNetworkReply::NetworkError netError) {
|
||||
emit error((int)netError, "Resource download error");
|
||||
}
|
52
libraries/animation/src/AnimNodeLoader.h
Normal file
52
libraries/animation/src/AnimNodeLoader.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// AnimNodeLoader.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimNodeLoader_h
|
||||
#define hifi_AnimNodeLoader_h
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
class Resource;
|
||||
|
||||
class AnimNodeLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AnimNodeLoader(const QUrl& url);
|
||||
|
||||
signals:
|
||||
void success(AnimNode::Pointer node);
|
||||
void error(int error, QString str);
|
||||
|
||||
protected:
|
||||
// synchronous
|
||||
static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl);
|
||||
|
||||
protected slots:
|
||||
void onRequestDone(QNetworkReply& request);
|
||||
void onRequestError(QNetworkReply::NetworkError error);
|
||||
|
||||
protected:
|
||||
QUrl _url;
|
||||
Resource* _resource;
|
||||
private:
|
||||
|
||||
// no copies
|
||||
AnimNodeLoader(const AnimNodeLoader&) = delete;
|
||||
AnimNodeLoader& operator=(const AnimNodeLoader&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimNodeLoader
|
181
libraries/animation/src/AnimOverlay.cpp
Normal file
181
libraries/animation/src/AnimOverlay.cpp
Normal file
|
@ -0,0 +1,181 @@
|
|||
//
|
||||
// AnimOverlay.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimOverlay.h"
|
||||
#include "AnimUtil.h"
|
||||
#include <queue>
|
||||
|
||||
AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet, float alpha) :
|
||||
AnimNode(AnimNode::Type::Overlay, id), _boneSet(boneSet), _alpha(alpha) {
|
||||
}
|
||||
|
||||
AnimOverlay::~AnimOverlay() {
|
||||
|
||||
}
|
||||
|
||||
void AnimOverlay::buildBoneSet(BoneSet boneSet) {
|
||||
assert(_skeleton);
|
||||
switch (boneSet) {
|
||||
case FullBodyBoneSet: buildFullBodyBoneSet(); break;
|
||||
case UpperBodyBoneSet: buildUpperBodyBoneSet(); break;
|
||||
case LowerBodyBoneSet: buildLowerBodyBoneSet(); break;
|
||||
case RightArmBoneSet: buildRightArmBoneSet(); break;
|
||||
case LeftArmBoneSet: buildLeftArmBoneSet(); break;
|
||||
case AboveTheHeadBoneSet: buildAboveTheHeadBoneSet(); break;
|
||||
case BelowTheHeadBoneSet: buildBelowTheHeadBoneSet(); break;
|
||||
case HeadOnlyBoneSet: buildHeadOnlyBoneSet(); break;
|
||||
case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break;
|
||||
default:
|
||||
case EmptyBoneSet: buildEmptyBoneSet(); break;
|
||||
}
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
// lookup parameters from animVars, using current instance variables as defaults.
|
||||
// NOTE: switching bonesets can be an expensive operation, let's try to avoid it.
|
||||
auto prevBoneSet = _boneSet;
|
||||
_boneSet = (BoneSet)animVars.lookup(_boneSetVar, (int)_boneSet);
|
||||
if (_boneSet != prevBoneSet && _skeleton) {
|
||||
buildBoneSet(_boneSet);
|
||||
}
|
||||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||
|
||||
if (_children.size() >= 2) {
|
||||
auto underPoses = _children[1]->evaluate(animVars, dt, triggersOut);
|
||||
auto overPoses = _children[0]->overlay(animVars, dt, triggersOut, underPoses);
|
||||
|
||||
if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) {
|
||||
_poses.resize(underPoses.size());
|
||||
assert(_boneSetVec.size() == _poses.size());
|
||||
|
||||
for (size_t i = 0; i < _poses.size(); i++) {
|
||||
float alpha = _boneSetVec[i] * _alpha;
|
||||
::blend(1, &underPoses[i], &overPoses[i], alpha, &_poses[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void for_each_child_joint(AnimSkeleton::ConstPointer skeleton, int startJoint, Func f) {
|
||||
std::queue<int> q;
|
||||
q.push(startJoint);
|
||||
while(q.size() > 0) {
|
||||
int jointIndex = q.front();
|
||||
for (int i = 0; i < skeleton->getNumJoints(); i++) {
|
||||
if (jointIndex == skeleton->getParentIndex(i)) {
|
||||
f(i);
|
||||
q.push(i);
|
||||
}
|
||||
}
|
||||
q.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimOverlay::buildFullBodyBoneSet() {
|
||||
assert(_skeleton);
|
||||
_boneSetVec.resize(_skeleton->getNumJoints());
|
||||
for (int i = 0; i < _skeleton->getNumJoints(); i++) {
|
||||
_boneSetVec[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimOverlay::buildUpperBodyBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int spineJoint = _skeleton->nameToJointIndex("Spine");
|
||||
for_each_child_joint(_skeleton, spineJoint, [&](int i) {
|
||||
_boneSetVec[i] = 1.0f;
|
||||
});
|
||||
}
|
||||
|
||||
void AnimOverlay::buildLowerBodyBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildFullBodyBoneSet();
|
||||
int hipsJoint = _skeleton->nameToJointIndex("Hips");
|
||||
int spineJoint = _skeleton->nameToJointIndex("Spine");
|
||||
_boneSetVec.resize(_skeleton->getNumJoints());
|
||||
for_each_child_joint(_skeleton, spineJoint, [&](int i) {
|
||||
_boneSetVec[i] = 0.0f;
|
||||
});
|
||||
_boneSetVec[hipsJoint] = 0.0f;
|
||||
}
|
||||
|
||||
void AnimOverlay::buildRightArmBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int rightShoulderJoint = _skeleton->nameToJointIndex("RightShoulder");
|
||||
for_each_child_joint(_skeleton, rightShoulderJoint, [&](int i) {
|
||||
_boneSetVec[i] = 1.0f;
|
||||
});
|
||||
}
|
||||
|
||||
void AnimOverlay::buildLeftArmBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int leftShoulderJoint = _skeleton->nameToJointIndex("LeftShoulder");
|
||||
for_each_child_joint(_skeleton, leftShoulderJoint, [&](int i) {
|
||||
_boneSetVec[i] = 1.0f;
|
||||
});
|
||||
}
|
||||
|
||||
void AnimOverlay::buildAboveTheHeadBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int headJoint = _skeleton->nameToJointIndex("Head");
|
||||
for_each_child_joint(_skeleton, headJoint, [&](int i) {
|
||||
_boneSetVec[i] = 1.0f;
|
||||
});
|
||||
}
|
||||
|
||||
void AnimOverlay::buildBelowTheHeadBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildFullBodyBoneSet();
|
||||
int headJoint = _skeleton->nameToJointIndex("Head");
|
||||
for_each_child_joint(_skeleton, headJoint, [&](int i) {
|
||||
_boneSetVec[i] = 0.0f;
|
||||
});
|
||||
}
|
||||
|
||||
void AnimOverlay::buildHeadOnlyBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int headJoint = _skeleton->nameToJointIndex("Head");
|
||||
_boneSetVec[headJoint] = 1.0f;
|
||||
}
|
||||
|
||||
void AnimOverlay::buildSpineOnlyBoneSet() {
|
||||
assert(_skeleton);
|
||||
buildEmptyBoneSet();
|
||||
int spineJoint = _skeleton->nameToJointIndex("Spine");
|
||||
_boneSetVec[spineJoint] = 1.0f;
|
||||
}
|
||||
|
||||
void AnimOverlay::buildEmptyBoneSet() {
|
||||
assert(_skeleton);
|
||||
_boneSetVec.resize(_skeleton->getNumJoints());
|
||||
for (int i = 0; i < _skeleton->getNumJoints(); i++) {
|
||||
_boneSetVec[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
const AnimPoseVec& AnimOverlay::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
||||
|
||||
void AnimOverlay::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||
_skeleton = skeleton;
|
||||
|
||||
// we have to re-build the bone set when the skeleton changes.
|
||||
buildBoneSet(_boneSet);
|
||||
}
|
80
libraries/animation/src/AnimOverlay.h
Normal file
80
libraries/animation/src/AnimOverlay.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// AnimOverlay.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimOverlay_h
|
||||
#define hifi_AnimOverlay_h
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
// Overlay the AnimPoses from one AnimNode on top of another AnimNode.
|
||||
// child[0] is overlayed on top of child[1]. The boneset is used
|
||||
// to control blending on a per-bone bases.
|
||||
// alpha gives the ability to fade in and fade out overlays.
|
||||
// alpha of 0, will have no overlay, final pose will be 100% from child[1].
|
||||
// alpha of 1, will be a full overlay.
|
||||
|
||||
class AnimOverlay : public AnimNode {
|
||||
public:
|
||||
friend class AnimTests;
|
||||
|
||||
enum BoneSet {
|
||||
FullBodyBoneSet = 0,
|
||||
UpperBodyBoneSet,
|
||||
LowerBodyBoneSet,
|
||||
RightArmBoneSet,
|
||||
LeftArmBoneSet,
|
||||
AboveTheHeadBoneSet,
|
||||
BelowTheHeadBoneSet,
|
||||
HeadOnlyBoneSet,
|
||||
SpineOnlyBoneSet,
|
||||
EmptyBoneSet,
|
||||
NumBoneSets,
|
||||
};
|
||||
|
||||
AnimOverlay(const std::string& id, BoneSet boneSet, float alpha);
|
||||
virtual ~AnimOverlay() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setBoneSetVar(const std::string& boneSetVar) { _boneSetVar = boneSetVar; }
|
||||
void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
protected:
|
||||
void buildBoneSet(BoneSet boneSet);
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
||||
|
||||
AnimPoseVec _poses;
|
||||
BoneSet _boneSet;
|
||||
float _alpha;
|
||||
std::vector<float> _boneSetVec;
|
||||
|
||||
std::string _boneSetVar;
|
||||
std::string _alphaVar;
|
||||
|
||||
void buildFullBodyBoneSet();
|
||||
void buildUpperBodyBoneSet();
|
||||
void buildLowerBodyBoneSet();
|
||||
void buildRightArmBoneSet();
|
||||
void buildLeftArmBoneSet();
|
||||
void buildAboveTheHeadBoneSet();
|
||||
void buildBelowTheHeadBoneSet();
|
||||
void buildHeadOnlyBoneSet();
|
||||
void buildSpineOnlyBoneSet();
|
||||
void buildEmptyBoneSet();
|
||||
|
||||
// no copies
|
||||
AnimOverlay(const AnimOverlay&) = delete;
|
||||
AnimOverlay& operator=(const AnimOverlay&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimOverlay_h
|
176
libraries/animation/src/AnimSkeleton.cpp
Normal file
176
libraries/animation/src/AnimSkeleton.cpp
Normal file
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// AnimSkeleton.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "GLMHelpers.h"
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
||||
glm::quat(),
|
||||
glm::vec3(0.0f));
|
||||
|
||||
AnimPose::AnimPose(const glm::mat4& mat) {
|
||||
scale = extractScale(mat);
|
||||
rot = extractRotation(mat);
|
||||
trans = extractTranslation(mat);
|
||||
}
|
||||
|
||||
glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const {
|
||||
return trans + (rot * (scale * rhs));
|
||||
}
|
||||
|
||||
glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const {
|
||||
return *this * rhs;
|
||||
}
|
||||
|
||||
// really slow
|
||||
glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
|
||||
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
||||
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
||||
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
||||
glm::mat3 mat(xAxis, yAxis, zAxis);
|
||||
glm::mat3 transInvMat = glm::inverse(glm::transpose(mat));
|
||||
return transInvMat * rhs;
|
||||
}
|
||||
|
||||
AnimPose AnimPose::operator*(const AnimPose& rhs) const {
|
||||
return AnimPose(static_cast<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
|
||||
}
|
||||
|
||||
AnimPose AnimPose::inverse() const {
|
||||
return AnimPose(glm::inverse(static_cast<glm::mat4>(*this)));
|
||||
}
|
||||
|
||||
AnimPose::operator glm::mat4() const {
|
||||
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
||||
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
|
||||
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
|
||||
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
|
||||
glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
|
||||
}
|
||||
|
||||
AnimSkeleton::AnimSkeleton(const std::vector<FBXJoint>& joints, const AnimPose& geometryOffset) {
|
||||
_joints = joints;
|
||||
|
||||
// build a cache of bind poses
|
||||
_absoluteBindPoses.reserve(joints.size());
|
||||
_relativeBindPoses.reserve(joints.size());
|
||||
|
||||
// iterate over FBXJoints and extract the bind pose information.
|
||||
for (size_t i = 0; i < joints.size(); i++) {
|
||||
if (_joints[i].bindTransformFoundInCluster) {
|
||||
// Use the FBXJoint::bindTransform, which is absolute model coordinates
|
||||
// i.e. not relative to it's parent.
|
||||
AnimPose absoluteBindPose(_joints[i].bindTransform);
|
||||
_absoluteBindPoses.push_back(absoluteBindPose);
|
||||
int parentIndex = getParentIndex(i);
|
||||
if (parentIndex >= 0) {
|
||||
AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse();
|
||||
_relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose);
|
||||
} else {
|
||||
_relativeBindPoses.push_back(absoluteBindPose);
|
||||
}
|
||||
} else {
|
||||
// use FBXJoint's local transform, instead
|
||||
glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation);
|
||||
glm::mat4 relBindMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform;
|
||||
AnimPose relBindPose(relBindMat);
|
||||
_relativeBindPoses.push_back(relBindPose);
|
||||
|
||||
int parentIndex = getParentIndex(i);
|
||||
if (parentIndex >= 0) {
|
||||
_absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relBindPose);
|
||||
} else {
|
||||
_absoluteBindPoses.push_back(relBindPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now we want to normalize scale from geometryOffset to all poses.
|
||||
// This will ensure our bone translations will be in meters, even if the model was authored with some other unit of mesure.
|
||||
for (auto& absPose : _absoluteBindPoses) {
|
||||
absPose.trans = (geometryOffset * absPose).trans;
|
||||
absPose.scale = vec3(1, 1, 1);
|
||||
}
|
||||
|
||||
// re-compute relative poses based on the modified absolute poses.
|
||||
for (size_t i = 0; i < _relativeBindPoses.size(); i++) {
|
||||
int parentIndex = getParentIndex(i);
|
||||
if (parentIndex >= 0) {
|
||||
_relativeBindPoses[i] = _absoluteBindPoses[parentIndex].inverse() * _absoluteBindPoses[i];
|
||||
} else {
|
||||
_relativeBindPoses[i] = _absoluteBindPoses[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AnimSkeleton::nameToJointIndex(const QString& jointName) const {
|
||||
for (size_t i = 0; i < _joints.size(); i++) {
|
||||
if (_joints[i].name == jointName) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int AnimSkeleton::getNumJoints() const {
|
||||
return _joints.size();
|
||||
}
|
||||
|
||||
AnimPose AnimSkeleton::getAbsoluteBindPose(int jointIndex) const {
|
||||
return _absoluteBindPoses[jointIndex];
|
||||
}
|
||||
|
||||
AnimPose AnimSkeleton::getRelativeBindPose(int jointIndex) const {
|
||||
return _relativeBindPoses[jointIndex];
|
||||
}
|
||||
|
||||
int AnimSkeleton::getParentIndex(int jointIndex) const {
|
||||
return _joints[jointIndex].parentIndex;
|
||||
}
|
||||
|
||||
const QString& AnimSkeleton::getJointName(int jointIndex) const {
|
||||
return _joints[jointIndex].name;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void AnimSkeleton::dump() const {
|
||||
qCDebug(animation) << "[";
|
||||
for (int i = 0; i < getNumJoints(); i++) {
|
||||
qCDebug(animation) << " {";
|
||||
qCDebug(animation) << " name =" << getJointName(i);
|
||||
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
|
||||
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
|
||||
if (getParentIndex(i) >= 0) {
|
||||
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));
|
||||
}
|
||||
qCDebug(animation) << " },";
|
||||
}
|
||||
qCDebug(animation) << "]";
|
||||
}
|
||||
|
||||
void AnimSkeleton::dump(const AnimPoseVec& poses) const {
|
||||
qCDebug(animation) << "[";
|
||||
for (int i = 0; i < getNumJoints(); i++) {
|
||||
qCDebug(animation) << " {";
|
||||
qCDebug(animation) << " name =" << getJointName(i);
|
||||
qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
|
||||
qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
|
||||
qCDebug(animation) << " pose =" << poses[i];
|
||||
if (getParentIndex(i) >= 0) {
|
||||
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));
|
||||
}
|
||||
qCDebug(animation) << " },";
|
||||
}
|
||||
qCDebug(animation) << "]";
|
||||
}
|
||||
#endif
|
78
libraries/animation/src/AnimSkeleton.h
Normal file
78
libraries/animation/src/AnimSkeleton.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// AnimSkeleton.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimSkeleton
|
||||
#define hifi_AnimSkeleton
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "FBXReader.h"
|
||||
|
||||
struct AnimPose {
|
||||
AnimPose() {}
|
||||
explicit AnimPose(const glm::mat4& mat);
|
||||
AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {}
|
||||
static const AnimPose identity;
|
||||
|
||||
glm::vec3 xformPoint(const glm::vec3& rhs) const;
|
||||
glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow
|
||||
|
||||
glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint
|
||||
AnimPose operator*(const AnimPose& rhs) const;
|
||||
|
||||
AnimPose inverse() const;
|
||||
operator glm::mat4() const;
|
||||
|
||||
glm::vec3 scale;
|
||||
glm::quat rot;
|
||||
glm::vec3 trans;
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AnimPose& pose) {
|
||||
debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")";
|
||||
return debug;
|
||||
}
|
||||
|
||||
using AnimPoseVec = std::vector<AnimPose>;
|
||||
|
||||
class AnimSkeleton {
|
||||
public:
|
||||
using Pointer = std::shared_ptr<AnimSkeleton>;
|
||||
using ConstPointer = std::shared_ptr<const AnimSkeleton>;
|
||||
|
||||
AnimSkeleton(const std::vector<FBXJoint>& joints, const AnimPose& geometryOffset);
|
||||
int nameToJointIndex(const QString& jointName) const;
|
||||
const QString& getJointName(int jointIndex) const;
|
||||
int getNumJoints() const;
|
||||
|
||||
// absolute pose, not relative to parent
|
||||
AnimPose getAbsoluteBindPose(int jointIndex) const;
|
||||
|
||||
// relative to parent pose
|
||||
AnimPose getRelativeBindPose(int jointIndex) const;
|
||||
|
||||
int getParentIndex(int jointIndex) const;
|
||||
|
||||
#ifndef NDEBUG
|
||||
void dump() const;
|
||||
void dump(const AnimPoseVec& poses) const;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::vector<FBXJoint> _joints;
|
||||
AnimPoseVec _absoluteBindPoses;
|
||||
AnimPoseVec _relativeBindPoses;
|
||||
|
||||
// no copies
|
||||
AnimSkeleton(const AnimSkeleton&) = delete;
|
||||
AnimSkeleton& operator=(const AnimSkeleton&) = delete;
|
||||
};
|
||||
|
||||
#endif
|
114
libraries/animation/src/AnimStateMachine.cpp
Normal file
114
libraries/animation/src/AnimStateMachine.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
//
|
||||
// AnimStateMachine.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimStateMachine.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimStateMachine::AnimStateMachine(const std::string& id) :
|
||||
AnimNode(AnimNode::Type::StateMachine, id) {
|
||||
|
||||
}
|
||||
|
||||
AnimStateMachine::~AnimStateMachine() {
|
||||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
|
||||
if (_currentState->getID() != desiredStateID) {
|
||||
// switch states
|
||||
bool foundState = false;
|
||||
for (auto& state : _states) {
|
||||
if (state->getID() == desiredStateID) {
|
||||
switchState(animVars, state);
|
||||
foundState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundState) {
|
||||
qCCritical(animation) << "AnimStateMachine could not find state =" << desiredStateID.c_str() << ", referenced by _currentStateVar =" << _currentStateVar.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate currentState transitions
|
||||
auto desiredState = evaluateTransitions(animVars);
|
||||
if (desiredState != _currentState) {
|
||||
switchState(animVars, desiredState);
|
||||
}
|
||||
|
||||
assert(_currentState);
|
||||
auto currentStateNode = _currentState->getNode();
|
||||
assert(currentStateNode);
|
||||
|
||||
if (_duringInterp) {
|
||||
_alpha += _alphaVel * dt;
|
||||
if (_alpha < 1.0f) {
|
||||
if (_poses.size() > 0) {
|
||||
::blend(_poses.size(), &_prevPoses[0], &_nextPoses[0], _alpha, &_poses[0]);
|
||||
}
|
||||
} else {
|
||||
_duringInterp = false;
|
||||
_prevPoses.clear();
|
||||
_nextPoses.clear();
|
||||
}
|
||||
}
|
||||
if (!_duringInterp) {
|
||||
_poses = currentStateNode->evaluate(animVars, dt, triggersOut);
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
||||
void AnimStateMachine::setCurrentState(State::Pointer state) {
|
||||
_currentState = state;
|
||||
}
|
||||
|
||||
void AnimStateMachine::addState(State::Pointer state) {
|
||||
_states.push_back(state);
|
||||
}
|
||||
|
||||
void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointer desiredState) {
|
||||
|
||||
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID().c_str() << "->" << desiredState->getID().c_str();
|
||||
|
||||
const float FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
auto prevStateNode = _currentState->getNode();
|
||||
auto nextStateNode = desiredState->getNode();
|
||||
|
||||
_duringInterp = true;
|
||||
_alpha = 0.0f;
|
||||
float duration = std::max(0.001f, animVars.lookup(desiredState->_interpDurationVar, desiredState->_interpDuration));
|
||||
_alphaVel = FRAMES_PER_SECOND / duration;
|
||||
_prevPoses = _poses;
|
||||
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
|
||||
|
||||
// because dt is 0, we should not encounter any triggers
|
||||
const float dt = 0.0f;
|
||||
Triggers triggers;
|
||||
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
|
||||
|
||||
_currentState = desiredState;
|
||||
}
|
||||
|
||||
AnimStateMachine::State::Pointer AnimStateMachine::evaluateTransitions(const AnimVariantMap& animVars) const {
|
||||
assert(_currentState);
|
||||
for (auto& transition : _currentState->_transitions) {
|
||||
if (animVars.lookup(transition._var, false)) {
|
||||
return transition._state;
|
||||
}
|
||||
}
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimStateMachine::getPosesInternal() const {
|
||||
return _poses;
|
||||
}
|
134
libraries/animation/src/AnimStateMachine.h
Normal file
134
libraries/animation/src/AnimStateMachine.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// AnimStateMachine.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimStateMachine_h
|
||||
#define hifi_AnimStateMachine_h
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "AnimNode.h"
|
||||
|
||||
// State Machine for transitioning between children AnimNodes
|
||||
//
|
||||
// This is mechinisim for playing animations and smoothly interpolating/fading
|
||||
// between them. A StateMachine has a set of States, which typically reference
|
||||
// child AnimNodes. Each State has a list of Transitions, which are evaluated
|
||||
// to determine when we should switch to a new State. Parameters for the smooth
|
||||
// interpolation/fading are read from the State that you are transitioning to.
|
||||
//
|
||||
// The currentState can be set directly via the setCurrentStateVar() and will override
|
||||
// any State transitions.
|
||||
//
|
||||
// Each State has two parameters that can be changed via AnimVars,
|
||||
// * interpTarget - (frames) The destination frame of the interpolation. i.e. the first frame of the animation that will
|
||||
// visible after interpolation is complete.
|
||||
// * interpDuration - (frames) The total length of time it will take to interp between the current pose and the
|
||||
// interpTarget frame.
|
||||
|
||||
class AnimStateMachine : public AnimNode {
|
||||
public:
|
||||
friend class AnimNodeLoader;
|
||||
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
|
||||
|
||||
protected:
|
||||
class State {
|
||||
public:
|
||||
friend AnimStateMachine;
|
||||
friend bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& nodeId, const QUrl& jsonUrl);
|
||||
|
||||
using Pointer = std::shared_ptr<State>;
|
||||
using ConstPointer = std::shared_ptr<const State>;
|
||||
|
||||
class Transition {
|
||||
public:
|
||||
friend AnimStateMachine;
|
||||
Transition(const std::string& var, State::Pointer state) : _var(var), _state(state) {}
|
||||
protected:
|
||||
std::string _var;
|
||||
State::Pointer _state;
|
||||
};
|
||||
|
||||
State(const std::string& id, AnimNode::Pointer node, float interpTarget, float interpDuration) :
|
||||
_id(id),
|
||||
_node(node),
|
||||
_interpTarget(interpTarget),
|
||||
_interpDuration(interpDuration) {}
|
||||
|
||||
void setInterpTargetVar(const std::string& interpTargetVar) { _interpTargetVar = interpTargetVar; }
|
||||
void setInterpDurationVar(const std::string& interpDurationVar) { _interpDurationVar = interpDurationVar; }
|
||||
|
||||
AnimNode::Pointer getNode() const { return _node; }
|
||||
const std::string& getID() const { return _id; }
|
||||
|
||||
protected:
|
||||
|
||||
void setInterpTarget(float interpTarget) { _interpTarget = interpTarget; }
|
||||
void setInterpDuration(float interpDuration) { _interpDuration = interpDuration; }
|
||||
|
||||
void addTransition(const Transition& transition) { _transitions.push_back(transition); }
|
||||
|
||||
std::string _id;
|
||||
AnimNode::Pointer _node;
|
||||
float _interpTarget; // frames
|
||||
float _interpDuration; // frames
|
||||
|
||||
std::string _interpTargetVar;
|
||||
std::string _interpDurationVar;
|
||||
|
||||
std::vector<Transition> _transitions;
|
||||
|
||||
private:
|
||||
// no copies
|
||||
State(const State&) = delete;
|
||||
State& operator=(const State&) = delete;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
AnimStateMachine(const std::string& id);
|
||||
virtual ~AnimStateMachine() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setCurrentStateVar(std::string& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
|
||||
protected:
|
||||
|
||||
void setCurrentState(State::Pointer state);
|
||||
|
||||
void addState(State::Pointer state);
|
||||
|
||||
void switchState(const AnimVariantMap& animVars, State::Pointer desiredState);
|
||||
State::Pointer evaluateTransitions(const AnimVariantMap& animVars) const;
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
AnimPoseVec _poses;
|
||||
|
||||
// interpolation state
|
||||
bool _duringInterp = false;
|
||||
float _alphaVel = 0.0f;
|
||||
float _alpha = 0.0f;
|
||||
AnimPoseVec _prevPoses;
|
||||
AnimPoseVec _nextPoses;
|
||||
|
||||
State::Pointer _currentState;
|
||||
std::vector<State::Pointer> _states;
|
||||
|
||||
std::string _currentStateVar;
|
||||
|
||||
private:
|
||||
// no copies
|
||||
AnimStateMachine(const AnimStateMachine&) = delete;
|
||||
AnimStateMachine& operator=(const AnimStateMachine&) = delete;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimStateMachine_h
|
22
libraries/animation/src/AnimUtil.cpp
Normal file
22
libraries/animation/src/AnimUtil.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// AnimUtil.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimUtil.h"
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
|
||||
for (size_t i = 0; i < numPoses; i++) {
|
||||
const AnimPose& aPose = a[i];
|
||||
const AnimPose& bPose = b[i];
|
||||
result[i].scale = lerp(aPose.scale, bPose.scale, alpha);
|
||||
result[i].rot = glm::normalize(glm::lerp(aPose.rot, bPose.rot, alpha));
|
||||
result[i].trans = lerp(aPose.trans, bPose.trans, alpha);
|
||||
}
|
||||
}
|
24
libraries/animation/src/AnimUtil.h
Normal file
24
libraries/animation/src/AnimUtil.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// AnimUtil.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimUtil_h
|
||||
#define hifi_AnimUtil_h
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
// TODO: use restrict keyword
|
||||
// TODO: excellent candidate for simd vectorization.
|
||||
|
||||
// this is where the magic happens
|
||||
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result);
|
||||
|
||||
#endif
|
||||
|
||||
|
161
libraries/animation/src/AnimVariant.h
Normal file
161
libraries/animation/src/AnimVariant.h
Normal file
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// AnimVariant.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/2/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimVariant_h
|
||||
#define hifi_AnimVariant_h
|
||||
|
||||
#include <cassert>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
class AnimVariant {
|
||||
public:
|
||||
enum class Type {
|
||||
Bool = 0,
|
||||
Int,
|
||||
Float,
|
||||
Vec3,
|
||||
Quat,
|
||||
Mat4,
|
||||
String,
|
||||
NumTypes
|
||||
};
|
||||
|
||||
AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); }
|
||||
AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; }
|
||||
AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; }
|
||||
AnimVariant(float value) : _type(Type::Float) { _val.floats[0] = value; }
|
||||
AnimVariant(const glm::vec3& value) : _type(Type::Vec3) { *reinterpret_cast<glm::vec3*>(&_val) = value; }
|
||||
AnimVariant(const glm::quat& value) : _type(Type::Quat) { *reinterpret_cast<glm::quat*>(&_val) = value; }
|
||||
AnimVariant(const glm::mat4& value) : _type(Type::Mat4) { *reinterpret_cast<glm::mat4*>(&_val) = value; }
|
||||
AnimVariant(const std::string& value) : _type(Type::String) { _stringVal = value; }
|
||||
|
||||
bool isBool() const { return _type == Type::Bool; }
|
||||
bool isInt() const { return _type == Type::Int; }
|
||||
bool isFloat() const { return _type == Type::Float; }
|
||||
bool isVec3() const { return _type == Type::Vec3; }
|
||||
bool isQuat() const { return _type == Type::Quat; }
|
||||
bool isMat4() const { return _type == Type::Mat4; }
|
||||
bool isString() const { return _type == Type::String; }
|
||||
|
||||
void setBool(bool value) { assert(_type == Type::Bool); _val.boolVal = value; }
|
||||
void setInt(int value) { assert(_type == Type::Int); _val.intVal = value; }
|
||||
void setFloat(float value) { assert(_type == Type::Float); _val.floats[0] = value; }
|
||||
void setVec3(const glm::vec3& value) { assert(_type == Type::Vec3); *reinterpret_cast<glm::vec3*>(&_val) = value; }
|
||||
void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast<glm::quat*>(&_val) = value; }
|
||||
void setMat4(const glm::mat4& value) { assert(_type == Type::Mat4); *reinterpret_cast<glm::mat4*>(&_val) = value; }
|
||||
void setString(const std::string& value) { assert(_type == Type::String); _stringVal = value; }
|
||||
|
||||
bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; }
|
||||
int getInt() const { assert(_type == Type::Int); return _val.intVal; }
|
||||
float getFloat() const { assert(_type == Type::Float); return _val.floats[0]; }
|
||||
const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast<const glm::vec3*>(&_val); }
|
||||
const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast<const glm::quat*>(&_val); }
|
||||
const glm::mat4& getMat4() const { assert(_type == Type::Mat4); return *reinterpret_cast<const glm::mat4*>(&_val); }
|
||||
const std::string& getString() const { assert(_type == Type::String); return _stringVal; }
|
||||
|
||||
protected:
|
||||
Type _type;
|
||||
std::string _stringVal;
|
||||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
float floats[16];
|
||||
} _val;
|
||||
};
|
||||
|
||||
class AnimVariantMap {
|
||||
public:
|
||||
|
||||
bool lookup(const std::string& key, bool defaultValue) const {
|
||||
// check triggers first, then map
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else if (_triggers.find(key) != _triggers.end()) {
|
||||
return true;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getBool() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
int lookup(const std::string& key, int defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getInt() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
float lookup(const std::string& key, float defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getFloat() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3& lookup(const std::string& key, const glm::vec3& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getVec3() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::quat& lookup(const std::string& key, const glm::quat& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getQuat() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::mat4& lookup(const std::string& key, const glm::mat4& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getMat4() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& lookup(const std::string& key, const std::string& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
return iter != _map.end() ? iter->second.getString() : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
void set(const std::string& key, bool value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, int value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, float value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, const glm::vec3& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, const glm::quat& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, const glm::mat4& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const std::string& key, const std::string& value) { _map[key] = AnimVariant(value); }
|
||||
|
||||
void setTrigger(const std::string& key) { _triggers.insert(key); }
|
||||
void clearTriggers() { _triggers.clear(); }
|
||||
|
||||
protected:
|
||||
std::map<std::string, AnimVariant> _map;
|
||||
std::set<std::string> _triggers;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimVariant_h
|
|
@ -10,7 +10,7 @@
|
|||
//
|
||||
|
||||
#include "AnimationHandle.h"
|
||||
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
void AnimationHandle::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
|
@ -51,8 +51,8 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
|
|||
}
|
||||
|
||||
void AnimationHandle::setRunning(bool running, bool doRestoreJoints) {
|
||||
if (running && isRunning()) {
|
||||
// if we're already running, this is the same as a restart
|
||||
if (running && isRunning() && (getFadePerSecond() >= 0.0f)) {
|
||||
// if we're already running, this is the same as a restart -- unless we're fading out.
|
||||
setFrameIndex(getFirstFrame());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,30 +9,34 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Rig.h"
|
||||
|
||||
#include <glm/gtx/vector_angle.hpp>
|
||||
#include <queue>
|
||||
|
||||
#include "AnimationHandle.h"
|
||||
#include "AnimationLogging.h"
|
||||
#include "AnimSkeleton.h"
|
||||
|
||||
#include "Rig.h"
|
||||
|
||||
void Rig::HeadParameters::dump() const {
|
||||
qCDebug(animation, "HeadParameters =");
|
||||
qCDebug(animation, " leanSideways = %0.5f", leanSideways);
|
||||
qCDebug(animation, " leanForward = %0.5f", leanForward);
|
||||
qCDebug(animation, " torsoTwist = %0.5f", torsoTwist);
|
||||
qCDebug(animation, " leanSideways = %0.5f", (double)leanSideways);
|
||||
qCDebug(animation, " leanForward = %0.5f", (double)leanForward);
|
||||
qCDebug(animation, " torsoTwist = %0.5f", (double)torsoTwist);
|
||||
glm::vec3 axis = glm::axis(localHeadOrientation);
|
||||
float theta = glm::angle(localHeadOrientation);
|
||||
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
axis = glm::axis(worldHeadOrientation);
|
||||
theta = glm::angle(worldHeadOrientation);
|
||||
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
axis = glm::axis(modelRotation);
|
||||
theta = glm::angle(modelRotation);
|
||||
qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta);
|
||||
qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", modelTranslation.x, modelTranslation.y, modelTranslation.z);
|
||||
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", eyeLookAt.x, eyeLookAt.y, eyeLookAt.z);
|
||||
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", eyeSaccade.x, eyeSaccade.y, eyeSaccade.z);
|
||||
qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
|
||||
qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", (double)modelTranslation.x, (double)modelTranslation.y, (double)modelTranslation.z);
|
||||
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", (double)eyeLookAt.x, (double)eyeLookAt.y, (double)eyeLookAt.z);
|
||||
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", (double)eyeSaccade.x, (double)eyeSaccade.y, (double)eyeSaccade.z);
|
||||
qCDebug(animation, " leanJointIndex = %.d", leanJointIndex);
|
||||
qCDebug(animation, " neckJointIndex = %.d", neckJointIndex);
|
||||
qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex);
|
||||
|
@ -103,7 +107,7 @@ AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QStrin
|
|||
const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/";
|
||||
if (role == "walk") {
|
||||
standard = base + "walk_fwd.fbx";
|
||||
} else if (role == "backup") {
|
||||
} else if (role == "backup") {
|
||||
standard = base + "walk_bwd.fbx";
|
||||
} else if (role == "leftTurn") {
|
||||
standard = base + "turn_left.fbx";
|
||||
|
@ -185,6 +189,12 @@ void Rig::deleteAnimations() {
|
|||
_animationHandles.clear();
|
||||
}
|
||||
|
||||
void Rig::destroyAnimGraph() {
|
||||
_animSkeleton = nullptr;
|
||||
_animLoader = nullptr;
|
||||
_animNode = nullptr;
|
||||
}
|
||||
|
||||
void Rig::initJointStates(QVector<JointState> states, glm::mat4 rootTransform,
|
||||
int rootJointIndex,
|
||||
int leftHandJointIndex,
|
||||
|
@ -406,86 +416,223 @@ glm::mat4 Rig::getJointVisibleTransform(int jointIndex) const {
|
|||
}
|
||||
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
|
||||
if (!_enableRig) {
|
||||
return;
|
||||
}
|
||||
bool isMoving = false;
|
||||
|
||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||
float forwardSpeed = glm::dot(worldVelocity, front);
|
||||
float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT);
|
||||
float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime;
|
||||
auto updateRole = [&](const QString& role, bool isOn) {
|
||||
isMoving = isMoving || isOn;
|
||||
if (isOn) {
|
||||
if (!isRunningRole(role)) {
|
||||
qCDebug(animation) << "Rig STARTING" << role;
|
||||
startAnimationByRole(role);
|
||||
}
|
||||
|
||||
// It can be more accurate/smooth to use velocity rather than position,
|
||||
// but some modes (e.g., hmd standing) update position without updating velocity.
|
||||
// It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...)
|
||||
// So, let's create our own workingVelocity from the worldPosition...
|
||||
glm::vec3 positionDelta = worldPosition - _lastPosition;
|
||||
glm::vec3 workingVelocity = positionDelta / deltaTime;
|
||||
|
||||
#if !WANT_DEBUG
|
||||
// But for smoothest (non-hmd standing) results, go ahead and use velocity:
|
||||
if (!positionDelta.x && !positionDelta.y && !positionDelta.z) {
|
||||
workingVelocity = worldVelocity;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_enableAnimGraph) {
|
||||
|
||||
glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity;
|
||||
float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT);
|
||||
float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT);
|
||||
float turningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime;
|
||||
|
||||
// sine wave LFO var for testing.
|
||||
static float t = 0.0f;
|
||||
_animVars.set("sine", static_cast<float>(0.5 * sin(t) + 0.5));
|
||||
|
||||
// default anim vars to notMoving and notTurning
|
||||
_animVars.set("isMovingForward", false);
|
||||
_animVars.set("isMovingBackward", false);
|
||||
_animVars.set("isMovingLeft", false);
|
||||
_animVars.set("isMovingRight", false);
|
||||
_animVars.set("isNotMoving", true);
|
||||
_animVars.set("isTurningLeft", false);
|
||||
_animVars.set("isTurningRight", false);
|
||||
_animVars.set("isNotTurning", true);
|
||||
|
||||
const float ANIM_WALK_SPEED = 1.4f; // m/s
|
||||
_animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED));
|
||||
|
||||
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
|
||||
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec
|
||||
const float TURN_ENTER_SPEED_THRESHOLD = 0.5f; // rad/sec
|
||||
const float TURN_EXIT_SPEED_THRESHOLD = 0.2f; // rad/sec
|
||||
|
||||
float moveThresh;
|
||||
if (_state != RigRole::Move) {
|
||||
moveThresh = MOVE_ENTER_SPEED_THRESHOLD;
|
||||
} else {
|
||||
if (isRunningRole(role)) {
|
||||
qCDebug(animation) << "Rig stopping" << role;
|
||||
stopAnimationByRole(role);
|
||||
moveThresh = MOVE_EXIT_SPEED_THRESHOLD;
|
||||
}
|
||||
|
||||
float turnThresh;
|
||||
if (_state != RigRole::Turn) {
|
||||
turnThresh = TURN_ENTER_SPEED_THRESHOLD;
|
||||
} else {
|
||||
turnThresh = TURN_EXIT_SPEED_THRESHOLD;
|
||||
}
|
||||
|
||||
if (glm::length(localVel) > moveThresh) {
|
||||
if (fabs(forwardSpeed) > 0.5f * fabs(lateralSpeed)) {
|
||||
if (forwardSpeed > 0.0f) {
|
||||
// forward
|
||||
_animVars.set("isMovingForward", true);
|
||||
_animVars.set("isNotMoving", false);
|
||||
|
||||
} else {
|
||||
// backward
|
||||
_animVars.set("isMovingBackward", true);
|
||||
_animVars.set("isNotMoving", false);
|
||||
}
|
||||
} else {
|
||||
if (lateralSpeed > 0.0f) {
|
||||
// right
|
||||
_animVars.set("isMovingRight", true);
|
||||
_animVars.set("isNotMoving", false);
|
||||
} else {
|
||||
// left
|
||||
_animVars.set("isMovingLeft", true);
|
||||
_animVars.set("isNotMoving", false);
|
||||
}
|
||||
}
|
||||
_state = RigRole::Move;
|
||||
} else {
|
||||
if (fabs(turningSpeed) > turnThresh) {
|
||||
if (turningSpeed > 0.0f) {
|
||||
// turning right
|
||||
_animVars.set("isTurningRight", true);
|
||||
_animVars.set("isNotTurning", false);
|
||||
} else {
|
||||
// turning left
|
||||
_animVars.set("isTurningLeft", true);
|
||||
_animVars.set("isNotTurning", false);
|
||||
}
|
||||
_state = RigRole::Turn;
|
||||
} else {
|
||||
// idle
|
||||
_state = RigRole::Idle;
|
||||
}
|
||||
}
|
||||
};
|
||||
updateRole("walk", forwardSpeed > 0.01f);
|
||||
updateRole("backup", forwardSpeed < -0.01f);
|
||||
bool isTurning = std::abs(rightTurningSpeed) > 0.5f;
|
||||
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0));
|
||||
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0));
|
||||
bool isStrafing = !isTurning && (std::abs(rightLateralSpeed) > 0.01f);
|
||||
updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f));
|
||||
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
|
||||
updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus.
|
||||
|
||||
t += deltaTime;
|
||||
}
|
||||
|
||||
if (_enableRig) {
|
||||
bool isMoving = false;
|
||||
|
||||
glm::vec3 right = worldRotation * IDENTITY_RIGHT;
|
||||
const float PERCEPTIBLE_DELTA = 0.001f;
|
||||
const float PERCEPTIBLE_SPEED = 0.1f;
|
||||
|
||||
// Note: Separately, we've arranged for starting/stopping animations by role (as we've done here) to pick up where they've left off when fading,
|
||||
// so that you wouldn't notice the start/stop if it happens fast enough (e.g., one frame). But the print below would still be noisy.
|
||||
|
||||
float forwardSpeed = glm::dot(workingVelocity, front);
|
||||
float rightLateralSpeed = glm::dot(workingVelocity, right);
|
||||
float rightTurningDelta = glm::orientedAngle(front, _lastFront, IDENTITY_UP);
|
||||
float rightTurningSpeed = rightTurningDelta / deltaTime;
|
||||
bool isTurning = (std::abs(rightTurningDelta) > PERCEPTIBLE_DELTA) && (std::abs(rightTurningSpeed) > PERCEPTIBLE_SPEED);
|
||||
bool isStrafing = std::abs(rightLateralSpeed) > PERCEPTIBLE_SPEED;
|
||||
auto updateRole = [&](const QString& role, bool isOn) {
|
||||
isMoving = isMoving || isOn;
|
||||
if (isOn) {
|
||||
if (!isRunningRole(role)) {
|
||||
qCDebug(animation) << "Rig STARTING" << role;
|
||||
startAnimationByRole(role);
|
||||
|
||||
}
|
||||
} else {
|
||||
if (isRunningRole(role)) {
|
||||
qCDebug(animation) << "Rig stopping" << role;
|
||||
stopAnimationByRole(role);
|
||||
}
|
||||
}
|
||||
};
|
||||
updateRole("walk", forwardSpeed > PERCEPTIBLE_SPEED);
|
||||
updateRole("backup", forwardSpeed < -PERCEPTIBLE_SPEED);
|
||||
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0.0f));
|
||||
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0.0f));
|
||||
isStrafing = isStrafing && !isMoving;
|
||||
updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f));
|
||||
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
|
||||
updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus.
|
||||
}
|
||||
|
||||
_lastFront = front;
|
||||
_lastPosition = worldPosition;
|
||||
}
|
||||
|
||||
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
||||
|
||||
// First normalize the fades so that they sum to 1.0.
|
||||
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
float fadePerSecond = handle->getFadePerSecond();
|
||||
float fade = handle->getFade();
|
||||
if (fadePerSecond != 0.0f) {
|
||||
fade += fadePerSecond * deltaTime;
|
||||
if ((0.0f >= fade) || (fade >= 1.0f)) {
|
||||
fade = glm::clamp(fade, 0.0f, 1.0f);
|
||||
handle->setFadePerSecond(0.0f);
|
||||
|
||||
if (_enableAnimGraph) {
|
||||
if (!_animNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// evaluate the animation
|
||||
AnimNode::Triggers triggersOut;
|
||||
AnimPoseVec poses = _animNode->evaluate(_animVars, deltaTime, triggersOut);
|
||||
_animVars.clearTriggers();
|
||||
for (auto& trigger : triggersOut) {
|
||||
_animVars.setTrigger(trigger);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// First normalize the fades so that they sum to 1.0.
|
||||
// update the fade data in each animation (not normalized as they are an independent propert of animation)
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
float fadePerSecond = handle->getFadePerSecond();
|
||||
float fade = handle->getFade();
|
||||
if (fadePerSecond != 0.0f) {
|
||||
fade += fadePerSecond * deltaTime;
|
||||
if ((0.0f >= fade) || (fade >= 1.0f)) {
|
||||
fade = glm::clamp(fade, 0.0f, 1.0f);
|
||||
handle->setFadePerSecond(0.0f);
|
||||
}
|
||||
handle->setFade(fade);
|
||||
if (fade <= 0.0f) { // stop any finished animations now
|
||||
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
|
||||
}
|
||||
}
|
||||
handle->setFade(fade);
|
||||
if (fade <= 0.0f) { // stop any finished animations now
|
||||
handle->setRunning(false, false); // but do not restore joints as it causes a flicker
|
||||
}
|
||||
}
|
||||
}
|
||||
// sum the remaining fade data
|
||||
float fadeTotal = 0.0f;
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
fadeTotal += handle->getFade();
|
||||
}
|
||||
float fadeSumSoFar = 0.0f;
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
handle->setPriority(1.0f);
|
||||
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
|
||||
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
|
||||
assert(normalizedFade != 0.0f);
|
||||
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
|
||||
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
|
||||
// The formula here for mix is based on the idea that, at each step:
|
||||
// fadeSum is to normalizedFade, as (1 - mix) is to mix
|
||||
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
|
||||
// Then we solve for mix.
|
||||
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
|
||||
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
|
||||
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
|
||||
assert((0.0f <= mix) && (mix <= 1.0f));
|
||||
fadeSumSoFar += normalizedFade;
|
||||
handle->setMix(mix);
|
||||
handle->simulate(deltaTime);
|
||||
}
|
||||
}
|
||||
// sum the remaining fade data
|
||||
float fadeTotal = 0.0f;
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
fadeTotal += handle->getFade();
|
||||
}
|
||||
float fadeSumSoFar = 0.0f;
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
handle->setPriority(1.0f);
|
||||
// if no fadeTotal, everyone's (typically just one running) is starting at zero. In that case, blend equally.
|
||||
float normalizedFade = (fadeTotal != 0.0f) ? (handle->getFade() / fadeTotal) : (1.0f / _runningAnimations.count());
|
||||
assert(normalizedFade != 0.0f);
|
||||
// simulate() will blend each animation result into the result so far, based on the pairwise mix at at each step.
|
||||
// i.e., slerp the 'mix' distance from the result so far towards this iteration's animation result.
|
||||
// The formula here for mix is based on the idea that, at each step:
|
||||
// fadeSum is to normalizedFade, as (1 - mix) is to mix
|
||||
// i.e., fadeSumSoFar/normalizedFade = (1 - mix)/mix
|
||||
// Then we solve for mix.
|
||||
// Sanity check: For the first animation, fadeSum = 0, and the mix will always be 1.
|
||||
// Sanity check: For equal blending, the formula is equivalent to mix = 1 / nAnimationsSoFar++
|
||||
float mix = 1.0f / ((fadeSumSoFar / normalizedFade) + 1.0f);
|
||||
assert((0.0f <= mix) && (mix <= 1.0f));
|
||||
fadeSumSoFar += normalizedFade;
|
||||
handle->setMix(mix);
|
||||
handle->simulate(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
updateJointState(i, rootTransform);
|
||||
}
|
||||
|
@ -838,6 +985,7 @@ void Rig::updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3&
|
|||
updateEyeJoint(leftEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
||||
updateEyeJoint(rightEyeIndex, modelTranslation, modelRotation, worldHeadOrientation, lookAtSpot, saccade);
|
||||
}
|
||||
|
||||
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
|
||||
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
|
||||
auto& state = _jointStates[index];
|
||||
|
@ -856,3 +1004,30 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
|||
state.getDefaultRotation(), DEFAULT_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
|
||||
if (!_enableAnimGraph) {
|
||||
return;
|
||||
}
|
||||
|
||||
// convert to std::vector of joints
|
||||
std::vector<FBXJoint> joints;
|
||||
joints.reserve(fbxGeometry.joints.size());
|
||||
for (auto& joint : fbxGeometry.joints) {
|
||||
joints.push_back(joint);
|
||||
}
|
||||
|
||||
// create skeleton
|
||||
AnimPose geometryOffset(fbxGeometry.offset);
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(joints, geometryOffset);
|
||||
|
||||
// load the anim graph
|
||||
_animLoader.reset(new AnimNodeLoader(url));
|
||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this](AnimNode::Pointer nodeIn) {
|
||||
_animNode = nodeIn;
|
||||
_animNode->setSkeleton(_animSkeleton);
|
||||
});
|
||||
connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) {
|
||||
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
|
||||
#include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS
|
||||
|
||||
#include "AnimNode.h"
|
||||
#include "AnimNodeLoader.h"
|
||||
|
||||
class AnimationHandle;
|
||||
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
|
||||
|
||||
|
@ -80,6 +83,7 @@ public:
|
|||
bool isRunningRole(const QString& role); // There can be multiple animations per role, so this is more general than isRunningAnimation.
|
||||
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
|
||||
void deleteAnimations();
|
||||
void destroyAnimGraph();
|
||||
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
||||
void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
|
||||
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
|
||||
|
@ -155,6 +159,8 @@ public:
|
|||
virtual void updateJointState(int index, glm::mat4 rootTransform) = 0;
|
||||
|
||||
void setEnableRig(bool isEnabled) { _enableRig = isEnabled; }
|
||||
void setEnableAnimGraph(bool isEnabled) { _enableAnimGraph = isEnabled; }
|
||||
bool getEnableAnimGraph() const { return _enableAnimGraph; }
|
||||
|
||||
void updateFromHeadParameters(const HeadParameters& params);
|
||||
void updateEyeJoints(int leftEyeIndex, int rightEyeIndex, const glm::vec3& modelTranslation, const glm::quat& modelRotation,
|
||||
|
@ -163,6 +169,11 @@ public:
|
|||
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
|
||||
float scale, float priority) = 0;
|
||||
|
||||
void initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry);
|
||||
|
||||
AnimNode::ConstPointer getAnimNode() const { return _animNode; }
|
||||
AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; }
|
||||
|
||||
protected:
|
||||
|
||||
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
|
||||
|
@ -183,9 +194,21 @@ public:
|
|||
QList<AnimationHandlePointer> _animationHandles;
|
||||
QList<AnimationHandlePointer> _runningAnimations;
|
||||
|
||||
bool _enableRig;
|
||||
bool _enableRig = false;
|
||||
bool _enableAnimGraph = false;
|
||||
glm::vec3 _lastFront;
|
||||
glm::vec3 _lastPosition;
|
||||
|
||||
std::shared_ptr<AnimNode> _animNode;
|
||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||
std::unique_ptr<AnimNodeLoader> _animLoader;
|
||||
AnimVariantMap _animVars;
|
||||
enum class RigRole {
|
||||
Idle = 0,
|
||||
Turn,
|
||||
Move
|
||||
};
|
||||
RigRole _state = RigRole::Idle;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Rig__) */
|
||||
|
|
|
@ -313,7 +313,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
|
|||
|
||||
QByteArray samples = sound->getByteArray();
|
||||
if (stretchFactor == 1.0f) {
|
||||
return playSound(samples, options, NULL);
|
||||
return playSoundAndDelete(samples, options, NULL);
|
||||
}
|
||||
|
||||
soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
|
||||
|
@ -333,9 +333,16 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
|
|||
qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate;
|
||||
resampled = samples;
|
||||
}
|
||||
return playSound(resampled, options, NULL);
|
||||
return playSoundAndDelete(resampled, options, NULL);
|
||||
}
|
||||
|
||||
AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
|
||||
AudioInjector* sound = playSound(buffer, options, localInterface);
|
||||
sound->triggerDeleteAfterFinish();
|
||||
return sound;
|
||||
}
|
||||
|
||||
|
||||
AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
|
||||
QThread* injectorThread = new QThread();
|
||||
injectorThread->setObjectName("Audio Injector Thread");
|
||||
|
|
|
@ -46,6 +46,7 @@ public:
|
|||
|
||||
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
|
||||
|
||||
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
|
||||
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
|
||||
static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position);
|
||||
|
||||
|
|
|
@ -31,10 +31,6 @@
|
|||
|
||||
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
|
||||
|
||||
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
|
||||
const float MIN_ROTATION_DOT = 0.9999999f;
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f);
|
||||
|
@ -145,7 +141,7 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) {
|
|||
_handPosition = glm::inverse(getOrientation()) * (handPosition - _position);
|
||||
}
|
||||
|
||||
QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
|
||||
QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
||||
// TODO: DRY this up to a shared method
|
||||
// that can pack any type given the number of bytes
|
||||
// and return the number of bytes to push the pointer
|
||||
|
@ -243,11 +239,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
|
|||
|
||||
_lastSentJointData.resize(_jointData.size());
|
||||
|
||||
// foreach (const JointData& data, _jointData) {
|
||||
for (int i=0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData.at(i);
|
||||
if (_lastSentJointData[i].rotation != data.rotation) {
|
||||
if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= MIN_ROTATION_DOT) {
|
||||
if (sendAll || _lastSentJointData[i].rotation != data.rotation) {
|
||||
if (sendAll ||
|
||||
!cullSmallChanges ||
|
||||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
|
||||
validity |= (1 << validityBit);
|
||||
}
|
||||
}
|
||||
|
@ -266,7 +263,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
|
|||
const JointData& data = _jointData[ i ];
|
||||
if (validity & (1 << validityBit)) {
|
||||
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation);
|
||||
_lastSentJointData[i].rotation = data.rotation;
|
||||
}
|
||||
if (++validityBit == BITS_IN_BYTE) {
|
||||
validityBit = 0;
|
||||
|
@ -277,6 +273,20 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
|
|||
return avatarDataByteArray.left(destinationBuffer - startPosition);
|
||||
}
|
||||
|
||||
void AvatarData::doneEncoding(bool cullSmallChanges) {
|
||||
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
|
||||
_lastSentJointData.resize(_jointData.size());
|
||||
for (int i = 0; i < _jointData.size(); i ++) {
|
||||
const JointData& data = _jointData[ i ];
|
||||
if (_lastSentJointData[i].rotation != data.rotation) {
|
||||
if (!cullSmallChanges ||
|
||||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
|
||||
_lastSentJointData[i].rotation = data.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarData::shouldLogError(const quint64& now) {
|
||||
if (now > _errorLogExpiry) {
|
||||
_errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||
|
@ -1081,11 +1091,15 @@ void AvatarData::setJointMappingsFromNetworkReply() {
|
|||
|
||||
void AvatarData::sendAvatarDataPacket() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray avatarByteArray = toByteArray(true);
|
||||
|
||||
static uint16_t sequenceNumber = 0;
|
||||
// about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
|
||||
// this is to guard against a joint moving once, the packet getting lost, and the joint never moving again.
|
||||
bool sendFullUpdate = randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO;
|
||||
QByteArray avatarByteArray = toByteArray(true, sendFullUpdate);
|
||||
doneEncoding(true);
|
||||
|
||||
static uint16_t sequenceNumber = 0;
|
||||
|
||||
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber));
|
||||
avatarPacket->writePrimitive(sequenceNumber++);
|
||||
avatarPacket->write(avatarByteArray);
|
||||
|
|
|
@ -111,6 +111,11 @@ const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
|
|||
// See also static AvatarData::defaultFullAvatarModelUrl().
|
||||
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
|
||||
|
||||
// how often should we send a full report about joint rotations, even if they haven't changed?
|
||||
const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02;
|
||||
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
|
||||
const float AVATAR_MIN_ROTATION_DOT = 0.9999999f;
|
||||
|
||||
|
||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
|
||||
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
||||
|
@ -171,7 +176,8 @@ public:
|
|||
glm::vec3 getHandPosition() const;
|
||||
void setHandPosition(const glm::vec3& handPosition);
|
||||
|
||||
virtual QByteArray toByteArray(bool cullSmallChanges);
|
||||
virtual QByteArray toByteArray(bool cullSmallChanges, bool sendAll);
|
||||
virtual void doneEncoding(bool cullSmallChanges);
|
||||
|
||||
/// \return true if an error should be logged
|
||||
bool shouldLogError(const quint64& now);
|
||||
|
|
|
@ -114,12 +114,14 @@ float HandData::getBaseScale() const {
|
|||
}
|
||||
|
||||
glm::vec3 PalmData::getFingerDirection() const {
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
// finger points along yAxis in hand-frame
|
||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION));
|
||||
}
|
||||
|
||||
glm::vec3 PalmData::getNormal() const {
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f);
|
||||
// palm normal points along zAxis in hand-frame
|
||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, 0.0f, 1.0f);
|
||||
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION));
|
||||
}
|
||||
|
||||
|
|
|
@ -64,12 +64,13 @@ public:
|
|||
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
|
||||
const PalmData*& collidingPalm) const;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
|
||||
friend class AvatarData;
|
||||
protected:
|
||||
AvatarData* _owningAvatarData;
|
||||
std::vector<PalmData> _palms;
|
||||
|
||||
glm::quat getBaseOrientation() const;
|
||||
glm::vec3 getBasePosition() const;
|
||||
float getBaseScale() const;
|
||||
|
||||
|
@ -95,6 +96,7 @@ public:
|
|||
|
||||
void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; };
|
||||
glm::quat getRawRotation() const { return _rawRotation; }
|
||||
glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; }
|
||||
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
|
||||
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
|
||||
const glm::vec3& getRawVelocity() const { return _rawVelocity; }
|
||||
|
@ -147,6 +149,7 @@ public:
|
|||
glm::vec3 getNormal() const;
|
||||
|
||||
private:
|
||||
// unless marked otherwise, these are all in the model-frame
|
||||
glm::quat _rawRotation;
|
||||
glm::vec3 _rawPosition;
|
||||
glm::vec3 _rawVelocity;
|
||||
|
@ -156,6 +159,7 @@ private:
|
|||
glm::vec3 _tipPosition;
|
||||
glm::vec3 _tipVelocity;
|
||||
glm::vec3 _totalPenetration; // accumulator for per-frame penetrations
|
||||
|
||||
unsigned int _controllerButtons;
|
||||
unsigned int _lastControllerButtons;
|
||||
float _trigger;
|
||||
|
|
|
@ -57,6 +57,11 @@ public:
|
|||
|
||||
// Rendering support
|
||||
|
||||
// Stop requesting renders, but don't do full deactivation
|
||||
// needed to work around the issues caused by Oculus
|
||||
// processing messages in the middle of submitFrame
|
||||
virtual void stop() = 0;
|
||||
|
||||
/**
|
||||
* Called by the application before the frame rendering. Can be used for
|
||||
* render timing related calls (for instance, the Oculus begin frame timing
|
||||
|
@ -120,6 +125,7 @@ public:
|
|||
virtual void resetSensors() {}
|
||||
virtual float devicePixelRatio() { return 1.0; }
|
||||
|
||||
|
||||
static const QString& MENU_PATH();
|
||||
signals:
|
||||
void recommendedFramebufferSizeChanged(const QSize & size);
|
||||
|
|
|
@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {}
|
|||
|
||||
void NullDisplayPlugin::activate() {}
|
||||
void NullDisplayPlugin::deactivate() {}
|
||||
void NullDisplayPlugin::stop() {}
|
||||
|
|
|
@ -17,6 +17,7 @@ public:
|
|||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void stop() override;
|
||||
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
virtual bool hasFocus() const override;
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
|
||||
connect(&_timer, &QTimer::timeout, this, [&] {
|
||||
emit requestRender();
|
||||
if (_active) {
|
||||
emit requestRender();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::activate() {
|
||||
_active = true;
|
||||
_timer.start(1);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::stop() {
|
||||
_active = false;
|
||||
_timer.stop();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::deactivate() {
|
||||
_active = false;
|
||||
_timer.stop();
|
||||
|
||||
makeCurrent();
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
virtual void activate() override;
|
||||
virtual void deactivate() override;
|
||||
|
||||
virtual void stop() override;
|
||||
virtual bool eventFilter(QObject* receiver, QEvent* event) override;
|
||||
|
||||
virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override;
|
||||
|
@ -44,6 +44,7 @@ protected:
|
|||
mutable QTimer _timer;
|
||||
ProgramPtr _program;
|
||||
ShapeWrapperPtr _plane;
|
||||
bool _active{ false };
|
||||
bool _vsyncSupported{ false };
|
||||
};
|
||||
|
||||
|
|
|
@ -310,8 +310,6 @@ void OculusDisplayPlugin::activate() {
|
|||
// not needed since the structure was zeroed on init, but explicit
|
||||
sceneLayer.ColorTexture[1] = nullptr;
|
||||
|
||||
PerformanceTimer::setActive(true);
|
||||
|
||||
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
|
||||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
|
@ -322,24 +320,19 @@ void OculusDisplayPlugin::activate() {
|
|||
void OculusDisplayPlugin::customizeContext() {
|
||||
WindowOpenGLDisplayPlugin::customizeContext();
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
//_texture = DependencyManager::get<TextureCache>()->
|
||||
// getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png");
|
||||
uvec2 mirrorSize = toGlm(_window->geometry().size());
|
||||
_mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd));
|
||||
_mirrorFbo->Init(mirrorSize);
|
||||
|
||||
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
|
||||
_sceneFbo->Init(getRecommendedRenderSize());
|
||||
#endif
|
||||
enableVsync(false);
|
||||
// Only enable mirroring if we know vsync is disabled
|
||||
_enableMirror = !isVsyncEnabled();
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::deactivate() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
makeCurrent();
|
||||
_sceneFbo.reset();
|
||||
_mirrorFbo.reset();
|
||||
doneCurrent();
|
||||
PerformanceTimer::setActive(false);
|
||||
|
||||
WindowOpenGLDisplayPlugin::deactivate();
|
||||
|
||||
|
@ -350,17 +343,22 @@ void OculusDisplayPlugin::deactivate() {
|
|||
}
|
||||
|
||||
void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
static bool inDisplay = false;
|
||||
if (inDisplay) {
|
||||
return;
|
||||
}
|
||||
inDisplay = true;
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
using namespace oglplus;
|
||||
// Need to make sure only the display plugin is responsible for
|
||||
// controlling vsync
|
||||
wglSwapIntervalEXT(0);
|
||||
|
||||
// screen mirroring
|
||||
if (_enableMirror) {
|
||||
auto windowSize = toGlm(_window->size());
|
||||
Context::Viewport(windowSize.x, windowSize.y);
|
||||
glBindTexture(GL_TEXTURE_2D, finalTexture);
|
||||
GLenum err = glGetError();
|
||||
Q_ASSERT(0 == err);
|
||||
drawUnitQuad();
|
||||
}
|
||||
|
||||
_sceneFbo->Bound([&] {
|
||||
auto size = _sceneFbo->size;
|
||||
Context::Viewport(size.x, size.y);
|
||||
|
@ -375,23 +373,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
});
|
||||
|
||||
auto windowSize = toGlm(_window->size());
|
||||
|
||||
/*
|
||||
Two alternatives for mirroring to the screen, the first is to copy our own composited
|
||||
scene to the window framebuffer, before distortion. Note this only works if we're doing
|
||||
ui compositing ourselves, and not relying on the Oculus SDK compositor (or we don't want
|
||||
the UI visible in the output window (unlikely). This should be done before
|
||||
_sceneFbo->Increment or we're be using the wrong texture
|
||||
*/
|
||||
_sceneFbo->Bound(Framebuffer::Target::Read, [&] {
|
||||
glBlitFramebuffer(
|
||||
0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
|
||||
0, 0, windowSize.x, windowSize.y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
});
|
||||
|
||||
{
|
||||
PerformanceTimer("OculusSubmit");
|
||||
ovrViewScaleDesc viewScaleDesc;
|
||||
viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
|
||||
viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0];
|
||||
|
@ -405,36 +387,12 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
}
|
||||
_sceneFbo->Increment();
|
||||
|
||||
/*
|
||||
The other alternative for mirroring is to use the Oculus mirror texture support, which
|
||||
will contain the post-distorted and fully composited scene regardless of how many layers
|
||||
we send.
|
||||
*/
|
||||
//auto mirrorSize = _mirrorFbo->size;
|
||||
//_mirrorFbo->Bound(Framebuffer::Target::Read, [&] {
|
||||
// Context::BlitFramebuffer(
|
||||
// 0, mirrorSize.y, mirrorSize.x, 0,
|
||||
// 0, 0, windowSize.x, windowSize.y,
|
||||
// BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
|
||||
//});
|
||||
|
||||
++_frameIndex;
|
||||
#endif
|
||||
inDisplay = false;
|
||||
}
|
||||
|
||||
// Pass input events on to the application
|
||||
bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (event->type() == QEvent::Resize) {
|
||||
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||
qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height();
|
||||
auto newSize = toGlm(resizeEvent->size());
|
||||
makeCurrent();
|
||||
_mirrorFbo->Resize(newSize);
|
||||
doneCurrent();
|
||||
}
|
||||
#endif
|
||||
return WindowOpenGLDisplayPlugin::eventFilter(receiver, event);
|
||||
}
|
||||
|
||||
|
@ -444,7 +402,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
|||
otherwise the swapbuffer delay will interefere with the framerate of the headset
|
||||
*/
|
||||
void OculusDisplayPlugin::finishFrame() {
|
||||
//swapBuffers();
|
||||
if (_enableMirror) {
|
||||
swapBuffers();
|
||||
}
|
||||
doneCurrent();
|
||||
};
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ private:
|
|||
mat4 _compositeEyeProjections[2];
|
||||
uvec2 _desiredFramebufferSize;
|
||||
ovrTrackingState _trackingState;
|
||||
bool _enableMirror{ false };
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrHmd _hmd;
|
||||
|
@ -70,7 +71,6 @@ private:
|
|||
ovrLayerEyeFov& getSceneLayer();
|
||||
ovrHmdDesc _hmdDesc;
|
||||
SwapFboPtr _sceneFbo;
|
||||
MirrorFboPtr _mirrorFbo;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
#endif
|
||||
#if (OVR_MAJOR_VERSION == 7)
|
||||
|
|
|
@ -78,7 +78,7 @@ void StereoDisplayPlugin::activate() {
|
|||
}
|
||||
|
||||
void StereoDisplayPlugin::updateScreen() {
|
||||
for (int i = 0; i < (int) _screenActions.size(); ++i) {
|
||||
for (uint32_t i = 0; i < _screenActions.size(); ++i) {
|
||||
if (_screenActions[i]->isChecked()) {
|
||||
CONTAINER->setFullscreen(qApp->screens().at(i));
|
||||
break;
|
||||
|
|
|
@ -68,6 +68,19 @@ RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() {
|
|||
}
|
||||
|
||||
|
||||
bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) {
|
||||
switch (surfaceStyle) {
|
||||
case PolyVoxEntityItem::SURFACE_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
|
||||
return false;
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) {
|
||||
_voxelDataLock.lockForWrite();
|
||||
if (_voxelData == voxelData) {
|
||||
|
@ -88,10 +101,8 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel
|
|||
}
|
||||
|
||||
// if we are switching to or from "edged" we need to force a resize of _volData.
|
||||
bool wasEdged = (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC ||
|
||||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES);
|
||||
bool willBeEdged = (voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC ||
|
||||
voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES);
|
||||
bool wasEdged = isEdged(_voxelSurfaceStyle);
|
||||
bool willBeEdged = isEdged(voxelSurfaceStyle);
|
||||
|
||||
if (wasEdged != willBeEdged) {
|
||||
_volDataLock.lockForWrite();
|
||||
|
@ -113,15 +124,10 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel
|
|||
|
||||
glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const {
|
||||
glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units
|
||||
switch (_voxelSurfaceStyle) {
|
||||
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
|
||||
case PolyVoxEntityItem::SURFACE_CUBIC:
|
||||
return scale / 2.0f;
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
|
||||
return scale / -2.0f;
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
return scale / -2.0f;
|
||||
}
|
||||
return glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
return scale / 2.0f;
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,7 +136,7 @@ glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const {
|
|||
glm::vec3 center = getCenterPosition();
|
||||
glm::vec3 position = getPosition();
|
||||
glm::vec3 positionToCenter = center - position;
|
||||
positionToCenter -= getDimensions() * glm::vec3(0.5f, 0.5f, 0.5f) - getSurfacePositionAdjustment();
|
||||
positionToCenter -= getDimensions() * Vectors::HALF - getSurfacePositionAdjustment();
|
||||
glm::mat4 centerToCorner = glm::translate(glm::mat4(), positionToCenter);
|
||||
glm::mat4 scaled = glm::scale(centerToCorner, scale);
|
||||
return scaled;
|
||||
|
@ -195,6 +201,37 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
|
|||
}
|
||||
|
||||
|
||||
bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue) {
|
||||
bool result = false;
|
||||
if (_locked) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int xLow = std::max(std::min((int)roundf(lowPosition.x), (int)roundf(_voxelVolumeSize.x) - 1), 0);
|
||||
int yLow = std::max(std::min((int)roundf(lowPosition.y), (int)roundf(_voxelVolumeSize.y) - 1), 0);
|
||||
int zLow = std::max(std::min((int)roundf(lowPosition.z), (int)roundf(_voxelVolumeSize.z) - 1), 0);
|
||||
int xHigh = std::max(std::min(xLow + (int)roundf(cuboidSize.x), (int)roundf(_voxelVolumeSize.x)), xLow);
|
||||
int yHigh = std::max(std::min(yLow + (int)roundf(cuboidSize.y), (int)roundf(_voxelVolumeSize.y)), yLow);
|
||||
int zHigh = std::max(std::min(zLow + (int)roundf(cuboidSize.z), (int)roundf(_voxelVolumeSize.z)), zLow);
|
||||
|
||||
_volDataLock.lockForWrite();
|
||||
_volDataDirty = true;
|
||||
|
||||
for (int x = xLow; x < xHigh; x++) {
|
||||
for (int y = yLow; y < yHigh; y++) {
|
||||
for (int z = zLow; z < zHigh; z++) {
|
||||
result |= setVoxelInternal(x, y, z, toValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
_volDataLock.unlock();
|
||||
if (result) {
|
||||
compressVolumeDataAndSendEditPacket();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool RenderablePolyVoxEntityItem::setVoxelInVolume(glm::vec3 position, uint8_t toValue) {
|
||||
if (_locked) {
|
||||
|
@ -213,7 +250,6 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi
|
|||
|
||||
// This three-level for loop iterates over every voxel in the volume
|
||||
_volDataLock.lockForWrite();
|
||||
_volDataDirty = true;
|
||||
for (int z = 0; z < _voxelVolumeSize.z; z++) {
|
||||
for (int y = 0; y < _voxelVolumeSize.y; y++) {
|
||||
for (int x = 0; x < _voxelVolumeSize.x; x++) {
|
||||
|
@ -228,20 +264,52 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radi
|
|||
}
|
||||
}
|
||||
}
|
||||
_volDataLock.unlock();
|
||||
|
||||
if (result) {
|
||||
_volDataDirty = true;
|
||||
_volDataLock.unlock();
|
||||
compressVolumeDataAndSendEditPacket();
|
||||
} else {
|
||||
_volDataLock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) {
|
||||
// glm::vec3 centerVoxelCoords = worldToVoxelCoordinates(centerWorldCoords);
|
||||
glm::vec4 centerVoxelCoords = worldToVoxelMatrix() * glm::vec4(centerWorldCoords, 1.0f);
|
||||
glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units
|
||||
float scaleY = scale.y;
|
||||
float radiusVoxelCoords = radiusWorldCoords / scaleY;
|
||||
return setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue);
|
||||
bool result = false;
|
||||
if (_locked) {
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||
|
||||
// This three-level for loop iterates over every voxel in the volume
|
||||
_volDataLock.lockForWrite();
|
||||
for (int z = 0; z < _voxelVolumeSize.z; z++) {
|
||||
for (int y = 0; y < _voxelVolumeSize.y; y++) {
|
||||
for (int x = 0; x < _voxelVolumeSize.x; x++) {
|
||||
// Store our current position as a vector...
|
||||
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
||||
// convert to world coordinates
|
||||
glm::vec3 worldPos = glm::vec3(vtwMatrix * pos);
|
||||
// compute how far the current position is from the center of the volume
|
||||
float fDistToCenter = glm::distance(worldPos, centerWorldCoords);
|
||||
// If the current voxel is less than 'radius' units from the center then we set its value
|
||||
if (fDistToCenter <= radiusWorldCoords) {
|
||||
result |= setVoxelInternal(x, y, z, toValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
_volDataDirty = true;
|
||||
_volDataLock.unlock();
|
||||
compressVolumeDataAndSendEditPacket();
|
||||
} else {
|
||||
_volDataLock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class RaycastFunctor
|
||||
|
@ -296,7 +364,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
|||
|
||||
glm::mat4 wtvMatrix = worldToVoxelMatrix();
|
||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||
glm::mat4 vtlMatrix = voxelToLocalMatrix();
|
||||
glm::vec3 normDirection = glm::normalize(direction);
|
||||
|
||||
// the PolyVox ray intersection code requires a near and far point.
|
||||
|
@ -304,67 +371,33 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
|||
float distanceToEntity = glm::distance(origin, getPosition());
|
||||
float largestDimension = glm::max(getDimensions().x, getDimensions().y, getDimensions().z) * 2.0f;
|
||||
glm::vec3 farPoint = origin + normDirection * (distanceToEntity + largestDimension);
|
||||
|
||||
glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f);
|
||||
glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f);
|
||||
|
||||
glm::vec4 result;
|
||||
glm::vec4 directionInVoxel = glm::normalize(farInVoxel - originInVoxel);
|
||||
|
||||
glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
PolyVox::RaycastResult raycastResult = doRayCast(originInVoxel, farInVoxel, result);
|
||||
if (raycastResult == PolyVox::RaycastResults::Completed) {
|
||||
// the ray completed its path -- nothing was hit.
|
||||
return false;
|
||||
}
|
||||
|
||||
// set up ray tests against each face of the voxel.
|
||||
glm::vec3 minXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.0f, 0.5f, 0.5f, 0.0f)));
|
||||
glm::vec3 maxXPosition = glm::vec3(vtwMatrix * (result + glm::vec4(1.0f, 0.5f, 0.5f, 0.0f)));
|
||||
glm::vec3 minYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.0f, 0.5f, 0.0f)));
|
||||
glm::vec3 maxYPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 1.0f, 0.5f, 0.0f)));
|
||||
glm::vec3 minZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 0.0f, 0.0f)));
|
||||
glm::vec3 maxZPosition = glm::vec3(vtwMatrix * (result + glm::vec4(0.5f, 0.5f, 1.0f, 0.0f)));
|
||||
glm::vec3 result3 = glm::vec3(result);
|
||||
|
||||
glm::vec4 baseDimensions = glm::vec4(1.0, 1.0, 1.0, 0.0);
|
||||
glm::vec3 worldDimensions = glm::vec3(vtlMatrix * baseDimensions);
|
||||
glm::vec2 xDimensions = glm::vec2(worldDimensions.z, worldDimensions.y);
|
||||
glm::vec2 yDimensions = glm::vec2(worldDimensions.x, worldDimensions.z);
|
||||
glm::vec2 zDimensions = glm::vec2(worldDimensions.x, worldDimensions.y);
|
||||
AABox voxelBox;
|
||||
voxelBox += result3 - Vectors::HALF;
|
||||
voxelBox += result3 + Vectors::HALF;
|
||||
|
||||
glm::quat vtwRotation = extractRotation(vtwMatrix);
|
||||
glm::quat minXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
|
||||
glm::quat maxXRotation = vtwRotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
|
||||
glm::quat minYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f));
|
||||
glm::quat maxYRotation = vtwRotation * glm::quat(glm::vec3(PI_OVER_TWO, 0.0f, 0.0f));
|
||||
glm::quat minZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
glm::quat maxZRotation = vtwRotation * glm::quat(glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
float voxelDistance;
|
||||
|
||||
float bestDx = FLT_MAX;
|
||||
bool hit[ 6 ];
|
||||
float dx[ 6 ] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX};
|
||||
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face);
|
||||
|
||||
hit[0] = findRayRectangleIntersection(origin, direction, minXRotation, minXPosition, xDimensions, dx[0]);
|
||||
hit[1] = findRayRectangleIntersection(origin, direction, maxXRotation, maxXPosition, xDimensions, dx[1]);
|
||||
hit[2] = findRayRectangleIntersection(origin, direction, minYRotation, minYPosition, yDimensions, dx[2]);
|
||||
hit[3] = findRayRectangleIntersection(origin, direction, maxYRotation, maxYPosition, yDimensions, dx[3]);
|
||||
hit[4] = findRayRectangleIntersection(origin, direction, minZRotation, minZPosition, zDimensions, dx[4]);
|
||||
hit[5] = findRayRectangleIntersection(origin, direction, maxZRotation, maxZPosition, zDimensions, dx[5]);
|
||||
|
||||
bool ok = false;
|
||||
for (int i = 0; i < 6; i ++) {
|
||||
if (hit[ i ] && dx[ i ] < bestDx) {
|
||||
face = (BoxFace)i;
|
||||
distance = dx[ i ];
|
||||
ok = true;
|
||||
bestDx = dx[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
// if the attempt to put the ray against one of the voxel-faces fails, just return the center
|
||||
glm::vec4 intersectedWorldPosition = vtwMatrix * (result + vec4(0.5f, 0.5f, 0.5f, 0.0f));
|
||||
distance = glm::distance(glm::vec3(intersectedWorldPosition), origin);
|
||||
face = BoxFace::MIN_X_FACE;
|
||||
}
|
||||
|
||||
return true;
|
||||
glm::vec4 voxelIntersectionPoint = glm::vec4(glm::vec3(originInVoxel) + glm::vec3(directionInVoxel) * voxelDistance, 1.0);
|
||||
glm::vec4 intersectionPoint = vtwMatrix * voxelIntersectionPoint;
|
||||
distance = glm::distance(origin, glm::vec3(intersectionPoint));
|
||||
return hit;
|
||||
}
|
||||
|
||||
|
||||
|
@ -380,7 +413,7 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn
|
|||
_volDataLock.unlock();
|
||||
|
||||
// result is in voxel-space coordinates.
|
||||
result = callback._result - glm::vec4(0.5f, 0.5f, 0.5f, 0.0f);
|
||||
result = callback._result;
|
||||
return raycastResult;
|
||||
}
|
||||
|
||||
|
@ -402,21 +435,29 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
|||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
_shapeInfoLock.lockForRead();
|
||||
QReadLocker(&this->_shapeInfoLock);
|
||||
info = _shapeInfo;
|
||||
_shapeInfoLock.unlock();
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setXTextureURL(QString xTextureURL) {
|
||||
PolyVoxEntityItem::setXTextureURL(xTextureURL);
|
||||
if (xTextureURL != _xTextureURL) {
|
||||
_xTexture.clear();
|
||||
PolyVoxEntityItem::setXTextureURL(xTextureURL);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setYTextureURL(QString yTextureURL) {
|
||||
PolyVoxEntityItem::setYTextureURL(yTextureURL);
|
||||
if (yTextureURL != _yTextureURL) {
|
||||
_yTexture.clear();
|
||||
PolyVoxEntityItem::setYTextureURL(yTextureURL);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setZTextureURL(QString zTextureURL) {
|
||||
PolyVoxEntityItem::setZTextureURL(zTextureURL);
|
||||
if (zTextureURL != _zTextureURL) {
|
||||
_zTexture.clear();
|
||||
PolyVoxEntityItem::setZTextureURL(zTextureURL);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
||||
|
@ -426,9 +467,12 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
|
||||
_volDataLock.lockForRead();
|
||||
if (_volDataDirty) {
|
||||
_volDataLock.unlock();
|
||||
getMesh();
|
||||
} else {
|
||||
_volDataLock.unlock();
|
||||
}
|
||||
_volDataLock.unlock();
|
||||
|
||||
|
||||
_meshLock.lockForRead();
|
||||
model::MeshPointer mesh = _mesh;
|
||||
|
@ -543,23 +587,21 @@ namespace render {
|
|||
|
||||
|
||||
glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const {
|
||||
return glm::vec3(voxelToWorldMatrix() * glm::vec4(voxelCoords, 1.0f));
|
||||
glm::vec3 adjustedCoords;
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
adjustedCoords = voxelCoords + Vectors::HALF;
|
||||
} else {
|
||||
adjustedCoords = voxelCoords - Vectors::HALF;
|
||||
}
|
||||
return glm::vec3(voxelToWorldMatrix() * glm::vec4(adjustedCoords, 1.0f));
|
||||
}
|
||||
|
||||
glm::vec3 RenderablePolyVoxEntityItem::worldCoordsToVoxelCoords(glm::vec3& worldCoords) const {
|
||||
glm::vec3 result = glm::vec3(worldToVoxelMatrix() * glm::vec4(worldCoords, 1.0f));
|
||||
switch (_voxelSurfaceStyle) {
|
||||
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
|
||||
case PolyVoxEntityItem::SURFACE_CUBIC:
|
||||
result += glm::vec3(0.5f, 0.5f, 0.5f);
|
||||
break;
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
|
||||
result -= glm::vec3(0.5f, 0.5f, 0.5f);
|
||||
break;
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
return result - Vectors::HALF;
|
||||
}
|
||||
|
||||
return result;
|
||||
return result + Vectors::HALF;
|
||||
}
|
||||
|
||||
glm::vec3 RenderablePolyVoxEntityItem::voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const {
|
||||
|
@ -585,8 +627,7 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize)
|
|||
}
|
||||
_onCount = 0;
|
||||
|
||||
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC ||
|
||||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) {
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
// with _EDGED_ we maintain an extra box of voxels around those that the user asked for. This
|
||||
// changes how the surface extractor acts -- mainly it becomes impossible to have holes in the
|
||||
// generated mesh. The non _EDGED_ modes will leave holes in the mesh at the edges of the
|
||||
|
@ -598,9 +639,11 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize)
|
|||
_volData = new PolyVox::SimpleVolume<uint8_t>(PolyVox::Region(lowCorner, highCorner));
|
||||
} else {
|
||||
PolyVox::Vector3DInt32 lowCorner(0, 0, 0);
|
||||
PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x - 1, // -1 because these corners are inclusive
|
||||
_voxelVolumeSize.y - 1,
|
||||
_voxelVolumeSize.z - 1);
|
||||
// these should each have -1 after them, but if we leave layers on the upper-axis faces,
|
||||
// they act more like I expect.
|
||||
PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize.x,
|
||||
_voxelVolumeSize.y,
|
||||
_voxelVolumeSize.z);
|
||||
_volData = new PolyVox::SimpleVolume<uint8_t>(PolyVox::Region(lowCorner, highCorner));
|
||||
}
|
||||
|
||||
|
@ -613,35 +656,27 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize)
|
|||
|
||||
bool RenderablePolyVoxEntityItem::inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol,
|
||||
PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
|
||||
int x, int y, int z) {
|
||||
int x, int y, int z) const {
|
||||
// x, y, z are in user voxel-coords, not adjusted-for-edge voxel-coords.
|
||||
switch (surfaceStyle) {
|
||||
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES:
|
||||
case PolyVoxEntityItem::SURFACE_CUBIC:
|
||||
if (x < 0 || y < 0 || z < 0 ||
|
||||
x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
|
||||
if (x < 0 || y < 0 || z < 0 ||
|
||||
x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (isEdged(surfaceStyle)) {
|
||||
if (x < 0 || y < 0 || z < 0 ||
|
||||
x >= vol->getWidth() - 2 || y >= vol->getHeight() - 2 || z >= vol->getDepth() - 2) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (x < 0 || y < 0 || z < 0 ||
|
||||
x >= vol->getWidth() || y >= vol->getHeight() || z >= vol->getDepth()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) {
|
||||
_volDataLock.lockForRead();
|
||||
auto result = getVoxelInternal(x, y, z);
|
||||
_volDataLock.unlock();
|
||||
return result;
|
||||
QReadLocker(&this->_volDataLock);
|
||||
return getVoxelInternal(x, y, z);
|
||||
}
|
||||
|
||||
|
||||
|
@ -650,19 +685,13 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// if _voxelSurfaceStyle is SURFACE_EDGED_CUBIC, we maintain an extra layer of
|
||||
// if _voxelSurfaceStyle is *_EDGED_*, we maintain an extra layer of
|
||||
// voxels all around the requested voxel space. Having the empty voxels around
|
||||
// the edges changes how the surface extractor behaves.
|
||||
|
||||
uint8_t result;
|
||||
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC ||
|
||||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) {
|
||||
result = _volData->getVoxelAt(x + 1, y + 1, z + 1);
|
||||
} else {
|
||||
result = _volData->getVoxelAt(x, y, z);
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
return _volData->getVoxelAt(x + 1, y + 1, z + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
return _volData->getVoxelAt(x, y, z);
|
||||
}
|
||||
|
||||
|
||||
|
@ -675,9 +704,7 @@ bool RenderablePolyVoxEntityItem::setVoxelInternal(int x, int y, int z, uint8_t
|
|||
|
||||
result = updateOnCount(x, y, z, toValue);
|
||||
|
||||
assert(_volData);
|
||||
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_CUBIC ||
|
||||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) {
|
||||
if (isEdged(_voxelSurfaceStyle)) {
|
||||
_volData->setVoxelAt(x + 1, y + 1, z + 1, toValue);
|
||||
} else {
|
||||
_volData->setVoxelAt(x, y, z, toValue);
|
||||
|
@ -710,13 +737,11 @@ bool RenderablePolyVoxEntityItem::updateOnCount(int x, int y, int z, uint8_t toV
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::decompressVolumeData() {
|
||||
_threadRunning.acquire();
|
||||
QtConcurrent::run(this, &RenderablePolyVoxEntityItem::decompressVolumeDataAsync);
|
||||
}
|
||||
|
||||
|
||||
// take compressed data and expand it into _volData.
|
||||
void RenderablePolyVoxEntityItem::decompressVolumeDataAsync() {
|
||||
_voxelDataLock.lockForRead();
|
||||
|
@ -777,7 +802,6 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
|
|||
QtConcurrent::run(this, &RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync);
|
||||
}
|
||||
|
||||
|
||||
// compress the data in _volData and save the results. The compressed form is used during
|
||||
// saves to disk and for transmission over the wire
|
||||
void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacketAsync() {
|
||||
|
@ -850,23 +874,157 @@ void RenderablePolyVoxEntityItem::getMesh() {
|
|||
QtConcurrent::run(this, &RenderablePolyVoxEntityItem::getMeshAsync);
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::clearOutOfDateNeighbors() {
|
||||
if (_xNNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentXNNeighbor = _xNNeighbor.lock();
|
||||
if (currentXNNeighbor && currentXNNeighbor->getID() != _xNNeighborID) {
|
||||
_xNNeighbor.reset();
|
||||
}
|
||||
}
|
||||
if (_yNNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentYNNeighbor = _yNNeighbor.lock();
|
||||
if (currentYNNeighbor && currentYNNeighbor->getID() != _yNNeighborID) {
|
||||
_yNNeighbor.reset();
|
||||
}
|
||||
}
|
||||
if (_zNNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentZNNeighbor = _zNNeighbor.lock();
|
||||
if (currentZNNeighbor && currentZNNeighbor->getID() != _zNNeighborID) {
|
||||
_zNNeighbor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (_xPNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentXPNeighbor = _xPNeighbor.lock();
|
||||
if (currentXPNeighbor && currentXPNeighbor->getID() != _xPNeighborID) {
|
||||
_xPNeighbor.reset();
|
||||
}
|
||||
}
|
||||
if (_yPNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentYPNeighbor = _yPNeighbor.lock();
|
||||
if (currentYPNeighbor && currentYPNeighbor->getID() != _yPNeighborID) {
|
||||
_yPNeighbor.reset();
|
||||
}
|
||||
}
|
||||
if (_zPNeighborID != UNKNOWN_ENTITY_ID) {
|
||||
EntityItemPointer currentZPNeighbor = _zPNeighbor.lock();
|
||||
if (currentZPNeighbor && currentZPNeighbor->getID() != _zPNeighborID) {
|
||||
_zPNeighbor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::cacheNeighbors() {
|
||||
clearOutOfDateNeighbors();
|
||||
EntityTreeElement* element = getElement();
|
||||
EntityTree* tree = element ? element->getTree() : nullptr;
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_xNNeighborID != UNKNOWN_ENTITY_ID && _xNNeighbor.expired()) {
|
||||
_xNNeighbor = tree->findEntityByID(_xNNeighborID);
|
||||
}
|
||||
if (_yNNeighborID != UNKNOWN_ENTITY_ID && _yNNeighbor.expired()) {
|
||||
_yNNeighbor = tree->findEntityByID(_yNNeighborID);
|
||||
}
|
||||
if (_zNNeighborID != UNKNOWN_ENTITY_ID && _zNNeighbor.expired()) {
|
||||
_zNNeighbor = tree->findEntityByID(_zNNeighborID);
|
||||
}
|
||||
|
||||
if (_xPNeighborID != UNKNOWN_ENTITY_ID && _xPNeighbor.expired()) {
|
||||
_xPNeighbor = tree->findEntityByID(_xPNeighborID);
|
||||
}
|
||||
if (_yPNeighborID != UNKNOWN_ENTITY_ID && _yPNeighbor.expired()) {
|
||||
_yPNeighbor = tree->findEntityByID(_yPNeighborID);
|
||||
}
|
||||
if (_zPNeighborID != UNKNOWN_ENTITY_ID && _zPNeighbor.expired()) {
|
||||
_zPNeighbor = tree->findEntityByID(_zPNeighborID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
|
||||
if (_voxelSurfaceStyle != PolyVoxEntityItem::SURFACE_MARCHING_CUBES) {
|
||||
return;
|
||||
}
|
||||
|
||||
EntityItemPointer currentXPNeighbor = _xPNeighbor.lock();
|
||||
EntityItemPointer currentYPNeighbor = _yPNeighbor.lock();
|
||||
EntityItemPointer currentZPNeighbor = _zPNeighbor.lock();
|
||||
|
||||
if (currentXPNeighbor) {
|
||||
auto polyVoxXPNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentXPNeighbor);
|
||||
if (polyVoxXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
|
||||
for (int y = 0; y < _volData->getHeight(); y++) {
|
||||
for (int z = 0; z < _volData->getDepth(); z++) {
|
||||
uint8_t neighborValue = polyVoxXPNeighbor->getVoxel(0, y, z);
|
||||
_volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentYPNeighbor) {
|
||||
auto polyVoxYPNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentYPNeighbor);
|
||||
if (polyVoxYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
|
||||
for (int x = 0; x < _volData->getWidth(); x++) {
|
||||
for (int z = 0; z < _volData->getDepth(); z++) {
|
||||
uint8_t neighborValue = polyVoxYPNeighbor->getVoxel(x, 0, z);
|
||||
_volData->setVoxelAt(x, _volData->getWidth() - 1, z, neighborValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentZPNeighbor) {
|
||||
auto polyVoxZPNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentZPNeighbor);
|
||||
if (polyVoxZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
|
||||
for (int x = 0; x < _volData->getWidth(); x++) {
|
||||
for (int y = 0; y < _volData->getHeight(); y++) {
|
||||
uint8_t neighborValue = polyVoxZPNeighbor->getVoxel(x, y, 0);
|
||||
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::getMeshAsync() {
|
||||
model::MeshPointer mesh(new model::Mesh());
|
||||
|
||||
cacheNeighbors();
|
||||
|
||||
// A mesh object to hold the result of surface extraction
|
||||
PolyVox::SurfaceMesh<PolyVox::PositionMaterialNormal> polyVoxMesh;
|
||||
|
||||
_volDataLock.lockForRead();
|
||||
if (!_volData) {
|
||||
_volDataLock.unlock();
|
||||
return;
|
||||
}
|
||||
copyUpperEdgesFromNeighbors();
|
||||
|
||||
switch (_voxelSurfaceStyle) {
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: {
|
||||
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
|
||||
(_volData, _volData->getEnclosingRegion(), &polyVoxMesh);
|
||||
surfaceExtractor.execute();
|
||||
break;
|
||||
}
|
||||
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: {
|
||||
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
|
||||
(_volData, _volData->getEnclosingRegion(), &polyVoxMesh);
|
||||
surfaceExtractor.execute();
|
||||
break;
|
||||
}
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
|
||||
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: {
|
||||
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
|
||||
(_volData, _volData->getEnclosingRegion(), &polyVoxMesh);
|
||||
surfaceExtractor.execute();
|
||||
break;
|
||||
}
|
||||
case PolyVoxEntityItem::SURFACE_CUBIC: {
|
||||
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
|
||||
(_volData, _volData->getEnclosingRegion(), &polyVoxMesh);
|
||||
|
@ -908,7 +1066,7 @@ void RenderablePolyVoxEntityItem::getMeshAsync() {
|
|||
_meshLock.unlock();
|
||||
_volDataDirty = false;
|
||||
_volDataLock.unlock();
|
||||
|
||||
bonkNeighbors();
|
||||
_threadRunning.release();
|
||||
}
|
||||
|
||||
|
@ -925,7 +1083,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() {
|
|||
|
||||
if (_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_MARCHING_CUBES ||
|
||||
_voxelSurfaceStyle == PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES) {
|
||||
/* pull each triangle in the mesh into a polyhedron which can be collided with */
|
||||
// pull each triangle in the mesh into a polyhedron which can be collided with
|
||||
unsigned int i = 0;
|
||||
|
||||
_meshLock.lockForRead();
|
||||
|
@ -972,10 +1130,16 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() {
|
|||
} else {
|
||||
unsigned int i = 0;
|
||||
|
||||
_volDataLock.lockForRead();
|
||||
if (!_volData) {
|
||||
_volDataLock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int z = 0; z < _voxelVolumeSize.z; z++) {
|
||||
for (int y = 0; y < _voxelVolumeSize.y; y++) {
|
||||
for (int x = 0; x < _voxelVolumeSize.x; x++) {
|
||||
if (getVoxel(x, y, z) > 0) {
|
||||
if (getVoxelInternal(x, y, z) > 0) {
|
||||
|
||||
if ((x > 0 && getVoxel(x - 1, y, z) > 0) &&
|
||||
(y > 0 && getVoxel(x, y - 1, z) > 0) &&
|
||||
|
@ -1033,6 +1197,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() {
|
|||
}
|
||||
}
|
||||
}
|
||||
_volDataLock.unlock();
|
||||
}
|
||||
|
||||
if (points.isEmpty()) {
|
||||
|
@ -1056,3 +1221,93 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorkerAsync() {
|
|||
_threadRunning.release();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighborID) {
|
||||
if (xNNeighborID != _xNNeighborID) {
|
||||
PolyVoxEntityItem::setXNNeighborID(xNNeighborID);
|
||||
cacheNeighbors();
|
||||
EntityItemPointer currentXNNeighbor = _xNNeighbor.lock();
|
||||
if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxXNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentXNNeighbor);
|
||||
polyVoxXNNeighbor->setXPNeighborID(_id);
|
||||
polyVoxXNNeighbor->rebakeMesh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighborID) {
|
||||
if (yNNeighborID != _yNNeighborID) {
|
||||
PolyVoxEntityItem::setYNNeighborID(yNNeighborID);
|
||||
cacheNeighbors();
|
||||
EntityItemPointer currentYNNeighbor = _yNNeighbor.lock();
|
||||
if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxYNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentYNNeighbor);
|
||||
polyVoxYNNeighbor->setYPNeighborID(_id);
|
||||
polyVoxYNNeighbor->rebakeMesh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighborID) {
|
||||
if (zNNeighborID != _zNNeighborID) {
|
||||
PolyVoxEntityItem::setZNNeighborID(zNNeighborID);
|
||||
cacheNeighbors();
|
||||
EntityItemPointer currentZNNeighbor = _yNNeighbor.lock();
|
||||
if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxZNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentZNNeighbor);
|
||||
polyVoxZNNeighbor->setZPNeighborID(_id);
|
||||
polyVoxZNNeighbor->rebakeMesh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighborID) {
|
||||
if (xPNeighborID != _xPNeighborID) {
|
||||
PolyVoxEntityItem::setXPNeighborID(xPNeighborID);
|
||||
rebakeMesh();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighborID) {
|
||||
if (yPNeighborID != _yPNeighborID) {
|
||||
PolyVoxEntityItem::setYPNeighborID(yPNeighborID);
|
||||
rebakeMesh();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighborID) {
|
||||
if (zPNeighborID != _zPNeighborID) {
|
||||
PolyVoxEntityItem::setZPNeighborID(zPNeighborID);
|
||||
rebakeMesh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::rebakeMesh() {
|
||||
QReadLocker(&this->_volDataLock);
|
||||
_volDataDirty = true;
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::bonkNeighbors() {
|
||||
clearOutOfDateNeighbors();
|
||||
cacheNeighbors();
|
||||
|
||||
EntityItemPointer currentXNNeighbor = _xNNeighbor.lock();
|
||||
EntityItemPointer currentYNNeighbor = _yNNeighbor.lock();
|
||||
EntityItemPointer currentZNNeighbor = _zNNeighbor.lock();
|
||||
|
||||
if (currentXNNeighbor && currentXNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxXNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentXNNeighbor);
|
||||
polyVoxXNNeighbor->rebakeMesh();
|
||||
}
|
||||
if (currentYNNeighbor && currentYNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxYNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentYNNeighbor);
|
||||
polyVoxYNNeighbor->rebakeMesh();
|
||||
}
|
||||
if (currentZNNeighbor && currentZNNeighbor->getType() == EntityTypes::PolyVox) {
|
||||
auto polyVoxZNNeighbor = std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(currentZNNeighbor);
|
||||
polyVoxZNNeighbor->rebakeMesh();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ public:
|
|||
// coords are in world-space
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue);
|
||||
virtual bool setAll(uint8_t toValue);
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue);
|
||||
|
||||
virtual void setXTextureURL(QString xTextureURL);
|
||||
virtual void setYTextureURL(QString yTextureURL);
|
||||
|
@ -105,6 +106,16 @@ public:
|
|||
std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges);
|
||||
|
||||
virtual void setXNNeighborID(const EntityItemID& xNNeighborID);
|
||||
virtual void setYNNeighborID(const EntityItemID& yNNeighborID);
|
||||
virtual void setZNNeighborID(const EntityItemID& zNNeighborID);
|
||||
|
||||
virtual void setXPNeighborID(const EntityItemID& xPNeighborID);
|
||||
virtual void setYPNeighborID(const EntityItemID& yPNeighborID);
|
||||
virtual void setZPNeighborID(const EntityItemID& zPNeighborID);
|
||||
|
||||
virtual void rebakeMesh();
|
||||
|
||||
private:
|
||||
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
|
||||
// may not match _voxelVolumeSize.
|
||||
|
@ -130,7 +141,7 @@ private:
|
|||
int _onCount; // how many non-zero voxels are in _volData
|
||||
|
||||
bool inUserBounds(const PolyVox::SimpleVolume<uint8_t>* vol, PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle,
|
||||
int x, int y, int z);
|
||||
int x, int y, int z) const;
|
||||
uint8_t getVoxelInternal(int x, int y, int z);
|
||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||
bool updateOnCount(int x, int y, int z, uint8_t toValue);
|
||||
|
@ -147,6 +158,18 @@ private:
|
|||
void computeShapeInfoWorkerAsync();
|
||||
|
||||
QSemaphore _threadRunning{1};
|
||||
|
||||
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
|
||||
EntityItemWeakPointer _xNNeighbor; // neighor found by going along negative X axis
|
||||
EntityItemWeakPointer _yNNeighbor;
|
||||
EntityItemWeakPointer _zNNeighbor;
|
||||
EntityItemWeakPointer _xPNeighbor; // neighor found by going along positive X axis
|
||||
EntityItemWeakPointer _yPNeighbor;
|
||||
EntityItemWeakPointer _zPNeighbor;
|
||||
void clearOutOfDateNeighbors();
|
||||
void cacheNeighbors();
|
||||
void copyUpperEdgesFromNeighbors();
|
||||
void bonkNeighbors();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -12,16 +12,12 @@
|
|||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
|
||||
layout(location = 0) out vec4 _fragColor0;
|
||||
layout(location = 1) out vec4 _fragColor1;
|
||||
layout(location = 2) out vec4 _fragColor2;
|
||||
|
||||
<@include model/Material.slh@>
|
||||
<@include DeferredBufferWrite.slh@>
|
||||
|
||||
in vec3 _normal;
|
||||
in vec4 _position;
|
||||
in vec4 _inPosition;
|
||||
in vec4 _worldPosition;
|
||||
|
||||
uniform sampler2D xMap;
|
||||
uniform sampler2D yMap;
|
||||
|
@ -29,12 +25,12 @@ uniform sampler2D zMap;
|
|||
uniform vec3 voxelVolumeSize;
|
||||
|
||||
void main(void) {
|
||||
vec3 worldNormal = cross(dFdy(_inPosition.xyz), dFdx(_inPosition.xyz));
|
||||
vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz));
|
||||
worldNormal = normalize(worldNormal);
|
||||
|
||||
float inPositionX = (_inPosition.x - 0.5) / voxelVolumeSize.x;
|
||||
float inPositionY = (_inPosition.y - 0.5) / voxelVolumeSize.y;
|
||||
float inPositionZ = (_inPosition.z - 0.5) / voxelVolumeSize.z;
|
||||
float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x;
|
||||
float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y;
|
||||
float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z;
|
||||
|
||||
vec4 xyDiffuse = texture(xMap, vec2(-inPositionX, -inPositionY));
|
||||
vec4 xzDiffuse = texture(yMap, vec2(-inPositionX, inPositionZ));
|
||||
|
@ -43,12 +39,7 @@ void main(void) {
|
|||
vec3 xyDiffuseScaled = xyDiffuse.rgb * abs(worldNormal.z);
|
||||
vec3 xzDiffuseScaled = xzDiffuse.rgb * abs(worldNormal.y);
|
||||
vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x);
|
||||
|
||||
vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0);
|
||||
|
||||
Material mat = getMaterial();
|
||||
|
||||
_fragColor0 = vec4(diffuse.rgb, 0.0);
|
||||
_fragColor1 = vec4(_normal, 1.0);
|
||||
_fragColor2 = vec4(getMaterialSpecular(mat), getMaterialShininess(mat) / 128.0);
|
||||
packDeferredFragment(_normal, 0.0, vec3(diffuse), vec3(0.01, 0.01, 0.01), 10.0);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<$declareStandardTransform()$>
|
||||
|
||||
out vec4 _position;
|
||||
out vec4 _inPosition;
|
||||
out vec4 _worldPosition;
|
||||
out vec3 _normal;
|
||||
|
||||
void main(void) {
|
||||
|
@ -26,5 +26,5 @@ void main(void) {
|
|||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToEyeAndClipPos(cam, obj, inPosition, _position, gl_Position)$>
|
||||
<$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$>
|
||||
_inPosition = inPosition;
|
||||
_worldPosition = inPosition;
|
||||
}
|
||||
|
|
|
@ -32,26 +32,8 @@ public:
|
|||
QScriptValue toScriptValue(QScriptEngine* engine) const;
|
||||
|
||||
bool isInvalidID() const { return *this == UNKNOWN_ENTITY_ID; }
|
||||
|
||||
// QUuid id;
|
||||
};
|
||||
|
||||
// inline bool operator<(const EntityItemID& a, const EntityItemID& b) {
|
||||
// return a.id < b.id;
|
||||
// }
|
||||
|
||||
// inline bool operator==(const EntityItemID& a, const EntityItemID& b) {
|
||||
// return a.id == b.id;
|
||||
// }
|
||||
|
||||
// inline bool operator!=(const EntityItemID& a, const EntityItemID& b) {
|
||||
// return !(a == b);
|
||||
// }
|
||||
|
||||
// inline uint qHash(const EntityItemID& a, uint seed) {
|
||||
// return qHash(a.id, seed);
|
||||
// }
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const EntityItemID& id) {
|
||||
debug << "[entity-id:" << id.toString() << "]";
|
||||
return debug;
|
||||
|
|
|
@ -109,6 +109,13 @@ CONSTRUCT_PROPERTY(strokeWidths, QVector<float>()),
|
|||
CONSTRUCT_PROPERTY(xTextureURL, ""),
|
||||
CONSTRUCT_PROPERTY(yTextureURL, ""),
|
||||
CONSTRUCT_PROPERTY(zTextureURL, ""),
|
||||
CONSTRUCT_PROPERTY(xNNeighborID, UNKNOWN_ENTITY_ID),
|
||||
CONSTRUCT_PROPERTY(yNNeighborID, UNKNOWN_ENTITY_ID),
|
||||
CONSTRUCT_PROPERTY(zNNeighborID, UNKNOWN_ENTITY_ID),
|
||||
CONSTRUCT_PROPERTY(xPNeighborID, UNKNOWN_ENTITY_ID),
|
||||
CONSTRUCT_PROPERTY(yPNeighborID, UNKNOWN_ENTITY_ID),
|
||||
CONSTRUCT_PROPERTY(zPNeighborID, UNKNOWN_ENTITY_ID),
|
||||
|
||||
|
||||
_id(UNKNOWN_ENTITY_ID),
|
||||
_idSet(false),
|
||||
|
@ -377,6 +384,12 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_X_TEXTURE_URL, xTextureURL);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Y_TEXTURE_URL, yTextureURL);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Z_TEXTURE_URL, zTextureURL);
|
||||
CHECK_PROPERTY_CHANGE(PROP_X_N_NEIGHBOR_ID, xNNeighborID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Y_N_NEIGHBOR_ID, yNNeighborID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Z_N_NEIGHBOR_ID, zNNeighborID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_X_P_NEIGHBOR_ID, xPNeighborID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Y_P_NEIGHBOR_ID, yPNeighborID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID);
|
||||
|
||||
changedProperties += _stage.getChangedProperties();
|
||||
changedProperties += _atmosphere.getChangedProperties();
|
||||
|
@ -521,6 +534,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(yTextureURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(zTextureURL);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(xNNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(yNNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(zNNeighborID);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(xPNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(yPNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(zPNeighborID);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -620,6 +641,14 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(yTextureURL, QString, setYTextureURL);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(zTextureURL, QString, setZTextureURL);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(xNNeighborID, EntityItemID, setXNNeighborID);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(yNNeighborID, EntityItemID, setYNNeighborID);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(zNNeighborID, EntityItemID, setZNNeighborID);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(xPNeighborID, EntityItemID, setXPNeighborID);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(yPNeighborID, EntityItemID, setYPNeighborID);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(zPNeighborID, EntityItemID, setZPNeighborID);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -852,6 +881,12 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_X_TEXTURE_URL, properties.getXTextureURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_TEXTURE_URL, properties.getYTextureURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_TEXTURE_URL, properties.getZTextureURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_X_N_NEIGHBOR_ID, properties.getXNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_N_NEIGHBOR_ID, properties.getYNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_N_NEIGHBOR_ID, properties.getZNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_X_P_NEIGHBOR_ID, properties.getXPNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_P_NEIGHBOR_ID, properties.getYPNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_P_NEIGHBOR_ID, properties.getZPNeighborID());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Line) {
|
||||
|
@ -1115,6 +1150,12 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_X_TEXTURE_URL, QString, setXTextureURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Y_TEXTURE_URL, QString, setYTextureURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Z_TEXTURE_URL, QString, setZTextureURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_X_N_NEIGHBOR_ID, EntityItemID, setXNNeighborID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Y_N_NEIGHBOR_ID, EntityItemID, setYNNeighborID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Z_N_NEIGHBOR_ID, EntityItemID, setZNNeighborID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_X_P_NEIGHBOR_ID, EntityItemID, setXPNeighborID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Y_P_NEIGHBOR_ID, EntityItemID, setYPNeighborID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Z_P_NEIGHBOR_ID, EntityItemID, setZPNeighborID);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Line) {
|
||||
|
@ -1248,13 +1289,21 @@ void EntityItemProperties::markAllChanged() {
|
|||
_descriptionChanged = true;
|
||||
_faceCameraChanged = true;
|
||||
_actionDataChanged = true;
|
||||
|
||||
|
||||
_normalsChanged = true;
|
||||
_strokeWidthsChanged = true;
|
||||
|
||||
_xTextureURLChanged = true;
|
||||
_yTextureURLChanged = true;
|
||||
_zTextureURLChanged = true;
|
||||
|
||||
_xNNeighborIDChanged = true;
|
||||
_yNNeighborIDChanged = true;
|
||||
_zNNeighborIDChanged = true;
|
||||
|
||||
_xPNeighborIDChanged = true;
|
||||
_yPNeighborIDChanged = true;
|
||||
_zPNeighborIDChanged = true;
|
||||
}
|
||||
|
||||
/// The maximum bounding cube for the entity, independent of it's rotation.
|
||||
|
|
|
@ -161,6 +161,12 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString);
|
||||
DEFINE_PROPERTY_REF(PROP_X_N_NEIGHBOR_ID, XNNeighborID, xNNeighborID, EntityItemID);
|
||||
DEFINE_PROPERTY_REF(PROP_Y_N_NEIGHBOR_ID, YNNeighborID, yNNeighborID, EntityItemID);
|
||||
DEFINE_PROPERTY_REF(PROP_Z_N_NEIGHBOR_ID, ZNNeighborID, zNNeighborID, EntityItemID);
|
||||
DEFINE_PROPERTY_REF(PROP_X_P_NEIGHBOR_ID, XPNeighborID, xPNeighborID, EntityItemID);
|
||||
DEFINE_PROPERTY_REF(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID);
|
||||
DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID);
|
||||
|
||||
static QString getBackgroundModeString(BackgroundMode mode);
|
||||
|
||||
|
@ -327,6 +333,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, XTextureURL, xTextureURL, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, YTextureURL, yTextureURL, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ZTextureURL, zTextureURL, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, XNNeighborID, xNNeighborID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, YNNeighborID, yNNeighborID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ZNNeighborID, zNNeighborID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, XPNeighborID, xPNeighborID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, YPNeighborID, yPNeighborID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ZPNeighborID, zPNeighborID, "");
|
||||
|
||||
properties.getStage().debugDump();
|
||||
properties.getAtmosphere().debugDump();
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#ifndef hifi_EntityItemPropertiesMacros_h
|
||||
#define hifi_EntityItemPropertiesMacros_h
|
||||
|
||||
#include "EntityItemID.h"
|
||||
|
||||
#define APPEND_ENTITY_PROPERTY(P,V) \
|
||||
if (requestedProperties.getHasProperty(P)) { \
|
||||
LevelDetails propertyLevel = packetData->startLevel(); \
|
||||
|
@ -106,6 +108,9 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) {
|
|||
return QScriptValue(QString(b64));
|
||||
}
|
||||
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, const EntityItemID& v) { return QScriptValue(QUuid(v).toString()); }
|
||||
|
||||
|
||||
#define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(G,g,P,p) \
|
||||
if (!skipDefaults || defaultEntityProperties.get##G().get##P() != get##P()) { \
|
||||
QScriptValue groupProperties = properties.property(#g); \
|
||||
|
@ -143,6 +148,9 @@ inline int int_convertFromScriptValue(const QScriptValue& v, bool& isValid) { re
|
|||
inline bool bool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); }
|
||||
inline QString QString_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toString().trimmed(); }
|
||||
inline QUuid QUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); }
|
||||
inline EntityItemID EntityItemID_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); }
|
||||
|
||||
|
||||
|
||||
inline QDateTime QDateTime_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
|
||||
isValid = true;
|
||||
|
|
|
@ -124,21 +124,28 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_FACE_CAMERA,
|
||||
PROP_SCRIPT_TIMESTAMP,
|
||||
|
||||
|
||||
PROP_ACTION_DATA,
|
||||
|
||||
|
||||
PROP_X_TEXTURE_URL, // used by PolyVox
|
||||
PROP_Y_TEXTURE_URL, // used by PolyVox
|
||||
PROP_Z_TEXTURE_URL, // used by PolyVox
|
||||
|
||||
|
||||
// Used by PolyLine entity
|
||||
PROP_NORMALS,
|
||||
PROP_STROKE_WIDTHS,
|
||||
|
||||
|
||||
// used by particles
|
||||
PROP_VELOCITY_SPREAD,
|
||||
PROP_ACCELERATION_SPREAD,
|
||||
|
||||
PROP_X_N_NEIGHBOR_ID, // used by PolyVox
|
||||
PROP_Y_N_NEIGHBOR_ID, // used by PolyVox
|
||||
PROP_Z_N_NEIGHBOR_ID, // used by PolyVox
|
||||
PROP_X_P_NEIGHBOR_ID, // used by PolyVox
|
||||
PROP_Y_P_NEIGHBOR_ID, // used by PolyVox
|
||||
PROP_Z_P_NEIGHBOR_ID, // used by PolyVox
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -493,6 +493,13 @@ bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
|
|||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||
const glm::vec3& cuboidSize, int value) {
|
||||
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
|
||||
EntityItemPointer entity = static_cast<EntityItemPointer>(_entityTree->findEntityByEntityItemID(entityID));
|
||||
if (!entity) {
|
||||
|
|
|
@ -122,6 +122,8 @@ public slots:
|
|||
Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value);
|
||||
Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value);
|
||||
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
||||
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||
const glm::vec3& cuboidSize, int value);
|
||||
|
||||
Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector<glm::vec3>& points);
|
||||
Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QWriteLocker>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
|
||||
|
@ -56,13 +57,14 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent
|
|||
_voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE),
|
||||
_xTextureURL(PolyVoxEntityItem::DEFAULT_X_TEXTURE_URL),
|
||||
_yTextureURL(PolyVoxEntityItem::DEFAULT_Y_TEXTURE_URL),
|
||||
_zTextureURL(PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL)
|
||||
{
|
||||
_zTextureURL(PolyVoxEntityItem::DEFAULT_Z_TEXTURE_URL) {
|
||||
_type = EntityTypes::PolyVox;
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
||||
QWriteLocker(&this->_voxelDataLock);
|
||||
|
||||
assert((int)_voxelVolumeSize.x == _voxelVolumeSize.x);
|
||||
assert((int)_voxelVolumeSize.y == _voxelVolumeSize.y);
|
||||
assert((int)_voxelVolumeSize.z == _voxelVolumeSize.z);
|
||||
|
@ -96,6 +98,12 @@ void PolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
|||
}
|
||||
}
|
||||
|
||||
const glm::vec3& PolyVoxEntityItem::getVoxelVolumeSize() const {
|
||||
QWriteLocker locker(&this->_voxelDataLock);
|
||||
return _voxelVolumeSize;
|
||||
}
|
||||
|
||||
|
||||
EntityItemProperties PolyVoxEntityItem::getProperties() const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(voxelVolumeSize, getVoxelVolumeSize);
|
||||
|
@ -104,6 +112,12 @@ EntityItemProperties PolyVoxEntityItem::getProperties() const {
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(xTextureURL, getXTextureURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(yTextureURL, getYTextureURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(zTextureURL, getZTextureURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(xNNeighborID, getXNNeighborID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(yNNeighborID, getYNNeighborID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(zNNeighborID, getZNNeighborID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(xPNeighborID, getXPNeighborID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(yPNeighborID, getYPNeighborID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(zPNeighborID, getZPNeighborID);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@ -116,6 +130,12 @@ bool PolyVoxEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(xTextureURL, setXTextureURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(yTextureURL, setYTextureURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(zTextureURL, setZTextureURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(xNNeighborID, setXNNeighborID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(yNNeighborID, setYNNeighborID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(zNNeighborID, setZNNeighborID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(xPNeighborID, setXPNeighborID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(yPNeighborID, setYPNeighborID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(zPNeighborID, setZPNeighborID);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -143,6 +163,12 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat
|
|||
READ_ENTITY_PROPERTY(PROP_X_TEXTURE_URL, QString, setXTextureURL);
|
||||
READ_ENTITY_PROPERTY(PROP_Y_TEXTURE_URL, QString, setYTextureURL);
|
||||
READ_ENTITY_PROPERTY(PROP_Z_TEXTURE_URL, QString, setZTextureURL);
|
||||
READ_ENTITY_PROPERTY(PROP_X_N_NEIGHBOR_ID, EntityItemID, setXNNeighborID);
|
||||
READ_ENTITY_PROPERTY(PROP_Y_N_NEIGHBOR_ID, EntityItemID, setYNNeighborID);
|
||||
READ_ENTITY_PROPERTY(PROP_Z_N_NEIGHBOR_ID, EntityItemID, setZNNeighborID);
|
||||
READ_ENTITY_PROPERTY(PROP_X_P_NEIGHBOR_ID, EntityItemID, setXPNeighborID);
|
||||
READ_ENTITY_PROPERTY(PROP_Y_P_NEIGHBOR_ID, EntityItemID, setYPNeighborID);
|
||||
READ_ENTITY_PROPERTY(PROP_Z_P_NEIGHBOR_ID, EntityItemID, setZPNeighborID);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -157,16 +183,22 @@ EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams
|
|||
requestedProperties += PROP_X_TEXTURE_URL;
|
||||
requestedProperties += PROP_Y_TEXTURE_URL;
|
||||
requestedProperties += PROP_Z_TEXTURE_URL;
|
||||
requestedProperties += PROP_X_N_NEIGHBOR_ID;
|
||||
requestedProperties += PROP_Y_N_NEIGHBOR_ID;
|
||||
requestedProperties += PROP_Z_N_NEIGHBOR_ID;
|
||||
requestedProperties += PROP_X_P_NEIGHBOR_ID;
|
||||
requestedProperties += PROP_Y_P_NEIGHBOR_ID;
|
||||
requestedProperties += PROP_Z_P_NEIGHBOR_ID;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void PolyVoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
void PolyVoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
bool successPropertyFits = true;
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_VOXEL_VOLUME_SIZE, getVoxelVolumeSize());
|
||||
|
@ -175,7 +207,12 @@ void PolyVoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeB
|
|||
APPEND_ENTITY_PROPERTY(PROP_X_TEXTURE_URL, getXTextureURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_TEXTURE_URL, getYTextureURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_TEXTURE_URL, getZTextureURL());
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_X_N_NEIGHBOR_ID, getXNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_N_NEIGHBOR_ID, getYNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_N_NEIGHBOR_ID, getZNNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_X_P_NEIGHBOR_ID, getXPNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Y_P_NEIGHBOR_ID, getYPNeighborID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_Z_P_NEIGHBOR_ID, getZPNeighborID());
|
||||
}
|
||||
|
||||
void PolyVoxEntityItem::debugDump() const {
|
||||
|
@ -187,15 +224,12 @@ void PolyVoxEntityItem::debugDump() const {
|
|||
}
|
||||
|
||||
void PolyVoxEntityItem::setVoxelData(QByteArray voxelData) {
|
||||
_voxelDataLock.lockForWrite();
|
||||
QWriteLocker(&this->_voxelDataLock);
|
||||
_voxelData = voxelData;
|
||||
_voxelDataDirty = true;
|
||||
_voxelDataLock.unlock();
|
||||
}
|
||||
|
||||
const QByteArray PolyVoxEntityItem::getVoxelData() const {
|
||||
_voxelDataLock.lockForRead();
|
||||
auto result = _voxelData;
|
||||
_voxelDataLock.unlock();
|
||||
return result;
|
||||
QReadLocker(&this->_voxelDataLock);
|
||||
return _voxelData;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual void debugDump() const;
|
||||
|
||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
||||
virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; }
|
||||
virtual const glm::vec3& getVoxelVolumeSize() const;
|
||||
|
||||
virtual void setVoxelData(QByteArray voxelData);
|
||||
virtual const QByteArray getVoxelData() const;
|
||||
|
@ -85,6 +85,7 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
// coords are in world-space
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; }
|
||||
virtual bool setAll(uint8_t toValue) { return false; }
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value) { return false; }
|
||||
|
||||
virtual uint8_t getVoxel(int x, int y, int z) { return 0; }
|
||||
virtual bool setVoxel(int x, int y, int z, uint8_t toValue) { return false; }
|
||||
|
@ -103,6 +104,28 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual void setZTextureURL(QString zTextureURL) { _zTextureURL = zTextureURL; }
|
||||
virtual const QString& getZTextureURL() const { return _zTextureURL; }
|
||||
|
||||
virtual void setXNNeighborID(const EntityItemID& xNNeighborID) { _xNNeighborID = xNNeighborID; }
|
||||
void setXNNeighborID(const QString& xNNeighborID) { setXNNeighborID(QUuid(xNNeighborID)); }
|
||||
virtual const EntityItemID& getXNNeighborID() const { return _xNNeighborID; }
|
||||
virtual void setYNNeighborID(const EntityItemID& yNNeighborID) { _yNNeighborID = yNNeighborID; }
|
||||
void setYNNeighborID(const QString& yNNeighborID) { setYNNeighborID(QUuid(yNNeighborID)); }
|
||||
virtual const EntityItemID& getYNNeighborID() const { return _yNNeighborID; }
|
||||
virtual void setZNNeighborID(const EntityItemID& zNNeighborID) { _zNNeighborID = zNNeighborID; }
|
||||
void setZNNeighborID(const QString& zNNeighborID) { setZNNeighborID(QUuid(zNNeighborID)); }
|
||||
virtual const EntityItemID& getZNNeighborID() const { return _zNNeighborID; }
|
||||
|
||||
virtual void setXPNeighborID(const EntityItemID& xPNeighborID) { _xPNeighborID = xPNeighborID; }
|
||||
void setXPNeighborID(const QString& xPNeighborID) { setXPNeighborID(QUuid(xPNeighborID)); }
|
||||
virtual const EntityItemID& getXPNeighborID() const { return _xPNeighborID; }
|
||||
virtual void setYPNeighborID(const EntityItemID& yPNeighborID) { _yPNeighborID = yPNeighborID; }
|
||||
void setYPNeighborID(const QString& yPNeighborID) { setYPNeighborID(QUuid(yPNeighborID)); }
|
||||
virtual const EntityItemID& getYPNeighborID() const { return _yPNeighborID; }
|
||||
virtual void setZPNeighborID(const EntityItemID& zPNeighborID) { _zPNeighborID = zPNeighborID; }
|
||||
void setZPNeighborID(const QString& zPNeighborID) { setZPNeighborID(QUuid(zPNeighborID)); }
|
||||
virtual const EntityItemID& getZPNeighborID() const { return _zPNeighborID; }
|
||||
|
||||
virtual void rebakeMesh() {};
|
||||
|
||||
protected:
|
||||
glm::vec3 _voxelVolumeSize; // this is always 3 bytes
|
||||
|
||||
|
@ -116,6 +139,14 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
QString _yTextureURL;
|
||||
QString _zTextureURL;
|
||||
|
||||
// for non-edged surface styles, these are used to compute the high-axis edges
|
||||
EntityItemID _xNNeighborID{UNKNOWN_ENTITY_ID};
|
||||
EntityItemID _yNNeighborID{UNKNOWN_ENTITY_ID};
|
||||
EntityItemID _zNNeighborID{UNKNOWN_ENTITY_ID};
|
||||
|
||||
EntityItemID _xPNeighborID{UNKNOWN_ENTITY_ID};
|
||||
EntityItemID _yPNeighborID{UNKNOWN_ENTITY_ID};
|
||||
EntityItemID _zPNeighborID{UNKNOWN_ENTITY_ID};
|
||||
};
|
||||
|
||||
#endif // hifi_PolyVoxEntityItem_h
|
||||
|
|
|
@ -1722,7 +1722,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
glm::vec3 rotationOffset;
|
||||
glm::vec3 preRotation, rotation, postRotation;
|
||||
glm::vec3 scale = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||
glm::vec3 scalePivot, rotationPivot;
|
||||
glm::vec3 scalePivot, rotationPivot, scaleOffset;
|
||||
bool rotationMinX = false, rotationMinY = false, rotationMinZ = false;
|
||||
bool rotationMaxX = false, rotationMaxY = false, rotationMaxZ = false;
|
||||
glm::vec3 rotationMin, rotationMax;
|
||||
|
@ -1771,12 +1771,14 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
} else if (property.properties.at(0) == "Lcl Scaling") {
|
||||
scale = getVec3(property.properties, index);
|
||||
|
||||
} else if (property.properties.at(0) == "ScalingOffset") {
|
||||
scaleOffset = getVec3(property.properties, index);
|
||||
|
||||
// NOTE: these rotation limits are stored in degrees (NOT radians)
|
||||
} else if (property.properties.at(0) == "RotationMin") {
|
||||
rotationMin = getVec3(property.properties, index);
|
||||
|
||||
}
|
||||
// NOTE: these rotation limits are stored in degrees (NOT radians)
|
||||
else if (property.properties.at(0) == "RotationMax") {
|
||||
} else if (property.properties.at(0) == "RotationMax") {
|
||||
rotationMax = getVec3(property.properties, index);
|
||||
|
||||
} else if (property.properties.at(0) == "RotationMinX") {
|
||||
|
@ -1843,8 +1845,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
model.preRotation = glm::quat(glm::radians(preRotation));
|
||||
model.rotation = glm::quat(glm::radians(rotation));
|
||||
model.postRotation = glm::quat(glm::radians(postRotation));
|
||||
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) *
|
||||
glm::scale(scale) * glm::translate(-scalePivot);
|
||||
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scaleOffset) *
|
||||
glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot);
|
||||
// NOTE: angles from the FBX file are in degrees
|
||||
// so we convert them to radians for the FBXModel class
|
||||
model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f,
|
||||
|
@ -2306,7 +2308,9 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
joint.bindTransformFoundInCluster = false;
|
||||
|
||||
geometry.joints.append(joint);
|
||||
geometry.jointIndices.insert(model.name, geometry.joints.size());
|
||||
|
||||
|
@ -2534,7 +2538,8 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
FBXJoint& joint = geometry.joints[fbxCluster.jointIndex];
|
||||
joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink));
|
||||
joint.bindTransform = cluster.transformLink;
|
||||
|
||||
joint.bindTransformFoundInCluster = true;
|
||||
|
||||
// update the bind pose extents
|
||||
glm::vec3 bindTranslation = extractTranslation(geometry.offset * joint.bindTransform);
|
||||
geometry.bindExtents.addPoint(bindTranslation);
|
||||
|
@ -2557,6 +2562,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
int maxJointIndex = firstFBXCluster.jointIndex;
|
||||
glm::mat4 inverseModelTransform = glm::inverse(modelTransform);
|
||||
if (clusterIDs.size() > 1) {
|
||||
// this is a multi-mesh joint
|
||||
extracted.mesh.clusterIndices.resize(extracted.mesh.vertices.size());
|
||||
extracted.mesh.clusterWeights.resize(extracted.mesh.vertices.size());
|
||||
float maxWeight = 0.0f;
|
||||
|
@ -2640,6 +2646,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// this is a single-mesh joint
|
||||
int jointIndex = maxJointIndex;
|
||||
FBXJoint& joint = geometry.joints[jointIndex];
|
||||
JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex];
|
||||
|
@ -2660,6 +2667,7 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
}
|
||||
float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix);
|
||||
|
||||
// compute average vertex
|
||||
glm::vec3 averageVertex(0.0f);
|
||||
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
|
||||
float proj = glm::dot(boneDirection, boneEnd - vertex);
|
||||
|
@ -2669,16 +2677,26 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
++jointShapeInfo.numVertexWeights;
|
||||
averageVertex += vertex;
|
||||
}
|
||||
|
||||
// compute joint's radius
|
||||
int numVertices = extracted.mesh.vertices.size();
|
||||
jointShapeInfo.numVertices = numVertices;
|
||||
if (numVertices > 0) {
|
||||
// compute average radius
|
||||
averageVertex /= (float)jointShapeInfo.numVertices;
|
||||
float averageRadius = 0.0f;
|
||||
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
|
||||
averageRadius += glm::distance(vertex, averageVertex);
|
||||
}
|
||||
jointShapeInfo.averageRadius = averageRadius * radiusScale;
|
||||
averageRadius *= radiusScale / (float)jointShapeInfo.numVertices;
|
||||
|
||||
// final radius is minimum of average and weighted
|
||||
float weightedRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights;
|
||||
jointShapeInfo.averageRadius = glm::min(weightedRadius, averageRadius);
|
||||
}
|
||||
|
||||
// clear sumVertexWeights (this flags it as a single-mesh joint for later)
|
||||
jointShapeInfo.sumVertexWeights = 0.0f;
|
||||
}
|
||||
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
|
||||
|
||||
|
@ -2714,23 +2732,12 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
|
|||
}
|
||||
|
||||
if (jointShapeInfo.sumVertexWeights > 0.0f) {
|
||||
// mutiple meshes contributed to the bone radius and now that all
|
||||
// contributing meshes are done we can finally compute the boneRadius
|
||||
joint.boneRadius = jointShapeInfo.sumWeightedRadii / jointShapeInfo.sumVertexWeights;
|
||||
}
|
||||
|
||||
// the joint is "capsule-like" if it had ANY mesh vertices successfully projected onto the bone
|
||||
// AND its boneRadius is not too close to zero
|
||||
bool collideLikeCapsule = jointShapeInfo.numVertexWeights > 0
|
||||
&& glm::length(jointShapeInfo.boneBegin) > EPSILON;
|
||||
|
||||
if (!collideLikeCapsule) {
|
||||
// this joint's mesh did not successfully project onto the bone axis
|
||||
// so it isn't "capsule-like" and we need to estimate its radius a different way:
|
||||
// the average radius to the average point.
|
||||
if (jointShapeInfo.numVertexWeights == 0
|
||||
&& jointShapeInfo.numVertices > 0) {
|
||||
jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices;
|
||||
joint.boneRadius = jointShapeInfo.averageRadius;
|
||||
}
|
||||
} else {
|
||||
// single-mesh joint
|
||||
joint.boneRadius = jointShapeInfo.averageRadius;
|
||||
}
|
||||
}
|
||||
geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString());
|
||||
|
|
|
@ -64,12 +64,18 @@ public:
|
|||
int parentIndex;
|
||||
float distanceToParent;
|
||||
float boneRadius;
|
||||
glm::vec3 translation;
|
||||
glm::mat4 preTransform;
|
||||
glm::quat preRotation;
|
||||
glm::quat rotation;
|
||||
glm::quat postRotation;
|
||||
glm::mat4 postTransform;
|
||||
|
||||
// http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/SDKRef/a00209.html
|
||||
|
||||
glm::vec3 translation; // T
|
||||
glm::mat4 preTransform; // Roff * Rp
|
||||
glm::quat preRotation; // Rpre
|
||||
glm::quat rotation; // R
|
||||
glm::quat postRotation; // Rpost
|
||||
glm::mat4 postTransform; // Rp-1 * Soff * Sp * S * Sp-1
|
||||
|
||||
// World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1)
|
||||
|
||||
glm::mat4 transform;
|
||||
glm::vec3 rotationMin; // radians
|
||||
glm::vec3 rotationMax; // radians
|
||||
|
@ -78,6 +84,7 @@ public:
|
|||
glm::mat4 bindTransform;
|
||||
QString name;
|
||||
bool isSkeletonJoint;
|
||||
bool bindTransformFoundInCluster;
|
||||
};
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue