mirror of
https://github.com/overte-org/overte.git
synced 2025-07-22 16:13:34 +02:00
Merge pull request #1 from imgntn/ericrius1-grabAPI
Updates to handcontroller grab script
This commit is contained in:
commit
aa5a2aa741
224 changed files with 10570 additions and 6730 deletions
|
@ -71,7 +71,7 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll
|
|||
|
||||
To prevent these problems, install OpenSSL yourself. Download the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html):
|
||||
* Visual C++ 2008 Redistributables
|
||||
* Win32 OpenSSL v1.0.1m
|
||||
* Win32 OpenSSL v1.0.1p
|
||||
|
||||
Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version.
|
||||
|
||||
|
|
|
@ -1744,13 +1744,26 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
|
||||
// if the domain-server has just restarted,
|
||||
// check if there are static assignments that we need to throw into the assignment queue
|
||||
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _allAssignments;
|
||||
QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin();
|
||||
while (staticAssignment != staticHashCopy.end()) {
|
||||
auto sharedAssignments = _allAssignments.values();
|
||||
|
||||
// sort the assignments to put the server/mixer assignments first
|
||||
qSort(sharedAssignments.begin(), sharedAssignments.end(), [](SharedAssignmentPointer a, SharedAssignmentPointer b){
|
||||
if (a->getType() == b->getType()) {
|
||||
return true;
|
||||
} else if (a->getType() != Assignment::AgentType && b->getType() != Assignment::AgentType) {
|
||||
return a->getType() < b->getType();
|
||||
} else {
|
||||
return a->getType() != Assignment::AgentType;
|
||||
}
|
||||
});
|
||||
|
||||
auto staticAssignment = sharedAssignments.begin();
|
||||
|
||||
while (staticAssignment != sharedAssignments.end()) {
|
||||
// add any of the un-matched static assignments to the queue
|
||||
|
||||
// enumerate the nodes and check if there is one with an attached assignment with matching UUID
|
||||
if (!DependencyManager::get<LimitedNodeList>()->nodeWithUUID(staticAssignment->data()->getUUID())) {
|
||||
if (!DependencyManager::get<LimitedNodeList>()->nodeWithUUID((*staticAssignment)->getUUID())) {
|
||||
// this assignment has not been fulfilled - reset the UUID and add it to the assignment queue
|
||||
refreshStaticAssignmentAndAddToQueue(*staticAssignment);
|
||||
}
|
||||
|
|
|
@ -8,74 +8,91 @@
|
|||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
|
||||
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var RADIUS_FACTOR = 4;
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// these tune time-averaging and "on" value for analog trigger
|
||||
//
|
||||
|
||||
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
|
||||
var rightTriggerAction = RIGHT_HAND_CLICK;
|
||||
var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value
|
||||
var TRIGGER_ON_VALUE = 0.2;
|
||||
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// distant manipulation
|
||||
//
|
||||
|
||||
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
|
||||
var leftTriggerAction = LEFT_HAND_CLICK;
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
}; // line color when pick misses
|
||||
var INTERSECT_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
}; // line color when pick hits
|
||||
var LINE_ENTITY_DIMENSIONS = {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
};
|
||||
var LINE_LENGTH = 500;
|
||||
|
||||
var LIFETIME = 10;
|
||||
var EXTRA_TIME = 5;
|
||||
var POINTER_CHECK_TIME = 5000;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// near grabbing
|
||||
//
|
||||
|
||||
var GRAB_RADIUS = 0.3; // if the ray misses but an object is this close, it will still be selected
|
||||
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
|
||||
var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable.
|
||||
var NEAR_PICK_MAX_DISTANCE = 0.6; // max length of pick-ray for close grabbing to be selected
|
||||
var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// other constants
|
||||
//
|
||||
|
||||
var RIGHT_HAND = 1;
|
||||
var LEFT_HAND = 0;
|
||||
|
||||
var ZERO_VEC = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
var LINE_LENGTH = 500;
|
||||
var THICK_LINE_WIDTH = 7;
|
||||
var THIN_LINE_WIDTH = 2;
|
||||
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
};
|
||||
var INTERSECT_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
};
|
||||
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
|
||||
var MSEC_PER_SEC = 1000.0;
|
||||
|
||||
var GRAB_RADIUS = 0.3;
|
||||
|
||||
var GRAB_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 250
|
||||
};
|
||||
var SHOW_LINE_THRESHOLD = 0.2;
|
||||
var DISTANCE_HOLD_THRESHOLD = 0.8;
|
||||
|
||||
var right4Action = 18;
|
||||
var left4Action = 17;
|
||||
|
||||
var RIGHT = 1;
|
||||
var LEFT = 0;
|
||||
var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right");
|
||||
var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left");
|
||||
// these control how long an abandoned pointer line will hang around
|
||||
var startTime = Date.now();
|
||||
var LIFETIME = 10;
|
||||
|
||||
// states for the state machine
|
||||
var STATE_SEARCHING = 0;
|
||||
var STATE_DISTANCE_HOLDING = 1;
|
||||
var STATE_CONTINUE_DISTANCE_HOLDING = 2;
|
||||
var STATE_NEAR_GRABBING = 3;
|
||||
var STATE_CONTINUE_NEAR_GRABBING = 4;
|
||||
var STATE_NEAR_GRABBING_NON_COLLIDING = 5;
|
||||
var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 6;
|
||||
var STATE_RELEASE = 7;
|
||||
|
||||
|
||||
//Need to wait before calling these methods for some reason...
|
||||
Script.setTimeout(function() {
|
||||
rightController.checkPointer();
|
||||
leftController.checkPointer();
|
||||
}, 100)
|
||||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
|
||||
function controller(side, triggerAction, pullAction, hand) {
|
||||
function controller(hand, triggerAction) {
|
||||
this.hand = hand;
|
||||
if (hand === "right") {
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
this.getHandPosition = MyAvatar.getRightPalmPosition;
|
||||
this.getHandRotation = MyAvatar.getRightPalmRotation;
|
||||
} else {
|
||||
|
@ -83,309 +100,469 @@ function controller(side, triggerAction, pullAction, hand) {
|
|||
this.getHandRotation = MyAvatar.getLeftPalmRotation;
|
||||
}
|
||||
this.triggerAction = triggerAction;
|
||||
this.pullAction = pullAction;
|
||||
this.actionID = null;
|
||||
this.distanceHolding = false;
|
||||
this.closeGrabbing = false;
|
||||
this.triggerValue = 0;
|
||||
this.prevTriggerValue = 0;
|
||||
this.palm = 2 * side;
|
||||
this.tip = 2 * side + 1;
|
||||
this.pointer = null;
|
||||
}
|
||||
this.palm = 2 * hand;
|
||||
// this.tip = 2 * hand + 1; // unused, but I'm leaving this here for fear it will be needed
|
||||
|
||||
|
||||
controller.prototype.updateLine = function() {
|
||||
if (this.pointer != null) {
|
||||
if (Entities.getEntityProperties(this.pointer).id != this.pointer) {
|
||||
this.pointer = null;
|
||||
this.actionID = null; // action this script created...
|
||||
this.grabbedEntity = null; // on this entity.
|
||||
this.grabbedVelocity = ZERO_VEC; // rolling average of held object's velocity
|
||||
this.state = 0;
|
||||
this.pointer = null; // entity-id of line object
|
||||
this.triggerValue = 0; // rolling average of trigger value
|
||||
var _this = this;
|
||||
this.update = function () {
|
||||
switch (this.state) {
|
||||
case STATE_SEARCHING:
|
||||
this.search();
|
||||
this.touchTest();
|
||||
break;
|
||||
case STATE_DISTANCE_HOLDING:
|
||||
this.distanceHolding();
|
||||
break;
|
||||
case STATE_CONTINUE_DISTANCE_HOLDING:
|
||||
this.continueDistanceHolding();
|
||||
break;
|
||||
case STATE_NEAR_GRABBING:
|
||||
this.nearGrabbing();
|
||||
break;
|
||||
case STATE_CONTINUE_NEAR_GRABBING:
|
||||
this.continueNearGrabbing();
|
||||
break;
|
||||
case STATE_NEAR_GRABBING_NON_COLLIDING:
|
||||
this.nearGrabbingNonColliding();
|
||||
break;
|
||||
case STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING:
|
||||
this.continueNearGrabbingNonColliding();
|
||||
break;
|
||||
case STATE_RELEASE:
|
||||
this.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pointer == null) {
|
||||
this.lineCreationTime = Date.now();
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
color: NO_INTERSECT_COLOR,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
visible: true,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
}
|
||||
|
||||
var handPosition = this.getHandPosition();
|
||||
var direction = Quat.getUp(this.getHandRotation());
|
||||
|
||||
//only check if we havent already grabbed an object
|
||||
if (this.distanceHolding) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [ ZERO_VEC, Vec3.subtract(Entities.getEntityProperties(this.grabbedEntity).position, handPosition) ],
|
||||
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Entities.editEntity(this.pointer, {
|
||||
position: handPosition,
|
||||
linePoints: [ ZERO_VEC, Vec3.multiply(direction, LINE_LENGTH) ],
|
||||
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||
});
|
||||
|
||||
if (this.checkForIntersections(handPosition, direction)) {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: INTERSECT_COLOR,
|
||||
});
|
||||
} else {
|
||||
Entities.editEntity(this.pointer, {
|
||||
color: NO_INTERSECT_COLOR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
controller.prototype.checkPointer = function() {
|
||||
var self = this;
|
||||
Script.setTimeout(function() {
|
||||
var props = Entities.getEntityProperties(self.pointer);
|
||||
Entities.editEntity(self.pointer, {
|
||||
lifetime: (Date.now() - startTime) / 1000.0 + LIFETIME
|
||||
});
|
||||
self.checkPointer();
|
||||
}, POINTER_CHECK_TIME);
|
||||
}
|
||||
|
||||
controller.prototype.checkForIntersections = function(origin, direction) {
|
||||
var pickRay = {
|
||||
origin: origin,
|
||||
direction: direction
|
||||
};
|
||||
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && intersection.properties.collisionsWillMove === 1) {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.distanceToEntity = Vec3.distance(handPosition, intersection.properties.position);
|
||||
var intersectionDistance = Vec3.distance(handPosition, intersection.intersection);
|
||||
|
||||
if (intersectionDistance < 0.6) {
|
||||
//We are grabbing an entity, so let it know we've grabbed it
|
||||
this.grabbedEntity = intersection.entityID;
|
||||
this.activateEntity(this.grabbedEntity);
|
||||
this.hidePointer();
|
||||
this.shouldDisplayLine = false;
|
||||
this.grabEntity();
|
||||
return true;
|
||||
_this.pointerIDs = [];
|
||||
this.lineOn = function (closePoint, farPoint, color) {
|
||||
// draw a line
|
||||
if (this.pointer === null) {
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "pointer",
|
||||
dimensions: LINE_ENTITY_DIMENSIONS,
|
||||
visible: true,
|
||||
position: closePoint,
|
||||
linePoints: [ZERO_VEC, farPoint],
|
||||
color: color,
|
||||
lifetime: LIFETIME
|
||||
});
|
||||
_this.pointerIDs.push(this.pointer);
|
||||
} else {
|
||||
Entities.editEntity(this.pointer, {
|
||||
linePoints: [
|
||||
ZERO_VEC,
|
||||
Vec3.multiply(direction, this.distanceToEntity)
|
||||
]
|
||||
position: closePoint,
|
||||
linePoints: [ZERO_VEC, farPoint],
|
||||
color: color,
|
||||
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
this.lineOff = function () {
|
||||
if (this.pointer !== null) {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
}
|
||||
var index = _this.pointerIDs.indexOf(this.pointer);
|
||||
if (index > -1) {
|
||||
_this.pointerIDs.splice(index, 1);
|
||||
}
|
||||
this.pointer = null;
|
||||
};
|
||||
|
||||
|
||||
this.triggerSmoothedSqueezed = function () {
|
||||
var triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
// smooth out trigger value
|
||||
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
||||
(triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO));
|
||||
return this.triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
|
||||
this.triggerSqueezed = function () {
|
||||
var triggerValue = Controller.getActionValue(this.triggerAction);
|
||||
return triggerValue > TRIGGER_ON_VALUE;
|
||||
};
|
||||
|
||||
|
||||
this.search = function () {
|
||||
|
||||
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
||||
// the trigger is being pressed, do a ray test
|
||||
var handPosition = this.getHandPosition();
|
||||
var pickRay = {
|
||||
origin: handPosition,
|
||||
direction: Quat.getUp(this.getHandRotation())
|
||||
};
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects &&
|
||||
intersection.properties.collisionsWillMove === 1 &&
|
||||
intersection.properties.locked === 0) {
|
||||
// the ray is intersecting something we can move.
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var intersectionDistance = Vec3.distance(handControllerPosition, intersection.intersection);
|
||||
this.grabbedEntity = intersection.entityID;
|
||||
return true;
|
||||
if (intersectionDistance < NEAR_PICK_MAX_DISTANCE) {
|
||||
// the hand is very close to the intersected object. go into close-grabbing mode.
|
||||
this.state = STATE_NEAR_GRABBING;
|
||||
} else {
|
||||
// the hand is far from the intersected object. go into distance-holding mode
|
||||
this.state = STATE_DISTANCE_HOLDING;
|
||||
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||
}
|
||||
} else {
|
||||
// forward ray test failed, try sphere test.
|
||||
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
||||
var minDistance = GRAB_RADIUS;
|
||||
var i, props, distance;
|
||||
for (i = 0; i < nearbyEntities.length; i++) {
|
||||
props = Entities.getEntityProperties(nearbyEntities[i], ["position", "name", "collisionsWillMove", "locked"]);
|
||||
distance = Vec3.distance(props.position, handPosition);
|
||||
if (distance < minDistance && props.name !== "pointer") {
|
||||
this.grabbedEntity = nearbyEntities[i];
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
if (this.grabbedEntity === null) {
|
||||
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||
} else if (props.locked === 0 && props.collisionsWillMove === 1) {
|
||||
this.state = STATE_NEAR_GRABBING;
|
||||
} else if (props.collisionsWillMove === 0) {
|
||||
// We have grabbed a non-physical object, so we want to trigger a non-colliding event as opposed to a grab event
|
||||
this.state = STATE_NEAR_GRABBING_NON_COLLIDING;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
controller.prototype.attemptMove = function() {
|
||||
if (this.grabbedEntity || this.distanceHolding) {
|
||||
var handPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.distanceHolding = function () {
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
|
||||
|
||||
this.distanceHolding = true;
|
||||
if (this.actionID === null) {
|
||||
this.currentObjectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
|
||||
this.currentObjectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
|
||||
// add the action and initialize some variables
|
||||
this.currentObjectPosition = grabbedProperties.position;
|
||||
this.currentObjectRotation = grabbedProperties.rotation;
|
||||
this.currentObjectTime = Date.now();
|
||||
this.handPreviousPosition = handControllerPosition;
|
||||
this.handPreviousRotation = handRotation;
|
||||
|
||||
this.handPreviousPosition = handPosition;
|
||||
this.handPreviousRotation = handRotation;
|
||||
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: .1,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: .1
|
||||
});
|
||||
} else {
|
||||
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handPosition) * RADIUS_FACTOR, 1.0);
|
||||
|
||||
var handMoved = Vec3.subtract(handPosition, this.handPreviousPosition);
|
||||
this.handPreviousPosition = handPosition;
|
||||
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||
|
||||
// ---------------- this tracks hand rotation
|
||||
// var handChange = Quat.multiply(handRotation, Quat.inverse(this.handPreviousRotation));
|
||||
// this.handPreviousRotation = handRotation;
|
||||
// this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||
// ----------------
|
||||
|
||||
// ---------------- this doubles hand rotation
|
||||
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation, 2.0),
|
||||
Quat.inverse(this.handPreviousRotation));
|
||||
this.handPreviousRotation = handRotation;
|
||||
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||
// ----------------
|
||||
|
||||
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: this.currentObjectPosition, linearTimeScale: .1,
|
||||
targetRotation: this.currentObjectRotation, angularTimeScale: .1
|
||||
});
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
this.actionID = null;
|
||||
this.distanceHolding = false;
|
||||
this.closeGrabbing = false;
|
||||
}
|
||||
|
||||
controller.prototype.update = function() {
|
||||
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;
|
||||
if (this.actionID !== null) {
|
||||
this.state = STATE_CONTINUE_DISTANCE_HOLDING;
|
||||
this.activateEntity(this.grabbedEntity);
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
|
||||
} else {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||
}
|
||||
}
|
||||
} else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) {
|
||||
this.hidePointer();
|
||||
this.letGo();
|
||||
this.shouldDisplayLine = false;
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
|
||||
|
||||
if (this.shouldDisplayLine) {
|
||||
this.updateLine();
|
||||
}
|
||||
if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) {
|
||||
this.attemptMove();
|
||||
}
|
||||
|
||||
this.prevTriggerValue = this.triggerValue;
|
||||
}
|
||||
};
|
||||
|
||||
controller.prototype.grabEntity = function() {
|
||||
var handRotation = this.getHandRotation();
|
||||
var handPosition = this.getHandPosition();
|
||||
this.closeGrabbing = true;
|
||||
//check if our entity has instructions on how to be grabbed, otherwise, just use default relative position and rotation
|
||||
var userData = getEntityUserData(this.grabbedEntity);
|
||||
|
||||
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);
|
||||
|
||||
var relativePosition = offsetPosition;
|
||||
var relativeRotation = offsetRotation;
|
||||
if (userData.grabFrame) {
|
||||
if (userData.grabFrame.relativePosition) {
|
||||
relativePosition = userData.grabFrame.relativePosition;
|
||||
this.continueDistanceHolding = function () {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
if (userData.grabFrame.relativeRotation) {
|
||||
relativeRotation = userData.grabFrame.relativeRotation;
|
||||
}
|
||||
}
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
hand: this.hand,
|
||||
timeScale: 0.05,
|
||||
relativePosition: relativePosition,
|
||||
relativeRotation: relativeRotation
|
||||
});
|
||||
}
|
||||
|
||||
var handPosition = this.getHandPosition();
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
|
||||
|
||||
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
|
||||
|
||||
// the action was set up on a previous call. update the targets.
|
||||
var radius = Math.max(Vec3.distance(this.currentObjectPosition,
|
||||
handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR,
|
||||
DISTANCE_HOLDING_RADIUS_FACTOR);
|
||||
|
||||
var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition);
|
||||
this.handPreviousPosition = handControllerPosition;
|
||||
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||
|
||||
var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||
var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters
|
||||
var now = Date.now();
|
||||
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
|
||||
this.computeReleaseVelocity(deltaPosition, deltaTime, false);
|
||||
|
||||
this.currentObjectPosition = newObjectPosition;
|
||||
this.currentObjectTime = now;
|
||||
|
||||
// this doubles hand rotation
|
||||
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation, handRotation,
|
||||
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
||||
Quat.inverse(this.handPreviousRotation));
|
||||
this.handPreviousRotation = handRotation;
|
||||
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab");
|
||||
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
this.nearGrabbing = function () {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (grabbedEntity === null) {
|
||||
return false;
|
||||
} else {
|
||||
//We are grabbing an entity, so let it know we've grabbed it
|
||||
this.grabbedEntity = grabbedEntity;
|
||||
|
||||
this.lineOff();
|
||||
|
||||
this.activateEntity(this.grabbedEntity);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
|
||||
|
||||
controller.prototype.activateEntity = function(entity) {
|
||||
var data = {
|
||||
activated: true,
|
||||
avatarId: MyAvatar.sessionUUID
|
||||
var handRotation = this.getHandRotation();
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
var objectRotation = grabbedProperties.rotation;
|
||||
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
||||
var currentObjectPosition = grabbedProperties.position;
|
||||
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||
relativePosition: offsetPosition,
|
||||
relativeRotation: offsetRotation
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
} else {
|
||||
this.state = STATE_CONTINUE_NEAR_GRABBING;
|
||||
if (this.hand === RIGHT_HAND) {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
|
||||
} else {
|
||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
|
||||
|
||||
}
|
||||
|
||||
this.currentHandControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.currentObjectTime = Date.now();
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
}
|
||||
|
||||
controller.prototype.deactivateEntity = function(entity) {
|
||||
var data = {
|
||||
activated: false,
|
||||
avatarId: null
|
||||
this.continueNearGrabbing = function () {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
||||
// keep track of the measured velocity of the held object
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var now = Date.now();
|
||||
|
||||
var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerPosition); // meters
|
||||
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
|
||||
this.computeReleaseVelocity(deltaPosition, deltaTime, true);
|
||||
|
||||
this.currentHandControllerPosition = handControllerPosition;
|
||||
this.currentObjectTime = now;
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
|
||||
};
|
||||
|
||||
this.nearGrabbingNonColliding = function () {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding");
|
||||
this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING;
|
||||
};
|
||||
|
||||
this.continueNearGrabbingNonColliding = function () {
|
||||
if (!this.triggerSmoothedSqueezed()) {
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrabbingNonColliding");
|
||||
};
|
||||
|
||||
_this.allTouchedIDs = {};
|
||||
this.touchTest = function () {
|
||||
//print('touch test');
|
||||
var maxDistance = 0.05;
|
||||
var leftHandPosition = MyAvatar.getLeftPalmPosition();
|
||||
var rightHandPosition = MyAvatar.getRightPalmPosition();
|
||||
var leftEntities = Entities.findEntities(leftHandPosition, maxDistance);
|
||||
var rightEntities = Entities.findEntities(rightHandPosition, maxDistance);
|
||||
var ids = [];
|
||||
if (leftEntities.length !== 0) {
|
||||
leftEntities.forEach(function (entity) {
|
||||
ids.push(entity);
|
||||
});
|
||||
|
||||
}
|
||||
if (rightEntities.length !== 0) {
|
||||
rightEntities.forEach(function (entity) {
|
||||
ids.push(entity);
|
||||
});
|
||||
}
|
||||
|
||||
ids.forEach(function (id) {
|
||||
|
||||
var props = Entities.getEntityProperties(id, ["boundingBox", "name"]);
|
||||
if (props.name === 'pointer') {
|
||||
return;
|
||||
} else {
|
||||
var entityMinPoint = props.boundingBox.brn;
|
||||
var entityMaxPoint = props.boundingBox.tfl;
|
||||
var leftIsTouching = pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint);
|
||||
var rightIsTouching = pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint);
|
||||
|
||||
if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === undefined) {
|
||||
// we haven't been touched before, but either right or left is touching us now
|
||||
_this.allTouchedIDs[id] = true;
|
||||
_this.startTouch(id);
|
||||
} else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === true) {
|
||||
// we have been touched before and are still being touched
|
||||
// continue touch
|
||||
_this.continueTouch(id);
|
||||
} else if (_this.allTouchedIDs[id] === true) {
|
||||
delete _this.allTouchedIDs[id];
|
||||
_this.stopTouch(id);
|
||||
|
||||
} else {
|
||||
//we are in another state
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.startTouch = function (entityID) {
|
||||
// print('START TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "startTouch");
|
||||
};
|
||||
|
||||
this.continueTouch = function (entityID) {
|
||||
// print('CONTINUE TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "continueTouch");
|
||||
};
|
||||
|
||||
this.stopTouch = function (entityID) {
|
||||
// print('STOP TOUCH' + entityID);
|
||||
Entities.callEntityMethod(entityID, "stopTouch");
|
||||
|
||||
};
|
||||
|
||||
this.computeReleaseVelocity = function (deltaPosition, deltaTime, useMultiplier) {
|
||||
if (deltaTime > 0.0 && !vec3equal(deltaPosition, ZERO_VEC)) {
|
||||
var grabbedVelocity = Vec3.multiply(deltaPosition, 1.0 / deltaTime);
|
||||
// don't update grabbedVelocity if the trigger is off. the smoothing of the trigger
|
||||
// value would otherwise give the held object time to slow down.
|
||||
if (this.triggerSqueezed()) {
|
||||
this.grabbedVelocity =
|
||||
Vec3.sum(Vec3.multiply(this.grabbedVelocity, (1.0 - NEAR_GRABBING_VELOCITY_SMOOTH_RATIO)),
|
||||
Vec3.multiply(grabbedVelocity, NEAR_GRABBING_VELOCITY_SMOOTH_RATIO));
|
||||
}
|
||||
|
||||
if (useMultiplier) {
|
||||
this.grabbedVelocity = Vec3.multiply(this.grabbedVelocity, RELEASE_VELOCITY_MULTIPLIER);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.release = function () {
|
||||
this.lineOff();
|
||||
|
||||
if (this.grabbedEntity !== null && this.actionID !== null) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
||||
}
|
||||
|
||||
// the action will tend to quickly bring an object's velocity to zero. now that
|
||||
// the action is gone, set the objects velocity to something the holder might expect.
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
velocity: this.grabbedVelocity
|
||||
});
|
||||
this.deactivateEntity(this.grabbedEntity);
|
||||
|
||||
this.grabbedVelocity = ZERO_VEC;
|
||||
this.grabbedEntity = null;
|
||||
this.actionID = null;
|
||||
this.state = STATE_SEARCHING;
|
||||
};
|
||||
|
||||
|
||||
this.cleanup = function () {
|
||||
this.release();
|
||||
};
|
||||
|
||||
this.activateEntity = function () {
|
||||
var data = {
|
||||
activated: true,
|
||||
avatarId: MyAvatar.sessionUUID
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
};
|
||||
|
||||
this.deactivateEntity = function () {
|
||||
var data = {
|
||||
activated: false,
|
||||
avatarId: null
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
};
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
|
||||
}
|
||||
|
||||
controller.prototype.cleanup = function() {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
if (this.grabbedEntity) {
|
||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
||||
}
|
||||
}
|
||||
|
||||
var rightController = new controller(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK"));
|
||||
var leftController = new controller(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK"));
|
||||
|
||||
|
||||
function update() {
|
||||
rightController.update();
|
||||
leftController.update();
|
||||
}
|
||||
|
||||
|
||||
function cleanup() {
|
||||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
}
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update)
|
||||
Script.update.connect(update);
|
|
@ -16,7 +16,7 @@ var PARTICLE_MAX_SIZE = 2.50;
|
|||
var LIFETIME = 600;
|
||||
var boxes = [];
|
||||
|
||||
var ids = Entities.findEntities({ x: 512, y: 512, z: 512 }, 50);
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var id = ids[i];
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
|
@ -33,7 +33,7 @@ for (var x = 0; x < SIDE_SIZE; x++) {
|
|||
var gray = Math.random() * 155;
|
||||
var cube = Math.random() > 0.5;
|
||||
var color = { red: 100 + gray, green: 100 + gray, blue: 100 + gray };
|
||||
var position = { x: 512 + x * 0.2, y: 512 + y * 0.2, z: 512 + z * 0.2};
|
||||
var position = Vec3.sum(MyAvatar.position, { x: x * 0.2, y: y * 0.2, z: z * 0.2});
|
||||
var radius = Math.random() * 0.1;
|
||||
boxes.push(Entities.addEntity({
|
||||
type: cube ? "Box" : "Sphere",
|
||||
|
@ -52,7 +52,7 @@ for (var x = 0; x < SIDE_SIZE; x++) {
|
|||
|
||||
function scriptEnding() {
|
||||
for (var i = 0; i < boxes.length; i++) {
|
||||
//Entities.deleteEntity(boxes[i]);
|
||||
Entities.deleteEntity(boxes[i]);
|
||||
}
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -245,6 +245,10 @@ var toolBar = (function () {
|
|||
that.setActive(false);
|
||||
}
|
||||
|
||||
that.clearEntityList = function() {
|
||||
entityListTool.clearEntityList();
|
||||
};
|
||||
|
||||
that.setActive = function(active) {
|
||||
if (active != isActive) {
|
||||
if (active && !Entities.canAdjustLocks()) {
|
||||
|
@ -510,6 +514,7 @@ var toolBar = (function () {
|
|||
|
||||
Window.domainChanged.connect(function() {
|
||||
that.setActive(false);
|
||||
that.clearEntityList();
|
||||
});
|
||||
|
||||
Entities.canAdjustLocksChanged.connect(function(canAdjustLocks) {
|
||||
|
@ -1315,7 +1320,7 @@ PropertiesTool = function(opts) {
|
|||
if (data.action == "moveSelectionToGrid") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
selectionManager.saveProperties();
|
||||
var dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2),
|
||||
var dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2);
|
||||
var diff = { x: 0, y: dY, z: 0 };
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
var properties = selectionManager.savedProperties[selectionManager.selections[i]];
|
||||
|
|
71
examples/entityScripts/changeColorOnTouch.js
Normal file
71
examples/entityScripts/changeColorOnTouch.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// changeColorOnTouch.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 11/1/14.
|
||||
// Additions by James B. Pollack @imgntn on 9/23/2015
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// ATTENTION: Requires you to run handControllerGrab.js
|
||||
// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will
|
||||
// change the color of the entity when you touch it.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function () {
|
||||
ChangeColorOnTouch = function () {
|
||||
this.oldColor = {};
|
||||
this.oldColorKnown = false;
|
||||
};
|
||||
|
||||
ChangeColorOnTouch.prototype = {
|
||||
|
||||
storeOldColor: function (entityID) {
|
||||
var oldProperties = Entities.getEntityProperties(entityID);
|
||||
this.oldColor = oldProperties.color;
|
||||
this.oldColorKnown = true;
|
||||
print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
|
||||
},
|
||||
|
||||
preload: function (entityID) {
|
||||
print("preload");
|
||||
this.entityID = entityID;
|
||||
this.storeOldColor(entityID);
|
||||
},
|
||||
|
||||
startTouch: function () {
|
||||
print("startTouch");
|
||||
if (!this.oldColorKnown) {
|
||||
this.storeOldColor(this.entityID);
|
||||
}
|
||||
Entities.editEntity(this.entityID, {
|
||||
color: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 255
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
continueTouch: function () {
|
||||
//unused here
|
||||
return;
|
||||
},
|
||||
|
||||
stopTouch: function () {
|
||||
print("stopTouch");
|
||||
if (this.oldColorKnown) {
|
||||
print("leave restoring old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
|
||||
Entities.editEntity(this.entityID, {
|
||||
color: this.oldColor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
return new ChangeColorOnTouch();
|
||||
})
|
|
@ -12,7 +12,6 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
Script.include("../libraries/utils.js");
|
||||
|
||||
var _this;
|
||||
|
||||
|
@ -24,39 +23,29 @@
|
|||
|
||||
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";
|
||||
setRightHand: function () {
|
||||
print("I am being held in a right hand... entity:" + this.entityID);
|
||||
},
|
||||
setLeftHand: function () {
|
||||
print("I am being held in a left hand... entity:" + this.entityID);
|
||||
},
|
||||
|
||||
// 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;
|
||||
startDistantGrab: function () {
|
||||
print("I am being distance held... entity:" + this.entityID);
|
||||
},
|
||||
continueDistantGrab: function () {
|
||||
print("I continue to be distance held... entity:" + 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 };
|
||||
startNearGrab: function () {
|
||||
print("I was just grabbed... entity:" + this.entityID);
|
||||
},
|
||||
continueNearGrab: function () {
|
||||
print("I am still being grabbed... entity:" + this.entityID);
|
||||
},
|
||||
|
||||
// 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...");
|
||||
}
|
||||
releaseGrab: function () {
|
||||
print("I was released... entity:" + this.entityID);
|
||||
},
|
||||
|
||||
// preload() will be called when the entity has become visible (or known) to the interface
|
||||
|
@ -65,14 +54,6 @@
|
|||
// * 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);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(function() {
|
||||
// Script.include("../libraries/utils.js");
|
||||
//Need absolute path for now, for testing before PR merge and s3 cloning. Will change post-merge
|
||||
Script.include("https://hifi-public.s3.amazonaws.com/scripts/libraries/utils.js");
|
||||
|
||||
Script.include("../libraries/utils.js");
|
||||
GRAB_FRAME_USER_DATA_KEY = "grabFrame";
|
||||
this.userData = {};
|
||||
|
||||
|
@ -56,26 +57,21 @@
|
|||
timeSinceLastMoved = 0;
|
||||
}
|
||||
|
||||
if (self.userData.grabKey && self.userData.grabKey.activated === true) {
|
||||
//Only activate for the user who grabbed the object
|
||||
if (self.userData.grabKey && self.userData.grabKey.activated === true && self.userData.grabKey.avatarId == MyAvatar.sessionUUID) {
|
||||
if (self.activated !== true) {
|
||||
//We were just grabbed, so create a particle system
|
||||
self.grab();
|
||||
Entities.editEntity(self.paintStream, {
|
||||
animationSettings: startSetting
|
||||
});
|
||||
}
|
||||
//Move emitter to where entity is always when its activated
|
||||
self.sprayStream();
|
||||
} else if (self.userData.grabKey && self.userData.grabKey.activated === false && self.activated) {
|
||||
Entities.editEntity(self.paintStream, {
|
||||
animationSettings: stopSetting
|
||||
});
|
||||
self.activated = false;
|
||||
self.letGo();
|
||||
}
|
||||
}
|
||||
|
||||
this.grab = function() {
|
||||
self.activated = true;
|
||||
this.activated = true;
|
||||
var animationSettings = JSON.stringify({
|
||||
fps: 30,
|
||||
loop: true,
|
||||
|
@ -92,9 +88,9 @@
|
|||
emitVelocity: ZERO_VEC,
|
||||
emitAcceleration: ZERO_VEC,
|
||||
velocitySpread: {
|
||||
x: .02,
|
||||
y: .02,
|
||||
z: 0.02
|
||||
x: .1,
|
||||
y: .1,
|
||||
z: 0.1
|
||||
},
|
||||
emitRate: 100,
|
||||
particleRadius: 0.01,
|
||||
|
@ -103,14 +99,14 @@
|
|||
green: 20,
|
||||
blue: 150
|
||||
},
|
||||
lifetime: 500, //probably wont be holding longer than this straight
|
||||
lifetime: 50, //probably wont be holding longer than this straight
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.letGo = function() {
|
||||
self.activated = false;
|
||||
this.activated = false;
|
||||
Entities.deleteEntity(this.paintStream);
|
||||
this.paintStream = null;
|
||||
}
|
||||
|
||||
this.reset = function() {
|
||||
|
@ -123,8 +119,7 @@
|
|||
}
|
||||
|
||||
this.sprayStream = function() {
|
||||
var forwardVec = Quat.getFront(self.properties.rotation);
|
||||
forwardVec = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 90, 0), forwardVec);
|
||||
var forwardVec = Quat.getFront(Quat.multiply(self.properties.rotation , Quat.fromPitchYawRollDegrees(0, 90, 0)));
|
||||
forwardVec = Vec3.normalize(forwardVec);
|
||||
|
||||
var upVec = Quat.getUp(self.properties.rotation);
|
||||
|
@ -132,11 +127,10 @@
|
|||
position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y))
|
||||
Entities.editEntity(self.paintStream, {
|
||||
position: position,
|
||||
emitVelocity: Vec3.multiply(forwardVec, 4)
|
||||
emitVelocity: Vec3.multiply(5, forwardVec)
|
||||
});
|
||||
|
||||
//Now check for an intersection with an entity
|
||||
|
||||
//move forward so ray doesnt intersect with gun
|
||||
var origin = Vec3.sum(position, forwardVec);
|
||||
var pickRay = {
|
||||
|
@ -216,6 +210,8 @@
|
|||
this.entityId = entityId;
|
||||
this.properties = Entities.getEntityProperties(self.entityId);
|
||||
this.getUserData();
|
||||
|
||||
//Only activate for the avatar who is grabbing the can!
|
||||
if (this.userData.grabKey && this.userData.grabKey.activated) {
|
||||
this.activated = true;
|
||||
}
|
||||
|
@ -235,7 +231,9 @@
|
|||
|
||||
this.unload = function() {
|
||||
Script.update.disconnect(this.update);
|
||||
Entities.deleteEntity(this.paintStream);
|
||||
if(this.paintStream) {
|
||||
Entities.deleteEntity(this.paintStream);
|
||||
}
|
||||
this.strokes.forEach(function(stroke) {
|
||||
Entities.deleteEntity(stroke);
|
||||
});
|
||||
|
@ -244,6 +242,7 @@
|
|||
});
|
||||
|
||||
|
||||
|
||||
function randFloat(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
|
1221
examples/example/entities/platform.js
Normal file
1221
examples/example/entities/platform.js
Normal file
File diff suppressed because it is too large
Load diff
98
examples/faceBlendCoefficients.js
Normal file
98
examples/faceBlendCoefficients.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// faceBlendCoefficients.js
|
||||
//
|
||||
// version 2.0
|
||||
//
|
||||
// Created by Bob Long, 9/14/2015
|
||||
// A simple panel that can select and display the blending coefficient of the Avatar's face model.
|
||||
//
|
||||
// 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('utilities/tools/cookies.js')
|
||||
|
||||
var panel;
|
||||
var coeff;
|
||||
var interval;
|
||||
var item = 0;
|
||||
var DEVELOPER_MENU = "Developer";
|
||||
var AVATAR_MENU = DEVELOPER_MENU + " > Avatar";
|
||||
var SHOW_FACE_BLEND_COEFFICIENTS = "Show face blend coefficients"
|
||||
|
||||
function MenuConnect(menuItem) {
|
||||
if (menuItem == SHOW_FACE_BLEND_COEFFICIENTS) {
|
||||
if(Menu.isOptionChecked(SHOW_FACE_BLEND_COEFFICIENTS)) {
|
||||
panel.show();
|
||||
Overlays.editOverlay(coeff, { visible : true });
|
||||
} else {
|
||||
panel.hide();
|
||||
Overlays.editOverlay(coeff, { visible : false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a menu item to show/hide the coefficients
|
||||
function setupMenu() {
|
||||
if (!Menu.menuExists(DEVELOPER_MENU)) {
|
||||
Menu.addMenu(DEVELOPER_MENU);
|
||||
}
|
||||
|
||||
if (!Menu.menuExists(AVATAR_MENU)) {
|
||||
Menu.addMenu(AVATAR_MENU);
|
||||
}
|
||||
|
||||
Menu.addMenuItem({ menuName: AVATAR_MENU, menuItemName: SHOW_FACE_BLEND_COEFFICIENTS, isCheckable: true, isChecked: true });
|
||||
Menu.menuItemEvent.connect(MenuConnect);
|
||||
}
|
||||
|
||||
function setupPanel() {
|
||||
panel = new Panel(10, 400);
|
||||
|
||||
// Slider to select which coefficient to display
|
||||
panel.newSlider("Select Coefficient Index",
|
||||
0,
|
||||
100,
|
||||
function(value) { item = value.toFixed(0); },
|
||||
function() { return item; },
|
||||
function(value) { return "index = " + item; }
|
||||
);
|
||||
|
||||
// The raw overlay used to show the actual coefficient value
|
||||
coeff = Overlays.addOverlay("text", {
|
||||
x: 10,
|
||||
y: 420,
|
||||
width: 300,
|
||||
height: 50,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
alpha: 1.0,
|
||||
backgroundColor: { red: 127, green: 127, blue: 127 },
|
||||
backgroundAlpha: 0.5,
|
||||
topMargin: 15,
|
||||
leftMargin: 20,
|
||||
text: "Coefficient: 0.0"
|
||||
});
|
||||
|
||||
// Set up the interval (0.5 sec) to update the coefficient.
|
||||
interval = Script.setInterval(function() {
|
||||
Overlays.editOverlay(coeff, { text: "Coefficient: " + MyAvatar.getFaceBlendCoef(item).toFixed(4) });
|
||||
}, 500);
|
||||
|
||||
// Mouse event setup
|
||||
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
|
||||
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
|
||||
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
|
||||
}
|
||||
|
||||
// Clean up
|
||||
function scriptEnding() {
|
||||
panel.destroy();
|
||||
Overlays.deleteOverlay(coeff);
|
||||
Script.clearInterval(interval);
|
||||
|
||||
Menu.removeMenuItem(AVATAR_MENU, SHOW_FACE_BLEND_COEFFICIENTS);
|
||||
}
|
||||
|
||||
setupMenu();
|
||||
setupPanel();
|
||||
Script.scriptEnding.connect(scriptEnding);
|
|
@ -201,7 +201,9 @@
|
|||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data.type == "selectionUpdate") {
|
||||
if (data.type === "clearEntityList") {
|
||||
clearEntities();
|
||||
} else if (data.type == "selectionUpdate") {
|
||||
var notFound = updateSelectedEntities(data.selectedIDs);
|
||||
if (notFound) {
|
||||
refreshEntities();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Properties</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/colpick.css">
|
||||
<script src="jquery-2.1.4.min.js"></script>
|
||||
|
@ -961,7 +962,7 @@
|
|||
</div>
|
||||
<div id="id" class="property">
|
||||
<span class="label" style="float: left; margin-right: 6px">
|
||||
<label>ID: <label>
|
||||
<label>ID: </label>
|
||||
</span>
|
||||
<div class="value">
|
||||
<span id="property-id" class="selectable"></span>
|
||||
|
@ -970,7 +971,7 @@
|
|||
<div class="property">
|
||||
<span class="label" style="float: left; margin-right: 6px">Name</span>
|
||||
<div class="value" style="overflow: hidden;">
|
||||
<input type="text" id="property-name"></input>
|
||||
<input type="text" id="property-name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1003,13 +1004,13 @@
|
|||
<div class="property">
|
||||
<div class="label">Href</div>
|
||||
<div class="value">
|
||||
<input id="property-hyperlink-href" class="url"></input>
|
||||
<input id="property-hyperlink-href" class="url">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Description</div>
|
||||
<div class="value">
|
||||
<input id="property-hyperlink-description" class="url"></input>
|
||||
<input id="property-hyperlink-description" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1021,9 +1022,9 @@
|
|||
<div class="property">
|
||||
<div class="label">Position</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-pos-x"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-pos-y"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-pos-z"></input></div>
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-pos-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-pos-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-pos-z"></div>
|
||||
<div>
|
||||
<input type="button" id="move-selection-to-grid" value="Selection to Grid">
|
||||
<input type="button" id="move-all-to-grid" value="All to Grid">
|
||||
|
@ -1034,26 +1035,26 @@
|
|||
<div class="property">
|
||||
<div class="label">Registration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Dimensions</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-dim-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-dim-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z"></div>
|
||||
<div>
|
||||
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<input class="" type='number' id="dimension-rescale-pct" value=100></input>%
|
||||
<input class="" type='number' id="dimension-rescale-pct" value=100>%
|
||||
</div>
|
||||
<span>
|
||||
<input type="button" id="dimension-rescale-button" value="Rescale"></input>
|
||||
<input type="button" id="dimension-rescale-button" value="Rescale">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1061,9 +1062,9 @@
|
|||
<div class="poly-vox-section property">
|
||||
<div class="label">Voxel Volume Size</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></input></div>
|
||||
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></div>
|
||||
</div>
|
||||
|
||||
<div class="label">Surface Extractor</div>
|
||||
|
@ -1078,26 +1079,26 @@
|
|||
|
||||
<div class="label">X-axis Texture URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-x-texture-url" class="url"></input>
|
||||
<input type="text" id="property-x-texture-url" class="url">
|
||||
</div>
|
||||
|
||||
<div class="label">Y-axis Texture URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-y-texture-url" class="url"></input>
|
||||
<input type="text" id="property-y-texture-url" class="url">
|
||||
</div>
|
||||
|
||||
<div class="label">Z-axis Texture URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-z-texture-url" class="url"></input>
|
||||
<input type="text" id="property-z-texture-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Rotation</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x"></input></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y"></input></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"></input></div>
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1109,66 +1110,66 @@
|
|||
<div class="property">
|
||||
<div class="label">Linear Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Linear Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-ldamping"></input>
|
||||
<input class="coord" type='number' id="property-ldamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></input></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></input></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></input></div>
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-adamping"></input>
|
||||
<input class="coord" type='number' id="property-adamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Restitution</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-restitution"></input>
|
||||
<input class="coord" type='number' id="property-restitution">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Friction</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-friction"></input>
|
||||
<input class="coord" type='number' id="property-friction">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Gravity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Acceleration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Density</div>
|
||||
<div>
|
||||
<input type='number' id="property-density"></input>
|
||||
<input type='number' id="property-density">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1176,9 +1177,9 @@
|
|||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div id="property-color" class='color-picker'></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1190,38 +1191,38 @@
|
|||
<div class="property">
|
||||
<span class="label">Ignore For Collisions</span>
|
||||
<span class="value">
|
||||
<input type='checkbox' id="property-ignore-for-collisions"></input>
|
||||
<input type='checkbox' id="property-ignore-for-collisions">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<span class="label">Collisions Will Move</span>
|
||||
<span class="value">
|
||||
<input type='checkbox' id="property-collisions-will-move"></input>
|
||||
<input type='checkbox' id="property-collisions-will-move">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Collision Sound URL</div>
|
||||
<div class="value">
|
||||
<input id="property-collision-sound-url" class="url"></input>
|
||||
<input id="property-collision-sound-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Lifetime</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-lifetime"></input>
|
||||
<input type='number' id="property-lifetime">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Script URL
|
||||
<input type="hidden" id="property-script-timestamp" class="value"></input>
|
||||
<input type="button" id="reload-script-button" value="Reload"></input>
|
||||
<input type="hidden" id="property-script-timestamp" class="value">
|
||||
<input type="button" id="reload-script-button" value="Reload">
|
||||
</div>
|
||||
<div class="value">
|
||||
<input id="property-script-url" class="url"></input>
|
||||
<input id="property-script-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1233,14 +1234,14 @@
|
|||
<div class="model-section property">
|
||||
<div class="label">Model URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-model-url" class="url"></input>
|
||||
<input type="text" id="property-model-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="model-section zone-section property">
|
||||
<div class="label">Shape Type</div>
|
||||
<div class="value">
|
||||
<select name="SelectShapeType" id="property-shape-type" name="SelectShapeType">
|
||||
<select name="SelectShapeType" id="property-shape-type">
|
||||
<option value='none'>none</option>
|
||||
<option value='box'>box</option>
|
||||
<option value='sphere'>sphere</option>
|
||||
|
@ -1251,13 +1252,13 @@
|
|||
<div class="model-section zone-section property">
|
||||
<div class="label">Compound Shape URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-compound-shape-url" class="url"></input>
|
||||
<input type="text" id="property-compound-shape-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-model-animation-url" class="url"></input>
|
||||
<input type="text" id="property-model-animation-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
|
@ -1269,13 +1270,13 @@
|
|||
<div class="model-section property">
|
||||
<div class="label">Animation FPS</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-fps"></input>
|
||||
<input class="coord" type='number' id="property-model-animation-fps">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-frame"></input>
|
||||
<input class="coord" type='number' id="property-model-animation-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
|
@ -1305,7 +1306,7 @@
|
|||
<div class="web-section property">
|
||||
<div class="label">Source URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-web-source-url" class="url"></input>
|
||||
<input type="text" id="property-web-source-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1317,45 +1318,45 @@
|
|||
<div class="particle-section property">
|
||||
<div class="label">Max Particles</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-particle-maxparticles" min="0" max="2048" step="1"></input>
|
||||
<input type='number' id="property-particle-maxparticles" min="0" max="2048" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Life Span</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-particle-lifespan" min="0" step="0.1"></input>
|
||||
<input type='number' id="property-particle-lifespan" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Emission Rate</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-particle-emit-rate" min="0" step="0.5"></input>
|
||||
<input type='number' id="property-particle-emit-rate" min="0" step="0.5">
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Emission Direction</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-particle-emit-direction-x"></input></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-particle-emit-direction-y"></input></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-particle-emit-direction-z"></input></div>
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-particle-emit-direction-x"></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-particle-emit-direction-y"></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-particle-emit-direction-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Emission Strength</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-particle-emit-strength" min="0" step="0.1"></input>
|
||||
<input type='number' id="property-particle-emit-strength" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Local Gravity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-particle-localgravity" step="0.05"></input>
|
||||
<input class="coord" type='number' id="property-particle-localgravity" step="0.05">
|
||||
</div>
|
||||
</div>
|
||||
<div class="particle-section property">
|
||||
<div class="label">Particle Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-particle-radius" min="0" step="0.005"></input>
|
||||
<input class="coord" type='number' id="property-particle-radius" min="0" step="0.005">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1367,31 +1368,31 @@
|
|||
<div class="text-section property">
|
||||
<div class="label">Text Content</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-text-text"></input>
|
||||
<input type="text" id="property-text-text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Line Height</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005"></input>
|
||||
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Text Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-text-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Background Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-background-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1410,27 +1411,27 @@
|
|||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-intensity"></input>
|
||||
<input class="coord" type='number' id="property-light-intensity">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Exponent</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-exponent"></input>
|
||||
<input class="coord" type='number' id="property-light-exponent">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Cutoff (degrees)</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-cutoff"></input>
|
||||
<input class="coord" type='number' id="property-light-cutoff">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1450,48 +1451,48 @@
|
|||
<div class="label">Key Light Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-key-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Key Light Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1"></input>
|
||||
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Key Light Ambient Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1"></input>
|
||||
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Key Light Direction</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></input></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></input></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></input></div>
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section property">
|
||||
<div class="label">Stage Latitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Stage Longitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Stage Altitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-altitude" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-stage-altitude" step="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1505,20 +1506,20 @@
|
|||
<div class="zone-section property">
|
||||
<div class="label">Stage Day</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section property">
|
||||
<div class="label">Stage Hour</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5"></input>
|
||||
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section property">
|
||||
<div class="label">Background Mode</div>
|
||||
<div class="value">
|
||||
<select name="SelectBackgroundMode" id="property-zone-background-mode" name="SelectBackgroundMode">
|
||||
<select name="SelectBackgroundMode" id="property-zone-background-mode">
|
||||
<option value='inherit'>Nothing</option>
|
||||
<option value='skybox'>Skybox</option>
|
||||
<option value='atmosphere'>Atmosphere</option>
|
||||
|
@ -1535,15 +1536,15 @@
|
|||
<div class="label">Skybox Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-skybox-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></input></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></input></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></input></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section skybox-section property">
|
||||
<div class="label">Skybox URL</div>
|
||||
<div class="value">
|
||||
<input type="text" id="property-zone-skybox-url" class="url"></input>
|
||||
<input type="text" id="property-zone-skybox-url" class="url">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1555,9 +1556,9 @@
|
|||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Center</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-zone-atmosphere-center-x"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-zone-atmosphere-center-y"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-zone-atmosphere-center-z"></input></div>
|
||||
<div class="input-area">X <br><input class="coord" type='number' id="property-zone-atmosphere-center-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-zone-atmosphere-center-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-zone-atmosphere-center-z"></div>
|
||||
<div>
|
||||
<input type="button" id="center-atmosphere-in-zone" value="Center to Zone">
|
||||
</div>
|
||||
|
@ -1566,33 +1567,33 @@
|
|||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Inner Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-inner-radius" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-inner-radius" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Outer Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-outer-radius" step="1"></input>
|
||||
<input class="coord" type='number' id="property-zone-atmosphere-outer-radius" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Mie Scattering</div>
|
||||
<div class="value">
|
||||
<input class="coord no-spin" type='number' id="property-zone-atmosphere-mie-scattering" min="0" max="0.5" step="any"></input>
|
||||
<input class="coord no-spin" type='number' id="property-zone-atmosphere-mie-scattering" min="0" max="0.5" step="any">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Rayleigh Scattering</div>
|
||||
<div class="value">
|
||||
<input class="coord no-spin" type='number' id="property-zone-atmosphere-rayleigh-scattering" min="0" max="0.5" step="any"></input>
|
||||
<input class="coord no-spin" type='number' id="property-zone-atmosphere-rayleigh-scattering" min="0" max="0.5" step="any">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section atmosphere-section property">
|
||||
<div class="label">Atmosphere Scattering Wavelenghts</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-x" min="0" max="1" step="any"></input></div>
|
||||
<div class="input-area">Y <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-y" min="0" max="1" step="any"></input></div>
|
||||
<div class="input-area">Z <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-z" min="0" max="1" step="any"></input></div>
|
||||
<div class="input-area">X <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-x" min="0" max="1" step="any"></div>
|
||||
<div class="input-area">Y <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-y" min="0" max="1" step="any"></div>
|
||||
<div class="input-area">Z <br><input class="coord no-spin" type='number' id="property-zone-atmosphere-scattering-wavelengths-z" min="0" max="1" step="any"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section atmosphere-section property" style="display:none">
|
||||
|
|
|
@ -26,13 +26,20 @@ EntityListTool = function(opts) {
|
|||
selectedIDs.push(selectionManager.selections[i]);
|
||||
}
|
||||
|
||||
data = {
|
||||
var data = {
|
||||
type: 'selectionUpdate',
|
||||
selectedIDs: selectedIDs,
|
||||
};
|
||||
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
|
||||
});
|
||||
|
||||
that.clearEntityList = function () {
|
||||
var data = {
|
||||
type: 'clearEntityList'
|
||||
}
|
||||
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
|
||||
};
|
||||
|
||||
that.sendUpdate = function() {
|
||||
var entities = [];
|
||||
var ids = Entities.findEntities(MyAvatar.position, searchRadius);
|
||||
|
|
|
@ -28,13 +28,10 @@ if (this.Vec2 == undefined) {
|
|||
return new Vec2(v.x, v.y);
|
||||
}
|
||||
} else if (this.Vec2.clone == undefined) {
|
||||
print("Vec2 exists; adding Vec2.clone");
|
||||
this.Vec2.clone = function (v) {
|
||||
return { 'x': v.x || 0.0, 'y': v.y || 0.0 };
|
||||
}
|
||||
} else {
|
||||
print("Vec2...?");
|
||||
}
|
||||
} else {}
|
||||
})();
|
||||
|
||||
var Rect = function (xmin, ymin, xmax, ymax) {
|
||||
|
@ -566,46 +563,51 @@ var Slider = UI.Slider = function (properties) {
|
|||
this.slider = new Box(properties.slider);
|
||||
this.slider.parent = this;
|
||||
|
||||
var updateSliderPos = function (event, widget) {
|
||||
var rx = Math.max(event.x * 1.0 - widget.position.x - widget.slider.width * 0.5, 0.0);
|
||||
var clickOffset = { x: 0.0, y: 0.0 }; // offset relative to slider knob
|
||||
var widget = this;
|
||||
var updateDrag = function (event) {
|
||||
var rx = Math.max(event.x * 1.0 - widget.position.x - clickOffset.x, 0.0);
|
||||
var width = Math.max(widget.width - widget.slider.width - widget.padding.x * 2.0, 0.0);
|
||||
var v = Math.min(rx, width) / (width || 1);
|
||||
|
||||
widget.value = widget.minValue + (
|
||||
widget.maxValue - widget.minValue) * v;
|
||||
// print("dragging slider: rx = " + rx + ", width = " + width + ", v = " + v);
|
||||
|
||||
widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v;
|
||||
widget.onValueChanged(widget.value);
|
||||
UI.updateLayout();
|
||||
}
|
||||
var startDrag = function (event) {
|
||||
// calculate position of slider knob
|
||||
var x0 = widget.position.x + widget.padding.x;
|
||||
var width = (widget.width - widget.slider.width - widget.padding.x * 2.0);
|
||||
var normalizedValue = (widget.value - widget.minValue) / (widget.maxValue - widget.minValue)
|
||||
|
||||
var widget = this;
|
||||
this.addAction('onMouseDown', function (event) {
|
||||
sliderRel.x = sliderRel.y = 0.0;
|
||||
// sliderRel.x = widget.slider.width * 0.5;
|
||||
// sliderRel.y = widget.slider.height * 0.5;
|
||||
updateSliderPos(event, widget);
|
||||
var sliderX = x0 + normalizedValue * width;
|
||||
var sliderWidth = widget.slider.width;
|
||||
|
||||
// hack
|
||||
ui.clickedWidget = ui.draggedWidget = widget.slider;
|
||||
});
|
||||
if (event.x >= sliderX && event.x <= sliderX + sliderWidth) {
|
||||
// print("Start drag -- on slider knob");
|
||||
clickOffset.x = event.x - sliderX;
|
||||
} else if (event.x >= x0 && event.x <= x0 + width) {
|
||||
// print("Start drag -- on slider bar");
|
||||
clickOffset.x = sliderWidth * 0.5;
|
||||
} else {
|
||||
clickOffset.x = 0.0;
|
||||
// print("Start drag -- out of bounds!");
|
||||
// print("event.x = " + event.x);
|
||||
// print("x0 = " + x0 + ", x1 = " + (x0 + width) + " (width = " + width + ")");
|
||||
// print("s0 = " + sliderX + ", s1 = " + (sliderX + sliderWidth) + "(slider width = " + sliderWidth + ")");
|
||||
// print("widget = " + widget);
|
||||
// print("widget.slider = " + widget.slider);
|
||||
// print("widget.width = " + widget.width + ", widget.slider.width = " + widget.slider.width);
|
||||
}
|
||||
updateDrag(event);
|
||||
}
|
||||
|
||||
var sliderRel = {};
|
||||
this.slider.addAction('onMouseDown', function (event) {
|
||||
sliderRel.x = widget.slider.position.x - event.x;
|
||||
sliderRel.y = widget.slider.position.y - event.y;
|
||||
event.x += sliderRel.x;
|
||||
event.y += sliderRel.y;
|
||||
updateSliderPos(event, widget);
|
||||
});
|
||||
this.slider.addAction('onDragBegin', function (event) {
|
||||
event.x += sliderRel.x;
|
||||
event.y += sliderRel.y;
|
||||
updateSliderPos(event, widget);
|
||||
})
|
||||
this.slider.addAction('onDragUpdate', function (event) {
|
||||
event.x += sliderRel.x;
|
||||
event.y += sliderRel.y;
|
||||
updateSliderPos(event, widget);
|
||||
})
|
||||
this.addAction('onMouseDown', startDrag);
|
||||
this.addAction('onDragBegin', updateDrag);
|
||||
this.addAction('onDragUpdate', updateDrag);
|
||||
this.slider.actions = this.actions;
|
||||
};
|
||||
Slider.prototype = new Box();
|
||||
Slider.prototype.constructor = Slider;
|
||||
|
@ -947,16 +949,25 @@ var dispatchEvent = function (action, event, widget) {
|
|||
}
|
||||
}
|
||||
|
||||
function hasAction (widget, action) {
|
||||
// print("widget = " + widget);
|
||||
// print("action = " + action);
|
||||
// if (widget) {
|
||||
// print("widget.actions[<action>] = " + widget.actions[action]);
|
||||
// print("widget.parent = " + widget.parent);
|
||||
// }
|
||||
return widget && (widget.actions[action] || hasAction(widget.parent, action));
|
||||
}
|
||||
|
||||
UI.handleMouseMove = function (event, canStartDrag) {
|
||||
if (canStartDrag === undefined)
|
||||
// if (canStartDrag === undefined)
|
||||
if (arguments.length < 2)
|
||||
canStartDrag = true;
|
||||
|
||||
// print("mouse moved x = " + event.x + ", y = " + event.y);
|
||||
var focused = getFocusedWidget(event);
|
||||
|
||||
// print("got focus: " + focused);
|
||||
|
||||
if (canStartDrag && !ui.draggedWidget && ui.clickedWidget && ui.clickedWidget.actions['onDragBegin']) {
|
||||
if (!ui.draggedWidget && ui.clickedWidget && hasAction(ui.clickedWidget, 'onDragBegin')) {
|
||||
ui.draggedWidget = ui.clickedWidget;
|
||||
dispatchEvent('onDragBegin', event, ui.draggedWidget);
|
||||
} else if (ui.draggedWidget) {
|
||||
|
@ -980,26 +991,24 @@ UI.handleMousePress = function (event) {
|
|||
}
|
||||
|
||||
UI.handleMouseDoublePress = function (event) {
|
||||
// print("DOUBLE CLICK!");
|
||||
var focused = getFocusedWidget(event);
|
||||
UI.handleMouseMove(event);
|
||||
if (focused) {
|
||||
// print("dispatched onDoubleClick");
|
||||
dispatchEvent('onDoubleClick', event, focused);
|
||||
}
|
||||
}
|
||||
|
||||
UI.handleMouseRelease = function (event) {
|
||||
// print("Mouse released");
|
||||
|
||||
if (ui.draggedWidget) {
|
||||
dispatchEvent('onDragEnd', event, ui.draggedWidget);
|
||||
} else {
|
||||
UI.handleMouseMove(event, false);
|
||||
var clicked = ui.clickedWidget;
|
||||
ui.clickedWidget = null;
|
||||
UI.handleMouseMove(event);
|
||||
if (ui.focusedWidget) {
|
||||
dispatchEvent('onMouseUp', event, ui.focusedWidget);
|
||||
|
||||
if (ui.clickedWidget == ui.focusedWidget) {
|
||||
if (clicked == ui.focusedWidget) {
|
||||
dispatchEvent('onClick', event, ui.focusedWidget);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
vec3toStr = function (v, digits) {
|
||||
vec3toStr = function(v, digits) {
|
||||
if (!digits) { digits = 3; }
|
||||
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
|
||||
}
|
||||
|
||||
vec3equal = function(v0, v1) {
|
||||
return (v0.x == v1.x) && (v0.y == v1.y) && (v0.z == v1.z);
|
||||
}
|
||||
|
||||
colorMix = function(colorA, colorB, mix) {
|
||||
var result = {};
|
||||
|
@ -60,7 +63,7 @@ setEntityUserData = function(id, data) {
|
|||
// FIXME do non-destructive modification of the existing user data
|
||||
getEntityUserData = function(id) {
|
||||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
var properties = Entities.getEntityProperties(id, "userData");
|
||||
if (properties.userData) {
|
||||
try {
|
||||
results = JSON.parse(properties.userData);
|
||||
|
@ -175,4 +178,4 @@ pointInExtents = function(point, minPoint, maxPoint) {
|
|||
return (point.x >= minPoint.x && point.x <= maxPoint.x) &&
|
||||
(point.y >= minPoint.y && point.y <= maxPoint.y) &&
|
||||
(point.z >= minPoint.z && point.z <= maxPoint.z);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
//Just temporarily using my own bucket here so others can test the entity. Once PR is tested and merged, then the entity script will appear in its proper place in S3, and I wil switch it
|
||||
var scriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/sprayPaintCan.js?=v1";
|
||||
// var scriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/sprayPaintCan.js?=v6 ";
|
||||
var scriptURL = Script.resolvePath("entityScripts/sprayPaintCan.js?v2");
|
||||
var modelURL = "https://hifi-public.s3.amazonaws.com/eric/models/paintcan.fbx";
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var sprayCan = Entities.addEntity({
|
||||
type: "Model",
|
||||
|
@ -32,7 +32,10 @@ var sprayCan = Entities.addEntity({
|
|||
});
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(sprayCan);
|
||||
|
||||
// Uncomment the below line to delete sprayCan on script reload- for faster iteration during development
|
||||
// Entities.deleteEntity(sprayCan);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
// bubble.js
|
||||
// part of bubblewand
|
||||
//
|
||||
// Created by James B. Pollack @imgntn -- 09/03/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// example of a nested entity. it doesn't do much now besides delete itself if it collides with something (bubbles are fragile! it would be cool if it sometimes merged with other bubbbles it hit)
|
||||
// todo: play bubble sounds from the bubble itself instead of the wand.
|
||||
// blocker: needs some sound fixes and a way to find its own position before unload for spatialization
|
||||
//
|
||||
// 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("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/utilities.js");
|
||||
// Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/libraries/utils.js");
|
||||
|
||||
//var popSound;
|
||||
this.preload = function(entityID) {
|
||||
// print('bubble preload')
|
||||
this.entityID = entityID;
|
||||
// popSound = SoundCache.getSound("http://hifi-public.s3.amazonaws.com/james/bubblewand/sounds/pop.wav");
|
||||
|
||||
}
|
||||
|
||||
this.collisionWithEntity = function(myID, otherID, collision) {
|
||||
//if(Entites.getEntityProperties(otherID).userData.objectType==='') { merge bubbles?}
|
||||
// Entities.deleteEntity(myID);
|
||||
// this.burstBubbleSound(collision.contactPoint)
|
||||
|
||||
};
|
||||
|
||||
this.unload = function(entityID) {
|
||||
// this.properties = Entities.getEntityProperties(entityID);
|
||||
//var location = this.properties.position;
|
||||
//this.burstBubbleSound();
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.burstBubbleSound = function(location) {
|
||||
|
||||
// var audioOptions = {
|
||||
// volume: 0.5,
|
||||
// position: location
|
||||
// }
|
||||
|
||||
//Audio.playSound(popSound, audioOptions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
})
|
|
@ -1,42 +1,43 @@
|
|||
// createWand.js
|
||||
// part of bubblewand
|
||||
//
|
||||
// Script Type: Entity Spawner
|
||||
// Created by James B. Pollack @imgntn -- 09/03/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Loads a wand model and attaches the bubble wand behavior.
|
||||
//
|
||||
// Loads a wand model and attaches the bubble wand behavior.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
Script.include("../../utilities.js");
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx';
|
||||
var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/collisionHull.obj';
|
||||
var WAND_SCRIPT_URL = Script.resolvePath("wand.js");
|
||||
|
||||
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/utilities.js");
|
||||
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/libraries/utils.js");
|
||||
//create the wand in front of the avatar
|
||||
|
||||
var wandModel = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx?" + randInt(0, 10000);
|
||||
var scriptURL = "http://hifi-public.s3.amazonaws.com/james/bubblewand/scripts/wand.js?" + randInt(1, 100500)
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
|
||||
//create the wand in front of the avatar
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
|
||||
var wand = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: wandModel,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.1,
|
||||
y: 1,
|
||||
z: 0.1
|
||||
},
|
||||
//must be enabled to be grabbable in the physics engine
|
||||
collisionsWillMove: true,
|
||||
shapeType: 'box',
|
||||
script: scriptURL
|
||||
});
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(wand);
|
||||
}
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
name: 'Bubble Wand',
|
||||
type: "Model",
|
||||
modelURL: WAND_MODEL,
|
||||
position: center,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
},
|
||||
dimensions: {
|
||||
x: 0.05,
|
||||
y: 0.25,
|
||||
z: 0.05
|
||||
},
|
||||
//must be enabled to be grabbable in the physics engine
|
||||
collisionsWillMove: true,
|
||||
compoundShapeURL: WAND_COLLISION_SHAPE,
|
||||
script: WAND_SCRIPT_URL
|
||||
});
|
|
@ -1,317 +1,203 @@
|
|||
// wand.js
|
||||
// part of bubblewand
|
||||
//
|
||||
// Script Type: Entity Script
|
||||
// Created by James B. Pollack @imgntn -- 09/03/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Makes bubbles when you wave the object around, or hold it near your mouth and make noise into the microphone.
|
||||
// Makes bubbles when you wave the object around.
|
||||
//
|
||||
// For the example, it's attached to a wand -- but you can attach it to whatever entity you want. I dream of BubbleBees :) bzzzz...pop!
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
function convertRange(value, r1, r2) {
|
||||
return (value - r1[0]) * (r2[1] - r2[0]) / (r1[1] - r1[0]) + r2[0];
|
||||
}
|
||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
(function() {
|
||||
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/utilities.js");
|
||||
Script.include("https://raw.githubusercontent.com/highfidelity/hifi/master/examples/libraries/utils.js");
|
||||
(function () {
|
||||
|
||||
var bubbleModel = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/bubble/bubble.fbx";
|
||||
var bubbleScript = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/scripts/bubble.js?' + randInt(1, 10000);
|
||||
var popSound = SoundCache.getSound("http://hifi-public.s3.amazonaws.com/james/bubblewand/sounds/pop.wav");
|
||||
Script.include("../../utilities.js");
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
var TARGET_SIZE = 0.4;
|
||||
var TARGET_COLOR = {
|
||||
red: 128,
|
||||
green: 128,
|
||||
blue: 128
|
||||
};
|
||||
var TARGET_COLOR_HIT = {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
var BUBBLE_MODEL = "http://hifi-public.s3.amazonaws.com/james/bubblewand/models/bubble/bubble.fbx";
|
||||
|
||||
var BUBBLE_INITIAL_DIMENSIONS = {
|
||||
x: 0.01,
|
||||
y: 0.01,
|
||||
z: 0.01
|
||||
};
|
||||
|
||||
var HAND_SIZE = 0.25;
|
||||
var leftCubePosition = MyAvatar.getLeftPalmPosition();
|
||||
var rightCubePosition = MyAvatar.getRightPalmPosition();
|
||||
var BUBBLE_LIFETIME_MIN = 3;
|
||||
var BUBBLE_LIFETIME_MAX = 8;
|
||||
var BUBBLE_SIZE_MIN = 0.02;
|
||||
var BUBBLE_SIZE_MAX = 0.1;
|
||||
var BUBBLE_LINEAR_DAMPING = 0.4;
|
||||
var BUBBLE_GRAVITY_MIN = 0.1;
|
||||
var BUBBLE_GRAVITY_MAX = 0.3;
|
||||
var GROWTH_FACTOR = 0.005;
|
||||
var SHRINK_FACTOR = 0.001;
|
||||
var SHRINK_LOWER_LIMIT = 0.02;
|
||||
var WAND_TIP_OFFSET = 0.095;
|
||||
var VELOCITY_THRESHOLD = 0.5;
|
||||
|
||||
var leftHand = Overlays.addOverlay("cube", {
|
||||
position: leftCubePosition,
|
||||
size: HAND_SIZE,
|
||||
color: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
//this helps us get the time passed since the last function call, for use in velocity calculations
|
||||
function interval() {
|
||||
var lastTime = new Date().getTime() / 1000;
|
||||
|
||||
var rightHand = Overlays.addOverlay("cube", {
|
||||
position: rightCubePosition,
|
||||
size: HAND_SIZE,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
var gustZoneOverlay = Overlays.addOverlay("cube", {
|
||||
position: getGustDetectorPosition(),
|
||||
size: TARGET_SIZE,
|
||||
color: TARGET_COLOR,
|
||||
alpha: 1,
|
||||
solid: false
|
||||
});
|
||||
|
||||
|
||||
function getGustDetectorPosition() {
|
||||
//put the zone in front of your avatar's face
|
||||
var DISTANCE_IN_FRONT = 0.2;
|
||||
var DISTANCE_UP = 0.5;
|
||||
var DISTANCE_TO_SIDE = 0.0;
|
||||
|
||||
var up = Quat.getUp(MyAvatar.orientation);
|
||||
var front = Quat.getFront(MyAvatar.orientation);
|
||||
var right = Quat.getRight(MyAvatar.orientation);
|
||||
|
||||
var upOffset = Vec3.multiply(up, DISTANCE_UP);
|
||||
var rightOffset = Vec3.multiply(right, DISTANCE_TO_SIDE);
|
||||
var frontOffset = Vec3.multiply(front, DISTANCE_IN_FRONT);
|
||||
|
||||
var offset = Vec3.sum(Vec3.sum(rightOffset, frontOffset), upOffset);
|
||||
var position = Vec3.sum(MyAvatar.position, offset);
|
||||
return position;
|
||||
return function getInterval() {
|
||||
var newTime = new Date().getTime() / 1000;
|
||||
var delta = newTime - lastTime;
|
||||
lastTime = newTime;
|
||||
return delta;
|
||||
};
|
||||
}
|
||||
|
||||
var checkInterval = interval();
|
||||
|
||||
var BUBBLE_GRAVITY = {
|
||||
x: 0,
|
||||
y: -0.05,
|
||||
z: 0
|
||||
function BubbleWand() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var wandEntity = this;
|
||||
|
||||
this.preload = function(entityID) {
|
||||
// print('PRELOAD')
|
||||
this.entityID = entityID;
|
||||
this.properties = Entities.getEntityProperties(this.entityID);
|
||||
}
|
||||
|
||||
this.unload = function(entityID) {
|
||||
Overlays.deleteOverlay(leftHand);
|
||||
Overlays.deleteOverlay(rightHand);
|
||||
Overlays.deleteOverlay(gustZoneOverlay)
|
||||
Entities.editEntity(entityID, {
|
||||
name: ""
|
||||
});
|
||||
Script.update.disconnect(BubbleWand.update);
|
||||
Entities.deleteEntity(BubbleWand.currentBubble);
|
||||
while (BubbleWand.bubbles.length > 0) {
|
||||
Entities.deleteEntity(BubbleWand.bubbles.pop());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var BubbleWand = {
|
||||
bubbles: [],
|
||||
BubbleWand.prototype = {
|
||||
timePassed: null,
|
||||
currentBubble: null,
|
||||
update: function() {
|
||||
BubbleWand.internalUpdate();
|
||||
preload: function (entityID) {
|
||||
this.entityID = entityID;
|
||||
},
|
||||
internalUpdate: function() {
|
||||
var _t = this;
|
||||
//get the current position of the wand
|
||||
var properties = Entities.getEntityProperties(wandEntity.entityID);
|
||||
getWandTipPosition: function (properties) {
|
||||
//the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position
|
||||
var upVector = Quat.getUp(properties.rotation);
|
||||
var upOffset = Vec3.multiply(upVector, WAND_TIP_OFFSET);
|
||||
var wandTipPosition = Vec3.sum(properties.position, upOffset);
|
||||
return wandTipPosition;
|
||||
},
|
||||
addCollisionsToBubbleAfterCreation: function (bubble) {
|
||||
//if the bubble collide immediately, we get weird effects. so we add collisions after release
|
||||
Entities.editEntity(bubble, {
|
||||
collisionsWillMove: true
|
||||
});
|
||||
},
|
||||
randomizeBubbleGravity: function () {
|
||||
//change up the gravity a little bit for variation in floating effects
|
||||
var randomNumber = randFloat(BUBBLE_GRAVITY_MIN, BUBBLE_GRAVITY_MAX);
|
||||
var gravity = {
|
||||
x: 0,
|
||||
y: -randomNumber,
|
||||
z: 0
|
||||
};
|
||||
return gravity;
|
||||
},
|
||||
growBubbleWithWandVelocity: function (properties, deltaTime) {
|
||||
//get the wand and tip position for calculations
|
||||
var wandPosition = properties.position;
|
||||
this.getWandTipPosition(properties);
|
||||
// velocity = change in position / time
|
||||
var velocity = Vec3.multiply(Vec3.subtract(wandPosition, this.lastPosition), 1 / deltaTime);
|
||||
|
||||
//debug overlays for mouth mode
|
||||
var leftHandPos = MyAvatar.getLeftPalmPosition();
|
||||
var rightHandPos = MyAvatar.getRightPalmPosition();
|
||||
|
||||
Overlays.editOverlay(leftHand, {
|
||||
position: leftHandPos
|
||||
});
|
||||
Overlays.editOverlay(rightHand, {
|
||||
position: rightHandPos
|
||||
});
|
||||
|
||||
//if the wand is in the gust detector, activate mouth mode and change the overlay color
|
||||
var hitTargetWithWand = findSphereSphereHit(wandPosition, HAND_SIZE / 2, getGustDetectorPosition(), TARGET_SIZE / 2)
|
||||
|
||||
var mouthMode;
|
||||
if (hitTargetWithWand) {
|
||||
Overlays.editOverlay(gustZoneOverlay, {
|
||||
position: getGustDetectorPosition(),
|
||||
color: TARGET_COLOR_HIT
|
||||
})
|
||||
mouthMode = true;
|
||||
|
||||
} else {
|
||||
Overlays.editOverlay(gustZoneOverlay, {
|
||||
position: getGustDetectorPosition(),
|
||||
color: TARGET_COLOR
|
||||
})
|
||||
mouthMode = false;
|
||||
}
|
||||
|
||||
var volumeLevel = MyAvatar.audioAverageLoudness;
|
||||
//volume numbers are pretty large, so lets scale them down.
|
||||
var convertedVolume = convertRange(volumeLevel, [0, 5000], [0, 10]);
|
||||
|
||||
// default is 'wave mode', where waving the object around grows the bubbles
|
||||
var velocity = Vec3.subtract(wandPosition, BubbleWand.lastPosition)
|
||||
var velocityStrength = Vec3.length(velocity);
|
||||
|
||||
//store the last position of the wand for velocity calculations
|
||||
_t.lastPosition = wandPosition;
|
||||
|
||||
// velocity numbers are pretty small, so lets make them a bit bigger
|
||||
var velocityStrength = Vec3.length(velocity) * 100;
|
||||
|
||||
if (velocityStrength > 10) {
|
||||
velocityStrength = 10
|
||||
}
|
||||
this.lastPosition = wandPosition;
|
||||
|
||||
//actually grow the bubble
|
||||
var dimensions = Entities.getEntityProperties(_t.currentBubble).dimensions;
|
||||
|
||||
if (velocityStrength > 1 || convertedVolume > 1) {
|
||||
var dimensions = Entities.getEntityProperties(this.currentBubble, "dimensions").dimensions;
|
||||
|
||||
if (velocityStrength > VELOCITY_THRESHOLD) {
|
||||
//add some variation in bubble sizes
|
||||
var bubbleSize = randInt(1, 5);
|
||||
bubbleSize = bubbleSize / 10;
|
||||
|
||||
var bubbleSize = randFloat(BUBBLE_SIZE_MIN, BUBBLE_SIZE_MAX);
|
||||
//release the bubble if its dimensions are bigger than the bubble size
|
||||
if (dimensions.x > bubbleSize) {
|
||||
|
||||
//bubbles pop after existing for a bit -- so set a random lifetime
|
||||
var lifetime = randInt(3, 8);
|
||||
var lifetime = randInt(BUBBLE_LIFETIME_MIN, BUBBLE_LIFETIME_MAX);
|
||||
|
||||
//sound is somewhat unstable at the moment so this is commented out. really audio should be played by the bubbles, but there's a blocker.
|
||||
// Script.setTimeout(function() {
|
||||
// _t.burstBubbleSound(_t.currentBubble)
|
||||
// }, lifetime * 1000)
|
||||
|
||||
|
||||
//todo: angular velocity without the controller -- forward velocity for mouth mode bubbles
|
||||
// var angularVelocity = Controller.getSpatialControlRawAngularVelocity(hands.leftHand.tip);
|
||||
|
||||
Entities.editEntity(_t.currentBubble, {
|
||||
velocity: Vec3.normalize(velocity),
|
||||
// angularVelocity: Controller.getSpatialControlRawAngularVelocity(hands.leftHand.tip),
|
||||
lifetime: lifetime
|
||||
//edit the bubble properties at release
|
||||
Entities.editEntity(this.currentBubble, {
|
||||
velocity: velocity,
|
||||
lifetime: lifetime,
|
||||
gravity: this.randomizeBubbleGravity()
|
||||
});
|
||||
|
||||
//wait to make the bubbles collidable, so that they dont hit each other and the wand
|
||||
Script.setTimeout(this.addCollisionsToBubbleAfterCreation(this.currentBubble), lifetime / 2);
|
||||
|
||||
//release the bubble -- when we create a new bubble, it will carry on and this update loop will affect the new bubble
|
||||
BubbleWand.spawnBubble();
|
||||
|
||||
return
|
||||
this.createBubbleAtTipOfWand();
|
||||
return;
|
||||
} else {
|
||||
if (mouthMode) {
|
||||
dimensions.x += 0.015 * convertedVolume;
|
||||
dimensions.y += 0.015 * convertedVolume;
|
||||
dimensions.z += 0.015 * convertedVolume;
|
||||
|
||||
} else {
|
||||
dimensions.x += 0.015 * velocityStrength;
|
||||
dimensions.y += 0.015 * velocityStrength;
|
||||
dimensions.z += 0.015 * velocityStrength;
|
||||
}
|
||||
//grow small bubbles
|
||||
dimensions.x += GROWTH_FACTOR * velocityStrength;
|
||||
dimensions.y += GROWTH_FACTOR * velocityStrength;
|
||||
dimensions.z += GROWTH_FACTOR * velocityStrength;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
if (dimensions.x >= 0.02) {
|
||||
dimensions.x -= 0.001;
|
||||
dimensions.y -= 0.001;
|
||||
dimensions.z -= 0.001;
|
||||
// if the wand is not moving, make the current bubble smaller
|
||||
if (dimensions.x >= SHRINK_LOWER_LIMIT) {
|
||||
dimensions.x -= SHRINK_FACTOR;
|
||||
dimensions.y -= SHRINK_FACTOR;
|
||||
dimensions.z -= SHRINK_FACTOR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//update the bubble to stay with the wand tip
|
||||
Entities.editEntity(_t.currentBubble, {
|
||||
position: _t.wandTipPosition,
|
||||
//adjust the bubble dimensions
|
||||
Entities.editEntity(this.currentBubble, {
|
||||
dimensions: dimensions
|
||||
});
|
||||
|
||||
},
|
||||
burstBubbleSound: function(bubble) {
|
||||
//we want to play the sound at the same location and orientation as the bubble
|
||||
var position = Entities.getEntityProperties(bubble).position;
|
||||
var orientation = Entities.getEntityProperties(bubble).orientation;
|
||||
createBubbleAtTipOfWand: function () {
|
||||
|
||||
//set the options for the audio injector
|
||||
var audioOptions = {
|
||||
volume: 0.5,
|
||||
position: position,
|
||||
orientation: orientation
|
||||
}
|
||||
|
||||
|
||||
//var audioInjector = Audio.playSound(popSound, audioOptions);
|
||||
|
||||
//remove this bubble from the array to keep things clean
|
||||
var i = BubbleWand.bubbles.indexOf(bubble);
|
||||
if (i != -1) {
|
||||
BubbleWand.bubbles.splice(i, 1);
|
||||
}
|
||||
|
||||
},
|
||||
spawnBubble: function() {
|
||||
var _t = this;
|
||||
//create a new bubble at the tip of the wand
|
||||
//the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position
|
||||
|
||||
var properties = Entities.getEntityProperties(wandEntity.entityID);
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||
var wandPosition = properties.position;
|
||||
var upVector = Quat.getUp(properties.rotation);
|
||||
var frontVector = Quat.getFront(properties.rotation);
|
||||
var upOffset = Vec3.multiply(upVector, 0.5);
|
||||
var forwardOffset = Vec3.multiply(frontVector, 0.1);
|
||||
var offsetVector = Vec3.sum(upOffset, forwardOffset);
|
||||
var wandTipPosition = Vec3.sum(wandPosition, offsetVector);
|
||||
_t.wandTipPosition = wandTipPosition;
|
||||
|
||||
//store the position of the tip on spawn for use in velocity calculations
|
||||
_t.lastPosition = wandTipPosition;
|
||||
//store the position of the tip for use in velocity calculations
|
||||
this.lastPosition = wandPosition;
|
||||
|
||||
//create a bubble at the wand tip
|
||||
_t.currentBubble = Entities.addEntity({
|
||||
this.currentBubble = Entities.addEntity({
|
||||
name: 'Bubble',
|
||||
type: 'Model',
|
||||
modelURL: bubbleModel,
|
||||
position: wandTipPosition,
|
||||
dimensions: {
|
||||
x: 0.01,
|
||||
y: 0.01,
|
||||
z: 0.01
|
||||
},
|
||||
modelURL: BUBBLE_MODEL,
|
||||
position: this.getWandTipPosition(properties),
|
||||
dimensions: BUBBLE_INITIAL_DIMENSIONS,
|
||||
collisionsWillMove: false,
|
||||
ignoreForCollisions: true,
|
||||
gravity: BUBBLE_GRAVITY,
|
||||
// collisionSoundURL:popSound,
|
||||
shapeType: "sphere",
|
||||
script: bubbleScript,
|
||||
ignoreForCollisions: false,
|
||||
linearDamping: BUBBLE_LINEAR_DAMPING,
|
||||
shapeType: "sphere"
|
||||
});
|
||||
|
||||
//add this bubble to an array of bubbles so we can keep track of them
|
||||
_t.bubbles.push(_t.currentBubble)
|
||||
},
|
||||
startNearGrab: function () {
|
||||
//create a bubble to grow at the start of the grab
|
||||
if (this.currentBubble === null) {
|
||||
this.createBubbleAtTipOfWand();
|
||||
}
|
||||
},
|
||||
continueNearGrab: function () {
|
||||
var deltaTime = checkInterval();
|
||||
//only get the properties that we need
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||
|
||||
|
||||
var wandTipPosition = this.getWandTipPosition(properties);
|
||||
|
||||
//update the bubble to stay with the wand tip
|
||||
Entities.editEntity(this.currentBubble, {
|
||||
position: wandTipPosition,
|
||||
});
|
||||
this.growBubbleWithWandVelocity(properties, deltaTime);
|
||||
|
||||
},
|
||||
init: function() {
|
||||
this.spawnBubble();
|
||||
Script.update.connect(BubbleWand.update);
|
||||
}
|
||||
}
|
||||
releaseGrab: function () {
|
||||
//delete the current buble and reset state when the wand is released
|
||||
Entities.deleteEntity(this.currentBubble);
|
||||
this.currentBubble = null;
|
||||
},
|
||||
|
||||
BubbleWand.init();
|
||||
};
|
||||
|
||||
})
|
||||
return new BubbleWand();
|
||||
|
||||
});
|
|
@ -15,30 +15,34 @@
|
|||
Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js");
|
||||
|
||||
|
||||
var scriptURL = "https://hifi-public.s3.amazonaws.com/scripts/toys/flashlight/flashlight.js?"+randInt(0,1000);
|
||||
var scriptURL = Script.resolvePath('flashlight.js?123123');
|
||||
|
||||
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
|
||||
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var flashlight = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: modelURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.04,
|
||||
y: 0.15,
|
||||
z: 0.04
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
shapeType: 'box',
|
||||
script: scriptURL
|
||||
type: "Model",
|
||||
modelURL: modelURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.30,
|
||||
z: 0.08
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
shapeType: 'box',
|
||||
script: scriptURL
|
||||
});
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(flashlight);
|
||||
//commenting out the line below makes this persistent. to delete at cleanup, uncomment
|
||||
//Entities.deleteEntity(flashlight);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
//
|
||||
// flashligh.js
|
||||
// examples/entityScripts
|
||||
// flashlight.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
//
|
||||
// Created by Sam Gateau on 9/9/15.
|
||||
// Additions by James B. Pollack @imgntn on 9/21/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is a toy script that can be added to the Flashlight model entity:
|
||||
|
@ -14,10 +16,6 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
|
||||
function debugPrint(message) {
|
||||
//print(message);
|
||||
}
|
||||
|
||||
Script.include("../../libraries/utils.js");
|
||||
|
||||
|
@ -25,150 +23,227 @@
|
|||
|
||||
// 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)
|
||||
Flashlight = function() {
|
||||
Flashlight = function() {
|
||||
_this = this;
|
||||
_this._hasSpotlight = false;
|
||||
_this._spotlight = null;
|
||||
};
|
||||
|
||||
|
||||
GRAB_FRAME_USER_DATA_KEY = "grabFrame";
|
||||
|
||||
// These constants define the Flashlight model Grab Frame
|
||||
var MODEL_GRAB_FRAME = {
|
||||
relativePosition: {x: 0, y: -0.1, z: 0},
|
||||
relativeRotation: Quat.angleAxis(180, {x: 1, y: 0, z: 0})
|
||||
};
|
||||
//if the trigger value goes below this while held, the flashlight will turn off. if it goes above, it will
|
||||
var DISABLE_LIGHT_THRESHOLD = 0.7;
|
||||
|
||||
// These constants define the Spotlight position and orientation relative to the model
|
||||
var MODEL_LIGHT_POSITION = {x: 0, y: 0, z: 0};
|
||||
var MODEL_LIGHT_ROTATION = Quat.angleAxis (-90, {x: 1, y: 0, z: 0});
|
||||
var MODEL_LIGHT_POSITION = {
|
||||
x: 0,
|
||||
y: -0.3,
|
||||
z: 0
|
||||
};
|
||||
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
|
||||
// Evaluate the world light entity position and orientation from the model ones
|
||||
var GLOW_LIGHT_POSITION = {
|
||||
x: 0,
|
||||
y: -0.1,
|
||||
z: 0
|
||||
}
|
||||
|
||||
// Evaluate the world light entity positions and orientations from the model ones
|
||||
function evalLightWorldTransform(modelPos, modelRot) {
|
||||
return {p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
|
||||
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)};
|
||||
|
||||
return {
|
||||
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
|
||||
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
|
||||
};
|
||||
};
|
||||
|
||||
function glowLightWorldTransform(modelPos, modelRot) {
|
||||
return {
|
||||
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, GLOW_LIGHT_POSITION)),
|
||||
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Flashlight.prototype = {
|
||||
lightOn: false,
|
||||
hand: null,
|
||||
whichHand: null,
|
||||
hasSpotlight: false,
|
||||
spotlight: null,
|
||||
setRightHand: function() {
|
||||
this.hand = 'RIGHT';
|
||||
},
|
||||
setLeftHand: function() {
|
||||
this.hand = 'LEFT';
|
||||
},
|
||||
startNearGrab: function() {
|
||||
if (!_this.hasSpotlight) {
|
||||
|
||||
//this light casts the beam
|
||||
this.spotlight = Entities.addEntity({
|
||||
type: "Light",
|
||||
isSpotlight: true,
|
||||
dimensions: {
|
||||
x: 2,
|
||||
y: 2,
|
||||
z: 20
|
||||
},
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
intensity: 2,
|
||||
exponent: 0.3,
|
||||
cutoff: 20
|
||||
});
|
||||
|
||||
//this light creates the effect of a bulb at the end of the flashlight
|
||||
this.glowLight = Entities.addEntity({
|
||||
type: "Light",
|
||||
dimensions: {
|
||||
x: 0.25,
|
||||
y: 0.25,
|
||||
z: 0.25
|
||||
},
|
||||
isSpotlight: false,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
exponent: 0,
|
||||
cutoff: 90, // in degrees
|
||||
});
|
||||
|
||||
// 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";
|
||||
this.debugBox = Entities.addEntity({
|
||||
type: 'Box',
|
||||
color: {
|
||||
red: 255,
|
||||
blue: 0,
|
||||
green: 0
|
||||
},
|
||||
dimensions: {
|
||||
x: 0.25,
|
||||
y: 0.25,
|
||||
z: 0.25
|
||||
},
|
||||
ignoreForCollisions:true
|
||||
})
|
||||
|
||||
this.hasSpotlight = true;
|
||||
|
||||
// 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;
|
||||
|
||||
var modelProperties = Entities.getEntityProperties(entityID);
|
||||
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
|
||||
|
||||
// Create the spot light driven by this model if we don;t have one yet
|
||||
// Or make sure to keep it's position in sync
|
||||
if (!_this._hasSpotlight) {
|
||||
|
||||
_this._spotlight = Entities.addEntity({
|
||||
type: "Light",
|
||||
position: lightTransform.p,
|
||||
rotation: lightTransform.q,
|
||||
isSpotlight: true,
|
||||
dimensions: { x: 2, y: 2, z: 20 },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
intensity: 2,
|
||||
exponent: 0.3,
|
||||
cutoff: 20
|
||||
});
|
||||
_this._hasSpotlight = true;
|
||||
|
||||
_this._startModelPosition = modelProperties.position;
|
||||
_this._startModelRotation = modelProperties.rotation;
|
||||
|
||||
debugPrint("Flashlight:: creating a spotlight");
|
||||
} else {
|
||||
// Updating the spotlight
|
||||
Entities.editEntity(_this._spotlight, {position: lightTransform.p, rotation: lightTransform.q});
|
||||
|
||||
debugPrint("Flashlight:: updating the spotlight");
|
||||
}
|
||||
|
||||
debugPrint("I'm being grabbed...");
|
||||
|
||||
} else if (_this.beingGrabbed) {
|
||||
|
||||
if (_this._hasSpotlight) {
|
||||
Entities.deleteEntity(_this._spotlight);
|
||||
debugPrint("Destroying flashlight spotlight...");
|
||||
}
|
||||
_this._hasSpotlight = false;
|
||||
_this._spotlight = null;
|
||||
|
||||
// Reset model to initial position
|
||||
Entities.editEntity(_this.entityID, {position: _this._startModelPosition, rotation: _this._startModelRotation,
|
||||
velocity: {x: 0, y: 0, z: 0}, angularVelocity: {x: 0, y: 0, z: 0}});
|
||||
|
||||
// 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;
|
||||
debugPrint("I'm was released...");
|
||||
},
|
||||
setWhichHand: function() {
|
||||
this.whichHand = this.hand;
|
||||
},
|
||||
continueNearGrab: function() {
|
||||
if (this.whichHand === null) {
|
||||
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
|
||||
this.setWhichHand();
|
||||
} else {
|
||||
this.updateLightPositions();
|
||||
this.changeLightWithTriggerPressure(this.whichHand);
|
||||
}
|
||||
},
|
||||
releaseGrab: function() {
|
||||
//delete the lights and reset state
|
||||
if (this.hasSpotlight) {
|
||||
Entities.deleteEntity(this.spotlight);
|
||||
Entities.deleteEntity(this.glowLight);
|
||||
this.hasSpotlight = false;
|
||||
this.glowLight = null;
|
||||
this.spotlight = null;
|
||||
this.whichHand = null;
|
||||
|
||||
}
|
||||
},
|
||||
updateLightPositions: function() {
|
||||
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
|
||||
|
||||
//move the two lights along the vectors we set above
|
||||
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
|
||||
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
|
||||
|
||||
|
||||
//move them with the entity model
|
||||
Entities.editEntity(this.spotlight, {
|
||||
position: lightTransform.p,
|
||||
rotation: lightTransform.q,
|
||||
})
|
||||
|
||||
|
||||
Entities.editEntity(this.glowLight, {
|
||||
position: glowLightTransform.p,
|
||||
rotation: glowLightTransform.q,
|
||||
})
|
||||
|
||||
// Entities.editEntity(this.debugBox, {
|
||||
// position: lightTransform.p,
|
||||
// rotation: lightTransform.q,
|
||||
// })
|
||||
|
||||
},
|
||||
changeLightWithTriggerPressure: function(flashLightHand) {
|
||||
|
||||
var handClickString = flashLightHand + "_HAND_CLICK";
|
||||
|
||||
var handClick = Controller.findAction(handClickString);
|
||||
|
||||
this.triggerValue = Controller.getActionValue(handClick);
|
||||
|
||||
if (this.triggerValue < DISABLE_LIGHT_THRESHOLD && this.lightOn === true) {
|
||||
this.turnLightOff();
|
||||
} else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) {
|
||||
this.turnLightOn();
|
||||
}
|
||||
return
|
||||
},
|
||||
turnLightOff: function() {
|
||||
Entities.editEntity(this.spotlight, {
|
||||
intensity: 0
|
||||
});
|
||||
Entities.editEntity(this.glowLight, {
|
||||
intensity: 0
|
||||
});
|
||||
this.lightOn = false
|
||||
},
|
||||
turnLightOn: function() {
|
||||
Entities.editEntity(this.glowLight, {
|
||||
intensity: 2
|
||||
});
|
||||
Entities.editEntity(this.spotlight, {
|
||||
intensity: 2
|
||||
});
|
||||
this.lightOn = true
|
||||
},
|
||||
// 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;
|
||||
|
||||
var modelProperties = Entities.getEntityProperties(entityID);
|
||||
_this._startModelPosition = modelProperties.position;
|
||||
_this._startModelRotation = modelProperties.rotation;
|
||||
|
||||
// Make sure the Flashlight entity has a correct grab frame setup
|
||||
var userData = getEntityUserData(entityID);
|
||||
debugPrint(JSON.stringify(userData));
|
||||
if (!userData.grabFrame) {
|
||||
setEntityCustomData(GRAB_FRAME_USER_DATA_KEY, entityID, MODEL_GRAB_FRAME);
|
||||
debugPrint(JSON.stringify(MODEL_GRAB_FRAME));
|
||||
debugPrint("Assigned the grab frmae for the Flashlight entity");
|
||||
}
|
||||
|
||||
Script.update.connect(this.update);
|
||||
this.entityID = entityID;
|
||||
},
|
||||
|
||||
// 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
|
||||
// or because we've left the domain or quit the application.
|
||||
unload: function(entityID) {
|
||||
|
||||
if (_this._hasSpotlight) {
|
||||
Entities.deleteEntity(_this._spotlight);
|
||||
if (this.hasSpotlight) {
|
||||
Entities.deleteEntity(this.spotlight);
|
||||
Entities.deleteEntity(this.glowLight);
|
||||
this.hasSpotlight = false;
|
||||
this.glowLight = null;
|
||||
this.spotlight = null;
|
||||
this.whichHand = null;
|
||||
}
|
||||
_this._hasSpotlight = false;
|
||||
_this._spotlight = null;
|
||||
|
||||
Script.update.disconnect(this.update);
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Flashlight();
|
||||
})
|
||||
})
|
|
@ -113,7 +113,7 @@ endif()
|
|||
target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES})
|
||||
|
||||
# link required hifi libraries
|
||||
link_hifi_libraries(shared octree environment gpu gpu-networking procedural model render fbx networking entities avatars
|
||||
link_hifi_libraries(shared octree environment gpu procedural model render fbx networking model-networking entities avatars
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer ui auto-updater
|
||||
plugins display-plugins input-plugins)
|
||||
|
|
BIN
interface/resources/images/NormalsFittingTexture.dds
Normal file
BIN
interface/resources/images/NormalsFittingTexture.dds
Normal file
Binary file not shown.
|
@ -0,0 +1,587 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"root": {
|
||||
"id": "ikOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 1.0,
|
||||
"boneSet": "fullBody"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "ik",
|
||||
"type": "inverseKinematics",
|
||||
"data": {
|
||||
"targets": [
|
||||
{
|
||||
"jointName": "RightHand",
|
||||
"positionVar": "rightHandPosition",
|
||||
"rotationVar": "rightHandRotation"
|
||||
},
|
||||
{
|
||||
"jointName": "LeftHand",
|
||||
"positionVar": "leftHandPosition",
|
||||
"rotationVar": "leftHandRotation"
|
||||
},
|
||||
{
|
||||
"jointName": "Head",
|
||||
"positionVar": "headPosition",
|
||||
"rotationVar": "headRotation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "manipulatorOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 1.0,
|
||||
"boneSet": "spineOnly"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "spineLean",
|
||||
"type": "manipulator",
|
||||
"data": {
|
||||
"alpha": 1.0,
|
||||
"joints": [
|
||||
{ "var": "lean", "jointName": "Spine" }
|
||||
]
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 1.0,
|
||||
"boneSet": "rightHand",
|
||||
"alphaVar": "rightHandOverlayAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightHandStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "rightHandIdle",
|
||||
"states": [
|
||||
{
|
||||
"id": "rightHandIdle",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandPoint", "state": "rightHandPointIntro" },
|
||||
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointIntro",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandIdle", "state": "rightHandIdle" },
|
||||
{ "var": "isRightHandPointIntroOnDone", "state": "rightHandPointHold" },
|
||||
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointHold",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandIdle", "state": "rightHandPointOutro" },
|
||||
{ "var": "isRightHandGrab", "state": "rightHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointOutro",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandPointOutroOnDone", "state": "rightHandIdle" },
|
||||
{ "var": "isRightHandGrab", "state": "rightHandGrab" },
|
||||
{ "var": "isRightHandPoint", "state": "rightHandPointHold" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightHandGrab",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandIdle", "state": "rightHandIdle" },
|
||||
{ "var": "isRightHandPoint_DISABLED", "state": "rightHandPointHold" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightHandIdle",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointHold",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
|
||||
"startFrame": 12.0,
|
||||
"endFrame": 12.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointIntro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 12.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandPointOutro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 65.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandGrab",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "rightHandGrabBlend"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightHandOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_right_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandClose",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/right_hand_anim.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 1.0,
|
||||
"boneSet": "leftHand",
|
||||
"alphaVar" : "leftHandOverlay"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "leftHandIdle",
|
||||
"states": [
|
||||
{
|
||||
"id": "leftHandIdle",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandPoint", "state": "leftHandPointIntro" },
|
||||
{ "var": "isLeftHandGrab", "state": "leftHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointIntro",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandIdle", "state": "leftHandIdle" },
|
||||
{ "var": "isLeftHandPointIntroOnDone", "state": "leftHandPointHold" },
|
||||
{ "var": "isLeftHandGrab", "state": "leftHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointHold",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandIdle", "state": "leftHandPointOutro" },
|
||||
{ "var": "isLeftHandGrab", "state": "leftHandGrab" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointOutro",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandPointOutroOnDone", "state": "leftHandIdle" },
|
||||
{ "var": "isLeftHandGrab", "state": "leftHandGrab" },
|
||||
{ "var": "isLeftHandPoint", "state": "leftHandPointHold" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandGrab",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandIdle", "state": "leftHandIdle" },
|
||||
{ "var": "isLeftHandPoint_DISABLED", "state": "leftHandPointHold" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandIdle",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointHold",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
|
||||
"startFrame": 12.0,
|
||||
"endFrame": 12.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointIntro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 12.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandPointOutro",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 65.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandGrab",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGrabBlend"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/hand_anims/point_left_hand.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandClose",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/squeeze_hands/left_hand_anim.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mainStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "idle",
|
||||
"states": [
|
||||
{
|
||||
"id": "idle",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkFwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningLeft", "state": "turnLeft" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotTurning", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
{ "var": "isMovingLeft", "state": "strafeLeft" },
|
||||
{ "var": "isTurningRight", "state": "turnRight" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idle",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "idleStand",
|
||||
"states": [
|
||||
{
|
||||
"id": "idleStand",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isTalking", "state": "idleTalk" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "idleTalk",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "notIsTalking", "state": "idleStand" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idleStand",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "idleTalk",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/talk/talk.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 801.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "walkFwd",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 35.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 37.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true,
|
||||
"timeScaleVar": "walkTimeScale"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 28.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "turnRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 30.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeLeft",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "strafeRight",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 31.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -168,7 +168,7 @@ Item {
|
|||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
text: "Triangles: " + root.triangles +
|
||||
" / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches
|
||||
" / Material Switches: " + root.materialSwitches
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
|
|
|
@ -278,6 +278,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
||||
auto geometryCache = DependencyManager::set<GeometryCache>();
|
||||
auto modelCache = DependencyManager::set<ModelCache>();
|
||||
auto scriptCache = DependencyManager::set<ScriptCache>();
|
||||
auto soundCache = DependencyManager::set<SoundCache>();
|
||||
auto faceshift = DependencyManager::set<Faceshift>();
|
||||
|
@ -418,12 +419,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
// put the NodeList and datagram processing on the node thread
|
||||
nodeList->moveToThread(nodeThread);
|
||||
|
||||
// geometry background downloads need to happen on the Datagram Processor Thread. The idle loop will
|
||||
// emit checkBackgroundDownloads to cause the GeometryCache to check it's queue for requested background
|
||||
// Model background downloads need to happen on the Datagram Processor Thread. The idle loop will
|
||||
// emit checkBackgroundDownloads to cause the ModelCache to check it's queue for requested background
|
||||
// downloads.
|
||||
QSharedPointer<GeometryCache> geometryCacheP = DependencyManager::get<GeometryCache>();
|
||||
ResourceCache* geometryCache = geometryCacheP.data();
|
||||
connect(this, &Application::checkBackgroundDownloads, geometryCache, &ResourceCache::checkAsynchronousGets);
|
||||
QSharedPointer<ModelCache> modelCacheP = DependencyManager::get<ModelCache>();
|
||||
ResourceCache* modelCache = modelCacheP.data();
|
||||
connect(this, &Application::checkBackgroundDownloads, modelCache, &ResourceCache::checkAsynchronousGets);
|
||||
|
||||
// put the audio processing on a separate thread
|
||||
QThread* audioThread = new QThread();
|
||||
|
@ -604,7 +605,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_overlays.init(); // do this before scripts load
|
||||
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript);
|
||||
|
||||
connect(this, SIGNAL(aboutToQuit()), this, SLOT(saveScripts()));
|
||||
connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
|
||||
|
@ -892,6 +892,7 @@ Application::~Application() {
|
|||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<FramebufferCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<ModelCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
@ -1063,7 +1064,7 @@ void Application::paintGL() {
|
|||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
|
||||
|
||||
RenderArgs renderArgs(_gpuContext, nullptr, getViewFrustum(), lodManager->getOctreeSizeScale(),
|
||||
RenderArgs renderArgs(_gpuContext, getEntities(), getViewFrustum(), lodManager->getOctreeSizeScale(),
|
||||
lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE,
|
||||
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);
|
||||
|
||||
|
@ -1120,43 +1121,61 @@ void Application::paintGL() {
|
|||
// The render mode is default or mirror if the camera is in mirror mode, assigned further below
|
||||
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
|
||||
|
||||
// Always use the default eye position, not the actual head eye position.
|
||||
// Using the latter will cause the camera to wobble with idle animations,
|
||||
// or with changes from the face tracker
|
||||
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
|
||||
// Always use the default eye position, not the actual head eye position.
|
||||
// Using the latter will cause the camera to wobble with idle animations,
|
||||
// or with changes from the face tracker
|
||||
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
|
||||
|
||||
if (!getActiveDisplayPlugin()->isHmd()) {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition());
|
||||
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
|
||||
} else {
|
||||
if (isHMDMode()) {
|
||||
mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix();
|
||||
_myCamera.setPosition(extractTranslation(camMat));
|
||||
_myCamera.setRotation(glm::quat_cast(camMat));
|
||||
} else {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition());
|
||||
_myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation());
|
||||
}
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
|
||||
if (isHMDMode()) {
|
||||
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation());
|
||||
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
|
||||
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * hmdRotation);
|
||||
// Ignore MenuOption::CenterPlayerInView in HMD view
|
||||
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
|
||||
+ _myAvatar->getOrientation()
|
||||
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f) + hmdOffset));
|
||||
} else {
|
||||
_myCamera.setRotation(_myAvatar->getHead()->getOrientation());
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
|
||||
+ _myCamera.getRotation()
|
||||
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||
} else {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
|
||||
+ _myAvatar->getOrientation()
|
||||
* (_myAvatar->getScale() * _myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||
}
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition() +
|
||||
_myCamera.getRotation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale());
|
||||
} else {
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition() +
|
||||
_myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, 1.0f) * _myAvatar->getBoomLength() * _myAvatar->getScale());
|
||||
}
|
||||
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition() +
|
||||
glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) +
|
||||
(_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
if (isHMDMode()) {
|
||||
glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix());
|
||||
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
|
||||
glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix());
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
|
||||
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
|
||||
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
|
||||
} else {
|
||||
_myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setPosition(_myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0)
|
||||
+ (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
}
|
||||
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
|
||||
}
|
||||
// Update camera position
|
||||
// Update camera position
|
||||
if (!isHMDMode()) {
|
||||
_myCamera.update(1.0f / _fps);
|
||||
}
|
||||
|
@ -2595,24 +2614,19 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
bool isLookingAtSomeone = false;
|
||||
bool isHMD = _avatarUpdate->isHMDMode();
|
||||
glm::vec3 lookAtSpot;
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
// When I am in mirror mode, just look right at the camera (myself); don't switch gaze points because when physically
|
||||
// looking in a mirror one's eyes appear steady.
|
||||
if (!isHMD) {
|
||||
lookAtSpot = _myCamera.getPosition();
|
||||
} else {
|
||||
lookAtSpot = _myCamera.getPosition() + transformPoint(_myAvatar->getSensorToWorldMatrix(), extractTranslation(getHMDSensorPose()));
|
||||
}
|
||||
} else if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) {
|
||||
if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) {
|
||||
// Look at the point that the user is looking at.
|
||||
glm::vec3 lookAtPosition = eyeTracker->getLookAtPosition();
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
lookAtPosition.x = -lookAtPosition.x;
|
||||
}
|
||||
if (isHMD) {
|
||||
glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose();
|
||||
glm::quat hmdRotation = glm::quat_cast(headPose);
|
||||
lookAtSpot = _myCamera.getPosition() +
|
||||
_myAvatar->getOrientation() * (hmdRotation * eyeTracker->getLookAtPosition());
|
||||
lookAtSpot = _myCamera.getPosition() + _myAvatar->getOrientation() * (hmdRotation * lookAtPosition);
|
||||
} else {
|
||||
lookAtSpot = _myAvatar->getHead()->getEyePosition() +
|
||||
(_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition());
|
||||
lookAtSpot = _myAvatar->getHead()->getEyePosition()
|
||||
+ (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * lookAtPosition);
|
||||
}
|
||||
} else {
|
||||
AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().lock();
|
||||
|
@ -2718,7 +2732,7 @@ void Application::reloadResourceCaches() {
|
|||
emptyLocalCache();
|
||||
|
||||
DependencyManager::get<AnimationCache>()->refreshAll();
|
||||
DependencyManager::get<GeometryCache>()->refreshAll();
|
||||
DependencyManager::get<ModelCache>()->refreshAll();
|
||||
DependencyManager::get<SoundCache>()->refreshAll();
|
||||
DependencyManager::get<TextureCache>()->refreshAll();
|
||||
}
|
||||
|
@ -3505,7 +3519,7 @@ namespace render {
|
|||
|
||||
skybox = skyStage->getSkybox();
|
||||
if (skybox) {
|
||||
model::Skybox::render(batch, *(Application::getInstance()->getDisplayViewFrustum()), *skybox);
|
||||
skybox->render(batch, *(Application::getInstance()->getDisplayViewFrustum()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3562,7 +3576,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
(RenderArgs::DebugFlags) (renderDebugFlags | (int)RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP);
|
||||
}
|
||||
renderArgs->_debugFlags = renderDebugFlags;
|
||||
_entities.render(renderArgs);
|
||||
//ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges);
|
||||
}
|
||||
}
|
||||
|
@ -3648,14 +3661,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
sceneInterface->setEngineDrawnOverlay3DItems(engineRC->_numDrawnOverlay3DItems);
|
||||
}
|
||||
|
||||
if (!selfAvatarOnly) {
|
||||
// give external parties a change to hook in
|
||||
{
|
||||
PerformanceTimer perfTimer("inWorldInterface");
|
||||
emit renderingInWorldInterface();
|
||||
}
|
||||
}
|
||||
|
||||
activeRenderingThread = nullptr;
|
||||
}
|
||||
|
||||
|
@ -4325,17 +4330,18 @@ void Application::stopAllScripts(bool restart) {
|
|||
_myAvatar->clearScriptableSettings();
|
||||
}
|
||||
|
||||
void Application::stopScript(const QString &scriptName, bool restart) {
|
||||
const QString& scriptURLString = QUrl(scriptName).toString();
|
||||
if (_scriptEnginesHash.contains(scriptURLString)) {
|
||||
ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURLString];
|
||||
bool Application::stopScript(const QString& scriptHash, bool restart) {
|
||||
bool stoppedScript = false;
|
||||
if (_scriptEnginesHash.contains(scriptHash)) {
|
||||
ScriptEngine* scriptEngine = _scriptEnginesHash[scriptHash];
|
||||
if (restart) {
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
scriptCache->deleteScript(scriptName);
|
||||
scriptCache->deleteScript(QUrl(scriptHash));
|
||||
connect(scriptEngine, SIGNAL(finished(const QString&)), SLOT(reloadScript(const QString&)));
|
||||
}
|
||||
scriptEngine->stop();
|
||||
qCDebug(interfaceapp) << "stopping script..." << scriptName;
|
||||
stoppedScript = true;
|
||||
qCDebug(interfaceapp) << "stopping script..." << scriptHash;
|
||||
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
|
||||
// whenever a script stops in case it happened to have been setting joint rotations.
|
||||
// TODO: expose animation priorities and provide a layered animation control system.
|
||||
|
@ -4344,6 +4350,7 @@ void Application::stopScript(const QString &scriptName, bool restart) {
|
|||
if (_scriptEnginesHash.empty()) {
|
||||
_myAvatar->clearScriptableSettings();
|
||||
}
|
||||
return stoppedScript;
|
||||
}
|
||||
|
||||
void Application::reloadAllScripts() {
|
||||
|
|
|
@ -292,7 +292,7 @@ public:
|
|||
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
|
||||
|
||||
QStringList getRunningScripts() { return _scriptEnginesHash.keys(); }
|
||||
ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; }
|
||||
ScriptEngine* getScriptEngine(const QString& scriptHash) { return _scriptEnginesHash.value(scriptHash, NULL); }
|
||||
|
||||
bool isLookingAtMyAvatar(AvatarSharedPointer avatar);
|
||||
|
||||
|
@ -347,9 +347,6 @@ signals:
|
|||
/// Fired when we're simulating; allows external parties to hook in.
|
||||
void simulating(float deltaTime);
|
||||
|
||||
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
|
||||
void renderingInWorldInterface();
|
||||
|
||||
/// Fired when the import window is closed
|
||||
void importDone();
|
||||
|
||||
|
@ -395,7 +392,7 @@ public slots:
|
|||
void reloadScript(const QString& scriptName, bool isUserLoaded = true);
|
||||
void scriptFinished(const QString& scriptName);
|
||||
void stopAllScripts(bool restart = false);
|
||||
void stopScript(const QString& scriptName, bool restart = false);
|
||||
bool stopScript(const QString& scriptHash, bool restart = false);
|
||||
void reloadAllScripts();
|
||||
void reloadOneScript(const QString& scriptName);
|
||||
void loadDefaultScripts();
|
||||
|
|
|
@ -340,25 +340,23 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
|
||||
void ModelPackager::listTextures() {
|
||||
_textures.clear();
|
||||
foreach (FBXMesh mesh, _geometry->meshes) {
|
||||
foreach (FBXMeshPart part, mesh.parts) {
|
||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
||||
!_textures.contains(part.diffuseTexture.filename)) {
|
||||
_textures << part.diffuseTexture.filename;
|
||||
}
|
||||
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() &&
|
||||
!_textures.contains(part.normalTexture.filename)) {
|
||||
foreach (const FBXMaterial mat, _geometry->materials) {
|
||||
if (!mat.diffuseTexture.filename.isEmpty() && mat.diffuseTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.diffuseTexture.filename)) {
|
||||
_textures << mat.diffuseTexture.filename;
|
||||
}
|
||||
if (!mat.normalTexture.filename.isEmpty() && mat.normalTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.normalTexture.filename)) {
|
||||
|
||||
_textures << part.normalTexture.filename;
|
||||
}
|
||||
if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() &&
|
||||
!_textures.contains(part.specularTexture.filename)) {
|
||||
_textures << part.specularTexture.filename;
|
||||
}
|
||||
if (!part.emissiveTexture.filename.isEmpty() && part.emissiveTexture.content.isEmpty() &&
|
||||
!_textures.contains(part.emissiveTexture.filename)) {
|
||||
_textures << part.emissiveTexture.filename;
|
||||
}
|
||||
_textures << mat.normalTexture.filename;
|
||||
}
|
||||
if (!mat.specularTexture.filename.isEmpty() && mat.specularTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.specularTexture.filename)) {
|
||||
_textures << mat.specularTexture.filename;
|
||||
}
|
||||
if (!mat.emissiveTexture.filename.isEmpty() && mat.emissiveTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.emissiveTexture.filename)) {
|
||||
_textures << mat.emissiveTexture.filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,8 +188,10 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
|
|||
colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element;
|
||||
});
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
batch.setViewTransform(Transform());
|
||||
|
@ -204,7 +206,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
|
|||
float msecs = (float)(usecTimestampNow() - start) / (float)USECS_PER_MSEC;
|
||||
float secs = msecs / (float)MSECS_PER_SECOND;
|
||||
batch._glUniform1f(_timeSlot, secs);
|
||||
geometryCache->renderUnitCube(batch);
|
||||
geometryCache->renderCube(batch);
|
||||
|
||||
static const size_t VERTEX_STRIDE = sizeof(StarVertex);
|
||||
size_t offset = offsetof(StarVertex, position);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
|
||||
#include "world.h"
|
||||
#include "Application.h"
|
||||
|
@ -93,29 +94,28 @@ void renderWorldBox(gpu::Batch& batch) {
|
|||
geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE),
|
||||
glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY);
|
||||
|
||||
geometryCache->renderWireCube(batch, TREE_SCALE, GREY4);
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
|
||||
deferredLighting->renderWireCubeInstance(batch, Transform(), GREY4);
|
||||
|
||||
// Draw meter markers along the 3 axis to help with measuring things
|
||||
const float MARKER_DISTANCE = 1.0f;
|
||||
const float MARKER_RADIUS = 0.05f;
|
||||
|
||||
geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED);
|
||||
transform = Transform().setScale(MARKER_RADIUS);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, RED);
|
||||
|
||||
transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f));
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED);
|
||||
transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f)).setScale(MARKER_RADIUS);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, RED);
|
||||
|
||||
transform.setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f));
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREEN);
|
||||
transform = Transform().setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f)).setScale(MARKER_RADIUS);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, GREEN);
|
||||
|
||||
transform.setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE));
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, BLUE);
|
||||
transform = Transform().setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, BLUE);
|
||||
|
||||
transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE));
|
||||
batch.setModelTransform(transform);
|
||||
geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREY);
|
||||
transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, GREY);
|
||||
}
|
||||
|
||||
// Return a random vector of average length 1
|
||||
|
|
|
@ -448,15 +448,14 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) {
|
||||
const float INDICATOR_OFFSET = 0.22f;
|
||||
const float INDICATOR_RADIUS = 0.03f;
|
||||
const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
static const float INDICATOR_OFFSET = 0.22f;
|
||||
static const float INDICATOR_RADIUS = 0.03f;
|
||||
static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + INDICATOR_OFFSET, _position.z);
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, INDICATOR_RADIUS,
|
||||
15, 15, LOOK_AT_INDICATOR_COLOR);
|
||||
transform.postScale(INDICATOR_RADIUS);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, transform, LOOK_AT_INDICATOR_COLOR);
|
||||
}
|
||||
|
||||
// If the avatar is looking at me, indicate that they are
|
||||
|
@ -473,27 +472,29 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
if (geometry && geometry->isLoaded()) {
|
||||
const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye
|
||||
const float RADIUS_INCREMENT = 0.005f;
|
||||
Transform transform;
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
glm::vec3 position = getHead()->getLeftEyePosition();
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
float eyeDiameter = geometry->getFBXGeometry().leftEyeSize;
|
||||
if (eyeDiameter == 0.0f) {
|
||||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch,
|
||||
eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
position = getHead()->getRightEyePosition();
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
eyeDiameter = geometry->getFBXGeometry().rightEyeSize;
|
||||
if (eyeDiameter == 0.0f) {
|
||||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch,
|
||||
eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -518,19 +519,16 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
|
||||
if (renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) &&
|
||||
(angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_position);
|
||||
transform.setScale(height);
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
if (_voiceSphereID == GeometryCache::UNKNOWN_ID) {
|
||||
_voiceSphereID = DependencyManager::get<GeometryCache>()->allocateID();
|
||||
}
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, sphereRadius, 15, 15,
|
||||
glm::vec4(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE), true,
|
||||
_voiceSphereID);
|
||||
transform.postScale(sphereRadius);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
transform,
|
||||
glm::vec4(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -645,7 +643,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) {
|
|||
// Using a unique URL ensures we don't get another avatar's texture from TextureCache
|
||||
QUrl uniqueUrl = QUrl(QUuid::createUuid().toString());
|
||||
_billboardTexture = DependencyManager::get<TextureCache>()->getTexture(
|
||||
uniqueUrl, DEFAULT_TEXTURE, false, _billboard);
|
||||
uniqueUrl, DEFAULT_TEXTURE, _billboard);
|
||||
}
|
||||
if (!_billboardTexture || !_billboardTexture->isLoaded()) {
|
||||
return;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <GeometryUtil.h>
|
||||
#include <RenderArgs.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
#include "AvatarManager.h"
|
||||
|
@ -50,7 +51,6 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
|||
const glm::vec3 greenColor(0.0f, 1.0f, 0.0f); // Color the hand targets red to be different than skin
|
||||
const glm::vec3 blueColor(0.0f, 0.0f, 1.0f); // Color the hand targets red to be different than skin
|
||||
const glm::vec3 grayColor(0.5f);
|
||||
const int NUM_FACETS = 8;
|
||||
const float SPHERE_RADIUS = 0.03f * avatarScale;
|
||||
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
|
@ -65,16 +65,16 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
|||
Transform transform = Transform();
|
||||
transform.setTranslation(position);
|
||||
transform.setRotation(palm.getRotation());
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, SPHERE_RADIUS,
|
||||
NUM_FACETS, NUM_FACETS, grayColor);
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, transform, grayColor);
|
||||
|
||||
// draw a green sphere at the old "finger tip"
|
||||
transform = Transform();
|
||||
position = palm.getTipPosition();
|
||||
transform.setTranslation(position);
|
||||
batch.setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, SPHERE_RADIUS,
|
||||
NUM_FACETS, NUM_FACETS, greenColor, false);
|
||||
transform.setRotation(palm.getRotation());
|
||||
transform.postScale(SPHERE_RADIUS);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, transform, greenColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -158,10 +158,11 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
bool forceBlink = false;
|
||||
const float TALKING_LOUDNESS = 100.0f;
|
||||
const float BLINK_AFTER_TALKING = 0.25f;
|
||||
_timeWithoutTalking += deltaTime;
|
||||
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
|
||||
_timeWithoutTalking = 0.0f;
|
||||
|
||||
} else if (_timeWithoutTalking < BLINK_AFTER_TALKING && (_timeWithoutTalking += deltaTime) >= BLINK_AFTER_TALKING) {
|
||||
} else if (_timeWithoutTalking < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
|
||||
forceBlink = true;
|
||||
}
|
||||
|
||||
|
@ -461,13 +462,10 @@ void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition)
|
|||
auto& batch = *renderArgs->_batch;
|
||||
auto transform = Transform{};
|
||||
transform.setTranslation(lookatPosition);
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
deferredLighting->bindSimpleProgram(batch);
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
const float LOOK_AT_TARGET_RADIUS = 0.075f;
|
||||
transform.postScale(LOOK_AT_TARGET_RADIUS);
|
||||
const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
geometryCache->renderSphere(batch, LOOK_AT_TARGET_RADIUS, 15, 15, LOOK_AT_TARGET_COLOR, true);
|
||||
deferredLighting->renderSolidSphereInstance(batch, transform, LOOK_AT_TARGET_COLOR);
|
||||
}
|
||||
|
|
|
@ -102,6 +102,8 @@ public:
|
|||
|
||||
void relaxLean(float deltaTime);
|
||||
void addLeanDeltas(float sideways, float forward);
|
||||
|
||||
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
|
||||
|
||||
private:
|
||||
glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * 0.5f; }
|
||||
|
|
|
@ -654,6 +654,7 @@ void MyAvatar::saveData() {
|
|||
|
||||
settings.setValue("fullAvatarURL", _fullAvatarURLFromPreferences);
|
||||
settings.setValue("fullAvatarModelName", _fullAvatarModelName);
|
||||
settings.setValue("animGraphURL", _animGraphUrl);
|
||||
|
||||
settings.beginWriteArray("attachmentData");
|
||||
for (int i = 0; i < _attachmentData.size(); i++) {
|
||||
|
@ -791,6 +792,7 @@ void MyAvatar::loadData() {
|
|||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||
setScale(_scale);
|
||||
|
||||
_animGraphUrl = settings.value("animGraphURL", "").toString();
|
||||
_fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
|
||||
_fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
|
||||
|
||||
|
@ -1299,10 +1301,15 @@ void MyAvatar::initAnimGraph() {
|
|||
// ik-avatar-hands.json
|
||||
// https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb
|
||||
//
|
||||
// ik-avatar-hands-idle.json
|
||||
// https://gist.githubusercontent.com/hyperlogic/d951c78532e7a20557ad
|
||||
//
|
||||
// or run a local web-server
|
||||
// python -m SimpleHTTPServer&
|
||||
//auto graphUrl = QUrl("http://localhost:8000/avatar.json");
|
||||
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/04a02c47eb56d8bfaebb/raw/72517b231f606b724c5169e02642e401f9af5a54/ik-avatar-hands.json");
|
||||
auto graphUrl = QUrl(_animGraphUrl.isEmpty() ?
|
||||
QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full/avatar-animation.json") :
|
||||
_animGraphUrl);
|
||||
_rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ public:
|
|||
Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); }
|
||||
Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); }
|
||||
Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); }
|
||||
Q_INVOKABLE int getFaceBlendCoefNum() const { return getHead()->getFaceModel().getBlendshapeCoefficientsNum(); }
|
||||
Q_INVOKABLE float getFaceBlendCoef(int index) const { return getHead()->getFaceModel().getBlendshapeCoefficient(index); }
|
||||
|
||||
Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
|
||||
|
||||
|
@ -195,10 +197,12 @@ public slots:
|
|||
bool getEnableRigAnimations() const { return _rig->getEnableRig(); }
|
||||
void setEnableRigAnimations(bool isEnabled);
|
||||
bool getEnableAnimGraph() const { return _rig->getEnableAnimGraph(); }
|
||||
const QString& getAnimGraphUrl() const { return _animGraphUrl; }
|
||||
void setEnableAnimGraph(bool isEnabled);
|
||||
void setEnableDebugDrawBindPose(bool isEnabled);
|
||||
void setEnableDebugDrawAnimPose(bool isEnabled);
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
void setAnimGraphUrl(const QString& url) { _animGraphUrl = url; }
|
||||
|
||||
signals:
|
||||
void transformChanged();
|
||||
|
@ -298,6 +302,7 @@ private:
|
|||
// Avatar Preferences
|
||||
QUrl _fullAvatarURLFromPreferences;
|
||||
QString _fullAvatarModelName;
|
||||
QString _animGraphUrl {""};
|
||||
|
||||
// cache of the current HMD sensor position and orientation
|
||||
// in sensor space.
|
||||
|
|
|
@ -146,6 +146,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
headParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||
headParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||
|
||||
headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
_rig->updateFromHeadParameters(headParams, deltaTime);
|
||||
|
||||
Rig::HandParameters handParams;
|
||||
|
@ -633,31 +635,27 @@ void SkeletonModel::computeBoundingShape() {
|
|||
}
|
||||
|
||||
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
Transform transform; // = Transform();
|
||||
|
||||
// draw a blue sphere at the capsule top point
|
||||
glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
transform.setTranslation(topPoint);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->bindSimpleProgram(batch);
|
||||
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
|
||||
glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
deferredLighting->renderSolidSphereInstance(batch,
|
||||
Transform().setTranslation(topPoint).postScale(_boundingCapsuleRadius),
|
||||
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 axis = topPoint - bottomPoint;
|
||||
transform.setTranslation(bottomPoint);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->bindSimpleProgram(batch);
|
||||
geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS,
|
||||
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
|
||||
|
||||
deferredLighting->renderSolidSphereInstance(batch,
|
||||
Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius),
|
||||
glm::vec4(0.8f, 0.8f, 0.6f, alpha));
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
batch.setModelTransform(Transform().setTranslation(bottomPoint));
|
||||
deferredLighting->bindSimpleProgram(batch);
|
||||
Avatar::renderJointConnectingCone(batch, origin, axis, _boundingCapsuleRadius, _boundingCapsuleRadius,
|
||||
glm::vec4(0.6f, 0.8f, 0.6f, alpha));
|
||||
}
|
||||
|
|
|
@ -425,6 +425,7 @@ bool ConnexionClient::InitializeRawInput(HWND hwndTarget) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// FIXME - http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe
|
||||
// Get OS version.
|
||||
OSVERSIONINFO osvi = { sizeof(OSVERSIONINFO), 0 };
|
||||
::GetVersionEx(&osvi);
|
||||
|
|
|
@ -22,8 +22,10 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
|
|||
connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
|
||||
|
||||
_downloading = false;
|
||||
connect(Application::getInstance(), &Application::renderingInWorldInterface,
|
||||
this, &GlobalServicesScriptingInterface::checkDownloadInfo);
|
||||
QTimer* checkDownloadTimer = new QTimer(this);
|
||||
connect(checkDownloadTimer, &QTimer::timeout, this, &GlobalServicesScriptingInterface::checkDownloadInfo);
|
||||
const int CHECK_DOWNLOAD_INTERVAL = MSECS_PER_SECOND / 2;
|
||||
checkDownloadTimer->start(CHECK_DOWNLOAD_INTERVAL);
|
||||
|
||||
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
||||
connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged,
|
||||
|
|
|
@ -39,13 +39,13 @@ void AssetUploadDialogFactory::showDialog() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (nodeList->getThisNodeCanRez()) {
|
||||
auto filename = QFileDialog::getOpenFileUrl(_dialogParent, "Select a file to upload");
|
||||
auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select a file to upload");
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
qDebug() << "Selected filename for upload to asset-server: " << filename;
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto upload = assetClient->createUpload(filename.path());
|
||||
auto upload = assetClient->createUpload(filename);
|
||||
|
||||
if (upload) {
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
|
@ -56,7 +56,7 @@ void AssetUploadDialogFactory::showDialog() {
|
|||
} else {
|
||||
// show a QMessageBox to say that there is no local asset server
|
||||
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
|
||||
" to a local asset-server.").arg(QFileInfo(filename.toString()).fileName());
|
||||
" to a local asset-server.").arg(QFileInfo(filename).fileName());
|
||||
|
||||
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ CachesSizeDialog::CachesSizeDialog(QWidget* parent) :
|
|||
|
||||
void CachesSizeDialog::confirmClicked(bool checked) {
|
||||
DependencyManager::get<AnimationCache>()->setUnusedResourceCacheSize(_animations->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<GeometryCache>()->setUnusedResourceCacheSize(_geometries->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<ModelCache>()->setUnusedResourceCacheSize(_geometries->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<SoundCache>()->setUnusedResourceCacheSize(_sounds->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<TextureCache>()->setUnusedResourceCacheSize(_textures->value() * BYTES_PER_MEGABYTES);
|
||||
|
||||
|
@ -65,7 +65,7 @@ void CachesSizeDialog::confirmClicked(bool checked) {
|
|||
|
||||
void CachesSizeDialog::resetClicked(bool checked) {
|
||||
_animations->setValue(DependencyManager::get<AnimationCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_geometries->setValue(DependencyManager::get<GeometryCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_geometries->setValue(DependencyManager::get<ModelCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_sounds->setValue(DependencyManager::get<SoundCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_textures->setValue(DependencyManager::get<TextureCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
}
|
||||
|
|
|
@ -190,6 +190,7 @@ void PreferencesDialog::loadPreferences() {
|
|||
ui.leanScaleSpin->setValue(myAvatar->getLeanScale());
|
||||
|
||||
ui.avatarScaleSpin->setValue(myAvatar->getScale());
|
||||
ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl());
|
||||
|
||||
ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond());
|
||||
|
||||
|
@ -248,7 +249,15 @@ void PreferencesDialog::savePreferences() {
|
|||
myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum());
|
||||
myAvatar->setLeanScale(ui.leanScaleSpin->value());
|
||||
myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());
|
||||
|
||||
if (myAvatar->getAnimGraphUrl() != ui.avatarAnimationEdit->text()) { // If changed, destroy the old and start with the new
|
||||
bool isEnabled = myAvatar->getEnableAnimGraph();
|
||||
myAvatar->setEnableAnimGraph(false);
|
||||
myAvatar->setAnimGraphUrl(ui.avatarAnimationEdit->text());
|
||||
if (isEnabled) {
|
||||
myAvatar->setEnableAnimGraph(true);
|
||||
}
|
||||
}
|
||||
|
||||
DependencyManager::get<AvatarManager>()->getMyAvatar()->setRealWorldFieldOfView(ui.realWorldFieldOfViewSpin->value());
|
||||
|
||||
qApp->setFieldOfView(ui.fieldOfViewSpin->value());
|
||||
|
|
|
@ -57,16 +57,15 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
|
|||
connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter);
|
||||
connect(ui->scriptTreeView, &QTreeView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList);
|
||||
|
||||
connect(ui->reloadAllButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::reloadAllScripts);
|
||||
connect(ui->stopAllButton, &QPushButton::clicked,
|
||||
this, &RunningScriptsWidget::allScriptsStopped);
|
||||
connect(ui->loadScriptFromDiskButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::loadDialog);
|
||||
connect(ui->loadScriptFromURLButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::loadScriptURLDialog);
|
||||
connect(&_reloadSignalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(reloadOneScript(const QString&)));
|
||||
connect(&_stopSignalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&)));
|
||||
connect(ui->reloadAllButton, &QPushButton::clicked, Application::getInstance(), &Application::reloadAllScripts);
|
||||
connect(ui->stopAllButton, &QPushButton::clicked, this, &RunningScriptsWidget::allScriptsStopped);
|
||||
connect(ui->loadScriptFromDiskButton, &QPushButton::clicked, Application::getInstance(), &Application::loadDialog);
|
||||
connect(ui->loadScriptFromURLButton, &QPushButton::clicked, Application::getInstance(), &Application::loadScriptURLDialog);
|
||||
connect(&_reloadSignalMapper, static_cast<void(QSignalMapper::*)(const QString&)>(&QSignalMapper::mapped),
|
||||
Application::getInstance(), &Application::reloadOneScript);
|
||||
|
||||
connect(&_stopSignalMapper, static_cast<void(QSignalMapper::*)(const QString&)>(&QSignalMapper::mapped),
|
||||
[](const QString& script) { Application::getInstance()->stopScript(script); });
|
||||
|
||||
UIUtil::scaleWidgetFontSizes(this);
|
||||
}
|
||||
|
@ -217,9 +216,6 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::scriptStopped(const QString& scriptName) {
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::allScriptsStopped() {
|
||||
Application::getInstance()->stopAllScripts();
|
||||
}
|
||||
|
@ -227,15 +223,16 @@ void RunningScriptsWidget::allScriptsStopped() {
|
|||
QVariantList RunningScriptsWidget::getRunning() {
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
QVariantList result;
|
||||
QStringList runningScripts = Application::getInstance()->getRunningScripts();
|
||||
for (int i = 0; i < runningScripts.size(); i++) {
|
||||
QUrl runningScriptURL = QUrl(runningScripts.at(i));
|
||||
foreach(const QString& runningScript, Application::getInstance()->getRunningScripts()) {
|
||||
QUrl runningScriptURL = QUrl(runningScript);
|
||||
if (runningScriptURL.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
runningScriptURL = QUrl::fromLocalFile(runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)));
|
||||
}
|
||||
QVariantMap resultNode;
|
||||
resultNode.insert("name", runningScriptURL.fileName());
|
||||
resultNode.insert("url", runningScriptURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)));
|
||||
// The path contains the exact path/URL of the script, which also is used in the stopScript function.
|
||||
resultNode.insert("path", runningScript);
|
||||
resultNode.insert("local", runningScriptURL.isLocalFile());
|
||||
result.append(resultNode);
|
||||
}
|
||||
|
@ -294,3 +291,16 @@ QVariantList RunningScriptsWidget::getLocal() {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RunningScriptsWidget::stopScriptByName(const QString& name) {
|
||||
foreach (const QString& runningScript, Application::getInstance()->getRunningScripts()) {
|
||||
if (QUrl(runningScript).fileName().toLower() == name.trimmed().toLower()) {
|
||||
return Application::getInstance()->stopScript(runningScript, false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RunningScriptsWidget::stopScript(const QString& name, bool restart) {
|
||||
return Application::getInstance()->stopScript(name, restart);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
const ScriptsModel* getScriptsModel() { return &_scriptsModel; }
|
||||
|
||||
signals:
|
||||
void stopScriptName(const QString& name, bool restart = false);
|
||||
void scriptStopped(const QString& scriptName);
|
||||
|
||||
protected:
|
||||
virtual bool eventFilter(QObject* sender, QEvent* event);
|
||||
|
@ -45,10 +45,11 @@ protected:
|
|||
virtual void showEvent(QShowEvent* event);
|
||||
|
||||
public slots:
|
||||
void scriptStopped(const QString& scriptName);
|
||||
QVariantList getRunning();
|
||||
QVariantList getPublic();
|
||||
QVariantList getLocal();
|
||||
bool stopScript(const QString& name, bool restart = false);
|
||||
bool stopScriptByName(const QString& name);
|
||||
|
||||
private slots:
|
||||
void allScriptsStopped();
|
||||
|
@ -63,9 +64,6 @@ private:
|
|||
QSignalMapper _stopSignalMapper;
|
||||
ScriptsModelFilter _scriptsModelFilter;
|
||||
ScriptsModel _scriptsModel;
|
||||
ScriptsTableWidget* _recentlyLoadedScriptsTable;
|
||||
QStringList _recentlyLoadedScripts;
|
||||
QString _lastStoppedScript;
|
||||
|
||||
QVariantList getPublicChildNodes(TreeNodeFolder* parent);
|
||||
};
|
||||
|
|
|
@ -97,7 +97,8 @@ bool ScriptEditorWidget::setRunning(bool run) {
|
|||
|
||||
if (run) {
|
||||
const QString& scriptURLString = QUrl(_currentScript).toString();
|
||||
_scriptEngine = Application::getInstance()->loadScript(scriptURLString, true, true);
|
||||
// Reload script so that an out of date copy is not retrieved from the cache
|
||||
_scriptEngine = Application::getInstance()->loadScript(scriptURLString, true, true, false, true);
|
||||
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
|
||||
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
|
||||
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
|
||||
|
|
|
@ -337,7 +337,6 @@ void Stats::updateStats() {
|
|||
|
||||
void Stats::setRenderDetails(const RenderDetails& details) {
|
||||
STAT_UPDATE(triangles, details._trianglesRendered);
|
||||
STAT_UPDATE(quads, details._quadsRendered);
|
||||
STAT_UPDATE(materialSwitches, details._materialSwitches);
|
||||
if (_expanded) {
|
||||
STAT_UPDATE(meshOpaque, details._opaque._rendered);
|
||||
|
|
|
@ -89,7 +89,6 @@ void Circle3DOverlay::render(RenderArgs* args) {
|
|||
const float SLICES = 180.0f; // The amount of segment to create the circle
|
||||
const float SLICE_ANGLE = FULL_CIRCLE / SLICES;
|
||||
|
||||
//const int slices = 15;
|
||||
xColor colorX = getColor();
|
||||
const float MAX_COLOR = 255.0f;
|
||||
glm::vec4 color(colorX.red / MAX_COLOR, colorX.green / MAX_COLOR, colorX.blue / MAX_COLOR, alpha);
|
||||
|
|
|
@ -61,8 +61,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
// }
|
||||
|
||||
transform.setScale(dimensions);
|
||||
batch->setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCube(*batch, 1.0f, cubeColor);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidCubeInstance(*batch, transform, cubeColor);
|
||||
} else {
|
||||
|
||||
if (getIsDashedLine()) {
|
||||
|
@ -98,9 +97,9 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
geometryCache->renderDashedLine(*batch, bottomRightFar, topRightFar, cubeColor);
|
||||
|
||||
} else {
|
||||
batch->setModelTransform(Transform());
|
||||
transform.setScale(dimensions);
|
||||
batch->setModelTransform(transform);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(*batch, 1.0f, cubeColor);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCubeInstance(*batch, transform, cubeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,16 @@
|
|||
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <DeferredLightingEffect.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
QString const Sphere3DOverlay::TYPE = "sphere";
|
||||
|
||||
// Sphere overlays should fit inside a cube of the specified dimensions, hence it needs to be a half unit sphere.
|
||||
// However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_OVERLAY_SCALE = 0.5f;
|
||||
|
||||
Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
|
||||
Volume3DOverlay(Sphere3DOverlay)
|
||||
{
|
||||
|
@ -27,7 +32,6 @@ void Sphere3DOverlay::render(RenderArgs* args) {
|
|||
return; // do nothing if we're not visible
|
||||
}
|
||||
|
||||
const int SLICES = 15;
|
||||
float alpha = getAlpha();
|
||||
xColor color = getColor();
|
||||
const float MAX_COLOR = 255.0f;
|
||||
|
@ -36,10 +40,15 @@ void Sphere3DOverlay::render(RenderArgs* args) {
|
|||
auto batch = args->_batch;
|
||||
|
||||
if (batch) {
|
||||
batch->setModelTransform(Transform());
|
||||
|
||||
Transform transform = _transform;
|
||||
transform.postScale(getDimensions());
|
||||
batch->setModelTransform(transform);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(*batch, 1.0f, SLICES, SLICES, sphereColor, _isSolid);
|
||||
transform.postScale(getDimensions() * SPHERE_OVERLAY_SCALE);
|
||||
if (_isSolid) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(*batch, transform, sphereColor);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireSphereInstance(*batch, transform, sphereColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,11 +29,7 @@
|
|||
|
||||
#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";
|
||||
|
|
|
@ -1587,6 +1587,71 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_anim">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_anim">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Avatar Animation JSON</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>avatarAnimationEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_anim">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="avatarAnimationEdit">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_9">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "AnimationLogging.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
AnimBlendLinear::AnimBlendLinear(const std::string& id, float alpha) :
|
||||
AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) :
|
||||
AnimNode(AnimNode::Type::BlendLinear, id),
|
||||
_alpha(alpha) {
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ class AnimBlendLinear : public AnimNode {
|
|||
public:
|
||||
friend class AnimTests;
|
||||
|
||||
AnimBlendLinear(const std::string& id, float alpha);
|
||||
AnimBlendLinear(const QString& 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; }
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
protected:
|
||||
// for AnimDebugDraw rendering
|
||||
|
@ -42,7 +42,7 @@ protected:
|
|||
|
||||
float _alpha;
|
||||
|
||||
std::string _alphaVar;
|
||||
QString _alphaVar;
|
||||
|
||||
// no copies
|
||||
AnimBlendLinear(const AnimBlendLinear&) = delete;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "AnimationLogging.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
AnimClip::AnimClip(const std::string& id, const std::string& url, float startFrame, float endFrame, float timeScale, bool loopFlag) :
|
||||
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) :
|
||||
AnimNode(AnimNode::Type::Clip, id),
|
||||
_startFrame(startFrame),
|
||||
_endFrame(endFrame),
|
||||
|
@ -68,9 +68,9 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
|
|||
return _poses;
|
||||
}
|
||||
|
||||
void AnimClip::loadURL(const std::string& url) {
|
||||
void AnimClip::loadURL(const QString& url) {
|
||||
auto animCache = DependencyManager::get<AnimationCache>();
|
||||
_networkAnim = animCache->getAnimation(QString::fromStdString(url));
|
||||
_networkAnim = animCache->getAnimation(url);
|
||||
_url = url;
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ void AnimClip::copyFromNetworkAnim() {
|
|||
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();
|
||||
qCWarning(animation) << "animation contains joint =" << animJoints.at(i).name << " which is not in the skeleton, url =" << _url;
|
||||
}
|
||||
jointMap.push_back(skeletonJoint);
|
||||
}
|
||||
|
|
|
@ -25,19 +25,19 @@ 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);
|
||||
AnimClip(const QString& id, const QString& 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; }
|
||||
void setStartFrameVar(const QString& startFrameVar) { _startFrameVar = startFrameVar; }
|
||||
void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; }
|
||||
void setTimeScaleVar(const QString& timeScaleVar) { _timeScaleVar = timeScaleVar; }
|
||||
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
||||
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
|
||||
|
||||
protected:
|
||||
void loadURL(const std::string& url);
|
||||
void loadURL(const QString& url);
|
||||
|
||||
virtual void setCurrentFrameInternal(float frame) override;
|
||||
|
||||
|
@ -53,18 +53,18 @@ protected:
|
|||
// _anim[frame][joint]
|
||||
std::vector<AnimPoseVec> _anim;
|
||||
|
||||
std::string _url;
|
||||
QString _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;
|
||||
QString _startFrameVar;
|
||||
QString _endFrameVar;
|
||||
QString _timeScaleVar;
|
||||
QString _loopFlagVar;
|
||||
QString _frameVar;
|
||||
|
||||
// no copies
|
||||
AnimClip(const AnimClip&) = delete;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "SwingTwistConstraint.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimInverseKinematics::AnimInverseKinematics(const std::string& id) : AnimNode(AnimNode::Type::InverseKinematics, id) {
|
||||
AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimNode::Type::InverseKinematics, id) {
|
||||
}
|
||||
|
||||
AnimInverseKinematics::~AnimInverseKinematics() {
|
||||
|
@ -33,8 +33,10 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
|
|||
assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
|
||||
if (_skeleton->getNumJoints() == (int)poses.size()) {
|
||||
_relativePoses = poses;
|
||||
_accumulators.resize(_relativePoses.size());
|
||||
} else {
|
||||
_relativePoses.clear();
|
||||
_accumulators.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,15 +61,15 @@ void AnimInverseKinematics::setTargetVars(const QString& jointName, const QStrin
|
|||
for (auto& targetVar: _targetVarVec) {
|
||||
if (targetVar.jointName == jointName) {
|
||||
// update existing targetVar
|
||||
targetVar.positionVar = positionVar.toStdString();
|
||||
targetVar.rotationVar = rotationVar.toStdString();
|
||||
targetVar.positionVar = positionVar;
|
||||
targetVar.rotationVar = rotationVar;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// create a new entry
|
||||
_targetVarVec.push_back(IKTargetVar(jointName, positionVar.toStdString(), rotationVar.toStdString()));
|
||||
_targetVarVec.push_back(IKTargetVar(jointName, positionVar, rotationVar));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,22 +84,8 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde
|
|||
return rootIndex;
|
||||
}
|
||||
|
||||
struct IKTarget {
|
||||
AnimPose pose;
|
||||
int index;
|
||||
int rootIndex;
|
||||
};
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
|
||||
|
||||
// NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called)
|
||||
if (_relativePoses.empty()) {
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets) {
|
||||
// build a list of valid targets from _targetVarVec and animVars
|
||||
bool removeUnfoundJoints = false;
|
||||
for (auto& targetVar : _targetVarVec) {
|
||||
if (targetVar.jointIndex == -1) {
|
||||
|
@ -113,15 +101,13 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
|||
}
|
||||
} else {
|
||||
// TODO: get this done without a double-lookup of each var in animVars
|
||||
if (animVars.hasKey(targetVar.positionVar) || animVars.hasKey(targetVar.rotationVar)) {
|
||||
IKTarget target;
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
|
||||
target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
|
||||
target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
|
||||
target.rootIndex = targetVar.rootIndex;
|
||||
target.index = targetVar.jointIndex;
|
||||
targets.push_back(target);
|
||||
}
|
||||
IKTarget target;
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
|
||||
target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
|
||||
target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
|
||||
target.rootIndex = targetVar.rootIndex;
|
||||
target.index = targetVar.jointIndex;
|
||||
targets.push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,138 +127,161 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
// compute absolute poses that correspond to relative target poses
|
||||
AnimPoseVec absolutePoses;
|
||||
computeAbsolutePoses(absolutePoses);
|
||||
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(std::vector<IKTarget>& targets) {
|
||||
// compute absolute poses that correspond to relative target poses
|
||||
AnimPoseVec absolutePoses;
|
||||
computeAbsolutePoses(absolutePoses);
|
||||
|
||||
float largestError = 0.0f;
|
||||
const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f;
|
||||
int numLoops = 0;
|
||||
const int MAX_IK_LOOPS = 16;
|
||||
const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC;
|
||||
quint64 expiry = usecTimestampNow() + MAX_IK_TIME;
|
||||
do {
|
||||
largestError = 0.0f;
|
||||
for (auto& target: targets) {
|
||||
int lowestMovedIndex = _relativePoses.size() - 1;
|
||||
int tipIndex = target.index;
|
||||
AnimPose targetPose = target.pose;
|
||||
int rootIndex = target.rootIndex;
|
||||
if (rootIndex != -1) {
|
||||
// transform targetPose into skeleton's absolute frame
|
||||
AnimPose& rootPose = _relativePoses[rootIndex];
|
||||
targetPose.trans = rootPose.trans + rootPose.rot * targetPose.trans;
|
||||
targetPose.rot = rootPose.rot * targetPose.rot;
|
||||
}
|
||||
// clear the accumulators before we start the IK solver
|
||||
for (auto& accumulator: _accumulators) {
|
||||
accumulator.clearAndClean();
|
||||
}
|
||||
|
||||
glm::vec3 tip = absolutePoses[tipIndex].trans;
|
||||
float error = glm::length(targetPose.trans - tip);
|
||||
float largestError = 0.0f;
|
||||
const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f;
|
||||
int numLoops = 0;
|
||||
const int MAX_IK_LOOPS = 16;
|
||||
const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC;
|
||||
quint64 expiry = usecTimestampNow() + MAX_IK_TIME;
|
||||
do {
|
||||
largestError = 0.0f;
|
||||
int lowestMovedIndex = _relativePoses.size();
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
AnimPose targetPose = target.pose;
|
||||
|
||||
// descend toward root, pivoting each joint to get tip closer to target
|
||||
int pivotIndex = _skeleton->getParentIndex(tipIndex);
|
||||
while (pivotIndex != -1 && error > ACCEPTABLE_RELATIVE_ERROR) {
|
||||
// compute the two lines that should be aligned
|
||||
glm::vec3 jointPosition = absolutePoses[pivotIndex].trans;
|
||||
glm::vec3 leverArm = tip - jointPosition;
|
||||
glm::vec3 targetLine = targetPose.trans - jointPosition;
|
||||
glm::vec3 tip = absolutePoses[tipIndex].trans;
|
||||
float error = glm::length(targetPose.trans - tip);
|
||||
|
||||
// compute the axis of the rotation that would align them
|
||||
glm::vec3 axis = glm::cross(leverArm, targetLine);
|
||||
float axisLength = glm::length(axis);
|
||||
if (axisLength > EPSILON) {
|
||||
// compute deltaRotation for alignment (brings tip closer to target)
|
||||
axis /= axisLength;
|
||||
float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)));
|
||||
// descend toward root, pivoting each joint to get tip closer to target
|
||||
int pivotIndex = _skeleton->getParentIndex(tipIndex);
|
||||
while (pivotIndex != -1 && error > ACCEPTABLE_RELATIVE_ERROR) {
|
||||
// compute the two lines that should be aligned
|
||||
glm::vec3 jointPosition = absolutePoses[pivotIndex].trans;
|
||||
glm::vec3 leverArm = tip - jointPosition;
|
||||
glm::vec3 targetLine = targetPose.trans - jointPosition;
|
||||
|
||||
// NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is
|
||||
// still possible for the angle to be zero so we also check that to avoid unnecessary calculations.
|
||||
if (angle > EPSILON) {
|
||||
// reduce angle by half: slows convergence but adds stability to IK solution
|
||||
angle = 0.5f * angle;
|
||||
glm::quat deltaRotation = glm::angleAxis(angle, axis);
|
||||
// compute the axis of the rotation that would align them
|
||||
glm::vec3 axis = glm::cross(leverArm, targetLine);
|
||||
float axisLength = glm::length(axis);
|
||||
if (axisLength > EPSILON) {
|
||||
// compute deltaRotation for alignment (brings tip closer to target)
|
||||
axis /= axisLength;
|
||||
float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)));
|
||||
|
||||
int parentIndex = _skeleton->getParentIndex(pivotIndex);
|
||||
if (parentIndex == -1) {
|
||||
// TODO? apply constraints to root?
|
||||
// TODO? harvest the root's transform as movement of entire skeleton?
|
||||
} else {
|
||||
// compute joint's new parent-relative rotation
|
||||
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
|
||||
glm::quat newRot = glm::normalize(glm::inverse(
|
||||
absolutePoses[parentIndex].rot) *
|
||||
deltaRotation *
|
||||
absolutePoses[pivotIndex].rot);
|
||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
||||
if (constraint) {
|
||||
bool constrained = constraint->apply(newRot);
|
||||
if (constrained) {
|
||||
// the constraint will modify the movement of the tip so we have to compute the modified
|
||||
// model-frame deltaRotation
|
||||
// Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^
|
||||
deltaRotation = absolutePoses[parentIndex].rot *
|
||||
newRot *
|
||||
glm::inverse(absolutePoses[pivotIndex].rot);
|
||||
}
|
||||
// NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is
|
||||
// still possible for the angle to be zero so we also check that to avoid unnecessary calculations.
|
||||
if (angle > EPSILON) {
|
||||
// reduce angle by half: slows convergence but adds stability to IK solution
|
||||
angle = 0.5f * angle;
|
||||
glm::quat deltaRotation = glm::angleAxis(angle, axis);
|
||||
|
||||
int parentIndex = _skeleton->getParentIndex(pivotIndex);
|
||||
if (parentIndex == -1) {
|
||||
// TODO? apply constraints to root?
|
||||
// TODO? harvest the root's transform as movement of entire skeleton?
|
||||
} else {
|
||||
// compute joint's new parent-relative rotation
|
||||
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
|
||||
glm::quat newRot = glm::normalize(glm::inverse(
|
||||
absolutePoses[parentIndex].rot) *
|
||||
deltaRotation *
|
||||
absolutePoses[pivotIndex].rot);
|
||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
||||
if (constraint) {
|
||||
bool constrained = constraint->apply(newRot);
|
||||
if (constrained) {
|
||||
// the constraint will modify the movement of the tip so we have to compute the modified
|
||||
// model-frame deltaRotation
|
||||
// Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^
|
||||
deltaRotation = absolutePoses[parentIndex].rot *
|
||||
newRot *
|
||||
glm::inverse(absolutePoses[pivotIndex].rot);
|
||||
}
|
||||
_relativePoses[pivotIndex].rot = newRot;
|
||||
}
|
||||
// this joint has been changed so we check to see if it has the lowest index
|
||||
if (pivotIndex < lowestMovedIndex) {
|
||||
lowestMovedIndex = pivotIndex;
|
||||
}
|
||||
|
||||
// keep track of tip's new position as we descend towards root
|
||||
tip = jointPosition + deltaRotation * leverArm;
|
||||
error = glm::length(targetPose.trans - tip);
|
||||
// store the rotation change in the accumulator
|
||||
_accumulators[pivotIndex].add(newRot);
|
||||
}
|
||||
}
|
||||
pivotIndex = _skeleton->getParentIndex(pivotIndex);
|
||||
}
|
||||
if (largestError < error) {
|
||||
largestError = error;
|
||||
}
|
||||
|
||||
if (lowestMovedIndex <= _maxTargetIndex && lowestMovedIndex < tipIndex) {
|
||||
// only update the absolutePoses that matter: those between lowestMovedIndex and _maxTargetIndex
|
||||
for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) {
|
||||
int parentIndex = _skeleton->getParentIndex(i);
|
||||
if (parentIndex != -1) {
|
||||
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
|
||||
// this joint has been changed so we check to see if it has the lowest index
|
||||
if (pivotIndex < lowestMovedIndex) {
|
||||
lowestMovedIndex = pivotIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally set the relative rotation of the tip to agree with absolute target rotation
|
||||
int parentIndex = _skeleton->getParentIndex(tipIndex);
|
||||
if (parentIndex != -1) {
|
||||
// compute tip's new parent-relative rotation
|
||||
// Q = Qp * q --> q' = Qp^ * Q
|
||||
glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot;
|
||||
RotationConstraint* constraint = getConstraint(tipIndex);
|
||||
if (constraint) {
|
||||
constraint->apply(newRelativeRotation);
|
||||
// TODO: ATM the final rotation target just fails but we need to provide
|
||||
// feedback to the IK system so that it can adjust the bones up the skeleton
|
||||
// to help this rotation target get met.
|
||||
// keep track of tip's new position as we descend towards root
|
||||
tip = jointPosition + deltaRotation * leverArm;
|
||||
error = glm::length(targetPose.trans - tip);
|
||||
}
|
||||
_relativePoses[tipIndex].rot = newRelativeRotation;
|
||||
absolutePoses[tipIndex].rot = targetPose.rot;
|
||||
}
|
||||
pivotIndex = _skeleton->getParentIndex(pivotIndex);
|
||||
}
|
||||
++numLoops;
|
||||
} while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry);
|
||||
if (largestError < error) {
|
||||
largestError = error;
|
||||
}
|
||||
}
|
||||
++numLoops;
|
||||
|
||||
// harvest accumulated rotations and apply the average
|
||||
const int numJoints = (int)_accumulators.size();
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
if (_accumulators[i].size() > 0) {
|
||||
_relativePoses[i].rot = _accumulators[i].getAverage();
|
||||
_accumulators[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
// only update the absolutePoses that need it: those between lowestMovedIndex and _maxTargetIndex
|
||||
for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) {
|
||||
int parentIndex = _skeleton->getParentIndex(i);
|
||||
if (parentIndex != -1) {
|
||||
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
|
||||
}
|
||||
}
|
||||
} while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry);
|
||||
|
||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
int parentIndex = _skeleton->getParentIndex(tipIndex);
|
||||
if (parentIndex != -1) {
|
||||
AnimPose targetPose = target.pose;
|
||||
// compute tip's new parent-relative rotation
|
||||
// Q = Qp * q --> q' = Qp^ * Q
|
||||
glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot;
|
||||
RotationConstraint* constraint = getConstraint(tipIndex);
|
||||
if (constraint) {
|
||||
constraint->apply(newRelativeRotation);
|
||||
// TODO: ATM the final rotation target just fails but we need to provide
|
||||
// feedback to the IK system so that it can adjust the bones up the skeleton
|
||||
// to help this rotation target get met.
|
||||
}
|
||||
_relativePoses[tipIndex].rot = newRelativeRotation;
|
||||
absolutePoses[tipIndex].rot = targetPose.rot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
|
||||
if (!_relativePoses.empty()) {
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
computeTargets(animVars, targets);
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
solveWithCyclicCoordinateDescent(targets);
|
||||
}
|
||||
}
|
||||
return _relativePoses;
|
||||
}
|
||||
|
@ -292,7 +301,11 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
int numJoints = (int)_relativePoses.size();
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot, underPoses[i].rot));
|
||||
_relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * underPoses[i].rot, blend));
|
||||
if (_accumulators[i].isDirty()) {
|
||||
_relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * underPoses[i].rot, blend));
|
||||
} else {
|
||||
_relativePoses[i].rot = underPoses[i].rot;
|
||||
}
|
||||
}
|
||||
}
|
||||
return evaluate(animVars, dt, triggersOut);
|
||||
|
@ -628,24 +641,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
|
|||
|
||||
_maxTargetIndex = 0;
|
||||
|
||||
_accumulators.clear();
|
||||
if (skeleton) {
|
||||
initConstraints();
|
||||
} else {
|
||||
clearConstraints();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::relaxTowardDefaults(float dt) {
|
||||
// NOTE: for now we just use a single relaxation timescale for all joints, but in the future
|
||||
// we could vary the timescale on a per-joint basis or do other fancy things.
|
||||
|
||||
// for each joint: lerp towards the default pose
|
||||
const float RELAXATION_TIMESCALE = 0.25f;
|
||||
const float alpha = glm::clamp(dt / RELAXATION_TIMESCALE, 0.0f, 1.0f);
|
||||
int numJoints = (int)_relativePoses.size();
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot, _defaultRelativePoses[i].rot));
|
||||
_relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * _defaultRelativePoses[i].rot, alpha));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,19 +12,23 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
#include "RotationAccumulator.h"
|
||||
|
||||
class RotationConstraint;
|
||||
|
||||
class AnimInverseKinematics : public AnimNode {
|
||||
public:
|
||||
|
||||
AnimInverseKinematics(const std::string& id);
|
||||
AnimInverseKinematics(const QString& id);
|
||||
virtual ~AnimInverseKinematics() override;
|
||||
|
||||
void loadDefaultPoses(const AnimPoseVec& poses);
|
||||
void loadPoses(const AnimPoseVec& poses);
|
||||
const AnimPoseVec& getRelativePoses() const { return _relativePoses; }
|
||||
void computeAbsolutePoses(AnimPoseVec& absolutePoses) const;
|
||||
|
||||
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar);
|
||||
|
@ -33,33 +37,40 @@ public:
|
|||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
||||
protected:
|
||||
struct IKTarget {
|
||||
AnimPose pose;
|
||||
int index;
|
||||
int rootIndex;
|
||||
};
|
||||
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets);
|
||||
void solveWithCyclicCoordinateDescent(std::vector<IKTarget>& targets);
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton);
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
|
||||
|
||||
void relaxTowardDefaults(float dt);
|
||||
|
||||
RotationConstraint* getConstraint(int index);
|
||||
void clearConstraints();
|
||||
void initConstraints();
|
||||
|
||||
struct IKTargetVar {
|
||||
IKTargetVar(const QString& jointNameIn, const std::string& positionVarIn, const std::string& rotationVarIn) :
|
||||
IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn) :
|
||||
positionVar(positionVarIn),
|
||||
rotationVar(rotationVarIn),
|
||||
jointName(jointNameIn),
|
||||
jointIndex(-1),
|
||||
rootIndex(-1) {}
|
||||
|
||||
std::string positionVar;
|
||||
std::string rotationVar;
|
||||
QString positionVar;
|
||||
QString rotationVar;
|
||||
QString jointName;
|
||||
int jointIndex; // cached joint index
|
||||
int rootIndex; // cached root index
|
||||
};
|
||||
|
||||
std::map<int, RotationConstraint*> _constraints;
|
||||
std::vector<RotationAccumulator> _accumulators;
|
||||
std::vector<IKTargetVar> _targetVarVec;
|
||||
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
|
||||
AnimPoseVec _relativePoses; // current relative poses
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "AnimUtil.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimManipulator::AnimManipulator(const std::string& id, float alpha) :
|
||||
AnimManipulator::AnimManipulator(const QString& id, float alpha) :
|
||||
AnimNode(AnimNode::Type::Manipulator, id),
|
||||
_alpha(alpha) {
|
||||
|
||||
|
@ -31,10 +31,9 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, floa
|
|||
|
||||
for (auto& jointVar : _jointVars) {
|
||||
if (!jointVar.hasPerformedJointLookup) {
|
||||
QString qJointName = QString::fromStdString(jointVar.jointName);
|
||||
jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName);
|
||||
jointVar.jointIndex = _skeleton->nameToJointIndex(jointVar.jointName);
|
||||
if (jointVar.jointIndex < 0) {
|
||||
qCWarning(animation) << "AnimManipulator could not find jointName" << qJointName << "in skeleton";
|
||||
qCWarning(animation) << "AnimManipulator could not find jointName" << jointVar.jointName << "in skeleton";
|
||||
}
|
||||
jointVar.hasPerformedJointLookup = true;
|
||||
}
|
||||
|
|
|
@ -19,20 +19,20 @@ class AnimManipulator : public AnimNode {
|
|||
public:
|
||||
friend class AnimTests;
|
||||
|
||||
AnimManipulator(const std::string& id, float alpha);
|
||||
AnimManipulator(const QString& id, float alpha);
|
||||
virtual ~AnimManipulator() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
||||
void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; }
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
||||
|
||||
struct JointVar {
|
||||
JointVar(const std::string& varIn, const std::string& jointNameIn) : var(varIn), jointName(jointNameIn), jointIndex(-1), hasPerformedJointLookup(false) {}
|
||||
std::string var = "";
|
||||
std::string jointName = "";
|
||||
JointVar(const QString& varIn, const QString& jointNameIn) : var(varIn), jointName(jointNameIn), jointIndex(-1), hasPerformedJointLookup(false) {}
|
||||
QString var = "";
|
||||
QString jointName = "";
|
||||
int jointIndex = -1;
|
||||
bool hasPerformedJointLookup = false;
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ protected:
|
|||
|
||||
AnimPoseVec _poses;
|
||||
float _alpha;
|
||||
std::string _alphaVar;
|
||||
QString _alphaVar;
|
||||
|
||||
std::vector<JointVar> _jointVars;
|
||||
|
||||
|
|
54
libraries/animation/src/AnimNode.cpp
Normal file
54
libraries/animation/src/AnimNode.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// AnimNode.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 "AnimNode.h"
|
||||
|
||||
void AnimNode::removeChild(AnimNode::Pointer child) {
|
||||
auto iter = std::find(_children.begin(), _children.end(), child);
|
||||
if (iter != _children.end()) {
|
||||
_children.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
AnimNode::Pointer AnimNode::getChild(int i) const {
|
||||
assert(i >= 0 && i < (int)_children.size());
|
||||
return _children[i];
|
||||
}
|
||||
|
||||
void AnimNode::setSkeleton(const AnimSkeleton::Pointer skeleton) {
|
||||
setSkeletonInternal(skeleton);
|
||||
for (auto&& child : _children) {
|
||||
child->setSkeleton(skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
const AnimPose AnimNode::getRootPose(int jointIndex) const {
|
||||
AnimPose pose = AnimPose::identity;
|
||||
if (_skeleton && jointIndex != -1) {
|
||||
const AnimPoseVec& poses = getPosesInternal();
|
||||
int numJoints = (int)(poses.size());
|
||||
if (jointIndex < numJoints) {
|
||||
int parentIndex = _skeleton->getParentIndex(jointIndex);
|
||||
while (parentIndex != -1 && parentIndex < numJoints) {
|
||||
jointIndex = parentIndex;
|
||||
parentIndex = _skeleton->getParentIndex(jointIndex);
|
||||
}
|
||||
pose = poses[jointIndex];
|
||||
}
|
||||
}
|
||||
return pose;
|
||||
}
|
||||
|
||||
void AnimNode::setCurrentFrame(float frame) {
|
||||
setCurrentFrameInternal(frame);
|
||||
for (auto&& child : _children) {
|
||||
child->setCurrentFrameInternal(frame);
|
||||
}
|
||||
}
|
|
@ -46,39 +46,27 @@ public:
|
|||
};
|
||||
using Pointer = std::shared_ptr<AnimNode>;
|
||||
using ConstPointer = std::shared_ptr<const AnimNode>;
|
||||
using Triggers = std::vector<std::string>;
|
||||
using Triggers = std::vector<QString>;
|
||||
|
||||
friend class AnimDebugDraw;
|
||||
friend void buildChildMap(std::map<std::string, Pointer>& map, Pointer node);
|
||||
friend void buildChildMap(std::map<QString, Pointer>& map, Pointer node);
|
||||
friend class AnimStateMachine;
|
||||
|
||||
AnimNode(Type type, const std::string& id) : _type(type), _id(id) {}
|
||||
AnimNode(Type type, const QString& id) : _type(type), _id(id) {}
|
||||
virtual ~AnimNode() {}
|
||||
|
||||
const std::string& getID() const { return _id; }
|
||||
const QString& 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];
|
||||
}
|
||||
void removeChild(Pointer child);
|
||||
|
||||
Pointer getChild(int i) const;
|
||||
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);
|
||||
}
|
||||
}
|
||||
void setSkeleton(const AnimSkeleton::Pointer skeleton);
|
||||
|
||||
AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; }
|
||||
|
||||
|
@ -87,25 +75,20 @@ public:
|
|||
return evaluate(animVars, dt, triggersOut);
|
||||
}
|
||||
|
||||
const AnimPose getRootPose(int jointIndex) const;
|
||||
|
||||
protected:
|
||||
|
||||
void setCurrentFrame(float frame) {
|
||||
setCurrentFrameInternal(frame);
|
||||
for (auto&& child : _children) {
|
||||
child->setCurrentFrameInternal(frame);
|
||||
}
|
||||
}
|
||||
void setCurrentFrame(float frame);
|
||||
|
||||
virtual void setCurrentFrameInternal(float frame) {}
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||
_skeleton = skeleton;
|
||||
}
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; }
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const = 0;
|
||||
|
||||
Type _type;
|
||||
std::string _id;
|
||||
QString _id;
|
||||
std::vector<AnimNode::Pointer> _children;
|
||||
AnimSkeleton::ConstPointer _skeleton;
|
||||
|
||||
|
|
|
@ -200,19 +200,19 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
|
|||
READ_OPTIONAL_STRING(timeScaleVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(loopFlagVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimClip>(id.toStdString(), url.toStdString(), startFrame, endFrame, timeScale, loopFlag);
|
||||
auto node = std::make_shared<AnimClip>(id, url, startFrame, endFrame, timeScale, loopFlag);
|
||||
|
||||
if (!startFrameVar.isEmpty()) {
|
||||
node->setStartFrameVar(startFrameVar.toStdString());
|
||||
node->setStartFrameVar(startFrameVar);
|
||||
}
|
||||
if (!endFrameVar.isEmpty()) {
|
||||
node->setEndFrameVar(endFrameVar.toStdString());
|
||||
node->setEndFrameVar(endFrameVar);
|
||||
}
|
||||
if (!timeScaleVar.isEmpty()) {
|
||||
node->setTimeScaleVar(timeScaleVar.toStdString());
|
||||
node->setTimeScaleVar(timeScaleVar);
|
||||
}
|
||||
if (!loopFlagVar.isEmpty()) {
|
||||
node->setLoopFlagVar(loopFlagVar.toStdString());
|
||||
node->setLoopFlagVar(loopFlagVar);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -224,10 +224,10 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q
|
|||
|
||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimBlendLinear>(id.toStdString(), alpha);
|
||||
auto node = std::make_shared<AnimBlendLinear>(id, alpha);
|
||||
|
||||
if (!alphaVar.isEmpty()) {
|
||||
node->setAlphaVar(alphaVar.toStdString());
|
||||
node->setAlphaVar(alphaVar);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -271,31 +271,31 @@ static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QStri
|
|||
READ_OPTIONAL_STRING(boneSetVar, jsonObj);
|
||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||
|
||||
auto node = std::make_shared<AnimOverlay>(id.toStdString(), boneSetEnum, alpha);
|
||||
auto node = std::make_shared<AnimOverlay>(id, boneSetEnum, alpha);
|
||||
|
||||
if (!boneSetVar.isEmpty()) {
|
||||
node->setBoneSetVar(boneSetVar.toStdString());
|
||||
node->setBoneSetVar(boneSetVar);
|
||||
}
|
||||
if (!alphaVar.isEmpty()) {
|
||||
node->setAlphaVar(alphaVar.toStdString());
|
||||
node->setAlphaVar(alphaVar);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
auto node = std::make_shared<AnimStateMachine>(id.toStdString());
|
||||
auto node = std::make_shared<AnimStateMachine>(id);
|
||||
return node;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
|
||||
auto node = std::make_shared<AnimManipulator>(id.toStdString(), alpha);
|
||||
auto node = std::make_shared<AnimManipulator>(id, alpha);
|
||||
|
||||
READ_OPTIONAL_STRING(alphaVar, jsonObj);
|
||||
if (!alphaVar.isEmpty()) {
|
||||
node->setAlphaVar(alphaVar.toStdString());
|
||||
node->setAlphaVar(alphaVar);
|
||||
}
|
||||
|
||||
auto jointsValue = jsonObj.value("joints");
|
||||
|
@ -315,7 +315,7 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q
|
|||
READ_STRING(var, jointObj, id, jsonUrl, nullptr);
|
||||
READ_STRING(jointName, jointObj, id, jsonUrl, nullptr);
|
||||
|
||||
AnimManipulator::JointVar jointVar(var.toStdString(), jointName.toStdString());
|
||||
AnimManipulator::JointVar jointVar(var, jointName);
|
||||
node->addJointVar(jointVar);
|
||||
};
|
||||
|
||||
|
@ -323,7 +323,7 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q
|
|||
}
|
||||
|
||||
AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
auto node = std::make_shared<AnimInverseKinematics>(id.toStdString());
|
||||
auto node = std::make_shared<AnimInverseKinematics>(id);
|
||||
|
||||
auto targetsValue = jsonObj.value("targets");
|
||||
if (!targetsValue.isArray()) {
|
||||
|
@ -349,9 +349,9 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
|
|||
return node;
|
||||
}
|
||||
|
||||
void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) {
|
||||
void buildChildMap(std::map<QString, AnimNode::Pointer>& map, AnimNode::Pointer node) {
|
||||
for ( auto child : node->_children ) {
|
||||
map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child));
|
||||
map.insert(std::pair<QString, AnimNode::Pointer>(child->_id, child));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,15 +368,15 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
}
|
||||
|
||||
// build a map for all children by name.
|
||||
std::map<std::string, AnimNode::Pointer> childMap;
|
||||
std::map<QString, 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 StringPair = std::pair<QString, QString>;
|
||||
using TransitionMap = std::multimap<AnimStateMachine::State::Pointer, StringPair>;
|
||||
TransitionMap transitionMap;
|
||||
|
||||
using StateMap = std::map<std::string, AnimStateMachine::State::Pointer>;
|
||||
using StateMap = std::map<QString, AnimStateMachine::State::Pointer>;
|
||||
StateMap stateMap;
|
||||
|
||||
auto statesArray = statesValue.toArray();
|
||||
|
@ -394,22 +394,20 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
READ_OPTIONAL_STRING(interpTargetVar, stateObj);
|
||||
READ_OPTIONAL_STRING(interpDurationVar, stateObj);
|
||||
|
||||
auto stdId = id.toStdString();
|
||||
|
||||
auto iter = childMap.find(stdId);
|
||||
auto iter = childMap.find(id);
|
||||
if (iter == childMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto statePtr = std::make_shared<AnimStateMachine::State>(stdId, iter->second, interpTarget, interpDuration);
|
||||
auto statePtr = std::make_shared<AnimStateMachine::State>(id, iter->second, interpTarget, interpDuration);
|
||||
assert(statePtr);
|
||||
|
||||
if (!interpTargetVar.isEmpty()) {
|
||||
statePtr->setInterpTargetVar(interpTargetVar.toStdString());
|
||||
statePtr->setInterpTargetVar(interpTargetVar);
|
||||
}
|
||||
if (!interpDurationVar.isEmpty()) {
|
||||
statePtr->setInterpDurationVar(interpDurationVar.toStdString());
|
||||
statePtr->setInterpDurationVar(interpDurationVar);
|
||||
}
|
||||
|
||||
smNode->addState(statePtr);
|
||||
|
@ -432,7 +430,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
READ_STRING(var, transitionObj, nodeId, jsonUrl, false);
|
||||
READ_STRING(state, transitionObj, nodeId, jsonUrl, false);
|
||||
|
||||
transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString())));
|
||||
transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var, state)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -443,12 +441,12 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
|
|||
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();
|
||||
qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id << "dstState =" << transition.second.second << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = stateMap.find(currentState.toStdString());
|
||||
auto iter = stateMap.find(currentState);
|
||||
if (iter == stateMap.end()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad currentState =" << currentState << "could not find child node" << "id =" << nodeId << "url = " << jsonUrl.toDisplayString();
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "AnimUtil.h"
|
||||
#include <queue>
|
||||
|
||||
AnimOverlay::AnimOverlay(const std::string& id, BoneSet boneSet, float alpha) :
|
||||
AnimOverlay::AnimOverlay(const QString& id, BoneSet boneSet, float alpha) :
|
||||
AnimNode(AnimNode::Type::Overlay, id), _boneSet(boneSet), _alpha(alpha) {
|
||||
}
|
||||
|
||||
|
|
|
@ -40,13 +40,13 @@ public:
|
|||
NumBoneSets
|
||||
};
|
||||
|
||||
AnimOverlay(const std::string& id, BoneSet boneSet, float alpha);
|
||||
AnimOverlay(const QString& 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; }
|
||||
void setBoneSetVar(const QString& boneSetVar) { _boneSetVar = boneSetVar; }
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
protected:
|
||||
void buildBoneSet(BoneSet boneSet);
|
||||
|
@ -60,8 +60,8 @@ public:
|
|||
float _alpha;
|
||||
std::vector<float> _boneSetVec;
|
||||
|
||||
std::string _boneSetVar;
|
||||
std::string _alphaVar;
|
||||
QString _boneSetVar;
|
||||
QString _alphaVar;
|
||||
|
||||
void buildFullBodyBoneSet();
|
||||
void buildUpperBodyBoneSet();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "AnimUtil.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
AnimStateMachine::AnimStateMachine(const std::string& id) :
|
||||
AnimStateMachine::AnimStateMachine(const QString& id) :
|
||||
AnimNode(AnimNode::Type::StateMachine, id) {
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ AnimStateMachine::~AnimStateMachine() {
|
|||
|
||||
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
std::string desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
|
||||
QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
|
||||
if (_currentState->getID() != desiredStateID) {
|
||||
// switch states
|
||||
bool foundState = false;
|
||||
|
@ -35,7 +35,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, fl
|
|||
}
|
||||
}
|
||||
if (!foundState) {
|
||||
qCCritical(animation) << "AnimStateMachine could not find state =" << desiredStateID.c_str() << ", referenced by _currentStateVar =" << _currentStateVar.c_str();
|
||||
qCCritical(animation) << "AnimStateMachine could not find state =" << desiredStateID << ", referenced by _currentStateVar =" << _currentStateVar;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ void AnimStateMachine::addState(State::Pointer state) {
|
|||
|
||||
void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointer desiredState) {
|
||||
|
||||
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID().c_str() << "->" << desiredState->getID().c_str();
|
||||
qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID();
|
||||
|
||||
const float FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
|
|
|
@ -49,23 +49,23 @@ protected:
|
|||
class Transition {
|
||||
public:
|
||||
friend AnimStateMachine;
|
||||
Transition(const std::string& var, State::Pointer state) : _var(var), _state(state) {}
|
||||
Transition(const QString& var, State::Pointer state) : _var(var), _state(state) {}
|
||||
protected:
|
||||
std::string _var;
|
||||
QString _var;
|
||||
State::Pointer _state;
|
||||
};
|
||||
|
||||
State(const std::string& id, AnimNode::Pointer node, float interpTarget, float interpDuration) :
|
||||
State(const QString& 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; }
|
||||
void setInterpTargetVar(const QString& interpTargetVar) { _interpTargetVar = interpTargetVar; }
|
||||
void setInterpDurationVar(const QString& interpDurationVar) { _interpDurationVar = interpDurationVar; }
|
||||
|
||||
AnimNode::Pointer getNode() const { return _node; }
|
||||
const std::string& getID() const { return _id; }
|
||||
const QString& getID() const { return _id; }
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -74,13 +74,13 @@ protected:
|
|||
|
||||
void addTransition(const Transition& transition) { _transitions.push_back(transition); }
|
||||
|
||||
std::string _id;
|
||||
QString _id;
|
||||
AnimNode::Pointer _node;
|
||||
float _interpTarget; // frames
|
||||
float _interpDuration; // frames
|
||||
|
||||
std::string _interpTargetVar;
|
||||
std::string _interpDurationVar;
|
||||
QString _interpTargetVar;
|
||||
QString _interpDurationVar;
|
||||
|
||||
std::vector<Transition> _transitions;
|
||||
|
||||
|
@ -92,12 +92,12 @@ protected:
|
|||
|
||||
public:
|
||||
|
||||
AnimStateMachine(const std::string& id);
|
||||
AnimStateMachine(const QString& id);
|
||||
virtual ~AnimStateMachine() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setCurrentStateVar(std::string& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -123,7 +123,7 @@ protected:
|
|||
State::Pointer _currentState;
|
||||
std::vector<State::Pointer> _states;
|
||||
|
||||
std::string _currentStateVar;
|
||||
QString _currentStateVar;
|
||||
|
||||
private:
|
||||
// no copies
|
||||
|
|
|
@ -37,7 +37,7 @@ public:
|
|||
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; }
|
||||
AnimVariant(const QString& value) : _type(Type::String) { _stringVal = value; }
|
||||
|
||||
bool isBool() const { return _type == Type::Bool; }
|
||||
bool isInt() const { return _type == Type::Int; }
|
||||
|
@ -53,7 +53,7 @@ public:
|
|||
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; }
|
||||
void setString(const QString& 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; }
|
||||
|
@ -61,11 +61,11 @@ public:
|
|||
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; }
|
||||
const QString& getString() const { assert(_type == Type::String); return _stringVal; }
|
||||
|
||||
protected:
|
||||
Type _type;
|
||||
std::string _stringVal;
|
||||
QString _stringVal;
|
||||
union {
|
||||
bool boolVal;
|
||||
int intVal;
|
||||
|
@ -76,9 +76,9 @@ protected:
|
|||
class AnimVariantMap {
|
||||
public:
|
||||
|
||||
bool lookup(const std::string& key, bool defaultValue) const {
|
||||
bool lookup(const QString& key, bool defaultValue) const {
|
||||
// check triggers first, then map
|
||||
if (key.empty()) {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else if (_triggers.find(key) != _triggers.end()) {
|
||||
return true;
|
||||
|
@ -88,8 +88,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
int lookup(const std::string& key, int defaultValue) const {
|
||||
if (key.empty()) {
|
||||
int lookup(const QString& key, int defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -97,8 +97,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
float lookup(const std::string& key, float defaultValue) const {
|
||||
if (key.empty()) {
|
||||
float lookup(const QString& key, float defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -106,8 +106,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
const glm::vec3& lookup(const std::string& key, const glm::vec3& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
const glm::vec3& lookup(const QString& key, const glm::vec3& defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -115,8 +115,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
const glm::quat& lookup(const std::string& key, const glm::quat& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
const glm::quat& lookup(const QString& key, const glm::quat& defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -124,8 +124,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
const glm::mat4& lookup(const std::string& key, const glm::mat4& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
const glm::mat4& lookup(const QString& key, const glm::mat4& defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -133,8 +133,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
const std::string& lookup(const std::string& key, const std::string& defaultValue) const {
|
||||
if (key.empty()) {
|
||||
const QString& lookup(const QString& key, const QString& defaultValue) const {
|
||||
if (key.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
auto iter = _map.find(key);
|
||||
|
@ -142,23 +142,23 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
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 unset(const std::string& key) { _map.erase(key); }
|
||||
void set(const QString& key, bool value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, int value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, float value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, const glm::vec3& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, const glm::quat& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, const glm::mat4& value) { _map[key] = AnimVariant(value); }
|
||||
void set(const QString& key, const QString& value) { _map[key] = AnimVariant(value); }
|
||||
void unset(const QString& key) { _map.erase(key); }
|
||||
|
||||
void setTrigger(const std::string& key) { _triggers.insert(key); }
|
||||
void setTrigger(const QString& key) { _triggers.insert(key); }
|
||||
void clearTriggers() { _triggers.clear(); }
|
||||
|
||||
bool hasKey(const std::string& key) const { return _map.find(key) != _map.end(); }
|
||||
bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); }
|
||||
|
||||
protected:
|
||||
std::map<std::string, AnimVariant> _map;
|
||||
std::set<std::string> _triggers;
|
||||
std::map<QString, AnimVariant> _map;
|
||||
std::set<QString> _triggers;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimVariant_h
|
||||
|
|
|
@ -44,6 +44,7 @@ void Rig::HeadParameters::dump() const {
|
|||
qCDebug(animation, " neckJointIndex = %.d", neckJointIndex);
|
||||
qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex);
|
||||
qCDebug(animation, " rightEyeJointIndex = %.d", rightEyeJointIndex);
|
||||
qCDebug(animation, " isTalking = %s", isTalking ? "true" : "false");
|
||||
}
|
||||
|
||||
void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
|
||||
|
@ -735,19 +736,12 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
|||
return;
|
||||
}
|
||||
|
||||
if (freeLineage.isEmpty()) {
|
||||
if (_enableAnimGraph && _animSkeleton) {
|
||||
// the hand data goes through a different path: Rig::updateFromHandParameters() --> early-exit
|
||||
return;
|
||||
}
|
||||
int numFree = freeLineage.size();
|
||||
|
||||
if (_enableAnimGraph && _animSkeleton) {
|
||||
if (endIndex == _leftHandJointIndex) {
|
||||
_animVars.set("leftHandPosition", targetPosition);
|
||||
_animVars.set("leftHandRotation", targetRotation);
|
||||
} else if (endIndex == _rightHandJointIndex) {
|
||||
_animVars.set("rightHandPosition", targetPosition);
|
||||
_animVars.set("rightHandRotation", targetRotation);
|
||||
}
|
||||
if (freeLineage.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -766,6 +760,7 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
|
|||
|
||||
// relax toward default rotation
|
||||
// NOTE: ideally this should use dt and a relaxation timescale to compute how much to relax
|
||||
int numFree = freeLineage.size();
|
||||
for (int j = 0; j < numFree; j++) {
|
||||
int nextIndex = freeLineage.at(j);
|
||||
JointState& nextState = _jointStates[nextIndex];
|
||||
|
@ -957,6 +952,11 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
|||
updateNeckJoint(params.neckJointIndex, params);
|
||||
updateEyeJoints(params.leftEyeJointIndex, params.rightEyeJointIndex, params.modelTranslation, params.modelRotation,
|
||||
params.worldHeadOrientation, params.eyeLookAt, params.eyeSaccade);
|
||||
|
||||
if (_enableAnimGraph) {
|
||||
_animVars.set("isTalking", params.isTalking);
|
||||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
}
|
||||
}
|
||||
|
||||
static const glm::vec3 X_AXIS(1.0f, 0.0f, 0.0f);
|
||||
|
@ -986,16 +986,20 @@ void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, floa
|
|||
|
||||
void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
||||
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
|
||||
if (_enableAnimGraph && _animSkeleton) {
|
||||
if (_enableAnimGraph && _animSkeleton && _animNode) {
|
||||
// the params.localHeadOrientation is composed incorrectly, so re-compose it correctly from pitch, yaw and roll.
|
||||
glm::quat realLocalHeadOrientation = (glm::angleAxis(glm::radians(-params.localHeadRoll), Z_AXIS) *
|
||||
glm::angleAxis(glm::radians(params.localHeadYaw), Y_AXIS) *
|
||||
glm::angleAxis(glm::radians(-params.localHeadPitch), X_AXIS));
|
||||
_animVars.set("headRotation", realLocalHeadOrientation);
|
||||
|
||||
// There's a theory that when not in hmd, we should _animVars.unset("headPosition").
|
||||
// However, until that works well, let's always request head be positioned where requested by hmd, camera, or default.
|
||||
_animVars.set("headPosition", params.localHeadPosition);
|
||||
if (params.isInHMD) {
|
||||
int headIndex = _animSkeleton->nameToJointIndex("Head");
|
||||
AnimPose rootPose = _animNode->getRootPose(headIndex);
|
||||
_animVars.set("headPosition", rootPose.trans + params.localHeadPosition); // rootPose.rot is handled in params?d
|
||||
} else {
|
||||
_animVars.unset("headPosition");
|
||||
}
|
||||
} else if (!_enableAnimGraph) {
|
||||
|
||||
auto& state = _jointStates[index];
|
||||
|
@ -1044,7 +1048,26 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
|
|||
|
||||
void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
|
||||
|
||||
if (_enableAnimGraph && _animSkeleton) {
|
||||
if (_enableAnimGraph && _animSkeleton && _animNode) {
|
||||
|
||||
// TODO: figure out how to obtain the yFlip from where it is actually stored
|
||||
glm::quat yFlipHACK = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
int leftHandIndex = _animSkeleton->nameToJointIndex("LeftHand");
|
||||
AnimPose rootPose = _animNode->getRootPose(leftHandIndex);
|
||||
if (params.isLeftEnabled) {
|
||||
_animVars.set("leftHandPosition", rootPose.trans + rootPose.rot * yFlipHACK * params.leftPosition);
|
||||
_animVars.set("leftHandRotation", rootPose.rot * yFlipHACK * params.leftOrientation);
|
||||
} else {
|
||||
_animVars.unset("leftHandPosition");
|
||||
_animVars.unset("leftHandRotation");
|
||||
}
|
||||
if (params.isRightEnabled) {
|
||||
_animVars.set("rightHandPosition", rootPose.trans + rootPose.rot * yFlipHACK * params.rightPosition);
|
||||
_animVars.set("rightHandRotation", rootPose.rot * yFlipHACK * params.rightOrientation);
|
||||
} else {
|
||||
_animVars.unset("rightHandPosition");
|
||||
_animVars.unset("rightHandRotation");
|
||||
}
|
||||
|
||||
// set leftHand grab vars
|
||||
_animVars.set("isLeftHandIdle", false);
|
||||
|
|
|
@ -72,6 +72,7 @@ public:
|
|||
int neckJointIndex = -1;
|
||||
int leftEyeJointIndex = -1;
|
||||
int rightEyeJointIndex = -1;
|
||||
bool isTalking = false;
|
||||
|
||||
void dump() const;
|
||||
};
|
||||
|
|
33
libraries/animation/src/RotationAccumulator.cpp
Normal file
33
libraries/animation/src/RotationAccumulator.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// RotationAccumulator.h
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RotationAccumulator.h"
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
void RotationAccumulator::add(const glm::quat& rotation) {
|
||||
// make sure both quaternions are on the same hyper-hemisphere before we add them linearly (lerp)
|
||||
_rotationSum += copysignf(1.0f, glm::dot(_rotationSum, rotation)) * rotation;
|
||||
++_numRotations;
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
glm::quat RotationAccumulator::getAverage() {
|
||||
return (_numRotations > 0) ? glm::normalize(_rotationSum) : glm::quat();
|
||||
}
|
||||
|
||||
void RotationAccumulator::clear() {
|
||||
_rotationSum *= 0.0f;
|
||||
_numRotations = 0;
|
||||
}
|
||||
|
||||
void RotationAccumulator::clearAndClean() {
|
||||
clear();
|
||||
_isDirty = false;
|
||||
}
|
40
libraries/animation/src/RotationAccumulator.h
Normal file
40
libraries/animation/src/RotationAccumulator.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// RotationAccumulator.h
|
||||
//
|
||||
// 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_RotationAccumulator_h
|
||||
#define hifi_RotationAccumulator_h
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
class RotationAccumulator {
|
||||
public:
|
||||
RotationAccumulator() : _rotationSum(0.0f, 0.0f, 0.0f, 0.0f), _numRotations(0), _isDirty(false) { }
|
||||
|
||||
int size() const { return _numRotations; }
|
||||
|
||||
void add(const glm::quat& rotation);
|
||||
|
||||
glm::quat getAverage();
|
||||
|
||||
/// \return true if any rotations were accumulated
|
||||
bool isDirty() const { return _isDirty; }
|
||||
|
||||
/// \brief clear accumulated rotation but don't change _isDirty
|
||||
void clear();
|
||||
|
||||
/// \brief clear accumulated rotation and set _isDirty to false
|
||||
void clearAndClean();
|
||||
|
||||
private:
|
||||
glm::quat _rotationSum;
|
||||
int _numRotations;
|
||||
bool _isDirty;
|
||||
};
|
||||
|
||||
#endif // hifi_RotationAccumulator_h
|
|
@ -73,7 +73,7 @@ AvatarData::~AvatarData() {
|
|||
// We cannot have a file-level variable (const or otherwise) in the header if it uses PathUtils, because that references Application, which will not yet initialized.
|
||||
// Thus we have a static class getter, referencing a static class var.
|
||||
QUrl AvatarData::_defaultFullAvatarModelUrl = {}; // In C++, if this initialization were in the header, every file would have it's own copy, even for class vars.
|
||||
const QUrl AvatarData::defaultFullAvatarModelUrl() {
|
||||
const QUrl& AvatarData::defaultFullAvatarModelUrl() {
|
||||
if (_defaultFullAvatarModelUrl.isEmpty()) {
|
||||
_defaultFullAvatarModelUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "meshes/defaultAvatar_full.fst");
|
||||
}
|
||||
|
@ -966,8 +966,9 @@ bool AvatarData::hasIdentityChangedAfterParsing(NLPacket& packet) {
|
|||
QByteArray AvatarData::identityByteArray() {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? QUrl("") : _skeletonModelURL;
|
||||
|
||||
identityStream << QUuid() << _faceModelURL << _skeletonModelURL << _attachmentData << _displayName;
|
||||
identityStream << QUuid() << _faceModelURL << urlToSend << _attachmentData << _displayName;
|
||||
|
||||
return identityData;
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ public:
|
|||
AvatarData();
|
||||
virtual ~AvatarData();
|
||||
|
||||
static const QUrl defaultFullAvatarModelUrl();
|
||||
static const QUrl& defaultFullAvatarModelUrl();
|
||||
|
||||
virtual bool isMyAvatar() const { return false; }
|
||||
|
||||
|
|
|
@ -99,8 +99,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<NLPacket> packet,
|
|||
avatar->setFaceModelURL(faceMeshURL);
|
||||
}
|
||||
|
||||
if (avatar->getSkeletonModelURL() != skeletonURL) {
|
||||
avatar->setSkeletonModelURL(skeletonURL);
|
||||
if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) {
|
||||
avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire
|
||||
}
|
||||
|
||||
if (avatar->getAttachmentData() != attachmentData) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "openvr/OpenVrDisplayPlugin.h"
|
||||
#include "oculus/OculusDisplayPlugin.h"
|
||||
#include "oculus/OculusDebugDisplayPlugin.h"
|
||||
#include "oculus/OculusLegacyDisplayPlugin.h"
|
||||
|
||||
const QString& DisplayPlugin::MENU_PATH() {
|
||||
|
@ -42,6 +43,10 @@ DisplayPluginList getDisplayPlugins() {
|
|||
|
||||
// Windows Oculus SDK
|
||||
new OculusDisplayPlugin(),
|
||||
// Windows Oculus Simulator... uses head tracking and the same rendering
|
||||
// as the connected hardware, but without using the SDK to display to the
|
||||
// Rift. Useful for debugging Rift performance with nSight.
|
||||
new OculusDebugDisplayPlugin(),
|
||||
// Mac/Linux Oculus SDK (0.5)
|
||||
new OculusLegacyDisplayPlugin(),
|
||||
#ifdef Q_OS_WIN
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2014/04/13.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "OculusBaseDisplayPlugin.h"
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "OculusHelpers.h"
|
||||
|
||||
uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const {
|
||||
return _desiredFramebufferSize;
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::preRender() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex);
|
||||
_trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds);
|
||||
ovr_CalcEyePoses(_trackingState.HeadPose.ThePose, _eyeOffsets, _eyePoses);
|
||||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const {
|
||||
return _eyeProjections[eye];
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::resetSensors() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovr_RecenterPose(_hmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusBaseDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return toGlm(_eyePoses[eye]);
|
||||
}
|
||||
|
||||
glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const {
|
||||
return toGlm(_trackingState.HeadPose.ThePose);
|
||||
}
|
||||
|
||||
bool OculusBaseDisplayPlugin::isSupported() const {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
if (ovrHmd_Detect() > 0) {
|
||||
result = true;
|
||||
}
|
||||
ovr_Shutdown();
|
||||
return result;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::init() {
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::deinit() {
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::activate() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
qFatal("Could not init OVR");
|
||||
}
|
||||
|
||||
#if (OVR_MAJOR_VERSION == 6)
|
||||
if (!OVR_SUCCESS(ovr_Create(0, &_hmd))) {
|
||||
#elif (OVR_MAJOR_VERSION == 7)
|
||||
if (!OVR_SUCCESS(ovr_Create(&_hmd, &_luid))) {
|
||||
#endif
|
||||
Q_ASSERT(false);
|
||||
qFatal("Failed to acquire HMD");
|
||||
}
|
||||
|
||||
_hmdDesc = ovr_GetHmdDesc(_hmd);
|
||||
|
||||
_ipd = ovr_GetFloat(_hmd, OVR_KEY_IPD, _ipd);
|
||||
|
||||
glm::uvec2 eyeSizes[2];
|
||||
ovr_for_each_eye([&](ovrEyeType eye) {
|
||||
_eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye];
|
||||
ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_hmd, eye, _eyeFovs[eye]);
|
||||
ovrMatrix4f ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded);
|
||||
_eyeProjections[eye] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded);
|
||||
_compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
_eyeOffsets[eye] = erd.HmdToEyeViewOffset;
|
||||
eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f));
|
||||
});
|
||||
ovrFovPort combined = _eyeFovs[Left];
|
||||
combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan);
|
||||
combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan);
|
||||
ovrMatrix4f ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded);
|
||||
_eyeProjections[Mono] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
|
||||
|
||||
_desiredFramebufferSize = uvec2(
|
||||
eyeSizes[0].x + eyeSizes[1].x,
|
||||
std::max(eyeSizes[0].y, eyeSizes[1].y));
|
||||
|
||||
_frameIndex = 0;
|
||||
|
||||
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
|
||||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
}
|
||||
|
||||
// Parent class relies on our _hmd intialization, so it must come after that.
|
||||
memset(&_sceneLayer, 0, sizeof(ovrLayerEyeFov));
|
||||
_sceneLayer.Header.Type = ovrLayerType_EyeFov;
|
||||
_sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;
|
||||
ovr_for_each_eye([&](ovrEyeType eye) {
|
||||
ovrFovPort & fov = _sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov;
|
||||
ovrSizei & size = _sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_hmd, eye, fov, 1.0f);
|
||||
_sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 };
|
||||
});
|
||||
|
||||
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
|
||||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
}
|
||||
#endif
|
||||
|
||||
WindowOpenGLDisplayPlugin::activate();
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::deactivate() {
|
||||
WindowOpenGLDisplayPlugin::deactivate();
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovr_Destroy(_hmd);
|
||||
_hmd = nullptr;
|
||||
ovr_Shutdown();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
++_frameIndex;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/05/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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "../WindowOpenGLDisplayPlugin.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <OVR_CAPI_GL.h>
|
||||
|
||||
class OculusBaseDisplayPlugin : public WindowOpenGLDisplayPlugin {
|
||||
public:
|
||||
virtual bool isSupported() const override;
|
||||
|
||||
virtual void init() override final;
|
||||
virtual void deinit() override final;
|
||||
|
||||
virtual void activate() override;
|
||||
virtual void deactivate() override;
|
||||
|
||||
// Stereo specific methods
|
||||
virtual bool isHmd() const override final { return true; }
|
||||
virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override;
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override final;
|
||||
virtual glm::uvec2 getRecommendedUiSize() const override final { return uvec2(1920, 1080); }
|
||||
virtual void resetSensors() override final;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override final;
|
||||
virtual glm::mat4 getHeadPose() const override final;
|
||||
|
||||
protected:
|
||||
virtual void preRender() override final;
|
||||
virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override;
|
||||
|
||||
protected:
|
||||
ovrPosef _eyePoses[2];
|
||||
|
||||
mat4 _eyeProjections[3];
|
||||
mat4 _compositeEyeProjections[2];
|
||||
uvec2 _desiredFramebufferSize;
|
||||
ovrTrackingState _trackingState;
|
||||
unsigned int _frameIndex{ 0 };
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrHmd _hmd;
|
||||
float _ipd{ OVR_DEFAULT_IPD };
|
||||
ovrEyeRenderDesc _eyeRenderDescs[2];
|
||||
ovrVector3f _eyeOffsets[2];
|
||||
ovrFovPort _eyeFovs[2];
|
||||
ovrHmdDesc _hmdDesc;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
#endif
|
||||
#if (OVR_MAJOR_VERSION == 7)
|
||||
ovrGraphicsLuid _luid;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if (OVR_MAJOR_VERSION == 6)
|
||||
#define ovr_Create ovrHmd_Create
|
||||
#define ovr_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL
|
||||
#define ovr_CreateMirrorTextureGL ovrHmd_CreateMirrorTextureGL
|
||||
#define ovr_Destroy ovrHmd_Destroy
|
||||
#define ovr_DestroySwapTextureSet ovrHmd_DestroySwapTextureSet
|
||||
#define ovr_DestroyMirrorTexture ovrHmd_DestroyMirrorTexture
|
||||
#define ovr_GetFloat ovrHmd_GetFloat
|
||||
#define ovr_GetFovTextureSize ovrHmd_GetFovTextureSize
|
||||
#define ovr_GetFrameTiming ovrHmd_GetFrameTiming
|
||||
#define ovr_GetTrackingState ovrHmd_GetTrackingState
|
||||
#define ovr_GetRenderDesc ovrHmd_GetRenderDesc
|
||||
#define ovr_RecenterPose ovrHmd_RecenterPose
|
||||
#define ovr_SubmitFrame ovrHmd_SubmitFrame
|
||||
#define ovr_ConfigureTracking ovrHmd_ConfigureTracking
|
||||
|
||||
#define ovr_GetHmdDesc(X) *X
|
||||
#endif
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2014/04/13.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "OculusDebugDisplayPlugin.h"
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
|
||||
const QString OculusDebugDisplayPlugin::NAME("Oculus Rift (Simulator)");
|
||||
|
||||
const QString & OculusDebugDisplayPlugin::getName() const {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
static const QString DEBUG_FLAG("HIFI_DEBUG_OCULUS");
|
||||
static bool enableDebugOculus = QProcessEnvironment::systemEnvironment().contains("HIFI_DEBUG_OCULUS");
|
||||
|
||||
bool OculusDebugDisplayPlugin::isSupported() const {
|
||||
if (!enableDebugOculus) {
|
||||
return false;
|
||||
}
|
||||
return OculusBaseDisplayPlugin::isSupported();
|
||||
}
|
||||
|
||||
void OculusDebugDisplayPlugin::customizeContext() {
|
||||
WindowOpenGLDisplayPlugin::customizeContext();
|
||||
enableVsync(false);
|
||||
}
|
||||
|
||||
void OculusDebugDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
WindowOpenGLDisplayPlugin::display(finalTexture, sceneSize);
|
||||
OculusBaseDisplayPlugin::display(finalTexture, sceneSize);
|
||||
}
|
||||
|
||||
void OculusDebugDisplayPlugin::finishFrame() {
|
||||
swapBuffers();
|
||||
doneCurrent();
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015/05/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
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "OculusBaseDisplayPlugin.h"
|
||||
|
||||
class OculusDebugDisplayPlugin : public OculusBaseDisplayPlugin {
|
||||
public:
|
||||
virtual const QString & getName() const override;
|
||||
virtual bool isSupported() const override;
|
||||
|
||||
protected:
|
||||
virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override;
|
||||
virtual void customizeContext() override;
|
||||
// Do not perform swap in finish
|
||||
virtual void finishFrame() override;
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
};
|
||||
|
|
@ -7,59 +7,10 @@
|
|||
//
|
||||
#include "OculusDisplayPlugin.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QGLWidget>
|
||||
#include <GLMHelpers.h>
|
||||
#include <GlWindow.h>
|
||||
#include <QEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QThread>
|
||||
|
||||
#include <OglplusHelpers.h>
|
||||
#include <oglplus/opt/list_init.hpp>
|
||||
#include <oglplus/shapes/vector.hpp>
|
||||
#include <oglplus/opt/list_init.hpp>
|
||||
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdouble-promotion"
|
||||
#endif
|
||||
|
||||
#include <oglplus/shapes/obj_mesh.hpp>
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "OculusHelpers.h"
|
||||
|
||||
#if (OVR_MAJOR_VERSION == 6)
|
||||
#define ovr_Create ovrHmd_Create
|
||||
#define ovr_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL
|
||||
#define ovr_CreateMirrorTextureGL ovrHmd_CreateMirrorTextureGL
|
||||
#define ovr_Destroy ovrHmd_Destroy
|
||||
#define ovr_DestroySwapTextureSet ovrHmd_DestroySwapTextureSet
|
||||
#define ovr_DestroyMirrorTexture ovrHmd_DestroyMirrorTexture
|
||||
#define ovr_GetFloat ovrHmd_GetFloat
|
||||
#define ovr_GetFovTextureSize ovrHmd_GetFovTextureSize
|
||||
#define ovr_GetFrameTiming ovrHmd_GetFrameTiming
|
||||
#define ovr_GetTrackingState ovrHmd_GetTrackingState
|
||||
#define ovr_GetRenderDesc ovrHmd_GetRenderDesc
|
||||
#define ovr_RecenterPose ovrHmd_RecenterPose
|
||||
#define ovr_SubmitFrame ovrHmd_SubmitFrame
|
||||
#define ovr_ConfigureTracking ovrHmd_ConfigureTracking
|
||||
|
||||
#define ovr_GetHmdDesc(X) *X
|
||||
#endif
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
|
||||
// A base class for FBO wrappers that need to use the Oculus C
|
||||
|
@ -180,160 +131,21 @@ private:
|
|||
|
||||
const QString OculusDisplayPlugin::NAME("Oculus Rift");
|
||||
|
||||
uvec2 OculusDisplayPlugin::getRecommendedRenderSize() const {
|
||||
return _desiredFramebufferSize;
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::preRender() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrFrameTiming ftiming = ovr_GetFrameTiming(_hmd, _frameIndex);
|
||||
_trackingState = ovr_GetTrackingState(_hmd, ftiming.DisplayMidpointSeconds);
|
||||
ovr_CalcEyePoses(_trackingState.HeadPose.ThePose, _eyeOffsets, _eyePoses);
|
||||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const {
|
||||
return _eyeProjections[eye];
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::resetSensors() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovr_RecenterPose(_hmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
glm::mat4 OculusDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return toGlm(_eyePoses[eye]);
|
||||
}
|
||||
|
||||
glm::mat4 OculusDisplayPlugin::getHeadPose() const {
|
||||
return toGlm(_trackingState.HeadPose.ThePose);
|
||||
}
|
||||
|
||||
const QString & OculusDisplayPlugin::getName() const {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
bool OculusDisplayPlugin::isSupported() const {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
if (ovrHmd_Detect() > 0) {
|
||||
result = true;
|
||||
}
|
||||
ovr_Shutdown();
|
||||
return result;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::init() {
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
qFatal("Could not init OVR");
|
||||
}
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::deinit() {
|
||||
ovr_Shutdown();
|
||||
}
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrLayerEyeFov& OculusDisplayPlugin::getSceneLayer() {
|
||||
return _sceneLayer;
|
||||
}
|
||||
#endif
|
||||
|
||||
//static gpu::TexturePointer _texture;
|
||||
|
||||
void OculusDisplayPlugin::activate() {
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
Q_ASSERT(false);
|
||||
qFatal("Failed to Initialize SDK");
|
||||
}
|
||||
|
||||
// CONTAINER->getPrimarySurface()->makeCurrent();
|
||||
#if (OVR_MAJOR_VERSION == 6)
|
||||
if (!OVR_SUCCESS(ovr_Create(0, &_hmd))) {
|
||||
#elif (OVR_MAJOR_VERSION == 7)
|
||||
if (!OVR_SUCCESS(ovr_Create(&_hmd, &_luid))) {
|
||||
#endif
|
||||
Q_ASSERT(false);
|
||||
qFatal("Failed to acquire HMD");
|
||||
}
|
||||
|
||||
_hmdDesc = ovr_GetHmdDesc(_hmd);
|
||||
|
||||
_ipd = ovr_GetFloat(_hmd, OVR_KEY_IPD, _ipd);
|
||||
|
||||
glm::uvec2 eyeSizes[2];
|
||||
ovr_for_each_eye([&](ovrEyeType eye) {
|
||||
_eyeFovs[eye] = _hmdDesc.DefaultEyeFov[eye];
|
||||
ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovr_GetRenderDesc(_hmd, eye, _eyeFovs[eye]);
|
||||
ovrMatrix4f ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded);
|
||||
_eyeProjections[eye] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded);
|
||||
_compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
_eyeOffsets[eye] = erd.HmdToEyeViewOffset;
|
||||
eyeSizes[eye] = toGlm(ovr_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f));
|
||||
});
|
||||
ovrFovPort combined = _eyeFovs[Left];
|
||||
combined.LeftTan = std::max(_eyeFovs[Left].LeftTan, _eyeFovs[Right].LeftTan);
|
||||
combined.RightTan = std::max(_eyeFovs[Left].RightTan, _eyeFovs[Right].RightTan);
|
||||
ovrMatrix4f ovrPerspectiveProjection =
|
||||
ovrMatrix4f_Projection(combined, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded);
|
||||
_eyeProjections[Mono] = toGlm(ovrPerspectiveProjection);
|
||||
|
||||
|
||||
|
||||
_desiredFramebufferSize = uvec2(
|
||||
eyeSizes[0].x + eyeSizes[1].x,
|
||||
std::max(eyeSizes[0].y, eyeSizes[1].y));
|
||||
|
||||
_frameIndex = 0;
|
||||
|
||||
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
|
||||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
}
|
||||
|
||||
WindowOpenGLDisplayPlugin::activate();
|
||||
|
||||
// Parent class relies on our _hmd intialization, so it must come after that.
|
||||
ovrLayerEyeFov& sceneLayer = getSceneLayer();
|
||||
memset(&sceneLayer, 0, sizeof(ovrLayerEyeFov));
|
||||
sceneLayer.Header.Type = ovrLayerType_EyeFov;
|
||||
sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft;
|
||||
ovr_for_each_eye([&](ovrEyeType eye) {
|
||||
ovrFovPort & fov = sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov;
|
||||
ovrSizei & size = sceneLayer.Viewport[eye].Size = ovr_GetFovTextureSize(_hmd, eye, fov, 1.0f);
|
||||
sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 };
|
||||
});
|
||||
// We're rendering both eyes to the same texture, so only one of the
|
||||
// pointers is populated
|
||||
sceneLayer.ColorTexture[0] = _sceneFbo->color;
|
||||
// not needed since the structure was zeroed on init, but explicit
|
||||
sceneLayer.ColorTexture[1] = nullptr;
|
||||
|
||||
if (!OVR_SUCCESS(ovr_ConfigureTracking(_hmd,
|
||||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::customizeContext() {
|
||||
WindowOpenGLDisplayPlugin::customizeContext();
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
|
||||
_sceneFbo->Init(getRecommendedRenderSize());
|
||||
|
||||
// We're rendering both eyes to the same texture, so only one of the
|
||||
// pointers is populated
|
||||
_sceneLayer.ColorTexture[0] = _sceneFbo->color;
|
||||
// not needed since the structure was zeroed on init, but explicit
|
||||
_sceneLayer.ColorTexture[1] = nullptr;
|
||||
#endif
|
||||
enableVsync(false);
|
||||
// Only enable mirroring if we know vsync is disabled
|
||||
|
@ -345,13 +157,9 @@ void OculusDisplayPlugin::deactivate() {
|
|||
makeCurrent();
|
||||
_sceneFbo.reset();
|
||||
doneCurrent();
|
||||
|
||||
WindowOpenGLDisplayPlugin::deactivate();
|
||||
|
||||
ovr_Destroy(_hmd);
|
||||
_hmd = nullptr;
|
||||
ovr_Shutdown();
|
||||
#endif
|
||||
|
||||
OculusBaseDisplayPlugin::deactivate();
|
||||
}
|
||||
|
||||
void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
|
||||
|
@ -379,9 +187,8 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
drawUnitQuad();
|
||||
});
|
||||
|
||||
ovrLayerEyeFov& sceneLayer = getSceneLayer();
|
||||
ovr_for_each_eye([&](ovrEyeType eye) {
|
||||
sceneLayer.RenderPose[eye] = _eyePoses[eye];
|
||||
_sceneLayer.RenderPose[eye] = _eyePoses[eye];
|
||||
});
|
||||
|
||||
auto windowSize = toGlm(_window->size());
|
||||
|
@ -391,7 +198,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0];
|
||||
viewScaleDesc.HmdToEyeViewOffset[1] = _eyeOffsets[1];
|
||||
|
||||
ovrLayerHeader* layers = &sceneLayer.Header;
|
||||
ovrLayerHeader* layers = &_sceneLayer.Header;
|
||||
ovrResult result = ovr_SubmitFrame(_hmd, 0, &viewScaleDesc, &layers, 1);
|
||||
if (!OVR_SUCCESS(result)) {
|
||||
qDebug() << result;
|
||||
|
@ -403,11 +210,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
|
|||
#endif
|
||||
}
|
||||
|
||||
// Pass input events on to the application
|
||||
bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
||||
return WindowOpenGLDisplayPlugin::eventFilter(receiver, event);
|
||||
}
|
||||
|
||||
/*
|
||||
The swapbuffer call here is only required if we want to mirror the content to the screen.
|
||||
However, it should only be done if we can reliably disable v-sync on the mirror surface,
|
||||
|
@ -419,36 +221,3 @@ void OculusDisplayPlugin::finishFrame() {
|
|||
}
|
||||
doneCurrent();
|
||||
};
|
||||
|
||||
|
||||
#if 0
|
||||
/*
|
||||
An alternative way to render the UI is to pass it specifically as a composition layer to
|
||||
the Oculus SDK which should technically result in higher quality. However, the SDK doesn't
|
||||
have a mechanism to present the image as a sphere section, which is our desired look.
|
||||
*/
|
||||
ovrLayerQuad& uiLayer = getUiLayer();
|
||||
if (nullptr == uiLayer.ColorTexture || overlaySize != _uiFbo->size) {
|
||||
_uiFbo->Resize(overlaySize);
|
||||
uiLayer.ColorTexture = _uiFbo->color;
|
||||
uiLayer.Viewport.Size.w = overlaySize.x;
|
||||
uiLayer.Viewport.Size.h = overlaySize.y;
|
||||
float overlayAspect = aspect(overlaySize);
|
||||
uiLayer.QuadSize.x = 1.0f;
|
||||
uiLayer.QuadSize.y = 1.0f / overlayAspect;
|
||||
}
|
||||
|
||||
_uiFbo->Bound([&] {
|
||||
Q_ASSERT(0 == glGetError());
|
||||
using namespace oglplus;
|
||||
Context::Viewport(_uiFbo->size.x, _uiFbo->size.y);
|
||||
glClearColor(0, 0, 0, 0);
|
||||
Context::Clear().ColorBuffer();
|
||||
|
||||
_program->Bind();
|
||||
glBindTexture(GL_TEXTURE_2D, overlayTexture);
|
||||
_plane->Use();
|
||||
_plane->Draw();
|
||||
Q_ASSERT(0 == glGetError());
|
||||
});
|
||||
#endif
|
||||
|
|
|
@ -7,43 +7,17 @@
|
|||
//
|
||||
#pragma once
|
||||
|
||||
#include "../WindowOpenGLDisplayPlugin.h"
|
||||
#include "OculusBaseDisplayPlugin.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include <OVR_CAPI.h>
|
||||
|
||||
class OffscreenGlCanvas;
|
||||
struct SwapFramebufferWrapper;
|
||||
struct MirrorFramebufferWrapper;
|
||||
|
||||
using SwapFboPtr = QSharedPointer<SwapFramebufferWrapper>;
|
||||
using MirrorFboPtr = QSharedPointer<MirrorFramebufferWrapper>;
|
||||
|
||||
class OculusDisplayPlugin : public WindowOpenGLDisplayPlugin {
|
||||
class OculusDisplayPlugin : public OculusBaseDisplayPlugin {
|
||||
public:
|
||||
virtual bool isSupported() const override;
|
||||
virtual void deactivate() override;
|
||||
virtual const QString & getName() const override;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void deinit() override;
|
||||
|
||||
virtual void activate() override;
|
||||
virtual void deactivate() override;
|
||||
|
||||
virtual bool eventFilter(QObject* receiver, QEvent* event) override;
|
||||
|
||||
// Stereo specific methods
|
||||
virtual bool isHmd() const override { return true; }
|
||||
virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override;
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); }
|
||||
virtual void resetSensors() override;
|
||||
virtual glm::mat4 getEyePose(Eye eye) const override;
|
||||
virtual glm::mat4 getHeadPose() const override;
|
||||
|
||||
protected:
|
||||
virtual void preRender() override;
|
||||
virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override;
|
||||
virtual void customizeContext() override;
|
||||
// Do not perform swap in finish
|
||||
|
@ -51,30 +25,10 @@ protected:
|
|||
|
||||
private:
|
||||
static const QString NAME;
|
||||
|
||||
ovrPosef _eyePoses[2];
|
||||
|
||||
mat4 _eyeProjections[3];
|
||||
mat4 _compositeEyeProjections[2];
|
||||
uvec2 _desiredFramebufferSize;
|
||||
ovrTrackingState _trackingState;
|
||||
bool _enableMirror{ false };
|
||||
|
||||
#if (OVR_MAJOR_VERSION >= 6)
|
||||
ovrHmd _hmd;
|
||||
float _ipd{ OVR_DEFAULT_IPD };
|
||||
unsigned int _frameIndex;
|
||||
ovrEyeRenderDesc _eyeRenderDescs[2];
|
||||
ovrVector3f _eyeOffsets[2];
|
||||
ovrFovPort _eyeFovs[2];
|
||||
|
||||
ovrLayerEyeFov& getSceneLayer();
|
||||
ovrHmdDesc _hmdDesc;
|
||||
SwapFboPtr _sceneFbo;
|
||||
ovrLayerEyeFov _sceneLayer;
|
||||
#endif
|
||||
#if (OVR_MAJOR_VERSION == 7)
|
||||
ovrGraphicsLuid _luid;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ void OpenVrDisplayPlugin::resetSensors() {
|
|||
}
|
||||
|
||||
glm::mat4 OpenVrDisplayPlugin::getEyePose(Eye eye) const {
|
||||
return _eyesData[eye]._eyeOffset * getHeadPose();
|
||||
return getHeadPose() * _eyesData[eye]._eyeOffset;
|
||||
}
|
||||
|
||||
glm::mat4 OpenVrDisplayPlugin::getHeadPose() const {
|
||||
|
|
|
@ -26,4 +26,4 @@ find_package(PolyVox REQUIRED)
|
|||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES})
|
||||
|
||||
link_hifi_libraries(shared gpu gpu-networking procedural script-engine render render-utils)
|
||||
link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils)
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include <ScriptEngine.h>
|
||||
#include <procedural/Procedural.h>
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#include "EntityTreeRenderer.h"
|
||||
|
@ -52,9 +52,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
_lastMouseEventValid(false),
|
||||
_viewState(viewState),
|
||||
_scriptingServices(scriptingServices),
|
||||
_displayElementChildProxies(false),
|
||||
_displayModelBounds(false),
|
||||
_displayModelElementProxy(false),
|
||||
_dontDoPrecisionPicking(false)
|
||||
{
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
|
||||
|
@ -112,6 +110,7 @@ void EntityTreeRenderer::init() {
|
|||
_scriptingServices->getControllerScriptingInterface());
|
||||
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
|
||||
_entitiesScriptEngine->runInThread();
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine);
|
||||
}
|
||||
|
||||
// make sure our "last avatar position" is something other than our current position, so that on our
|
||||
|
@ -151,6 +150,7 @@ void EntityTreeRenderer::update() {
|
|||
}
|
||||
|
||||
}
|
||||
deleteReleasedModels();
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
|
@ -167,12 +167,41 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
_tree->withReadLock([&] {
|
||||
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
|
||||
|
||||
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
|
||||
_bestZone = NULL; // NOTE: Is this what we want?
|
||||
_bestZoneVolume = std::numeric_limits<float>::max();
|
||||
|
||||
// create a list of entities that actually contain the avatar's position
|
||||
foreach(EntityItemPointer entity, foundEntities) {
|
||||
if (entity->contains(avatarPosition)) {
|
||||
entitiesContainingAvatar << entity->getEntityItemID();
|
||||
|
||||
// if this entity is a zone, use this time to determine the bestZone
|
||||
if (entity->getType() == EntityTypes::Zone) {
|
||||
float entityVolumeEstimate = entity->getVolumeEstimate();
|
||||
if (entityVolumeEstimate < _bestZoneVolume) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
} else if (entityVolumeEstimate == _bestZoneVolume) {
|
||||
if (!_bestZone) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
} else {
|
||||
// in the case of the volume being equal, we will use the
|
||||
// EntityItemID to deterministically pick one entity over the other
|
||||
if (entity->getEntityItemID() < _bestZone->getEntityItemID()) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
|
||||
});
|
||||
|
||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||
|
@ -265,16 +294,16 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_viewState->endOverrideEnvironmentData();
|
||||
auto stage = scene->getSkyStage();
|
||||
if (zone->getBackgroundMode() == BACKGROUND_MODE_SKYBOX) {
|
||||
auto skybox = stage->getSkybox();
|
||||
auto skybox = std::dynamic_pointer_cast<ProceduralSkybox>(stage->getSkybox());
|
||||
skybox->setColor(zone->getSkyboxProperties().getColorVec3());
|
||||
static QString userData;
|
||||
if (userData != zone->getUserData()) {
|
||||
userData = zone->getUserData();
|
||||
QSharedPointer<Procedural> procedural(new Procedural(userData));
|
||||
ProceduralPointer procedural(new Procedural(userData));
|
||||
if (procedural->_enabled) {
|
||||
skybox->setProcedural(procedural);
|
||||
} else {
|
||||
skybox->setProcedural(QSharedPointer<Procedural>());
|
||||
skybox->setProcedural(ProceduralPointer());
|
||||
}
|
||||
}
|
||||
if (zone->getSkyboxProperties().getURL().isEmpty()) {
|
||||
|
@ -307,27 +336,6 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::render(RenderArgs* renderArgs) {
|
||||
|
||||
if (_tree && !_shuttingDown) {
|
||||
renderArgs->_renderer = this;
|
||||
_tree->withReadLock([&] {
|
||||
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
|
||||
_bestZone = NULL; // NOTE: Is this what we want?
|
||||
_bestZoneVolume = std::numeric_limits<float>::max();
|
||||
|
||||
// FIX ME: right now the renderOperation does the following:
|
||||
// 1) determining the best zone (not really rendering)
|
||||
// 2) render the debug cell details
|
||||
// we should clean this up
|
||||
_tree->recurseTreeWithOperation(renderOperation, renderArgs);
|
||||
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
});
|
||||
}
|
||||
deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup
|
||||
}
|
||||
|
||||
const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) {
|
||||
const FBXGeometry* result = NULL;
|
||||
|
||||
|
@ -372,121 +380,6 @@ const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(EntityItemP
|
|||
return result;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args) {
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
Transform transform;
|
||||
|
||||
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter();
|
||||
float elementSize = entityTreeElement->getScale();
|
||||
|
||||
auto drawWireCube = [&](glm::vec3 offset, float size, glm::vec4 color) {
|
||||
transform.setTranslation(elementCenter + offset);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->renderWireCube(batch, size, color);
|
||||
};
|
||||
|
||||
drawWireCube(glm::vec3(), elementSize, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
if (_displayElementChildProxies) {
|
||||
// draw the children
|
||||
float halfSize = elementSize / 2.0f;
|
||||
float quarterSize = elementSize / 4.0f;
|
||||
|
||||
drawWireCube(glm::vec3(-quarterSize, -quarterSize, -quarterSize), halfSize, glm::vec4(1.0f, 1.0f, 0.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(quarterSize, -quarterSize, -quarterSize), halfSize, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(-quarterSize, quarterSize, -quarterSize), halfSize, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(-quarterSize, -quarterSize, quarterSize), halfSize, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(quarterSize, quarterSize, quarterSize), halfSize, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(-quarterSize, quarterSize, quarterSize), halfSize, glm::vec4(0.0f, 0.5f, 0.5f, 1.0f));
|
||||
drawWireCube(glm::vec3(quarterSize, -quarterSize, quarterSize), halfSize, glm::vec4(0.5f, 0.0f, 0.0f, 1.0f));
|
||||
drawWireCube(glm::vec3(quarterSize, quarterSize, -quarterSize), halfSize, glm::vec4(0.0f, 0.5f, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::renderProxies(EntityItemPointer entity, RenderArgs* args) {
|
||||
bool isShadowMode = args->_renderMode == RenderArgs::SHADOW_RENDER_MODE;
|
||||
if (!isShadowMode && _displayModelBounds) {
|
||||
PerformanceTimer perfTimer("renderProxies");
|
||||
|
||||
AACube maxCube = entity->getMaximumAACube();
|
||||
AACube minCube = entity->getMinimumAACube();
|
||||
AABox entityBox = entity->getAABox();
|
||||
|
||||
glm::vec3 maxCenter = maxCube.calcCenter();
|
||||
glm::vec3 minCenter = minCube.calcCenter();
|
||||
glm::vec3 entityBoxCenter = entityBox.calcCenter();
|
||||
glm::vec3 entityBoxScale = entityBox.getScale();
|
||||
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
Transform transform;
|
||||
|
||||
// draw the max bounding cube
|
||||
transform.setTranslation(maxCenter);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->renderWireCube(batch, maxCube.getScale(), glm::vec4(1.0f, 1.0f, 0.0f, 1.0f));
|
||||
|
||||
// draw the min bounding cube
|
||||
transform.setTranslation(minCenter);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->renderWireCube(batch, minCube.getScale(), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
|
||||
// draw the entityBox bounding box
|
||||
transform.setTranslation(entityBoxCenter);
|
||||
transform.setScale(entityBoxScale);
|
||||
batch.setModelTransform(transform);
|
||||
deferredLighting->renderWireCube(batch, 1.0f, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
|
||||
// Rotated bounding box
|
||||
batch.setModelTransform(entity->getTransformToCenter());
|
||||
deferredLighting->renderWireCube(batch, 1.0f, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::renderElement(OctreeElementPointer element, RenderArgs* args) {
|
||||
// actually render it here...
|
||||
// we need to iterate the actual entityItems of the element
|
||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
|
||||
|
||||
bool isShadowMode = args->_renderMode == RenderArgs::SHADOW_RENDER_MODE;
|
||||
|
||||
if (!isShadowMode && _displayModelElementProxy && entityTreeElement->size() > 0) {
|
||||
renderElementProxy(entityTreeElement, args);
|
||||
}
|
||||
|
||||
entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) {
|
||||
if (entityItem->isVisible()) {
|
||||
// NOTE: Zone Entities are a special case we handle here...
|
||||
if (entityItem->getType() == EntityTypes::Zone) {
|
||||
if (entityItem->contains(_viewState->getAvatarPosition())) {
|
||||
float entityVolumeEstimate = entityItem->getVolumeEstimate();
|
||||
if (entityVolumeEstimate < _bestZoneVolume) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entityItem);
|
||||
} else if (entityVolumeEstimate == _bestZoneVolume) {
|
||||
if (!_bestZone) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entityItem);
|
||||
} else {
|
||||
// in the case of the volume being equal, we will use the
|
||||
// EntityItemID to deterministically pick one entity over the other
|
||||
if (entityItem->getEntityItemID() < _bestZone->getEntityItemID()) {
|
||||
_bestZoneVolume = entityVolumeEstimate;
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entityItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
float EntityTreeRenderer::getSizeScale() const {
|
||||
return _viewState->getSizeScale();
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ public:
|
|||
virtual char getMyNodeType() const { return NodeType::EntityServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketType::EntityQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; }
|
||||
virtual void renderElement(OctreeElementPointer element, RenderArgs* args);
|
||||
virtual float getSizeScale() const;
|
||||
virtual int getBoundaryLevelAdjust() const;
|
||||
virtual void setTree(OctreePointer newTree);
|
||||
|
@ -53,7 +52,6 @@ public:
|
|||
void processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode);
|
||||
|
||||
virtual void init();
|
||||
virtual void render(RenderArgs* renderArgs) override;
|
||||
|
||||
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem);
|
||||
virtual const Model* getModelForEntityItem(EntityItemPointer entityItem);
|
||||
|
@ -114,9 +112,7 @@ public slots:
|
|||
void updateEntityRenderStatus(bool shouldRenderEntities);
|
||||
|
||||
// optional slots that can be wired to menu items
|
||||
void setDisplayElementChildProxies(bool value) { _displayElementChildProxies = value; }
|
||||
void setDisplayModelBounds(bool value) { _displayModelBounds = value; }
|
||||
void setDisplayModelElementProxy(bool value) { _displayModelElementProxy = value; }
|
||||
void setDontDoPrecisionPicking(bool value) { _dontDoPrecisionPicking = value; }
|
||||
|
||||
protected:
|
||||
|
@ -130,11 +126,9 @@ private:
|
|||
void addEntityToScene(EntityItemPointer entity);
|
||||
|
||||
void applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone);
|
||||
void renderElementProxy(EntityTreeElementPointer entityTreeElement, RenderArgs* args);
|
||||
void checkAndCallPreload(const EntityItemID& entityID, const bool reload = false);
|
||||
|
||||
QList<Model*> _releasedModels;
|
||||
void renderProxies(EntityItemPointer entity, RenderArgs* args);
|
||||
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType,
|
||||
bool precisionPicking);
|
||||
|
||||
|
@ -157,9 +151,7 @@ private:
|
|||
MouseEvent _lastMouseEvent;
|
||||
AbstractViewStateInterface* _viewState;
|
||||
AbstractScriptingServicesInterface* _scriptingServices;
|
||||
bool _displayElementChildProxies;
|
||||
bool _displayModelBounds;
|
||||
bool _displayModelElementProxy;
|
||||
bool _dontDoPrecisionPicking;
|
||||
|
||||
bool _shuttingDown = false;
|
||||
|
|
|
@ -39,9 +39,6 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
|
|||
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Box);
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
|
||||
glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(this->getUserData()));
|
||||
|
@ -54,11 +51,17 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
|
|||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
|
||||
if (_procedural->ready()) {
|
||||
batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well
|
||||
_procedural->prepare(batch, this->getDimensions());
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCube(batch, 1.0f, _procedural->getColor(cubeColor));
|
||||
auto color = _procedural->getColor(cubeColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderCube(batch);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidCube(batch, 1.0f, cubeColor);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor);
|
||||
}
|
||||
|
||||
RenderableDebugableEntityItem::render(this, args);
|
||||
|
|
|
@ -23,8 +23,13 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render
|
|||
float puffedOut, glm::vec4& color) {
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f + puffedOut, color);
|
||||
|
||||
auto shapeTransform = entity->getTransformToCenter();
|
||||
if (puffedOut != 0.0f) {
|
||||
shapeTransform.postScale(1.0f + puffedOut);
|
||||
}
|
||||
batch.setModelTransform(Transform()); // we want to include the scale as well
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderWireCubeInstance(batch, shapeTransform, color);
|
||||
}
|
||||
|
||||
void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) {
|
||||
|
|
|
@ -358,8 +358,8 @@ bool RenderableModelEntityItem::needsToCallUpdate() const {
|
|||
return _needsInitialSimulation || ModelEntityItem::needsToCallUpdate();
|
||||
}
|
||||
|
||||
EntityItemProperties RenderableModelEntityItem::getProperties() const {
|
||||
EntityItemProperties properties = ModelEntityItem::getProperties(); // get the properties from our base class
|
||||
EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = ModelEntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
if (_originalTexturesRead) {
|
||||
properties.setTextureNames(_originalTextures);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
|
||||
virtual ~RenderableModelEntityItem();
|
||||
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
#include "../render-utils/simple_vert.h"
|
||||
#include "../render-utils/simple_frag.h"
|
||||
|
||||
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
|
||||
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_ENTITY_SCALE = 0.5f;
|
||||
|
||||
|
||||
EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return std::make_shared<RenderableSphereEntityItem>(entityID, properties);
|
||||
}
|
||||
|
@ -39,15 +44,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) {
|
|||
PerformanceTimer perfTimer("RenderableSphereEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Sphere);
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(getTransformToCenter()); // use a transform with scale, rotation, registration point and translation
|
||||
|
||||
// TODO: it would be cool to select different slices/stacks geometry based on the size of the sphere
|
||||
// and the distance to the viewer. This would allow us to reduce the triangle count for smaller spheres
|
||||
// that aren't close enough to see the tessellation and use larger triangle count for spheres that would
|
||||
// expose that effect
|
||||
static const int SLICES = 15, STACKS = 15;
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(getUserData()));
|
||||
_procedural->_vertexSource = simple_vert;
|
||||
|
@ -59,12 +56,19 @@ void RenderableSphereEntityItem::render(RenderArgs* args) {
|
|||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
Transform modelTransform = getTransformToCenter();
|
||||
modelTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
if (_procedural->ready()) {
|
||||
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
|
||||
_procedural->prepare(batch, getDimensions());
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch, 0.5f, SLICES, STACKS, _procedural->getColor(sphereColor));
|
||||
auto color = _procedural->getColor(sphereColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor);
|
||||
batch.setModelTransform(Transform());
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch, modelTransform, sphereColor);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
#include <GeometryCache.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
|
||||
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_ENTITY_SCALE = 0.5f;
|
||||
|
||||
EntityItemPointer RenderableZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return std::make_shared<RenderableZoneEntityItem>(entityID, properties);
|
||||
}
|
||||
|
@ -121,15 +125,15 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
|
|||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setModelTransform(getTransformToCenter());
|
||||
|
||||
batch.setModelTransform(Transform());
|
||||
|
||||
auto shapeTransform = getTransformToCenter();
|
||||
auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>();
|
||||
|
||||
if (getShapeType() == SHAPE_TYPE_SPHERE) {
|
||||
const int SLICES = 15, STACKS = 15;
|
||||
deferredLightingEffect->renderWireSphere(batch, 0.5f, SLICES, STACKS, DEFAULT_COLOR);
|
||||
shapeTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
deferredLightingEffect->renderWireSphereInstance(batch, shapeTransform, DEFAULT_COLOR);
|
||||
} else {
|
||||
deferredLightingEffect->renderWireCube(batch, 1.0f, DEFAULT_COLOR);
|
||||
deferredLightingEffect->renderWireCubeInstance(batch, shapeTransform, DEFAULT_COLOR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -32,14 +32,14 @@ AtmospherePropertyGroup::AtmospherePropertyGroup() {
|
|||
_hasStars = true;
|
||||
}
|
||||
|
||||
void AtmospherePropertyGroup::copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const {
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, Center, center);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, InnerRadius, innerRadius);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, OuterRadius, outerRadius);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, MieScattering, mieScattering);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, RayleighScattering, rayleighScattering);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, ScatteringWavelengths, scatteringWavelengths);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Atmosphere, atmosphere, HasStars, hasStars);
|
||||
void AtmospherePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const {
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_OUTER_RADIUS, Atmosphere, atmosphere, OuterRadius, outerRadius);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_MIE_SCATTERING, Atmosphere, atmosphere, MieScattering, mieScattering);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, Atmosphere, atmosphere, RayleighScattering, rayleighScattering);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, Atmosphere, atmosphere, ScatteringWavelengths, scatteringWavelengths);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ATMOSPHERE_HAS_STARS, Atmosphere, atmosphere, HasStars, hasStars);
|
||||
}
|
||||
|
||||
void AtmospherePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) {
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
virtual ~AtmospherePropertyGroup() {}
|
||||
|
||||
// EntityItemProperty related helpers
|
||||
virtual void copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const;
|
||||
virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const;
|
||||
virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings);
|
||||
virtual void debugDump() const;
|
||||
|
||||
|
|
|
@ -32,8 +32,8 @@ BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemP
|
|||
setProperties(properties);
|
||||
}
|
||||
|
||||
EntityItemProperties BoxEntityItem::getProperties() const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class
|
||||
EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
|
||||
properties._color = getXColor();
|
||||
properties._colorChanged = false;
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
|
|
25
libraries/entities/src/EntitiesScriptEngineProvider.h
Normal file
25
libraries/entities/src/EntitiesScriptEngineProvider.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// EntitiesScriptEngineProvider.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on Sept. 18, 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
|
||||
//
|
||||
// TODO: How will we handle collision callbacks with Entities
|
||||
//
|
||||
|
||||
#ifndef hifi_EntitiesScriptEngineProvider_h
|
||||
#define hifi_EntitiesScriptEngineProvider_h
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include "EntityItemID.h"
|
||||
|
||||
class EntitiesScriptEngineProvider {
|
||||
public:
|
||||
virtual void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_EntitiesScriptEngineProvider_h
|
|
@ -1019,8 +1019,10 @@ quint64 EntityItem::getExpiry() const {
|
|||
return _created + (quint64)(_lifetime * (float)USECS_PER_SECOND);
|
||||
}
|
||||
|
||||
EntityItemProperties EntityItem::getProperties() const {
|
||||
EntityItemProperties properties;
|
||||
EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EncodeBitstreamParams params; // unknown
|
||||
EntityPropertyFlags propertyFlags = desiredProperties.isEmpty() ? getEntityProperties(params) : desiredProperties;
|
||||
EntityItemProperties properties(propertyFlags);
|
||||
properties._id = getID();
|
||||
properties._idSet = true;
|
||||
properties._created = _created;
|
||||
|
|
|
@ -131,7 +131,7 @@ public:
|
|||
EntityItemID getEntityItemID() const { return EntityItemID(_id); }
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties() const;
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
|
||||
|
||||
/// returns true if something changed
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
|
|
@ -36,7 +36,7 @@ StagePropertyGroup EntityItemProperties::_staticStage;
|
|||
|
||||
EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1);
|
||||
|
||||
EntityItemProperties::EntityItemProperties() :
|
||||
EntityItemProperties::EntityItemProperties(EntityPropertyFlags desiredProperties) :
|
||||
|
||||
CONSTRUCT_PROPERTY(visible, ENTITY_ITEM_DEFAULT_VISIBLE),
|
||||
CONSTRUCT_PROPERTY(position, 0.0f),
|
||||
|
@ -140,7 +140,8 @@ _localRenderAlphaChanged(false),
|
|||
|
||||
_defaultSettings(true),
|
||||
_naturalDimensions(1.0f, 1.0f, 1.0f),
|
||||
_naturalPosition(0.0f, 0.0f, 0.0f)
|
||||
_naturalPosition(0.0f, 0.0f, 0.0f),
|
||||
_desiredProperties(desiredProperties)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -423,105 +424,162 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
EntityItemProperties defaultEntityProperties;
|
||||
|
||||
if (_idSet) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(id, _id.toString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(id, _id.toString());
|
||||
}
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(type, EntityTypes::getEntityTypeName(_type));
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(position);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(dimensions);
|
||||
if (!skipDefaults) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(naturalDimensions); // gettable, but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(naturalPosition);
|
||||
}
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(rotation);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(velocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(gravity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(acceleration);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(restitution);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(friction);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(density);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(type, EntityTypes::getEntityTypeName(_type));
|
||||
auto created = QDateTime::fromMSecsSinceEpoch(getCreated() / 1000.0f, Qt::UTC); // usec per msec
|
||||
created.setTimeSpec(Qt::OffsetFromUTC);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate));
|
||||
|
||||
if (!skipDefaults || _lifetime != defaultEntityProperties._lifetime) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
|
||||
}
|
||||
|
||||
auto created = QDateTime::fromMSecsSinceEpoch(getCreated() / 1000.0f, Qt::UTC); // usec per msec
|
||||
created.setTimeSpec(Qt::OffsetFromUTC);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(created, created.toString(Qt::ISODate));
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions);
|
||||
if (!skipDefaults) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, naturalDimensions); // gettable, but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, naturalPosition);
|
||||
}
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ROTATION, rotation);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VELOCITY, velocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAVITY, gravity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACCELERATION, acceleration);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DAMPING, damping);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RESTITUTION, restitution);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FRICTION, friction);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DENSITY, density);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFETIME, lifetime);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT, script);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_REGISTRATION_POINT, registrationPoint);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_FOR_COLLISIONS, ignoreForCollisions);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONS_WILL_MOVE, collisionsWillMove);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MARKETPLACE_ID, marketplaceID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
|
||||
|
||||
// Boxes, Spheres, Light, Line, Model(??), Particle, PolyLine
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR, color);
|
||||
|
||||
// Particles only
|
||||
if (_type == EntityTypes::ParticleEffect) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_PARTICLES, maxParticles);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFESPAN, lifespan);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_RATE, emitRate);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_VELOCITY, emitVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VELOCITY_SPREAD, velocitySpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_ACCELERATION, emitAcceleration);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACCELERATION_SPREAD, accelerationSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_RADIUS, particleRadius);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_SPREAD, radiusSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_START, radiusStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_FINISH, radiusFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_SPREAD, colorSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_START, colorStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_FINISH, colorFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish);
|
||||
}
|
||||
|
||||
// Models only
|
||||
if (_type == EntityTypes::Model) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, animationURL);
|
||||
}
|
||||
|
||||
if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString());
|
||||
}
|
||||
|
||||
// FIXME - it seems like ParticleEffect should also support this
|
||||
if (_type == EntityTypes::Model || _type == EntityTypes::Zone) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL);
|
||||
}
|
||||
|
||||
// Models & Particles
|
||||
if (_type == EntityTypes::Model || _type == EntityTypes::ParticleEffect) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_PLAYING, animationIsPlaying);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, animationFPS);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ANIMATION_SETTINGS, animationSettings, getAnimationSettings());
|
||||
}
|
||||
|
||||
// Lights only
|
||||
if (_type == EntityTypes::Light) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff);
|
||||
}
|
||||
|
||||
// Text only
|
||||
if (_type == EntityTypes::Text) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_TEXT_COLOR, textColor, getTextColor());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_COLOR, backgroundColor, getBackgroundColor());
|
||||
}
|
||||
|
||||
// Zones only
|
||||
if (_type == EntityTypes::Zone) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, keyLightColor);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, keyLightIntensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_INTENSITY, keyLightAmbientIntensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, keyLightDirection);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString());
|
||||
|
||||
_stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
_atmosphere.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
_skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
}
|
||||
|
||||
// Web only
|
||||
if (_type == EntityTypes::Web) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
|
||||
}
|
||||
|
||||
// PolyVoxel only
|
||||
if (_type == EntityTypes::PolyVox) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VOXEL_DATA, voxelData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VOXEL_SURFACE_STYLE, voxelSurfaceStyle);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_X_TEXTURE_URL, xTextureURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Y_TEXTURE_URL, yTextureURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Z_TEXTURE_URL, zTextureURL);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_X_N_NEIGHBOR_ID, xNNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Y_N_NEIGHBOR_ID, yNNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Z_N_NEIGHBOR_ID, zNNeighborID);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_X_P_NEIGHBOR_ID, xPNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Y_P_NEIGHBOR_ID, yPNeighborID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID);
|
||||
}
|
||||
|
||||
// Lines & PolyLines
|
||||
if (_type == EntityTypes::Line || _type == EntityTypes::PolyLine) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_WIDTH, lineWidth);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_POINTS, linePoints);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths);
|
||||
}
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(script);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(scriptTimestamp);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(registrationPoint);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(angularVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(angularDamping);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(visible);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(color);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(colorSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(colorStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(colorFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(alpha);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(alphaSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(alphaStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(alphaFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(compoundShapeURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(animationSettings, getAnimationSettings());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(isSpotlight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(intensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(exponent);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(cutoff);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(locked);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(textures);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(userData);
|
||||
//COPY_PROPERTY_TO_QSCRIPTVALUE(simulationOwner); // TODO: expose this for JSON saves?
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(text);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lineHeight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(textColor, getTextColor());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundColor, getBackgroundColor());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(shapeType, getShapeTypeAsString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitVelocity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(velocitySpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(emitAcceleration);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(accelerationSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(radiusSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(radiusStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(radiusFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(name);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionSoundURL);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightColor);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightIntensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightAmbientIntensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightDirection);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundMode, getBackgroundModeAsString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(sourceUrl);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(voxelVolumeSize);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(voxelData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(voxelSurfaceStyle);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(lineWidth);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(linePoints);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(href);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(description);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(faceCamera);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(actionData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(normals);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(strokeWidths);
|
||||
|
||||
// Sitting properties support
|
||||
if (!skipDefaults) {
|
||||
|
@ -534,7 +592,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
sittingPoints.setProperty(i, sittingPoint);
|
||||
}
|
||||
sittingPoints.setProperty("length", _sittingPoints.size());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(sittingPoints, sittingPoints); // gettable, but not settable
|
||||
}
|
||||
|
||||
if (!skipDefaults) {
|
||||
|
@ -556,21 +614,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesList); // gettable, but not settable
|
||||
}
|
||||
|
||||
_stage.copyToScriptValue(properties, engine, skipDefaults, defaultEntityProperties);
|
||||
_atmosphere.copyToScriptValue(properties, engine, skipDefaults, defaultEntityProperties);
|
||||
_skybox.copyToScriptValue(properties, engine, skipDefaults, defaultEntityProperties);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(xTextureURL);
|
||||
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);
|
||||
// FIXME - I don't think these properties are supported any more
|
||||
//COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
|
||||
//COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@ -709,6 +755,155 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object
|
|||
}
|
||||
|
||||
|
||||
QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) {
|
||||
return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags);
|
||||
QScriptValue result = engine->newObject();
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) {
|
||||
EntityItemProperties::entityPropertyFlagsFromScriptValue(object, flags);
|
||||
}
|
||||
|
||||
|
||||
QScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) {
|
||||
QScriptValue result = engine->newObject();
|
||||
return result;
|
||||
}
|
||||
|
||||
static QHash<QString, EntityPropertyList> _propertyStringsToEnums;
|
||||
|
||||
void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) {
|
||||
static std::once_flag initMap;
|
||||
|
||||
std::call_once(initMap, [](){
|
||||
ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ROTATION, Rotation, rotation, glm::quat);
|
||||
ADD_PROPERTY_TO_MAP(PROP_DENSITY, Density, density, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_VELOCITY, Velocity, velocity, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_GRAVITY, Gravity, gravity, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ACCELERATION, Acceleration, acceleration, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_DAMPING, Damping, damping, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_RESTITUTION, Restitution, restitution, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_FRICTION, Friction, friction, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LIFETIME, Lifetime, lifetime, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SCRIPT, Script, script, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR_START, ColorStart, colorStart, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLOR_FINISH, ColorFinish, colorFinish, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ALPHA, Alpha, alpha, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ALPHA_START, AlphaStart, alphaStart, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MODEL_URL, ModelURL, modelURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANIMATION_URL, AnimationURL, animationURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, AnimationFPS, animationFPS, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, AnimationFrameIndex, animationFrameIndex, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANIMATION_PLAYING, AnimationIsPlaying, animationIsPlaying, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_IGNORE_FOR_COLLISIONS, IgnoreForCollisions, ignoreForCollisions, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_COLLISIONS_WILL_MOVE, CollisionsWillMove, collisionsWillMove, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ANIMATION_SETTINGS, AnimationSettings, animationSettings, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_USER_DATA, UserData, userData, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner);
|
||||
ADD_PROPERTY_TO_MAP(PROP_TEXT, Text, text, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LINE_HEIGHT, LineHeight, lineHeight, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_TEXT_COLOR, TextColor, textColor, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LIFESPAN, Lifespan, lifespan, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EMIT_RATE, EmitRate, emitRate, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EMIT_VELOCITY, EmitVelocity, emitVelocity, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_VELOCITY_SPREAD, VelocitySpread, velocitySpread, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor);
|
||||
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3);
|
||||
ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray);
|
||||
ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t);
|
||||
ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>);
|
||||
ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ACTION_DATA, ActionData, actionData, QByteArray);
|
||||
ADD_PROPERTY_TO_MAP(PROP_NORMALS, Normals, normals, QVector<glm::vec3>);
|
||||
ADD_PROPERTY_TO_MAP(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>);
|
||||
ADD_PROPERTY_TO_MAP(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_X_N_NEIGHBOR_ID, XNNeighborID, xNNeighborID, EntityItemID);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Y_N_NEIGHBOR_ID, YNNeighborID, yNNeighborID, EntityItemID);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Z_N_NEIGHBOR_ID, ZNNeighborID, zNNeighborID, EntityItemID);
|
||||
ADD_PROPERTY_TO_MAP(PROP_X_P_NEIGHBOR_ID, XPNeighborID, xPNeighborID, EntityItemID);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID);
|
||||
ADD_PROPERTY_TO_MAP(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID);
|
||||
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url);
|
||||
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_CENTER, Atmosphere, atmosphere, Center, center);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_INNER_RADIUS, Atmosphere, atmosphere, InnerRadius, innerRadius);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_OUTER_RADIUS, Atmosphere, atmosphere, OuterRadius, outerRadius);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_MIE_SCATTERING, Atmosphere, atmosphere, MieScattering, mieScattering);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_RAYLEIGH_SCATTERING, Atmosphere, atmosphere, RayleighScattering, rayleighScattering);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS, Atmosphere, atmosphere, ScatteringWavelengths, scatteringWavelengths);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ATMOSPHERE_HAS_STARS, Atmosphere, atmosphere, HasStars, hasStars);
|
||||
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_SUN_MODEL_ENABLED, Stage, stage, SunModelEnabled, sunModelEnabled);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_LATITUDE, Stage, stage, Latitude, latitude);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_LONGITUDE, Stage, stage, Longitude, longitude);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_ALTITUDE, Stage, stage, Altitude, altitude);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_DAY, Stage, stage, Day, day);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_HOUR, Stage, stage, Hour, hour);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay);
|
||||
|
||||
// FIXME - these are not yet handled
|
||||
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
|
||||
|
||||
});
|
||||
|
||||
if (object.isString()) {
|
||||
if (_propertyStringsToEnums.contains(object.toString())) {
|
||||
flags << _propertyStringsToEnums[object.toString()];
|
||||
}
|
||||
} else if (object.isArray()) {
|
||||
quint32 length = object.property("length").toInt32();
|
||||
for (quint32 i = 0; i < length; i++) {
|
||||
QString propertyName = object.property(i).toString();
|
||||
if (_propertyStringsToEnums.contains(propertyName)) {
|
||||
flags << _propertyStringsToEnums[propertyName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement support for edit packets that can span an MTU sized buffer. We need to implement a mechanism for the
|
||||
// encodeEntityEditPacket() method to communicate the the caller which properties couldn't fit in the buffer. Similar
|
||||
// to how we handle this in the Octree streaming case.
|
||||
|
|
|
@ -58,7 +58,7 @@ class EntityItemProperties {
|
|||
friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
public:
|
||||
EntityItemProperties();
|
||||
EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags());
|
||||
virtual ~EntityItemProperties();
|
||||
|
||||
EntityTypes::EntityType getType() const { return _type; }
|
||||
|
@ -67,6 +67,9 @@ public:
|
|||
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults) const;
|
||||
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
|
||||
|
||||
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
|
||||
static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags);
|
||||
|
||||
// editing related features supported by all entities
|
||||
quint64 getLastEdited() const { return _lastEdited; }
|
||||
float getEditedAgo() const /// Elapsed seconds since this entity was last edited
|
||||
|
@ -259,13 +262,19 @@ private:
|
|||
QStringList _textureNames;
|
||||
glm::vec3 _naturalDimensions;
|
||||
glm::vec3 _naturalPosition;
|
||||
|
||||
EntityPropertyFlags _desiredProperties; // if set will narrow scopes of copy/to/from to just these properties
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(EntityItemProperties);
|
||||
QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties);
|
||||
QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties);
|
||||
void EntityItemPropertiesFromScriptValueIgnoreReadOnly(const QScriptValue &object, EntityItemProperties& properties);
|
||||
void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object, EntityItemProperties& properties);
|
||||
void EntityItemPropertiesFromScriptValueIgnoreReadOnly(const QScriptValue& object, EntityItemProperties& properties);
|
||||
void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue& object, EntityItemProperties& properties);
|
||||
|
||||
Q_DECLARE_METATYPE(EntityPropertyFlags);
|
||||
QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
|
||||
void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags);
|
||||
|
||||
|
||||
// define these inline here so the macros work
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue