Merge branch 'master' into ajt/new-anim-system

This commit is contained in:
Anthony J. Thibault 2015-09-03 19:20:06 -07:00
commit 3716d5612b
53 changed files with 2012 additions and 1312 deletions

View file

@ -295,7 +295,8 @@ void AvatarMixer::broadcastAvatarData() {
avatarPacketList.startSegment(); avatarPacketList.startSegment();
numAvatarDataBytes += avatarPacketList.write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList.write(otherNode->getUUID().toRfc4122());
numAvatarDataBytes += avatarPacketList.write(otherAvatar.toByteArray(false)); numAvatarDataBytes +=
avatarPacketList.write(otherAvatar.toByteArray(false, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO));
avatarPacketList.endSegment(); avatarPacketList.endSegment();
@ -364,6 +365,31 @@ void AvatarMixer::broadcastAvatarData() {
} }
); );
// We're done encoding this version of the otherAvatars. Update their "lastSent" joint-states so
// that we can notice differences, next time around.
nodeList->eachMatchingNode(
[&](const SharedNodePointer& otherNode)->bool {
if (!otherNode->getLinkedData()) {
return false;
}
if (otherNode->getType() != NodeType::Agent) {
return false;
}
if (!otherNode->getActiveSocket()) {
return false;
}
return true;
},
[&](const SharedNodePointer& otherNode) {
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
MutexTryLocker lock(otherNodeData->getMutex());
if (!lock.isLocked()) {
return;
}
AvatarData& otherAvatar = otherNodeData->getAvatar();
otherAvatar.doneEncoding(false);
});
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch(); _lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
} }

View file

@ -1,307 +1,346 @@
//
// hydraGrab.js // hydraGrab.js
// examples // examples
// //
// Created by Clément Brisset on 4/24/14. // Created by Eric Levin on 9/2/15
// Updated by Eric Levin on 5/14/15. // Copyright 2015 High Fidelity, Inc.
// Copyright 2014 High Fidelity, Inc.
// //
// This script allows you to grab and move/rotate physical objects with the hydra // Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller.
//
// Using the hydras :
// grab physical entities with the right trigger
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity; Script.include("../../libraries/utils.js");
var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance;
var LEFT = 0;
var RIGHT = 1;
var LASER_WIDTH = 3;
var LASER_COLOR = {
red: 50,
green: 150,
blue: 200
};
var LASER_HOVER_COLOR = {
red: 200,
green: 50,
blue: 50
};
var DROP_DISTANCE = 5.0; var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
var DROP_COLOR = { var rightTriggerAction = RIGHT_HAND_CLICK;
red: 200,
green: 200,
blue: 200
};
var FULL_STRENGTH = 0.05; var GRAB_USER_DATA_KEY = "grabKey";
var LASER_LENGTH_FACTOR = 500;
var CLOSE_ENOUGH = 0.001;
var SPRING_RATE = 1.5;
var DAMPING_RATE = 0.8;
var SCREEN_TO_METERS = 0.001;
var DISTANCE_SCALE_FACTOR = 1000
var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); var leftTriggerAction = LEFT_HAND_CLICK;
function getRayIntersection(pickRay) { var ZERO_VEC = {
var intersection = Entities.findRayIntersection(pickRay, true);
return intersection;
}
function controller(side) {
this.triggerHeld = false;
this.triggerThreshold = 0.9;
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.trigger = side;
this.originalGravity = {
x: 0, x: 0,
y: 0, y: 0,
z: 0 z: 0
}; }
var LINE_LENGTH = 500;
var THICK_LINE_WIDTH = 7;
var THIN_LINE_WIDTH = 2;
this.laser = Overlays.addOverlay("line3d", { var NO_INTERSECT_COLOR = {
start: { red: 10,
x: 0, green: 10,
y: 0, blue: 255
z: 0 };
}, var INTERSECT_COLOR = {
end: { red: 250,
x: 0, green: 10,
y: 0, blue: 10
z: 0 };
},
color: LASER_COLOR,
alpha: 1,
lineWidth: LASER_WIDTH,
anchor: "MyAvatar"
});
this.dropLine = Overlays.addOverlay("line3d", { var GRAB_RADIUS = 0.5;
color: DROP_COLOR,
alpha: 1,
visible: false,
lineWidth: 2
});
var GRAB_COLOR = {
red: 250,
green: 10,
blue: 250
};
var SHOW_LINE_THRESHOLD = 0.2;
var DISTANCE_HOLD_THRESHOLD = 0.8;
this.update = function(deltaTime) { var right4Action = 18;
this.updateControllerState(); var left4Action = 17;
this.moveLaser();
this.checkTrigger();
this.checkEntityIntersection();
if (this.grabbing) {
this.updateEntity(deltaTime);
}
this.oldPalmPosition = this.palmPosition; var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
this.oldTipPosition = this.tipPosition;
}
this.updateEntity = function(deltaTime) { var RIGHT = 1;
this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); var LEFT = 0;
this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right")
this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left")
this.entityProps = Entities.getEntityProperties(this.grabbedEntity); function controller(side, triggerAction, pullAction, hand) {
this.currentPosition = this.entityProps.position; this.hand = hand;
this.currentVelocity = this.entityProps.velocity; if (hand === "right") {
this.getHandPosition = MyAvatar.getRightPalmPosition;
var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); this.getHandRotation = MyAvatar.getRightPalmRotation;
this.distanceToTarget = Vec3.length(dPosition);
if (this.distanceToTarget > CLOSE_ENOUGH) {
// compute current velocity in the direction we want to move
this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition));
this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget);
// compute the speed we would like to be going toward the target position
this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
// compute how much we want to add to the existing velocity
this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget);
//If target is to far, roll off force as inverse square of distance
if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) {
this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0));
}
this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity);
this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE));
} else { } else {
this.newVelocity = {
x: 0,
y: 0,
z: 0
};
}
this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip);
this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity);
Entities.editEntity(this.grabbedEntity, { this.getHandPosition = MyAvatar.getLeftPalmPosition;
velocity: this.newVelocity, this.getHandRotation = MyAvatar.getLeftPalmRotation;
angularVelocity: this.transformedAngularVelocity }
this.triggerAction = triggerAction;
this.pullAction = pullAction;
this.actionID = null;
this.tractorBeamActive = false;
this.distanceHolding = false;
this.closeGrabbing = false;
this.triggerValue = 0;
this.prevTriggerValue = 0;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.pointer = Entities.addEntity({
type: "Line",
name: "pointer",
color: NO_INTERSECT_COLOR,
dimensions: {
x: 1000,
y: 1000,
z: 1000
},
visible: false,
});
}
controller.prototype.updateLine = function() {
var handPosition = Controller.getSpatialControlPosition(this.palm);
var direction = Controller.getSpatialControlNormal(this.tip);
Entities.editEntity(this.pointer, {
position: handPosition,
linePoints: [
ZERO_VEC,
Vec3.multiply(direction, LINE_LENGTH)
]
}); });
this.updateDropLine(this.targetPosition); //only check if we havent already grabbed an object
if (this.distanceHolding) {
} return;
this.updateControllerState = function() {
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.triggerValue = Controller.getTriggerValue(this.trigger);
}
this.checkTrigger = function() {
if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) {
this.triggerHeld = true;
} else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) {
this.triggerHeld = false;
if (this.grabbing) {
this.release();
}
} }
}
//move origin a bit away from hand so nothing gets in way
var origin = Vec3.sum(handPosition, direction);
if (this.checkForIntersections(origin, direction)) {
Entities.editEntity(this.pointer, {
color: INTERSECT_COLOR,
});
} else {
Entities.editEntity(this.pointer, {
color: NO_INTERSECT_COLOR,
});
}
}
this.updateDropLine = function(position) {
Overlays.editOverlay(this.dropLine, {
visible: true,
start: {
x: position.x,
y: position.y + DROP_DISTANCE,
z: position.z
},
end: {
x: position.x,
y: position.y - DROP_DISTANCE,
z: position.z
}
});
}
this.checkEntityIntersection = function() {
controller.prototype.checkForIntersections = function(origin, direction) {
var pickRay = { var pickRay = {
origin: this.palmPosition, origin: origin,
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) direction: direction
}; };
var intersection = getRayIntersection(pickRay, true);
if (intersection.intersects && intersection.properties.collisionsWillMove) { var intersection = Entities.findRayIntersection(pickRay, true);
this.laserWasHovered = true; if (intersection.intersects && intersection.properties.collisionsWillMove === 1) {
if (this.triggerHeld && !this.grabbing) { this.distanceToEntity = Vec3.distance(origin, intersection.properties.position);
this.grab(intersection.entityID); Entities.editEntity(this.pointer, {
} linePoints: [
Overlays.editOverlay(this.laser, { ZERO_VEC,
color: LASER_HOVER_COLOR Vec3.multiply(direction, this.distanceToEntity)
}); ]
} else if (this.laserWasHovered) { });
this.laserWasHovered = false; this.grabbedEntity = intersection.entityID;
Overlays.editOverlay(this.laser, { return true;
color: LASER_COLOR
});
} }
} return false;
}
this.grab = function(entityId) { controller.prototype.attemptMove = function() {
this.grabbing = true; if (this.tractorBeamActive) {
this.grabbedEntity = entityId; return;
this.entityProps = Entities.getEntityProperties(this.grabbedEntity); }
this.targetPosition = this.entityProps.position; if (this.grabbedEntity || this.distanceHolding) {
this.currentPosition = this.targetPosition; var handPosition = Controller.getSpatialControlPosition(this.palm);
this.oldPalmPosition = this.palmPosition; var direction = Controller.getSpatialControlNormal(this.tip);
this.originalGravity = this.entityProps.gravity;
Entities.editEntity(this.grabbedEntity, {
gravity: {
x: 0,
y: 0,
z: 0
}
});
Overlays.editOverlay(this.laser, {
visible: false
});
Audio.playSound(grabSound, {
position: this.entityProps.position,
volume: 0.25
});
}
this.release = function() { var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity))
this.grabbing = false; this.distanceHolding = true;
if (this.actionID === null) {
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
targetPosition: newPosition,
linearTimeScale: .1
});
} else {
Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: newPosition
});
}
}
}
controller.prototype.showPointer = function() {
Entities.editEntity(this.pointer, {
visible: true
});
}
controller.prototype.hidePointer = function() {
Entities.editEntity(this.pointer, {
visible: false
});
}
controller.prototype.letGo = function() {
if (this.grabbedEntity && this.actionID) {
this.deactivateEntity(this.grabbedEntity);
Entities.deleteAction(this.grabbedEntity, this.actionID);
}
this.grabbedEntity = null; this.grabbedEntity = null;
Overlays.editOverlay(this.laser, { this.actionID = null;
visible: true this.distanceHolding = false;
}); this.tractorBeamActive = false;
Overlays.editOverlay(this.dropLine, { this.checkForEntityArrival = false;
visible: false this.closeGrabbing = false;
}); }
Audio.playSound(releaseSound, { controller.prototype.update = function() {
position: this.entityProps.position, if (this.tractorBeamActive && this.checkForEntityArrival) {
volume: 0.25 var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity
}); if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) {
this.letGo();
// only restore the original gravity if it's not zero. This is to avoid... }
// 1. interface A grabs an entity and locally saves off its gravity return;
// 2. interface A sets the entity's gravity to zero }
// 3. interface B grabs the entity and saves off its gravity (which is zero) this.triggerValue = Controller.getActionValue(this.triggerAction);
// 4. interface A releases the entity and puts the original gravity back if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
// 5. interface B releases the entity and puts the original gravity back (to zero) //First check if an object is within close range and then run the close grabbing logic
if(vectorIsZero(this.originalGravity)) { if (this.checkForInRangeObject()) {
Entities.editEntity(this.grabbedEntity, { this.grabEntity();
gravity: this.originalGravity } else {
}); this.showPointer();
this.shouldDisplayLine = true;
}
} else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) {
this.hidePointer();
this.letGo();
this.shouldDisplayLine = false;
} }
}
this.moveLaser = function() { if (this.shouldDisplayLine) {
var inverseRotation = Quat.inverse(MyAvatar.orientation); this.updateLine();
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); }
// startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) {
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); this.attemptMove();
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); }
var endPosition = Vec3.sum(startPosition, direction);
Overlays.editOverlay(this.laser, {
start: startPosition, this.prevTriggerValue = this.triggerValue;
end: endPosition }
controller.prototype.grabEntity = function() {
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
var offset = Vec3.subtract(objectPosition, handPosition);
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
this.closeGrabbing = true;
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
relativePosition: offsetPosition,
relativeRotation: offsetRotation,
hand: this.hand,
timeScale: 0.05
}); });
}
this.cleanup = function() {
Overlays.deleteOverlay(this.laser);
Overlays.deleteOverlay(this.dropLine);
}
} }
function update(deltaTime) {
rightController.update(deltaTime); controller.prototype.checkForInRangeObject = function() {
leftController.update(deltaTime); var handPosition = Controller.getSpatialControlPosition(this.palm);
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = GRAB_RADIUS;
var grabbedEntity = null;
//Get nearby entities and assign nearest
for (var i = 0; i < entities.length; i++) {
var props = Entities.getEntityProperties(entities[i]);
var distance = Vec3.distance(props.position, handPosition);
if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) {
grabbedEntity = entities[i];
minDistance = distance;
}
}
if (grabbedEntity === null) {
return false;
} else {
//We are grabbing an entity, so let it know we've grabbed it
this.grabbedEntity = grabbedEntity;
this.activateEntity(this.grabbedEntity);
return true;
}
} }
function scriptEnding() { controller.prototype.activateEntity = function(entity) {
rightController.cleanup(); var data = {
leftController.cleanup(); activated: true,
avatarId: MyAvatar.sessionUUID
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
} }
function vectorIsZero(v) { controller.prototype.deactivateEntity = function(entity) {
return v.x === 0 && v.y === 0 && v.z === 0; var data = {
activated: false,
avatarId: null
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
} }
var rightController = new controller(RIGHT); controller.prototype.onActionEvent = function(action, state) {
var leftController = new controller(LEFT); if (this.pullAction === action && state === 1) {
if (this.actionID !== null) {
var self = this;
this.tractorBeamActive = true;
//We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some
//low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving!
Script.setTimeout(function() {
self.checkForEntityArrival = true;
}, 500);
var handPosition = Controller.getSpatialControlPosition(this.palm);
var direction = Controller.getSpatialControlNormal(this.tip);
//move final destination along line a bit, so it doesnt hit avatar hand
Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction))
});
}
}
}
controller.prototype.cleanup = function() {
Entities.deleteEntity(this.pointer);
if (this.grabbedEntity) {
Entities.deleteAction(this.grabbedEntity, this.actionID);
}
}
function update() {
rightController.update();
leftController.update();
}
function onActionEvent(action, state) {
rightController.onActionEvent(action, state);
leftController.onActionEvent(action, state);
}
Script.update.connect(update); function cleanup() {
Script.scriptEnding.connect(scriptEnding); rightController.cleanup();
leftController.cleanup();
}
Script.scriptEnding.connect(cleanup);
Script.update.connect(update)
Controller.actionEvent.connect(onActionEvent);

View file

@ -13,32 +13,7 @@ Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.j
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var nullActionID = "00000000-0000-0000-0000-000000000000";
var controllerID;
var controllerActive;
var leftHandObjectID = null;
var rightHandObjectID = null;
var leftHandActionID = nullActionID;
var rightHandActionID = nullActionID;
var TRIGGER_THRESHOLD = 0.2;
var GRAB_RADIUS = 0.15;
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
var ACTION1 = Controller.findAction("ACTION1");
var ACTION2 = Controller.findAction("ACTION2");
var rightHandGrabAction = RIGHT_HAND_CLICK;
var leftHandGrabAction = LEFT_HAND_CLICK;
var rightHandGrabValue = 0;
var leftHandGrabValue = 0;
var prevRightHandGrabValue = 0
var prevLeftHandGrabValue = 0;
var grabColor = { red: 0, green: 255, blue: 0};
var releaseColor = { red: 0, green: 0, blue: 255};
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() { var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() {
return { return {
@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", {
alpha: 1 alpha: 1
}); });
var overlays = false;
var leftHandOverlay;
var rightHandOverlay;
if (overlays) {
leftHandOverlay = Overlays.addOverlay("sphere", {
position: MyAvatar.getLeftPalmPosition(),
size: GRAB_RADIUS,
color: releaseColor,
alpha: 0.5,
solid: false
});
rightHandOverlay = Overlays.addOverlay("sphere", {
position: MyAvatar.getRightPalmPosition(),
size: GRAB_RADIUS,
color: releaseColor,
alpha: 0.5,
solid: false
});
}
var OBJECT_HEIGHT_OFFSET = 0.5; var OBJECT_HEIGHT_OFFSET = 0.5;
var MIN_OBJECT_SIZE = 0.05; var MIN_OBJECT_SIZE = 0.05;
@ -98,8 +54,6 @@ var GRAVITY = {
z: 0.0 z: 0.0
} }
var LEFT = 0;
var RIGHT = 1;
var tableCreated = false; var tableCreated = false;
@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table
var VELOCITY_MAG = 0.3; var VELOCITY_MAG = 0.3;
var entitiesToResize = [];
var MODELS = Array( var MODELS = Array(
{ modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" }, { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" },
@ -136,196 +89,15 @@ var COLLISION_SOUNDS = Array(
var RESIZE_TIMER = 0.0; var RESIZE_TIMER = 0.0;
var RESIZE_WAIT = 0.05; // 50 milliseconds var RESIZE_WAIT = 0.05; // 50 milliseconds
var leftFist = Entities.addEntity( {
type: "Sphere",
shapeType: 'sphere',
position: MyAvatar.getLeftPalmPosition(),
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
rotation: MyAvatar.getLeftPalmRotation(),
visible: false,
collisionsWillMove: false,
ignoreForCollisions: true
});
var rightFist = Entities.addEntity( {
type: "Sphere",
shapeType: 'sphere',
position: MyAvatar.getRightPalmPosition(),
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
rotation: MyAvatar.getRightPalmRotation(),
visible: false,
collisionsWillMove: false,
ignoreForCollisions: true
});
function letGo(hand) {
var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID;
var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity();
var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() :
MyAvatar.getRightPalmAngularVelocity();
if (actionIDToRemove != nullActionID && entityIDToEdit != null) {
Entities.deleteAction(entityIDToEdit, actionIDToRemove);
// TODO: upon successful letGo, restore collision groups
if (hand == LEFT) {
leftHandObjectID = null;
leftHandActionID = nullActionID;
} else {
rightHandObjectID = null;
rightHandActionID = nullActionID;
}
}
}
function setGrabbedObject(hand) {
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
var objectID = null;
var minDistance = GRAB_RADIUS;
for (var i = 0; i < entities.length; i++) {
// Don't grab the object in your other hands, your fists, or the table
if ((hand == LEFT && entities[i] == rightHandObjectID) ||
(hand == RIGHT && entities[i] == leftHandObjectID) ||
entities[i] == leftFist || entities[i] == rightFist ||
(tableCreated && entities[i] == tableEntities[0])) {
continue;
} else {
var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition);
if (distance <= minDistance) {
objectID = entities[i];
minDistance = distance;
}
}
}
if (objectID == null) {
return false;
}
if (hand == LEFT) {
leftHandObjectID = objectID;
} else {
rightHandObjectID = objectID;
}
return true;
}
function grab(hand) {
if (!setGrabbedObject(hand)) {
// If you don't grab an object, make a fist
Entities.editEntity((hand == LEFT) ? leftFist : rightFist, { ignoreForCollisions: false } );
return;
}
var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation();
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
var objectRotation = Entities.getEntityProperties(objectID).rotation;
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
var objectPosition = Entities.getEntityProperties(objectID).position;
var offset = Vec3.subtract(objectPosition, handPosition);
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
// print(JSON.stringify(offsetPosition));
var actionID = Entities.addAction("hold", objectID, {
relativePosition: { x: 0, y: 0, z: 0 },
relativeRotation: offsetRotation,
hand: (hand == LEFT) ? "left" : "right",
timeScale: 0.05
});
if (actionID == nullActionID) {
if (hand == LEFT) {
leftHandObjectID = null;
} else {
rightHandObjectID = null;
}
} else {
// TODO: upon successful grab, add to collision group so object doesn't collide with immovable entities
if (hand == LEFT) {
leftHandActionID = actionID;
} else {
rightHandActionID = actionID;
}
}
}
function resizeModels() {
var newEntitiesToResize = [];
for (var i = 0; i < entitiesToResize.length; i++) {
var naturalDimensions = Entities.getEntityProperties(entitiesToResize[i]).naturalDimensions;
if (naturalDimensions.x != 1.0 || naturalDimensions.y != 1.0 || naturalDimensions.z != 1.0) {
// bigger range of sizes for models
var dimensions = Vec3.multiply(randFloat(MIN_OBJECT_SIZE, 3.0*MAX_OBJECT_SIZE), Vec3.normalize(naturalDimensions));
Entities.editEntity(entitiesToResize[i], {
dimensions: dimensions,
shapeType: "box"
});
} else {
newEntitiesToResize.push(entitiesToResize[i]);
}
}
entitiesToResize = newEntitiesToResize;
}
function update(deltaTime) {
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() });
Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() });
}
// if (tableCreated && RESIZE_TIMER < RESIZE_WAIT) {
// RESIZE_TIMER += deltaTime;
// } else if (tableCreated) {
// resizeModels();
// }
rightHandGrabValue = Controller.getActionValue(rightHandGrabAction);
leftHandGrabValue = Controller.getActionValue(leftHandGrabAction);
Entities.editEntity(leftFist, { position: MyAvatar.getLeftPalmPosition() });
Entities.editEntity(rightFist, { position: MyAvatar.getRightPalmPosition() });
if (rightHandGrabValue > TRIGGER_THRESHOLD &&
prevRightHandGrabValue < TRIGGER_THRESHOLD) {
if (overlays) {
Overlays.editOverlay(rightHandOverlay, { color: grabColor });
}
grab(RIGHT);
} else if (rightHandGrabValue < TRIGGER_THRESHOLD &&
prevRightHandGrabValue > TRIGGER_THRESHOLD) {
Entities.editEntity(rightFist, { ignoreForCollisions: true } );
if (overlays) {
Overlays.editOverlay(rightHandOverlay, { color: releaseColor });
}
letGo(RIGHT);
}
if (leftHandGrabValue > TRIGGER_THRESHOLD &&
prevLeftHandGrabValue < TRIGGER_THRESHOLD) {
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { color: grabColor });
}
grab(LEFT);
} else if (leftHandGrabValue < TRIGGER_THRESHOLD &&
prevLeftHandGrabValue > TRIGGER_THRESHOLD) {
Entities.editEntity(leftFist, { ignoreForCollisions: true } );
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { color: releaseColor });
}
letGo(LEFT);
}
prevRightHandGrabValue = rightHandGrabValue;
prevLeftHandGrabValue = leftHandGrabValue;
}
function cleanUp() { function cleanUp() {
letGo(RIGHT); print("CLEANUP!!!")
letGo(LEFT);
if (overlays) { if (overlays) {
Overlays.deleteOverlay(leftHandOverlay); Overlays.deleteOverlay(leftHandOverlay);
Overlays.deleteOverlay(rightHandOverlay); Overlays.deleteOverlay(rightHandOverlay);
} }
Entities.deleteEntity(leftFist);
Entities.deleteEntity(rightFist);
removeTable(); removeTable();
toolBar.cleanup(); toolBar.cleanup();
} }
@ -405,7 +177,6 @@ function createTable() {
density: 0.5, density: 0.5,
collisionsWillMove: true, collisionsWillMove: true,
color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) }, color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) },
// collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)]
}); });
if (type == "Model") { if (type == "Model") {
var randModel = randInt(0, MODELS.length); var randModel = randInt(0, MODELS.length);
@ -413,7 +184,6 @@ function createTable() {
shapeType: "box", shapeType: "box",
modelURL: MODELS[randModel].modelURL modelURL: MODELS[randModel].modelURL
}); });
entitiesToResize.push(tableEntities[i]);
} }
} }
} }
@ -426,5 +196,4 @@ function removeTable() {
} }
Script.scriptEnding.connect(cleanUp); Script.scriptEnding.connect(cleanUp);
Script.update.connect(update);
Controller.mousePressEvent.connect(onClick); Controller.mousePressEvent.connect(onClick);

View file

@ -0,0 +1,63 @@
//
// Created by Bradley Austin Davis on 2015/08/27
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,
green: 255,
blue: 255,
},
BLACK: {
red: 0,
green: 0,
blue: 0,
},
GREY: {
red: 128,
green: 128,
blue: 128,
},
RED: {
red: 255,
green: 0,
blue: 0
},
BLUE: {
red: 0,
green: 0,
blue: 255
},
GREEN: {
red: 0,
green: 255,
blue: 0
},
CYAN: {
red: 0,
green: 255,
blue: 255
},
YELLOW: {
red: 255,
green: 255,
blue: 0
},
MAGENTA: {
red: 255,
green: 0,
blue: 255
}
}

View file

@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) {
}); });
} }
Highlighter.prototype.setRotation = function(newRotation) {
Overlays.editOverlay(this.highlightCube, {
rotation: newRotation
});
}
Highlighter.prototype.updateHighlight = function() { Highlighter.prototype.updateHighlight = function() {
if (this.hightlighted) { if (this.hightlighted) {
var properties = Entities.getEntityProperties(this.hightlighted); var properties = Entities.getEntityProperties(this.hightlighted);

View file

@ -0,0 +1,145 @@
HTML_COLORS = {
AliceBlue: "#F0F8FF",
AntiqueWhite: "#FAEBD7",
Aqua: "#00FFFF",
Aquamarine: "#7FFFD4",
Azure: "#F0FFFF",
Beige: "#F5F5DC",
Bisque: "#FFE4C4",
Black: "#000000",
BlanchedAlmond: "#FFEBCD",
Blue: "#0000FF",
BlueViolet: "#8A2BE2",
Brown: "#A52A2A",
BurlyWood: "#DEB887",
CadetBlue: "#5F9EA0",
Chartreuse: "#7FFF00",
Chocolate: "#D2691E",
Coral: "#FF7F50",
CornflowerBlue: "#6495ED",
Cornsilk: "#FFF8DC",
Crimson: "#DC143C",
Cyan: "#00FFFF",
DarkBlue: "#00008B",
DarkCyan: "#008B8B",
DarkGoldenRod: "#B8860B",
DarkGray: "#A9A9A9",
DarkGreen: "#006400",
DarkKhaki: "#BDB76B",
DarkMagenta: "#8B008B",
DarkOliveGreen: "#556B2F",
DarkOrange: "#FF8C00",
DarkOrchid: "#9932CC",
DarkRed: "#8B0000",
DarkSalmon: "#E9967A",
DarkSeaGreen: "#8FBC8F",
DarkSlateBlue: "#483D8B",
DarkSlateGray: "#2F4F4F",
DarkTurquoise: "#00CED1",
DarkViolet: "#9400D3",
DeepPink: "#FF1493",
DeepSkyBlue: "#00BFFF",
DimGray: "#696969",
DodgerBlue: "#1E90FF",
FireBrick: "#B22222",
FloralWhite: "#FFFAF0",
ForestGreen: "#228B22",
Fuchsia: "#FF00FF",
Gainsboro: "#DCDCDC",
GhostWhite: "#F8F8FF",
Gold: "#FFD700",
GoldenRod: "#DAA520",
Gray: "#808080",
Green: "#008000",
GreenYellow: "#ADFF2F",
HoneyDew: "#F0FFF0",
HotPink: "#FF69B4",
IndianRed: "#CD5C5C",
Indigo: "#4B0082",
Ivory: "#FFFFF0",
Khaki: "#F0E68C",
Lavender: "#E6E6FA",
LavenderBlush: "#FFF0F5",
LawnGreen: "#7CFC00",
LemonChiffon: "#FFFACD",
LightBlue: "#ADD8E6",
LightCoral: "#F08080",
LightCyan: "#E0FFFF",
LightGoldenRodYellow: "#FAFAD2",
LightGray: "#D3D3D3",
LightGreen: "#90EE90",
LightPink: "#FFB6C1",
LightSalmon: "#FFA07A",
LightSeaGreen: "#20B2AA",
LightSkyBlue: "#87CEFA",
LightSlateGray: "#778899",
LightSteelBlue: "#B0C4DE",
LightYellow: "#FFFFE0",
Lime: "#00FF00",
LimeGreen: "#32CD32",
Linen: "#FAF0E6",
Magenta: "#FF00FF",
Maroon: "#800000",
MediumAquaMarine: "#66CDAA",
MediumBlue: "#0000CD",
MediumOrchid: "#BA55D3",
MediumPurple: "#9370DB",
MediumSeaGreen: "#3CB371",
MediumSlateBlue: "#7B68EE",
MediumSpringGreen: "#00FA9A",
MediumTurquoise: "#48D1CC",
MediumVioletRed: "#C71585",
MidnightBlue: "#191970",
MintCream: "#F5FFFA",
MistyRose: "#FFE4E1",
Moccasin: "#FFE4B5",
NavajoWhite: "#FFDEAD",
Navy: "#000080",
OldLace: "#FDF5E6",
Olive: "#808000",
OliveDrab: "#6B8E23",
Orange: "#FFA500",
OrangeRed: "#FF4500",
Orchid: "#DA70D6",
PaleGoldenRod: "#EEE8AA",
PaleGreen: "#98FB98",
PaleTurquoise: "#AFEEEE",
PaleVioletRed: "#DB7093",
PapayaWhip: "#FFEFD5",
PeachPuff: "#FFDAB9",
Peru: "#CD853F",
Pink: "#FFC0CB",
Plum: "#DDA0DD",
PowderBlue: "#B0E0E6",
Purple: "#800080",
RebeccaPurple: "#663399",
Red: "#FF0000",
RosyBrown: "#BC8F8F",
RoyalBlue: "#4169E1",
SaddleBrown: "#8B4513",
Salmon: "#FA8072",
SandyBrown: "#F4A460",
SeaGreen: "#2E8B57",
SeaShell: "#FFF5EE",
Sienna: "#A0522D",
Silver: "#C0C0C0",
SkyBlue: "#87CEEB",
SlateBlue: "#6A5ACD",
SlateGray: "#708090",
Snow: "#FFFAFA",
SpringGreen: "#00FF7F",
SteelBlue: "#4682B4",
Tan: "#D2B48C",
Teal: "#008080",
Thistle: "#D8BFD8",
Tomato: "#FF6347",
Turquoise: "#40E0D0",
Violet: "#EE82EE",
Wheat: "#F5DEB3",
White: "#FFFFFF",
WhiteSmoke: "#F5F5F5",
Yellow: "#FFFF00",
YellowGreen: "#9ACD32",
}

View file

@ -0,0 +1,308 @@
//
// Created by Bradley Austin Davis on 2015/09/01
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("constants.js");
Script.include("utils.js");
Script.include("highlighter.js");
Script.include("omniTool/models/modelBase.js");
Script.include("omniTool/models/wand.js");
OmniToolModules = {};
OmniToolModuleType = null;
OmniTool = function(side) {
this.OMNI_KEY = "OmniTool";
this.MAX_FRAMERATE = 30;
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.SIDE = side;
this.PALM = 2 * side;
this.ACTION = findAction(side ? "ACTION2" : "ACTION1");
this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2");
this.highlighter = new Highlighter();
this.ignoreEntities = {};
this.nearestOmniEntity = {
id: null,
inside: false,
position: null,
distance: Infinity,
radius: 0,
omniProperties: {},
boundingBox: null,
};
this.activeOmniEntityId = null;
this.lastUpdateInterval = 0;
this.tipLength = 0.4;
this.active = false;
this.module = null;
this.moduleEntityId = null;
this.lastScanPosition = ZERO_VECTOR;
this.model = new Wand();
this.model.setLength(this.tipLength);
// Connect to desired events
var _this = this;
Controller.actionEvent.connect(function(action, state) {
_this.onActionEvent(action, state);
});
Script.update.connect(function(deltaTime) {
_this.lastUpdateInterval += deltaTime;
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
_this.onUpdate(_this.lastUpdateInterval);
_this.lastUpdateInterval = 0;
}
});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
}
OmniTool.prototype.onCleanup = function(action) {
this.unloadModule();
}
OmniTool.prototype.onActionEvent = function(action, state) {
// FIXME figure out the issues when only one spatial controller is active
// logDebug("Action: " + action + " " + state);
if (this.module && this.module.onActionEvent) {
this.module.onActionEvent(action, state);
}
if (action == this.ACTION) {
if (state) {
this.onClick();
} else {
this.onRelease();
}
}
// FIXME Does not work
//// with only one controller active (listed as 2 here because 'tip' + 'palm')
//// then treat the alt action button as the action button
}
OmniTool.prototype.getOmniToolData = function(entityId) {
return getEntityCustomData(this.OMNI_KEY, entityId, null);
}
OmniTool.prototype.setOmniToolData = function(entityId, data) {
setEntityCustomData(this.OMNI_KEY, entityId, data);
}
OmniTool.prototype.updateOmniToolData = function(entityId, data) {
var currentData = this.getOmniToolData(entityId) || {};
for (var key in data) {
currentData[key] = data[key];
}
setEntityCustomData(this.OMNI_KEY, entityId, currentData);
}
OmniTool.prototype.setActive = function(active) {
if (active === this.active) {
return;
}
logDebug("omnitool changing active state: " + active);
this.active = active;
this.model.setVisible(this.active);
if (this.module && this.module.onActiveChanged) {
this.module.onActiveChanged(this.side);
}
}
OmniTool.prototype.onUpdate = function(deltaTime) {
// FIXME this returns data if either the left or right controller is not on the base
this.position = Controller.getSpatialControlPosition(this.PALM);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.position) > 0.001);
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
this.model.setTransform({
rotation: this.rotation,
position: this.position,
});
this.scan();
if (this.module && this.module.onUpdate) {
this.module.onUpdate(deltaTime);
}
}
OmniTool.prototype.onClick = function() {
// First check to see if the user is switching to a new omni module
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
this.activateNewOmniModule();
return;
}
// Next check if there is an active module and if so propagate the click
// FIXME how to I switch to a new module?
if (this.module && this.module.onClick) {
this.module.onClick();
return;
}
}
OmniTool.prototype.onRelease = function() {
// FIXME how to I switch to a new module?
if (this.module && this.module.onRelease) {
this.module.onRelease();
return;
}
logDebug("Base omnitool does nothing on release");
}
// FIXME resturn a structure of all nearby entities to distances
OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) {
if (!maxDistance) {
maxDistance = 2.0;
}
var resultDistance = Infinity;
var resultId = null;
var resultProperties = null;
var resultOmniData = null;
var ids = Entities.findEntities(this.model.tipPosition, maxDistance);
for (var i in ids) {
var entityId = ids[i];
if (this.ignoreEntities[entityId]) {
continue;
}
var omniData = this.getOmniToolData(entityId);
if (!omniData) {
// FIXME find a place to flush this information
this.ignoreEntities[entityId] = true;
continue;
}
// Let searchers query specifically
if (selector && !selector(entityId, omniData)) {
continue;
}
var properties = Entities.getEntityProperties(entityId);
var distance = Vec3.distance(this.model.tipPosition, properties.position);
if (distance < resultDistance) {
resultDistance = distance;
resultId = entityId;
}
}
return resultId;
}
OmniTool.prototype.getPosition = function() {
return this.model.tipPosition;
}
OmniTool.prototype.onEnterNearestOmniEntity = function() {
this.nearestOmniEntity.inside = true;
this.highlighter.highlight(this.nearestOmniEntity.id);
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
}
OmniTool.prototype.onLeaveNearestOmniEntity = function() {
this.nearestOmniEntity.inside = false;
this.highlighter.highlight(null);
logDebug("On leave omniEntity " + this.nearestOmniEntity.id);
}
OmniTool.prototype.setNearestOmniEntity = function(entityId) {
if (entityId && entityId !== this.nearestOmniEntity.id) {
if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) {
this.onLeaveNearestOmniEntity();
}
this.nearestOmniEntity.id = entityId;
this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId);
var properties = Entities.getEntityProperties(entityId);
this.nearestOmniEntity.position = properties.position;
// FIXME use a real bounding box, not a sphere
var bbox = properties.boundingBox;
this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn));
this.highlighter.setRotation(properties.rotation);
this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions));
}
}
OmniTool.prototype.scan = function() {
var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition);
if (scanDistance < 0.005) {
return;
}
this.lastScanPosition = this.model.tipPosition;
this.setNearestOmniEntity(this.findNearestOmniEntity());
if (this.nearestOmniEntity.id) {
var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position);
// track distance on a half centimeter basis
if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) {
this.nearestOmniEntity.distance = distance;
if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) {
this.onEnterNearestOmniEntity();
}
if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) {
this.onLeaveNearestOmniEntity();
}
}
}
}
OmniTool.prototype.unloadModule = function() {
if (this.module && this.module.onUnload) {
this.module.onUnload();
}
this.module = null;
this.moduleEntityId = null;
}
OmniTool.prototype.activateNewOmniModule = function() {
// Support the ability for scripts to just run without replacing the current module
var script = this.nearestOmniEntity.omniProperties.script;
if (script.indexOf("/") < 0) {
script = "omniTool/modules/" + script;
}
// Reset the tool type
OmniToolModuleType = null;
logDebug("Including script path: " + script);
try {
Script.include(script);
} catch(err) {
logWarn("Failed to include script: " + script + "\n" + err);
return;
}
// If we're building a new module, unload the old one
if (OmniToolModuleType) {
logDebug("New OmniToolModule: " + OmniToolModuleType);
this.unloadModule();
try {
this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id);
this.moduleEntityId = this.nearestOmniEntity.id;
if (this.module.onLoad) {
this.module.onLoad();
}
} catch(err) {
logWarn("Failed to instantiate new module: " + err);
}
}
}
// FIXME find a good way to sync the two omni tools
OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ];

View file

@ -0,0 +1,19 @@
ModelBase = function() {
this.length = 0.2;
}
ModelBase.prototype.setVisible = function(visible) {
this.visible = visible;
}
ModelBase.prototype.setLength = function(length) {
this.length = length;
}
ModelBase.prototype.setTransform = function(transform) {
this.rotation = transform.rotation;
this.position = transform.position;
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
this.tipPosition = Vec3.sum(this.position, this.tipVector);
}

View file

@ -0,0 +1,120 @@
Wand = function() {
// Max updates fps
this.MAX_FRAMERATE = 30
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.DEFAULT_TIP_COLORS = [ {
red: 128,
green: 128,
blue: 128,
}, {
red: 64,
green: 64,
blue: 64,
}];
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
// FIXME does this need to be a member of this?
this.lastUpdateInterval = 0;
this.pointers = [
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: this.DEFAULT_TIP_COLORS[0],
alpha: 1.0,
solid: true,
visible: false,
}),
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: this.DEFAULT_TIP_COLORS[1],
alpha: 1.0,
solid: true,
visible: false,
})
];
this.wand = Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: COLORS.WHITE,
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
alpha: 1.0,
solid: true,
visible: false
});
var _this = this;
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(_this.pointers[0]);
Overlays.deleteOverlay(_this.pointers[1]);
Overlays.deleteOverlay(_this.wand);
});
Script.update.connect(function(deltaTime) {
_this.lastUpdateInterval += deltaTime;
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
_this.onUpdate(_this.lastUpdateInterval);
_this.lastUpdateInterval = 0;
}
});
}
Wand.prototype = Object.create( ModelBase.prototype );
Wand.prototype.setVisible = function(visible) {
ModelBase.prototype.setVisible.call(this, visible);
Overlays.editOverlay(this.pointers[0], {
visible: this.visible
});
Overlays.editOverlay(this.pointers[1], {
visible: this.visible
});
Overlays.editOverlay(this.wand, {
visible: this.visible
});
}
Wand.prototype.setTransform = function(transform) {
ModelBase.prototype.setTransform.call(this, transform);
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
Overlays.editOverlay(this.pointers[0], {
position: this.tipPosition,
rotation: this.rotation,
visible: true,
});
Overlays.editOverlay(this.pointers[1], {
position: this.tipPosition,
rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation),
visible: true,
});
Overlays.editOverlay(this.wand, {
dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 },
position: wandPosition,
rotation: this.rotation,
visible: true,
});
}
Wand.prototype.setTipColors = function(color1, color2) {
Overlays.editOverlay(this.pointers[0], {
color: color1 || this.DEFAULT_TIP_COLORS[0],
});
Overlays.editOverlay(this.pointers[1], {
color: color2 || this.DEFAULT_TIP_COLORS[1],
});
}
Wand.prototype.onUpdate = function(deltaTime) {
if (this.visible) {
var time = new Date().getTime() / 250;
var scale1 = Math.abs(Math.sin(time));
var scale2 = Math.abs(Math.cos(time));
Overlays.editOverlay(this.pointers[0], {
scale: scale1 * 0.01,
});
Overlays.editOverlay(this.pointers[1], {
scale: scale2 * 0.01,
});
}
}

View file

@ -0,0 +1,35 @@
//
// breakdanceOmniToolModule.js
// examples/libraries/omniTool/modules
//
// This is an omniTool module version of the breakdance game
//
// Created by Brad Hefta-Gaub on Sept 3, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../toys/breakdanceCore.js");
OmniToolModules.Breakdance = function() {
print("OmniToolModules.Breakdance...");
}
OmniToolModules.Breakdance.prototype.onLoad = function(deltaTime) {
print("OmniToolModules.Breakdance.prototype.onLoad()...");
breakdanceStart();
}
OmniToolModules.Breakdance.prototype.onUpdate = function(deltaTime) {
print("OmniToolModules.Breakdance.prototype.onUpdate()...");
breakdanceUpdate();
}
OmniToolModules.Breakdance.prototype.onUnload = function() {
print("OmniToolModules.Breakdance.prototype.onUnload()...");
breakdanceEnd();
}
OmniToolModuleType = "Breakdance";

View file

@ -0,0 +1,9 @@
OmniToolModules.Test = function() {
}
OmniToolModules.Test.prototype.onClick = function() {
logDebug("Test module onClick");
}
OmniToolModuleType = "Test"

View file

@ -53,11 +53,17 @@ getEntityUserData = function(id) {
var results = null; var results = null;
var properties = Entities.getEntityProperties(id); var properties = Entities.getEntityProperties(id);
if (properties.userData) { if (properties.userData) {
results = JSON.parse(properties.userData); try {
results = JSON.parse(properties.userData);
} catch(err) {
logDebug(err);
logDebug(properties.userData);
}
} }
return results ? results : {}; return results ? results : {};
} }
// Non-destructively modify the user data of an entity. // Non-destructively modify the user data of an entity.
setEntityCustomData = function(customKey, id, data) { setEntityCustomData = function(customKey, id, data) {
var userData = getEntityUserData(id); var userData = getEntityUserData(id);
@ -70,14 +76,6 @@ getEntityCustomData = function(customKey, id, defaultValue) {
return userData[customKey] ? userData[customKey] : defaultValue; return userData[customKey] ? userData[customKey] : defaultValue;
} }
getMagBallsData = function(id) {
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
}
mergeObjects = function(proto, custom) { mergeObjects = function(proto, custom) {
var result = {}; var result = {};
for (var attrname in proto) { for (var attrname in proto) {
@ -103,4 +101,29 @@ logInfo = function(str) {
logDebug = function(str) { logDebug = function(str) {
print(str); print(str);
} }
// Computes the penetration between a point and a sphere (centered at the origin)
// if point is inside sphere: returns true and stores the result in 'penetration'
// (the vector that would move the point outside the sphere)
// otherwise returns false
findSphereHit = function(point, sphereRadius) {
var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations
var vectorLength = Vec3.length(point);
if (vectorLength < EPSILON) {
return true;
}
var distance = vectorLength - sphereRadius;
if (distance < 0.0) {
return true;
}
return false;
}
findSpherePointHit = function(sphereCenter, sphereRadius, point) {
return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius);
}
findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) {
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
}

View file

@ -1,7 +1,8 @@
// //
// breakdanceToy.js // breakdanceCore.js
// examples // examples/toys
// //
// This is the core breakdance game library, it can be used as part of an entity script, or an omniTool module, or bootstapped on it's own
// Created by Brad Hefta-Gaub on August 24, 2015 // Created by Brad Hefta-Gaub on August 24, 2015
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// //
@ -9,34 +10,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
// helpers
// Computes the penetration between a point and a sphere (centered at the origin)
// if point is inside sphere: returns true and stores the result in 'penetration'
// (the vector that would move the point outside the sphere)
// otherwise returns false
function findSphereHit(point, sphereRadius) {
var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations
var vectorLength = Vec3.length(point);
if (vectorLength < EPSILON) {
return true;
}
var distance = vectorLength - sphereRadius;
if (distance < 0.0) {
return true;
}
return false;
}
function findSpherePointHit(sphereCenter, sphereRadius, point) {
return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius);
}
function findSphereSphereHit(firstCenter, firstRadius, secondCenter, secondRadius) {
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
}
Script.include("../libraries/utils.js");
function getPositionPuppet() { function getPositionPuppet() {
var DISTANCE_IN_FRONT = 2; var DISTANCE_IN_FRONT = 2;
@ -245,13 +220,18 @@ function getPositionRightOnBase() {
} }
// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines // some globals we will need access to
// our 3D cube that moves around... var HAND_SIZE = 0.25;
var handSize = 0.25; var TARGET_SIZE = 0.3;
var leftCubePosition = MyAvatar.getLeftPalmPosition(); var TARGET_COLOR = { red: 128, green: 128, blue: 128};
var rightCubePosition = MyAvatar.getRightPalmPosition(); var TARGET_COLOR_HIT = { red: 0, green: 255, blue: 0};
var text = Overlays.addOverlay("text", { var textOverlay, leftHandOverlay, rightHandOverlay,
leftOnBaseOverlay, leftLoweredOverlay, leftOverheadOverlay, leftSideOverlay, leftFrontOverlay,
rightOnBaseOverlay, rightLoweredOverlay, rightOverheadOverlay, rightSideOverlay, rightFrontOverlay;
function createOverlays() {
textOverlay = Overlays.addOverlay("text", {
x: 100, x: 100,
y: 300, y: 300,
width: 900, width: 900,
@ -265,31 +245,110 @@ var text = Overlays.addOverlay("text", {
backgroundAlpha: 0.5 backgroundAlpha: 0.5
}); });
var leftHand= Overlays.addOverlay("cube", { leftHandOverlay = Overlays.addOverlay("cube", {
position: leftCubePosition, position: MyAvatar.getLeftPalmPosition(),
size: handSize, size: HAND_SIZE,
color: { red: 0, green: 0, blue: 255}, color: { red: 0, green: 0, blue: 255},
alpha: 1, alpha: 1,
solid: false solid: false
}); });
var rightHand= Overlays.addOverlay("cube", { rightHandOverlay = Overlays.addOverlay("cube", {
position: rightCubePosition, position: MyAvatar.getRightPalmPosition(),
size: handSize, size: HAND_SIZE,
color: { red: 255, green: 0, blue: 0}, color: { red: 255, green: 0, blue: 0},
alpha: 1, alpha: 1,
solid: false solid: false
}); });
leftOnBaseOverlay = Overlays.addOverlay("cube", {
position: getPositionLeftOnBase(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
var targetSize = 0.3;
var targetColor = { red: 128, green: 128, blue: 128}; leftLoweredOverlay = Overlays.addOverlay("cube", {
var targetColorHit = { red: 0, green: 255, blue: 0}; position: getPositionLeftLowered(),
var moveCycleColor = { red: 255, green: 255, blue: 0}; size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
leftOverheadOverlay = Overlays.addOverlay("cube", {
position: getPositionLeftOverhead(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
leftSideOverlay = Overlays.addOverlay("cube", {
position: getPositionLeftSide(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
leftFrontOverlay = Overlays.addOverlay("cube", {
position: getPositionLeftFront(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
rightOnBaseOverlay = Overlays.addOverlay("cube", {
position: getPositionRightOnBase(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
rightLoweredOverlay = Overlays.addOverlay("cube", {
position: getPositionRightLowered(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
rightOverheadOverlay = Overlays.addOverlay("cube", {
position: getPositionRightOverhead(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
rightSideOverlay = Overlays.addOverlay("cube", {
position: getPositionRightSide(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
rightFrontOverlay = Overlays.addOverlay("cube", {
position: getPositionRightFront(),
size: TARGET_SIZE,
color: TARGET_COLOR,
alpha: 1,
solid: false
});
}
var TEMPORARY_LIFETIME = 60; var TEMPORARY_LIFETIME = 60;
var ANIMATION_SETTINGS = JSON.stringify({
var animationSettings = JSON.stringify({
fps: 30, fps: 30,
running: true, running: true,
loop: true, loop: true,
@ -297,107 +356,22 @@ var animationSettings = JSON.stringify({
lastFrame: 10000 lastFrame: 10000
}); });
var naturalDimensions = { x: 1.63, y: 1.67, z: 0.31 }; var NATURAL_DIMENSIONS = { x: 1.63, y: 1.67, z: 0.31 };
var dimensions = Vec3.multiply(naturalDimensions, 0.3); var DIMENSIONS = Vec3.multiply(NATURAL_DIMENSIONS, 0.3);
var puppetEntityID;
var puppetEntityID = Entities.addEntity({ function createPuppet() {
puppetEntityID = Entities.addEntity({
type: "Model", type: "Model",
modelURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/bboy1/bboy1.fbx", modelURL: "https://hifi-public.s3.amazonaws.com/models/Bboys/bboy1/bboy1.fbx",
animationURL: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx", animationURL: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx",
animationSettings: animationSettings, animationSettings: ANIMATION_SETTINGS,
position: getPositionPuppet(), position: getPositionPuppet(),
ignoreForCollisions: true, ignoreForCollisions: true,
dimensions: dimensions, dimensions: DIMENSIONS,
lifetime: TEMPORARY_LIFETIME lifetime: TEMPORARY_LIFETIME
}); });
}
var leftOnBase = Overlays.addOverlay("cube", {
position: getPositionLeftOnBase(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var leftLowered = Overlays.addOverlay("cube", {
position: getPositionLeftLowered(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var leftOverhead = Overlays.addOverlay("cube", {
position: getPositionLeftOverhead(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var leftSide= Overlays.addOverlay("cube", {
position: getPositionLeftSide(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var leftFront= Overlays.addOverlay("cube", {
position: getPositionLeftFront(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var rightOnBase = Overlays.addOverlay("cube", {
position: getPositionRightOnBase(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var rightLowered = Overlays.addOverlay("cube", {
position: getPositionRightLowered(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var rightOverhead = Overlays.addOverlay("cube", {
position: getPositionRightOverhead(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var rightSide= Overlays.addOverlay("cube", {
position: getPositionRightSide(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var rightFront= Overlays.addOverlay("cube", {
position: getPositionRightFront(),
size: targetSize,
color: targetColor,
alpha: 1,
solid: false
});
var startDate = new Date();
var lastTime = startDate.getTime();
var NO_POSE = 0; var NO_POSE = 0;
var LEFT_ON_BASE = 1; var LEFT_ON_BASE = 1;
@ -411,8 +385,6 @@ var RIGHT_LOWERED = 128;
var RIGHT_SIDE = 256; var RIGHT_SIDE = 256;
var RIGHT_FRONT = 512; var RIGHT_FRONT = 512;
var lastPoseValue = NO_POSE;
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx //http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_pose_to_idle.fbx //http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_pose_to_idle.fbx
//http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx //http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx
@ -461,8 +433,6 @@ poses[LEFT_ON_BASE + RIGHT_LOWERED ] = { name: "Left On Base + Right Lowered"
poses[LEFT_ON_BASE + RIGHT_SIDE ] = { name: "Left On Base + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; poses[LEFT_ON_BASE + RIGHT_SIDE ] = { name: "Left On Base + Right Side", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" };
poses[LEFT_ON_BASE + RIGHT_FRONT ] = { name: "Left On Base + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" }; poses[LEFT_ON_BASE + RIGHT_FRONT ] = { name: "Left On Base + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_ready.fbx" };
poses[LEFT_OVERHEAD + RIGHT_OVERHEAD ] = { name: "Left Overhead + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx" }; poses[LEFT_OVERHEAD + RIGHT_OVERHEAD ] = { name: "Left Overhead + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/bboy_uprock.fbx" };
poses[LEFT_LOWERED + RIGHT_OVERHEAD ] = { name: "Left Lowered + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_1.fbx" }; poses[LEFT_LOWERED + RIGHT_OVERHEAD ] = { name: "Left Lowered + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_1.fbx" };
poses[LEFT_SIDE + RIGHT_OVERHEAD ] = { name: "Left Side + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_2.fbx" }; poses[LEFT_SIDE + RIGHT_OVERHEAD ] = { name: "Left Side + Right Overhead", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_footwork_2.fbx" };
@ -484,43 +454,46 @@ poses[LEFT_SIDE + RIGHT_FRONT ] = { name: "Left Side + Right Front",
poses[LEFT_FRONT + RIGHT_FRONT ] = { name: "Left Front + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_end.fbx" }; poses[LEFT_FRONT + RIGHT_FRONT ] = { name: "Left Front + Right Front", animation: "http://s3.amazonaws.com/hifi-public/animations/Breakdancing/breakdance_uprock_var_1_end.fbx" };
Script.update.connect(function(deltaTime) { breakdanceStart = function() {
var date= new Date(); print("breakdanceStart...");
var now= date.getTime(); createOverlays();
var elapsed = now - lastTime; createPuppet();
var inMoveCycle = false; }
breakdanceUpdate = function(deltaTime) {
//print("breakdanceUpdate...");
var leftHandPos = MyAvatar.getLeftPalmPosition(); var leftHandPos = MyAvatar.getLeftPalmPosition();
var rightHandPos = MyAvatar.getRightPalmPosition(); var rightHandPos = MyAvatar.getRightPalmPosition();
Overlays.editOverlay(leftHand, { position: leftHandPos } ); Overlays.editOverlay(leftHandOverlay, { position: leftHandPos } );
Overlays.editOverlay(rightHand, { position: rightHandPos } ); Overlays.editOverlay(rightHandOverlay, { position: rightHandPos } );
var hitTargetLeftOnBase = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOnBase(), targetSize/2); var hitTargetLeftOnBase = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftOnBase(), TARGET_SIZE/2);
var hitTargetLeftOverhead = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftOverhead(), targetSize/2); var hitTargetLeftOverhead = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftOverhead(), TARGET_SIZE/2);
var hitTargetLeftLowered = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftLowered(), targetSize/2); var hitTargetLeftLowered = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftLowered(), TARGET_SIZE/2);
var hitTargetLeftSide = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftSide(), targetSize/2); var hitTargetLeftSide = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftSide(), TARGET_SIZE/2);
var hitTargetLeftFront = findSphereSphereHit(leftHandPos, handSize/2, getPositionLeftFront(), targetSize/2); var hitTargetLeftFront = findSphereSphereHit(leftHandPos, HAND_SIZE/2, getPositionLeftFront(), TARGET_SIZE/2);
var hitTargetRightOnBase = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOnBase(), targetSize/2); var hitTargetRightOnBase = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightOnBase(), TARGET_SIZE/2);
var hitTargetRightOverhead = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightOverhead(), targetSize/2); var hitTargetRightOverhead = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightOverhead(), TARGET_SIZE/2);
var hitTargetRightLowered = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightLowered(), targetSize/2); var hitTargetRightLowered = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightLowered(), TARGET_SIZE/2);
var hitTargetRightSide = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightSide(), targetSize/2); var hitTargetRightSide = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightSide(), TARGET_SIZE/2);
var hitTargetRightFront = findSphereSphereHit(rightHandPos, handSize/2, getPositionRightFront(), targetSize/2); var hitTargetRightFront = findSphereSphereHit(rightHandPos, HAND_SIZE/2, getPositionRightFront(), TARGET_SIZE/2);
// determine target colors // determine target colors
var targetColorLeftOnBase = hitTargetLeftOnBase ? targetColorHit : targetColor; var targetColorLeftOnBase = hitTargetLeftOnBase ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorLeftOverhead = hitTargetLeftOverhead ? targetColorHit : targetColor; var targetColorLeftOverhead = hitTargetLeftOverhead ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorLeftLowered = hitTargetLeftLowered ? targetColorHit : targetColor; var targetColorLeftLowered = hitTargetLeftLowered ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorLeftSide = hitTargetLeftSide ? targetColorHit : targetColor; var targetColorLeftSide = hitTargetLeftSide ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorLeftFront = hitTargetLeftFront ? targetColorHit : targetColor; var targetColorLeftFront = hitTargetLeftFront ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorRightOnBase = hitTargetRightOnBase ? targetColorHit : targetColor; var targetColorRightOnBase = hitTargetRightOnBase ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorRightOverhead = hitTargetRightOverhead ? targetColorHit : targetColor; var targetColorRightOverhead = hitTargetRightOverhead ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorRightLowered = hitTargetRightLowered ? targetColorHit : targetColor; var targetColorRightLowered = hitTargetRightLowered ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorRightSide = hitTargetRightSide ? targetColorHit : targetColor; var targetColorRightSide = hitTargetRightSide ? TARGET_COLOR_HIT : TARGET_COLOR;
var targetColorRightFront = hitTargetRightFront ? targetColorHit : targetColor; var targetColorRightFront = hitTargetRightFront ? TARGET_COLOR_HIT : TARGET_COLOR;
// calculate a combined arm pose based on left and right hits // calculate a combined arm pose based on left and right hits
var poseValue = NO_POSE; var poseValue = NO_POSE;
@ -536,47 +509,48 @@ Script.update.connect(function(deltaTime) {
poseValue += hitTargetRightFront ? RIGHT_FRONT : 0; poseValue += hitTargetRightFront ? RIGHT_FRONT : 0;
if (poses[poseValue] == undefined) { if (poses[poseValue] == undefined) {
Overlays.editOverlay(text, { text: "no pose -- value:" + poseValue }); Overlays.editOverlay(textOverlay, { text: "no pose -- value:" + poseValue });
} else { } else {
Overlays.editOverlay(text, { text: "pose:" + poses[poseValue].name + "\n" + "animation:" + poses[poseValue].animation }); Overlays.editOverlay(textOverlay, { text: "pose:" + poses[poseValue].name + "\n" + "animation:" + poses[poseValue].animation });
var props = Entities.getEntityProperties(puppetEntityID); var props = Entities.getEntityProperties(puppetEntityID);
print("puppetEntityID:" + puppetEntityID + "age:"+props.age);
Entities.editEntity(puppetEntityID, { Entities.editEntity(puppetEntityID, {
animationURL: poses[poseValue].animation, animationURL: poses[poseValue].animation,
lifetime: TEMPORARY_LIFETIME + props.age // renew lifetime lifetime: TEMPORARY_LIFETIME + props.age // renew lifetime
}); });
} }
lastPoseValue = poseValue; Overlays.editOverlay(leftOnBaseOverlay, { position: getPositionLeftOnBase(), color: targetColorLeftOnBase } );
Overlays.editOverlay(leftOverheadOverlay, { position: getPositionLeftOverhead(), color: targetColorLeftOverhead } );
Overlays.editOverlay(leftOnBase, { position: getPositionLeftOnBase(), color: targetColorLeftOnBase } ); Overlays.editOverlay(leftLoweredOverlay, { position: getPositionLeftLowered(), color: targetColorLeftLowered } );
Overlays.editOverlay(leftOverhead, { position: getPositionLeftOverhead(), color: targetColorLeftOverhead } ); Overlays.editOverlay(leftSideOverlay, { position: getPositionLeftSide() , color: targetColorLeftSide } );
Overlays.editOverlay(leftLowered, { position: getPositionLeftLowered(), color: targetColorLeftLowered } ); Overlays.editOverlay(leftFrontOverlay, { position: getPositionLeftFront() , color: targetColorLeftFront } );
Overlays.editOverlay(leftSide, { position: getPositionLeftSide() , color: targetColorLeftSide } );
Overlays.editOverlay(leftFront, { position: getPositionLeftFront() , color: targetColorLeftFront } );
Overlays.editOverlay(rightOnBase, { position: getPositionRightOnBase(), color: targetColorRightOnBase } ); Overlays.editOverlay(rightOnBaseOverlay, { position: getPositionRightOnBase(), color: targetColorRightOnBase } );
Overlays.editOverlay(rightOverhead, { position: getPositionRightOverhead(), color: targetColorRightOverhead } ); Overlays.editOverlay(rightOverheadOverlay, { position: getPositionRightOverhead(), color: targetColorRightOverhead } );
Overlays.editOverlay(rightLowered, { position: getPositionRightLowered(), color: targetColorRightLowered } ); Overlays.editOverlay(rightLoweredOverlay, { position: getPositionRightLowered(), color: targetColorRightLowered } );
Overlays.editOverlay(rightSide, { position: getPositionRightSide() , color: targetColorRightSide } ); Overlays.editOverlay(rightSideOverlay, { position: getPositionRightSide() , color: targetColorRightSide } );
Overlays.editOverlay(rightFront, { position: getPositionRightFront() , color: targetColorRightFront } ); Overlays.editOverlay(rightFrontOverlay, { position: getPositionRightFront() , color: targetColorRightFront } );
}); }
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(leftHand);
Overlays.deleteOverlay(rightHand);
Overlays.deleteOverlay(text); breakdanceEnd= function() {
Overlays.deleteOverlay(leftOnBase); print("breakdanceEnd...");
Overlays.deleteOverlay(leftOverhead);
Overlays.deleteOverlay(leftLowered); Overlays.deleteOverlay(leftHandOverlay);
Overlays.deleteOverlay(leftSide); Overlays.deleteOverlay(rightHandOverlay);
Overlays.deleteOverlay(leftFront);
Overlays.deleteOverlay(rightOnBase); Overlays.deleteOverlay(textOverlay);
Overlays.deleteOverlay(rightOverhead); Overlays.deleteOverlay(leftOnBaseOverlay);
Overlays.deleteOverlay(rightLowered); Overlays.deleteOverlay(leftOverheadOverlay);
Overlays.deleteOverlay(rightSide); Overlays.deleteOverlay(leftLoweredOverlay);
Overlays.deleteOverlay(rightFront); Overlays.deleteOverlay(leftSideOverlay);
Overlays.deleteOverlay(leftFrontOverlay);
Overlays.deleteOverlay(rightOnBaseOverlay);
Overlays.deleteOverlay(rightOverheadOverlay);
Overlays.deleteOverlay(rightLoweredOverlay);
Overlays.deleteOverlay(rightSideOverlay);
Overlays.deleteOverlay(rightFrontOverlay);
print("puppetEntityID:"+puppetEntityID);
Entities.deleteEntity(puppetEntityID); Entities.deleteEntity(puppetEntityID);
}); }

View file

@ -0,0 +1,17 @@
//
// breakdanceToy.js
// examples/toys
//
// This is an local script version of the breakdance game
//
// Created by Brad Hefta-Gaub on Sept 3, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("breakdanceCore.js");
breakdanceStart();
Script.update.connect(breakdanceUpdate);
Script.scriptEnding.connect(breakdanceEnd);

113
examples/toys/magBalls.js Normal file
View file

@ -0,0 +1,113 @@
//
// Created by Bradley Austin Davis on 2015/08/25
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
Script.include("../toys/magBalls/constants.js");
Script.include("../toys/magBalls/graph.js");
Script.include("../toys/magBalls/edgeSpring.js");
Script.include("../toys/magBalls/magBalls.js");
OmniToolModuleType = "MagBallsController"
OmniToolModules.MagBallsController = function(omniTool, entityId) {
this.omniTool = omniTool;
this.entityId = entityId;
this.highlighter = new Highlighter();
this.magBalls = new MagBalls();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
var MAG_BALLS_DATA_NAME = "magBalls";
getMagBallsData = function(id) {
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
}
//var magBalls = new MagBalls();
// DEBUGGING ONLY - Clear any previous balls
// magBalls.clear();
OmniToolModules.MagBallsController.prototype.onClick = function() {
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
this.selected = this.highlighter.hightlighted;
logDebug("This selected: " + this.selected);
if (!this.selected) {
this.selected = this.magBalls.createBall(this.tipPosition);
}
this.magBalls.selectBall(this.selected);
this.highlighter.highlight(null);
logDebug("Selected " + this.selected);
}
OmniToolModules.MagBallsController.prototype.onRelease = function() {
logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
this.clearGhostEdges();
if (this.selected) {
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
}
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
this.tipPosition = this.omniTool.getPosition();
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
if (!target) {
this.magBalls.onUpdate(deltaTime);
}
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onUnload = function() {
this.clearGhostEdges();
}

View file

@ -1,103 +0,0 @@
Script.include("handController.js");
Script.include("highlighter.js");
BallController = function(side, magBalls) {
HandController.call(this, side);
this.magBalls = magBalls;
this.highlighter = new Highlighter();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
BallController.prototype = Object.create( HandController.prototype );
BallController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
BallController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
BallController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
BallController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}
BallController.prototype.onAltClick = function() {
return;
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
if (!target) {
logDebug(target);
return;
}
// FIXME move to delete shape
var toDelete = {};
var deleteQueue = [ target ];
while (deleteQueue.length) {
var curNode = deleteQueue.shift();
if (toDelete[curNode]) {
continue;
}
toDelete[curNode] = true;
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
deleteQueue.push(nodeId);
}
}
for (var nodeId in toDelete) {
this.magBalls.destroyNode(nodeId);
}
}
BallController.prototype.onAltRelease = function() {
}

View file

@ -1,13 +1,3 @@
//
// Created by Bradley Austin Davis on 2015/08/27
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
// FIXME make this editable through some script UI, so the user can customize the size of the structure built // FIXME make this editable through some script UI, so the user can customize the size of the structure built
SCALE = 0.5; SCALE = 0.5;
@ -16,60 +6,9 @@ STICK_LENGTH = 0.24 * SCALE;
DEBUG_MAGSTICKS = true; DEBUG_MAGSTICKS = true;
CUSTOM_DATA_NAME = "magBalls";
BALL_NAME = "MagBall"; BALL_NAME = "MagBall";
EDGE_NAME = "MagStick"; EDGE_NAME = "MagStick";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,
green: 255,
blue: 255,
},
BLACK: {
red: 0,
green: 0,
blue: 0,
},
GREY: {
red: 128,
green: 128,
blue: 128,
},
RED: {
red: 255,
green: 0,
blue: 0
},
BLUE: {
red: 0,
green: 0,
blue: 255
},
GREEN: {
red: 0,
green: 255,
blue: 0
},
CYAN: {
red: 0,
green: 255,
blue: 255
},
YELLOW: {
red: 255,
green: 255,
blue: 0
},
MAGENTA: {
red: 255,
green: 0,
blue: 255
}
}
BALL_RADIUS = BALL_SIZE / 2.0; BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
@ -136,5 +75,3 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
// ignoreCollisions: true, // ignoreCollisions: true,
// collisionsWillMove: false // collisionsWillMove: false
// } // }

View file

@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) {
return result; return result;
} }
Graph.prototype.getNodesForEdge = function(edgeId) {
return Object.keys(this.edges[edgeId]);
}
Graph.prototype.getEdgeLength = function(edgeId) { Graph.prototype.getEdgeLength = function(edgeId) {
var nodesInEdge = Object.keys(this.edges[edgeId]); var nodesInEdge = Object.keys(this.edges[edgeId]);
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);

View file

@ -1,127 +0,0 @@
//
// Created by Bradley Austin Davis on 2015/08/29
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
LEFT_CONTROLLER = 0;
RIGHT_CONTROLLER = 1;
// FIXME add a customizable wand model and a mechanism to switch between wands
HandController = function(side) {
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.action = findAction(side ? "ACTION2" : "ACTION1");
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
this.active = false;
this.tipScale = 1.4;
this.pointer = Overlays.addOverlay("sphere", {
position: ZERO_VECTOR,
size: 0.01,
color: COLORS.YELLOW,
alpha: 1.0,
solid: true,
visible: false,
});
// Connect to desired events
var _this = this;
Controller.actionEvent.connect(function(action, state) {
_this.onActionEvent(action, state);
});
Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime);
});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
}
HandController.prototype.onActionEvent = function(action, state) {
var spatialControlCount = Controller.getNumberOfSpatialControls();
// If only 2 spacial controls, then we only have one controller active, so use either button
// otherwise, only use the specified action
if (action == this.action) {
if (state) {
this.onClick();
} else {
this.onRelease();
}
}
if (action == this.altAction) {
if (state) {
this.onAltClick();
} else {
this.onAltRelease();
}
}
}
HandController.prototype.setActive = function(active) {
if (active == this.active) {
return;
}
logDebug("Hand controller changing active state: " + active);
this.active = active;
Overlays.editOverlay(this.pointer, {
visible: this.active
});
Entities.editEntity(this.wand, {
visible: this.active
});
}
HandController.prototype.updateControllerState = function() {
// FIXME this returns data if either the left or right controller is not on the base
this.palmPos = Controller.getSpatialControlPosition(this.palm);
var tipPos = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.palmPos) > 0.001);
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
//if (this.active) {
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
//}
}
HandController.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.pointer);
}
HandController.prototype.onUpdate = function(deltaTime) {
this.updateControllerState();
if (this.active) {
Overlays.editOverlay(this.pointer, {
position: this.tipPosition
});
Entities.editEntity(this.wand, {
position: this.tipPosition
});
}
}
HandController.prototype.onClick = function() {
logDebug("Base hand controller does nothing on click");
}
HandController.prototype.onRelease = function() {
logDebug("Base hand controller does nothing on release");
}
HandController.prototype.onAltClick = function() {
logDebug("Base hand controller does nothing on alt click");
}
HandController.prototype.onAltRelease = function() {
logDebug("Base hand controller does nothing on alt click");
}

View file

@ -8,30 +8,34 @@
var UPDATE_INTERVAL = 0.1; var UPDATE_INTERVAL = 0.1;
Script.include("graph.js");
Script.include("edgeSpring.js");
// A collection of balls and edges connecting them. // A collection of balls and edges connecting them.
MagBalls = function() { MagBalls = function() {
Graph.call(this); Graph.call(this);
this.MAX_ADJUST_ITERATIONS = 100; this.MAX_ADJUST_ITERATIONS = 100;
this.REFRESH_WAIT_TICKS = 10;
this.MAX_VARIANCE = 0.25;
this.lastUpdateAge = 0; this.lastUpdateAge = 0;
this.stable = false; this.stable = true;
this.adjustIterations = 0; this.adjustIterations = 0;
this.selectedNodes = {}; this.selectedNodes = {};
this.edgeObjects = {}; this.edgeObjects = {};
this.unstableEdges = {};
this.refresh(); this.refresh();
var _this = this; var _this = this;
Script.update.connect(function(deltaTime) { //Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime); // _this.onUpdate(deltaTime);
}); //});
Script.scriptEnding.connect(function() { Script.scriptEnding.connect(function() {
_this.onCleanup(); _this.onCleanup();
}); });
Entities.addingEntity.connect(function(entityId) {
_this.onEntityAdded(entityId);
});
} }
MagBalls.prototype = Object.create( Graph.prototype ); MagBalls.prototype = Object.create( Graph.prototype );
@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
this.lastUpdateAge += deltaTime; this.lastUpdateAge += deltaTime;
if (this.lastUpdateAge > UPDATE_INTERVAL) { if (this.lastUpdateAge > UPDATE_INTERVAL) {
this.lastUpdateAge = 0; this.lastUpdateAge = 0;
if (!this.stable) { if (this.refreshNeeded) {
if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) {
logDebug("Refreshing");
this.refresh();
this.refreshNeeded = 0;
}
}
if (!this.stable && !Object.keys(this.selectedNodes).length) {
this.adjustIterations += 1; this.adjustIterations += 1;
// logDebug("Update");
var adjusted = false; var adjusted = false;
var nodeAdjustResults = {}; var nodeAdjustResults = {};
var fixupEdges = {}; var fixupEdges = {};
for(var edgeId in this.edges) { for(var edgeId in this.edges) {
if (!this.unstableEdges[edgeId]) {
continue;
}
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
} }
for (var nodeId in nodeAdjustResults) { for (var nodeId in nodeAdjustResults) {
@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
}, ((UPDATE_INTERVAL * 1000) / 2)); }, ((UPDATE_INTERVAL * 1000) / 2));
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
if (adjusted) {
logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning");
}
this.adjustIterations = 0; this.adjustIterations = 0;
this.stable = true; this.stable = true;
this.unstableEdges = {};
} }
} }
} }
@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
// Check distance to attempt // Check distance to attempt
var distance = this.getNodeDistance(nodeId, otherNodeId); var distance = this.getNodeDistance(nodeId, otherNodeId);
var variance = this.getVariance(distance); var variance = this.getVariance(distance);
if (Math.abs(variance) > 0.25) { if (Math.abs(variance) > this.MAX_VARIANCE) {
continue; continue;
} }
@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
return variances; return variances;
} }
MagBalls.prototype.grabBall = function(position, maxDist) { MagBalls.prototype.breakEdges = function(nodeId) {
var selected = this.findNearestNode(position, maxDist); //var unstableNodes = this.findShape(Object.keys.target);
if (!selected) { //for (var node in unstableNodes) {
selected = this.createNode({ position: position }); // this.unstableNodes[node] = true;
} //}
if (selected) { Graph.prototype.breakEdges.call(this, nodeId);
this.stable = true;
this.breakEdges(selected);
this.selectedNodes[selected] = true;
}
return selected;
} }
MagBalls.prototype.createBall = function(position) {
var created = this.createNode({ position: position });
this.selectBall(created);
return created;
}
MagBalls.prototype.selectBall = function(selected) {
if (!selected) {
return;
}
// stop updating shapes while manipulating
this.stable = true;
this.selectedNodes[selected] = true;
this.breakEdges(selected);
}
MagBalls.prototype.releaseBall = function(releasedBall) { MagBalls.prototype.releaseBall = function(releasedBall) {
delete this.selectedNodes[releasedBall]; delete this.selectedNodes[releasedBall];
logDebug("Released ball: " + releasedBall); logDebug("Released ball: " + releasedBall);
var releasePosition = this.getNodePosition(releasedBall);
this.stable = false; this.stable = false;
var releasePosition = this.getNodePosition(releasedBall);
// iterate through the other balls and ensure we don't intersect with // iterate through the other balls and ensure we don't intersect with
// any of them. If we do, just delete this ball and return. // any of them. If we do, just delete this ball and return.
@ -169,10 +198,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) {
for (var otherBallId in targets) { for (var otherBallId in targets) {
this.createEdge(otherBallId, releasedBall); this.createEdge(otherBallId, releasedBall);
} }
var unstableNodes = this.findShape(releasedBall);
for (var nodeId in unstableNodes) {
for (var edgeId in this.nodes[nodeId]) {
this.unstableEdges[edgeId] = true;
}
}
this.validate(); this.validate();
} }
MagBalls.prototype.findShape = function(nodeId) {
var result = {};
var queue = [ nodeId ];
while (queue.length) {
var curNode = queue.shift();
if (result[curNode]) {
continue;
}
result[curNode] = true;
for (var otherNodeId in this.getConnectedNodes(curNode)) {
queue.push(otherNodeId);
}
}
return result;
}
MagBalls.prototype.getVariance = function(distance) { MagBalls.prototype.getVariance = function(distance) {
// FIXME different balls or edges might have different ideas of variance... // FIXME different balls or edges might have different ideas of variance...
// let something else handle this // let something else handle this
@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() {
Script.setTimeout(function() { Script.setTimeout(function() {
for (var i in deleteEdges) { for (var i in deleteEdges) {
var edgeId = deleteEdges[i]; var edgeId = deleteEdges[i];
logDebug("deleting invalid edge " + edgeId); //logDebug("deleting invalid edge " + edgeId);
Entities.deleteEntity(edgeId); //Entities.deleteEntity(edgeId);
Entities.editEntity(edgeId, {
color: COLORS.RED
})
} }
}, 1000); }, 1000);
} }
@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) {
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
} }
MagBalls.prototype.onEntityAdded = function(entityId) {
// We already have it
if (this.nodes[entityId] || this.edges[entityId]) {
return;
}
var properties = Entities.getEntityProperties(entityId);
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
this.refreshNeeded = 1;
}
}

View file

@ -1,25 +0,0 @@
//
// Created by Bradley Austin Davis on 2015/08/25
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("constants.js");
Script.include("utils.js");
Script.include("magBalls.js");
Script.include("ballController.js");
var magBalls = new MagBalls();
// Clear any previous balls
// magBalls.clear();
MenuController = function(side) {
HandController.call(this, side);
}
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ];

View file

@ -1,66 +0,0 @@
Script.include("handController.js");
MenuController = function(side, magBalls) {
HandController.call(this, side);
}
MenuController.prototype = Object.create( HandController.prototype );
MenuController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
MenuController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
MenuController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
MenuController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
MenuController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}

View file

@ -64,6 +64,7 @@
#include <EntityScriptingInterface.h> #include <EntityScriptingInterface.h>
#include <ErrorDialog.h> #include <ErrorDialog.h>
#include <Finally.h>
#include <FramebufferCache.h> #include <FramebufferCache.h>
#include <gpu/Batch.h> #include <gpu/Batch.h>
#include <gpu/Context.h> #include <gpu/Context.h>
@ -1009,6 +1010,16 @@ void Application::paintGL() {
if (nullptr == _displayPlugin) { if (nullptr == _displayPlugin) {
return; return;
} }
// Some plugins process message events, potentially leading to
// re-entering a paint event. don't allow further processing if this
// happens
if (_inPaint) {
return;
}
_inPaint = true;
Finally clearFlagLambda([this] { _inPaint = false; });
auto displayPlugin = getActiveDisplayPlugin(); auto displayPlugin = getActiveDisplayPlugin();
displayPlugin->preRender(); displayPlugin->preRender();
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();
@ -1203,7 +1214,7 @@ void Application::paintGL() {
// Ensure all operations from the previous context are complete before we try to read the fbo // Ensure all operations from the previous context are complete before we try to read the fbo
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(sync); glDeleteSync(sync);
{ {
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
displayPlugin->display(finalTexture, toGlm(size)); displayPlugin->display(finalTexture, toGlm(size));
@ -1219,7 +1230,6 @@ void Application::paintGL() {
_frameCount++; _frameCount++;
Stats::getInstance()->setRenderDetails(renderArgs._details); Stats::getInstance()->setRenderDetails(renderArgs._details);
// Reset the gpu::Context Stages // Reset the gpu::Context Stages
// Back to the default framebuffer; // Back to the default framebuffer;
gpu::Batch batch; gpu::Batch batch;
@ -4708,53 +4718,68 @@ void Application::updateDisplayMode() {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
DisplayPluginPointer oldDisplayPlugin = _displayPlugin; DisplayPluginPointer oldDisplayPlugin = _displayPlugin;
if (oldDisplayPlugin != newDisplayPlugin) { if (newDisplayPlugin == oldDisplayPlugin) {
if (!_currentDisplayPluginActions.isEmpty()) { return;
auto menu = Menu::getInstance();
foreach(auto itemInfo, _currentDisplayPluginActions) {
menu->removeMenuItem(itemInfo.first, itemInfo.second);
}
_currentDisplayPluginActions.clear();
}
if (newDisplayPlugin) {
_offscreenContext->makeCurrent();
_activatingDisplayPlugin = true;
newDisplayPlugin->activate();
_activatingDisplayPlugin = false;
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
}
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
bool newPluginWantsHMDTools = newDisplayPlugin ?
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
// Only show the hmd tools after the correct plugin has
// been activated so that it's UI is setup correctly
if (newPluginWantsHMDTools) {
_pluginContainer->showDisplayPluginsTools();
}
if (oldDisplayPlugin) {
oldDisplayPlugin->deactivate();
_offscreenContext->makeCurrent();
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
DependencyManager::get<DialogsManager>()->hmdTools(false);
}
}
emit activeDisplayPluginChanged();
resetSensors();
} }
// Some plugins *cough* Oculus *cough* process message events from inside their
// display function, and we don't want to change the display plugin underneath
// the paintGL call, so we need to guard against that
if (_inPaint) {
qDebug() << "Deferring plugin switch until out of painting";
// Have the old plugin stop requesting renders
oldDisplayPlugin->stop();
QCoreApplication::postEvent(this, new LambdaEvent([this] {
updateDisplayMode();
}));
return;
}
if (!_currentDisplayPluginActions.isEmpty()) {
auto menu = Menu::getInstance();
foreach(auto itemInfo, _currentDisplayPluginActions) {
menu->removeMenuItem(itemInfo.first, itemInfo.second);
}
_currentDisplayPluginActions.clear();
}
if (newDisplayPlugin) {
_offscreenContext->makeCurrent();
_activatingDisplayPlugin = true;
newDisplayPlugin->activate();
_activatingDisplayPlugin = false;
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
}
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
bool newPluginWantsHMDTools = newDisplayPlugin ?
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
// Only show the hmd tools after the correct plugin has
// been activated so that it's UI is setup correctly
if (newPluginWantsHMDTools) {
_pluginContainer->showDisplayPluginsTools();
}
if (oldDisplayPlugin) {
oldDisplayPlugin->deactivate();
_offscreenContext->makeCurrent();
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
DependencyManager::get<DialogsManager>()->hmdTools(false);
}
}
emit activeDisplayPluginChanged();
resetSensors();
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
} }
@ -5057,3 +5082,15 @@ void Application::crashApplication() {
qCDebug(interfaceapp) << "Intentionally crashed Interface"; qCDebug(interfaceapp) << "Intentionally crashed Interface";
} }
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
if (pluginName == name) {
action->setChecked(true);
}
}
updateDisplayMode();
}

View file

@ -673,6 +673,7 @@ private:
int _simsPerSecondReport = 0; int _simsPerSecondReport = 0;
quint64 _lastSimsPerSecondUpdate = 0; quint64 _lastSimsPerSecondUpdate = 0;
bool _isForeground = true; // starts out assumed to be in foreground bool _isForeground = true; // starts out assumed to be in foreground
bool _inPaint = false;
friend class PluginContainerProxy; friend class PluginContainerProxy;
}; };

View file

@ -10,16 +10,39 @@
// //
#include "FileLogger.h" #include "FileLogger.h"
#include "HifiSockAddr.h"
#include <FileUtils.h>
#include <QDateTime>
#include <QFile>
#include <QDir>
#include <QDesktopServices>
const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; #include <QtCore/QDateTime>
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; #include <QtCore/QFile>
const QString LOGS_DIRECTORY = "Logs"; #include <QtCore/QDir>
#include <QtGui/QDesktopServices>
#include <NumericalConstants.h>
#include <FileUtils.h>
#include <SharedUtil.h>
#include "HifiSockAddr.h"
static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
static const QString LOGS_DIRECTORY = "Logs";
// Max log size is 1 MB
static const uint64_t MAX_LOG_SIZE = 1024 * 1024;
// Max log age is 1 hour
static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
QString getLogRollerFilename() {
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = getLocalAddress();
QDateTime now = QDateTime::currentDateTime();
result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
return result;
}
const QString& getLogFilename() {
static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt";
return fileName;
}
class FilePersistThread : public GenericQueueThread < QString > { class FilePersistThread : public GenericQueueThread < QString > {
public: public:
@ -28,8 +51,22 @@ public:
} }
protected: protected:
void rollFileIfNecessary(QFile& file) {
uint64_t now = usecTimestampNow();
if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) {
QString newFileName = getLogRollerFilename();
if (file.copy(newFileName)) {
_lastRollTime = now;
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.close();
qDebug() << "Rolled log file: " << newFileName;
}
}
}
virtual bool processQueueItems(const Queue& messages) { virtual bool processQueueItems(const Queue& messages) {
QFile file(_logger._fileName); QFile file(_logger._fileName);
rollFileIfNecessary(file);
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
QTextStream out(&file); QTextStream out(&file);
foreach(const QString& message, messages) { foreach(const QString& message, messages) {
@ -40,20 +77,17 @@ protected:
} }
private: private:
const FileLogger& _logger; const FileLogger& _logger;
uint64_t _lastRollTime = 0;
}; };
static FilePersistThread* _persistThreadInstance; static FilePersistThread* _persistThreadInstance;
FileLogger::FileLogger(QObject* parent) : FileLogger::FileLogger(QObject* parent) :
AbstractLoggerInterface(parent) AbstractLoggerInterface(parent), _fileName(getLogFilename())
{ {
_persistThreadInstance = new FilePersistThread(*this); _persistThreadInstance = new FilePersistThread(*this);
_persistThreadInstance->initialize(true, QThread::LowestPriority); _persistThreadInstance->initialize(true, QThread::LowestPriority);
_fileName = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = getLocalAddress();
QDateTime now = QDateTime::currentDateTime();
_fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
} }
FileLogger::~FileLogger() { FileLogger::~FileLogger() {

View file

@ -27,7 +27,7 @@ public:
virtual void locateLog() override; virtual void locateLog() override;
private: private:
QString _fileName; const QString _fileName;
friend class FilePersistThread; friend class FilePersistThread;
}; };

View file

@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() {
QGLWidget* PluginContainerProxy::getPrimarySurface() { QGLWidget* PluginContainerProxy::getPrimarySurface() {
return qApp->_glWidget; return qApp->_glWidget;
} }
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
if (pluginName == name) {
action->setChecked(true);
}
}
updateDisplayMode();
}

View file

@ -124,18 +124,18 @@ MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.reset(); _lookAtTargetAvatar.reset();
} }
QByteArray MyAvatar::toByteArray(bool cullSmallChanges) { QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
CameraMode mode = Application::getInstance()->getCamera()->getMode(); CameraMode mode = Application::getInstance()->getCamera()->getMode();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) { if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
// fake the avatar position that is sent up to the AvatarMixer // fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = _position; glm::vec3 oldPosition = _position;
_position = getSkeletonPosition(); _position = getSkeletonPosition();
QByteArray array = AvatarData::toByteArray(cullSmallChanges); QByteArray array = AvatarData::toByteArray(cullSmallChanges, sendAll);
// copy the correct position back // copy the correct position back
_position = oldPosition; _position = oldPosition;
return array; return array;
} }
return AvatarData::toByteArray(cullSmallChanges); return AvatarData::toByteArray(cullSmallChanges, sendAll);
} }
void MyAvatar::reset() { void MyAvatar::reset() {

View file

@ -206,9 +206,7 @@ private:
glm::vec3 getWorldBodyPosition() const; glm::vec3 getWorldBodyPosition() const;
glm::quat getWorldBodyOrientation() const; glm::quat getWorldBodyOrientation() const;
QByteArray toByteArray(bool cullSmallChanges, bool sendAll);
QByteArray toByteArray(bool cullSmallChanges);
void simulate(float deltaTime); void simulate(float deltaTime);
void updateFromTrackers(float deltaTime); void updateFromTrackers(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override; virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override;

View file

@ -66,16 +66,7 @@ Overlay::~Overlay() {
void Overlay::setProperties(const QScriptValue& properties) { void Overlay::setProperties(const QScriptValue& properties) {
QScriptValue color = properties.property("color"); QScriptValue color = properties.property("color");
if (color.isValid()) { xColorFromScriptValue(properties.property("color"), _color);
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_color.red = red.toVariant().toInt();
_color.green = green.toVariant().toInt();
_color.blue = blue.toVariant().toInt();
}
}
if (properties.property("alpha").isValid()) { if (properties.property("alpha").isValid()) {
setAlpha(properties.property("alpha").toVariant().toFloat()); setAlpha(properties.property("alpha").toVariant().toFloat());

View file

@ -30,6 +30,7 @@
#include "Grid3DOverlay.h" #include "Grid3DOverlay.h"
#include "TextOverlay.h" #include "TextOverlay.h"
#include "Text3DOverlay.h" #include "Text3DOverlay.h"
#include "Web3DOverlay.h"
Overlays::Overlays() : _nextOverlayID(1) { Overlays::Overlays() : _nextOverlayID(1) {
@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer()); thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == ModelOverlay::TYPE) { } else if (type == ModelOverlay::TYPE) {
thisOverlay = std::make_shared<ModelOverlay>(); thisOverlay = std::make_shared<ModelOverlay>();
} else if (type == Web3DOverlay::TYPE) {
thisOverlay = std::make_shared<Web3DOverlay>();
} }
if (thisOverlay) { if (thisOverlay) {

View file

@ -0,0 +1,163 @@
//
// Web3DOverlay.cpp
//
//
// Created by Clement on 7/1/14.
// Modified and renamed by Zander Otavka on 8/4/15
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Web3DOverlay.h"
#include <QtScript/QScriptValue>
#include <QtGui/QOpenGLContext>
#include <QtQuick/QQuickItem>
#include <DeferredLightingEffect.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <GeometryUtil.h>
#include <TextureCache.h>
#include <PathUtils.h>
#include <gpu/Batch.h>
#include <RegisteredMetaTypes.h>
#include <AbstractViewStateInterface.h>
#include <OffscreenQmlSurface.h>
// #include "Application.h"
// #include "GeometryUtil.h"
static const float DPI = 30.47f;
static const float METERS_TO_INCHES = 39.3701f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
QString const Web3DOverlay::TYPE = "web3d";
Web3DOverlay::Web3DOverlay() : _dpi(DPI) { }
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
Billboard3DOverlay(Web3DOverlay),
_url(Web3DOverlay->_url),
_dpi(Web3DOverlay->_dpi),
_resolution(Web3DOverlay->_resolution)
{
}
Web3DOverlay::~Web3DOverlay() {
if (_webSurface) {
_webSurface->pause();
_webSurface->disconnect(_connection);
// The lifetime of the QML surface MUST be managed by the main thread
// Additionally, we MUST use local variables copied by value, rather than
// member variables, since they would implicitly refer to a this that
// is no longer valid
auto webSurface = _webSurface;
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
webSurface->deleteLater();
});
}
}
void Web3DOverlay::update(float deltatime) {
applyTransformTo(_transform);
}
void Web3DOverlay::render(RenderArgs* args) {
if (!_visible || !getParentVisible()) {
return;
}
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->resize(QSize(_resolution.x, _resolution.y));
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
currentContext->makeCurrent(currentSurface);
}
vec2 size = _resolution / _dpi * INCHES_TO_METERS;
vec2 halfSize = size / 2.0f;
vec4 color(toGlm(getColor()), getAlpha());
applyTransformTo(_transform, true);
Transform transform = _transform;
if (glm::length2(getDimensions()) != 1.0f) {
transform.postScale(vec3(getDimensions(), 1.0f));
}
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
if (_texture) {
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
} else {
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
}
batch.setModelTransform(transform);
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true, false, false, true);
DependencyManager::get<GeometryCache>()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
void Web3DOverlay::setProperties(const QScriptValue &properties) {
Billboard3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
QString newURL = urlValue.toVariant().toString();
if (newURL != _url) {
setURL(newURL);
}
}
QScriptValue resolution = properties.property("resolution");
if (resolution.isValid()) {
vec2FromScriptValue(resolution, _resolution);
}
QScriptValue dpi = properties.property("dpi");
if (dpi.isValid()) {
_dpi = dpi.toVariant().toFloat();
}
}
QScriptValue Web3DOverlay::getProperty(const QString& property) {
if (property == "url") {
return _url;
}
if (property == "dpi") {
return _dpi;
}
return Billboard3DOverlay::getProperty(property);
}
void Web3DOverlay::setURL(const QString& url) {
_url = url;
_isLoaded = false;
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
//// Make sure position and rotation is updated.
applyTransformTo(_transform, true);
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance);
}
Web3DOverlay* Web3DOverlay::createClone() const {
return new Web3DOverlay(this);
}

View file

@ -0,0 +1,50 @@
//
// Created by Bradley Austin Davis on 2015/08/31
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Web3DOverlay_h
#define hifi_Web3DOverlay_h
#include "Billboard3DOverlay.h"
class OffscreenQmlSurface;
class Web3DOverlay : public Billboard3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Web3DOverlay();
Web3DOverlay(const Web3DOverlay* Web3DOverlay);
virtual ~Web3DOverlay();
virtual void render(RenderArgs* args);
virtual void update(float deltatime);
// setters
void setURL(const QString& url);
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual Web3DOverlay* createClone() const;
private:
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;
uint32_t _texture{ 0 };
QString _url;
float _dpi;
vec2 _resolution{ 640, 480 };
};
#endif // hifi_Web3DOverlay_h

View file

@ -313,7 +313,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
QByteArray samples = sound->getByteArray(); QByteArray samples = sound->getByteArray();
if (stretchFactor == 1.0f) { if (stretchFactor == 1.0f) {
return playSound(samples, options, NULL); return playSoundAndDelete(samples, options, NULL);
} }
soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
@ -333,9 +333,16 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate;
resampled = samples; resampled = samples;
} }
return playSound(resampled, options, NULL); return playSoundAndDelete(resampled, options, NULL);
} }
AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
AudioInjector* sound = playSound(buffer, options, localInterface);
sound->triggerDeleteAfterFinish();
return sound;
}
AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
QThread* injectorThread = new QThread(); QThread* injectorThread = new QThread();
injectorThread->setObjectName("Audio Injector Thread"); injectorThread->setObjectName("Audio Injector Thread");

View file

@ -46,6 +46,7 @@ public:
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position);

View file

@ -31,10 +31,6 @@
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
const float MIN_ROTATION_DOT = 0.9999999f;
using namespace std; using namespace std;
const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f); const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f);
@ -145,7 +141,7 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) {
_handPosition = glm::inverse(getOrientation()) * (handPosition - _position); _handPosition = glm::inverse(getOrientation()) * (handPosition - _position);
} }
QByteArray AvatarData::toByteArray(bool cullSmallChanges) { QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
// TODO: DRY this up to a shared method // TODO: DRY this up to a shared method
// that can pack any type given the number of bytes // that can pack any type given the number of bytes
// and return the number of bytes to push the pointer // and return the number of bytes to push the pointer
@ -244,11 +240,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
_lastSentJointData.resize(_jointData.size()); _lastSentJointData.resize(_jointData.size());
// foreach (const JointData& data, _jointData) {
for (int i=0; i < _jointData.size(); i++) { for (int i=0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i); const JointData& data = _jointData.at(i);
if (_lastSentJointData[i].rotation != data.rotation) { if (sendAll || _lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= MIN_ROTATION_DOT) { if (sendAll ||
!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
validity |= (1 << validityBit); validity |= (1 << validityBit);
} }
} }
@ -267,7 +264,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
const JointData& data = _jointData[ i ]; const JointData& data = _jointData[ i ];
if (validity & (1 << validityBit)) { if (validity & (1 << validityBit)) {
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation); destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation);
_lastSentJointData[i].rotation = data.rotation;
} }
if (++validityBit == BITS_IN_BYTE) { if (++validityBit == BITS_IN_BYTE) {
validityBit = 0; validityBit = 0;
@ -278,6 +274,20 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
return avatarDataByteArray.left(destinationBuffer - startPosition); return avatarDataByteArray.left(destinationBuffer - startPosition);
} }
void AvatarData::doneEncoding(bool cullSmallChanges) {
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
_lastSentJointData.resize(_jointData.size());
for (int i = 0; i < _jointData.size(); i ++) {
const JointData& data = _jointData[ i ];
if (_lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
_lastSentJointData[i].rotation = data.rotation;
}
}
}
}
bool AvatarData::shouldLogError(const quint64& now) { bool AvatarData::shouldLogError(const quint64& now) {
if (now > _errorLogExpiry) { if (now > _errorLogExpiry) {
_errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
@ -1083,7 +1093,11 @@ void AvatarData::setJointMappingsFromNetworkReply() {
void AvatarData::sendAvatarDataPacket() { void AvatarData::sendAvatarDataPacket() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
QByteArray avatarByteArray = toByteArray(true); // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
// this is to guard against a joint moving once, the packet getting lost, and the joint never moving again.
bool sendFullUpdate = randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO;
QByteArray avatarByteArray = toByteArray(true, sendFullUpdate);
doneEncoding(true);
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size()); auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
avatarPacket->write(avatarByteArray); avatarPacket->write(avatarByteArray);

View file

@ -111,6 +111,11 @@ const int AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS = 5000;
// See also static AvatarData::defaultFullAvatarModelUrl(). // See also static AvatarData::defaultFullAvatarModelUrl().
const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default"); const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
// how often should we send a full report about joint rotations, even if they haven't changed?
const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02;
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
const float AVATAR_MIN_ROTATION_DOT = 0.9999999f;
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
// This is the start location in the Sandbox (xyz: 6270, 211, 6000). // This is the start location in the Sandbox (xyz: 6270, 211, 6000).
@ -171,7 +176,8 @@ public:
glm::vec3 getHandPosition() const; glm::vec3 getHandPosition() const;
void setHandPosition(const glm::vec3& handPosition); void setHandPosition(const glm::vec3& handPosition);
virtual QByteArray toByteArray(bool cullSmallChanges); virtual QByteArray toByteArray(bool cullSmallChanges, bool sendAll);
virtual void doneEncoding(bool cullSmallChanges);
/// \return true if an error should be logged /// \return true if an error should be logged
bool shouldLogError(const quint64& now); bool shouldLogError(const quint64& now);

View file

@ -57,6 +57,11 @@ public:
// Rendering support // Rendering support
// Stop requesting renders, but don't do full deactivation
// needed to work around the issues caused by Oculus
// processing messages in the middle of submitFrame
virtual void stop() = 0;
/** /**
* Called by the application before the frame rendering. Can be used for * Called by the application before the frame rendering. Can be used for
* render timing related calls (for instance, the Oculus begin frame timing * render timing related calls (for instance, the Oculus begin frame timing
@ -120,6 +125,7 @@ public:
virtual void resetSensors() {} virtual void resetSensors() {}
virtual float devicePixelRatio() { return 1.0; } virtual float devicePixelRatio() { return 1.0; }
static const QString& MENU_PATH(); static const QString& MENU_PATH();
signals: signals:
void recommendedFramebufferSizeChanged(const QSize & size); void recommendedFramebufferSizeChanged(const QSize & size);

View file

@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {}
void NullDisplayPlugin::activate() {} void NullDisplayPlugin::activate() {}
void NullDisplayPlugin::deactivate() {} void NullDisplayPlugin::deactivate() {}
void NullDisplayPlugin::stop() {}

View file

@ -17,6 +17,7 @@ public:
void activate() override; void activate() override;
void deactivate() override; void deactivate() override;
void stop() override;
virtual glm::uvec2 getRecommendedRenderSize() const override; virtual glm::uvec2 getRecommendedRenderSize() const override;
virtual bool hasFocus() const override; virtual bool hasFocus() const override;

View file

@ -16,7 +16,9 @@
OpenGLDisplayPlugin::OpenGLDisplayPlugin() { OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
connect(&_timer, &QTimer::timeout, this, [&] { connect(&_timer, &QTimer::timeout, this, [&] {
emit requestRender(); if (_active) {
emit requestRender();
}
}); });
} }
@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() {
} }
void OpenGLDisplayPlugin::activate() { void OpenGLDisplayPlugin::activate() {
_active = true;
_timer.start(1); _timer.start(1);
} }
void OpenGLDisplayPlugin::stop() {
_active = false;
_timer.stop();
}
void OpenGLDisplayPlugin::deactivate() { void OpenGLDisplayPlugin::deactivate() {
_active = false;
_timer.stop(); _timer.stop();
makeCurrent(); makeCurrent();

View file

@ -25,7 +25,7 @@ public:
virtual void activate() override; virtual void activate() override;
virtual void deactivate() override; virtual void deactivate() override;
virtual void stop() override;
virtual bool eventFilter(QObject* receiver, QEvent* event) override; virtual bool eventFilter(QObject* receiver, QEvent* event) override;
virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override;
@ -44,6 +44,7 @@ protected:
mutable QTimer _timer; mutable QTimer _timer;
ProgramPtr _program; ProgramPtr _program;
ShapeWrapperPtr _plane; ShapeWrapperPtr _plane;
bool _active{ false };
bool _vsyncSupported{ false }; bool _vsyncSupported{ false };
}; };

View file

@ -325,19 +325,18 @@ void OculusDisplayPlugin::customizeContext() {
//_texture = DependencyManager::get<TextureCache>()-> //_texture = DependencyManager::get<TextureCache>()->
// getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png");
uvec2 mirrorSize = toGlm(_window->geometry().size()); uvec2 mirrorSize = toGlm(_window->geometry().size());
_mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd));
_mirrorFbo->Init(mirrorSize);
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
_sceneFbo->Init(getRecommendedRenderSize()); _sceneFbo->Init(getRecommendedRenderSize());
#endif #endif
enableVsync(false);
isVsyncEnabled();
} }
void OculusDisplayPlugin::deactivate() { void OculusDisplayPlugin::deactivate() {
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
makeCurrent(); makeCurrent();
_sceneFbo.reset(); _sceneFbo.reset();
_mirrorFbo.reset();
doneCurrent(); doneCurrent();
PerformanceTimer::setActive(false); PerformanceTimer::setActive(false);
@ -350,11 +349,6 @@ void OculusDisplayPlugin::deactivate() {
} }
void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
static bool inDisplay = false;
if (inDisplay) {
return;
}
inDisplay = true;
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
using namespace oglplus; using namespace oglplus;
// Need to make sure only the display plugin is responsible for // Need to make sure only the display plugin is responsible for
@ -383,12 +377,14 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
the UI visible in the output window (unlikely). This should be done before the UI visible in the output window (unlikely). This should be done before
_sceneFbo->Increment or we're be using the wrong texture _sceneFbo->Increment or we're be using the wrong texture
*/ */
_sceneFbo->Bound(Framebuffer::Target::Read, [&] { if (_enableMirror) {
glBlitFramebuffer( _sceneFbo->Bound(Framebuffer::Target::Read, [&] {
0, 0, _sceneFbo->size.x, _sceneFbo->size.y, glBlitFramebuffer(
0, 0, windowSize.x, windowSize.y, 0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST); 0, 0, windowSize.x, windowSize.y,
}); GL_COLOR_BUFFER_BIT, GL_NEAREST);
});
}
{ {
PerformanceTimer("OculusSubmit"); PerformanceTimer("OculusSubmit");
@ -409,6 +405,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
The other alternative for mirroring is to use the Oculus mirror texture support, which The other alternative for mirroring is to use the Oculus mirror texture support, which
will contain the post-distorted and fully composited scene regardless of how many layers will contain the post-distorted and fully composited scene regardless of how many layers
we send. we send.
Currently generates an error.
*/ */
//auto mirrorSize = _mirrorFbo->size; //auto mirrorSize = _mirrorFbo->size;
//_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] {
@ -420,21 +417,10 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
++_frameIndex; ++_frameIndex;
#endif #endif
inDisplay = false;
} }
// Pass input events on to the application // Pass input events on to the application
bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
#if (OVR_MAJOR_VERSION >= 6)
if (event->type() == QEvent::Resize) {
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height();
auto newSize = toGlm(resizeEvent->size());
makeCurrent();
_mirrorFbo->Resize(newSize);
doneCurrent();
}
#endif
return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); return WindowOpenGLDisplayPlugin::eventFilter(receiver, event);
} }
@ -444,7 +430,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
otherwise the swapbuffer delay will interefere with the framerate of the headset otherwise the swapbuffer delay will interefere with the framerate of the headset
*/ */
void OculusDisplayPlugin::finishFrame() { void OculusDisplayPlugin::finishFrame() {
//swapBuffers(); if (_enableMirror) {
swapBuffers();
}
doneCurrent(); doneCurrent();
}; };

View file

@ -58,6 +58,7 @@ private:
mat4 _compositeEyeProjections[2]; mat4 _compositeEyeProjections[2];
uvec2 _desiredFramebufferSize; uvec2 _desiredFramebufferSize;
ovrTrackingState _trackingState; ovrTrackingState _trackingState;
bool _enableMirror{ false };
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
ovrHmd _hmd; ovrHmd _hmd;
@ -70,7 +71,6 @@ private:
ovrLayerEyeFov& getSceneLayer(); ovrLayerEyeFov& getSceneLayer();
ovrHmdDesc _hmdDesc; ovrHmdDesc _hmdDesc;
SwapFboPtr _sceneFbo; SwapFboPtr _sceneFbo;
MirrorFboPtr _mirrorFbo;
ovrLayerEyeFov _sceneLayer; ovrLayerEyeFov _sceneLayer;
#endif #endif
#if (OVR_MAJOR_VERSION == 7) #if (OVR_MAJOR_VERSION == 7)

View file

@ -18,6 +18,26 @@
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "Quat.h" #include "Quat.h"
quat Quat::normalize(const glm::quat& q) {
return glm::normalize(q);
}
glm::quat Quat::rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
return ::rotationBetween(v1, v2);
}
glm::quat Quat::lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up) {
return glm::quat_cast(glm::lookAt(eye, center, up));
}
glm::quat Quat::lookAtSimple(const glm::vec3& eye, const glm::vec3& center) {
auto dir = glm::normalize(center - eye);
// if the direction is nearly aligned with the Y axis, then use the X axis for 'up'
if (dir.x < 0.001f && dir.z < 0.001f) {
return lookAt(eye, center, Vectors::UNIT_X);
}
return lookAt(eye, center, Vectors::UNIT_Y);
}
glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) {
return q1 * q2; return q1 * q2;

View file

@ -25,6 +25,10 @@ class Quat : public QObject {
public slots: public slots:
glm::quat multiply(const glm::quat& q1, const glm::quat& q2); glm::quat multiply(const glm::quat& q1, const glm::quat& q2);
glm::quat normalize(const glm::quat& q);
glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up);
glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
glm::quat fromVec3Degrees(const glm::vec3& vec3); // degrees glm::quat fromVec3Degrees(const glm::vec3& vec3); // degrees
glm::quat fromVec3Radians(const glm::vec3& vec3); // radians glm::quat fromVec3Radians(const glm::vec3& vec3); // radians
glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees

View file

@ -599,7 +599,8 @@ void ScriptEngine::run() {
/ (1000 * 1000)) + 0.5); / (1000 * 1000)) + 0.5);
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
QByteArray avatarByteArray = _avatarData->toByteArray(true); QByteArray avatarByteArray = _avatarData->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
_avatarData->doneEncoding(true);
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size()); auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size());
avatarPacket->write(avatarByteArray); avatarPacket->write(avatarByteArray);

View file

@ -13,66 +13,26 @@
#include <QDebug> #include <QDebug>
#include <GLMHelpers.h>
#include "ScriptEngineLogging.h" #include "ScriptEngineLogging.h"
#include "NumericalConstants.h" #include "NumericalConstants.h"
#include "Vec3.h" #include "Vec3.h"
glm::vec3 Vec3::reflect(const glm::vec3& v1, const glm::vec3& v2) {
return glm::reflect(v1, v2);
}
glm::vec3 Vec3::cross(const glm::vec3& v1, const glm::vec3& v2) {
return glm::cross(v1,v2);
}
float Vec3::dot(const glm::vec3& v1, const glm::vec3& v2) {
return glm::dot(v1,v2);
}
glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) {
return v1 * f;
}
glm::vec3 Vec3::multiply(float f, const glm::vec3& v1) {
return v1 * f;
}
glm::vec3 Vec3::multiplyQbyV(const glm::quat& q, const glm::vec3& v) {
return q * v;
}
glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) {
return v1 + v2;
}
glm::vec3 Vec3::subtract(const glm::vec3& v1, const glm::vec3& v2) {
return v1 - v2;
}
float Vec3::length(const glm::vec3& v) {
return glm::length(v);
}
float Vec3::distance(const glm::vec3& v1, const glm::vec3& v2) {
return glm::distance(v1, v2);
}
float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) {
return glm::degrees(glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3))); float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3));
return glm::degrees(radians);
} }
glm::vec3 Vec3::normalize(const glm::vec3& v) {
return glm::normalize(v);
}
glm::vec3 Vec3::mix(const glm::vec3& v1, const glm::vec3& v2, float m) {
return glm::mix(v1, v2, m);
}
void Vec3::print(const QString& lable, const glm::vec3& v) { void Vec3::print(const QString& lable, const glm::vec3& v) {
qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z; qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z;
} }
bool Vec3::equal(const glm::vec3& v1, const glm::vec3& v2) { bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) {
return v1 == v2; float distanceSquared = glm::length2(v1 - v2);
return (epsilon*epsilon) >= distanceSquared;
} }
glm::vec3 Vec3::toPolar(const glm::vec3& v) { glm::vec3 Vec3::toPolar(const glm::vec3& v) {

View file

@ -11,40 +11,59 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#pragma once
#ifndef hifi_Vec3_h #ifndef hifi_Vec3_h
#define hifi_Vec3_h #define hifi_Vec3_h
#include <glm/glm.hpp> #include <QtCore/QObject>
#include <glm/gtc/quaternion.hpp> #include <QtCore/QString>
#include <QObject> #include "GLMHelpers.h"
#include <QString>
/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API /// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
class Vec3 : public QObject { class Vec3 : public QObject {
Q_OBJECT Q_OBJECT
public slots: public slots:
glm::vec3 reflect(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 reflect(const glm::vec3& v1, const glm::vec3& v2) { return glm::reflect(v1, v2); }
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2) { return glm::cross(v1, v2); }
float dot(const glm::vec3& v1, const glm::vec3& v2); float dot(const glm::vec3& v1, const glm::vec3& v2) { return glm::dot(v1, v2); }
glm::vec3 multiply(const glm::vec3& v1, float f); glm::vec3 multiply(const glm::vec3& v1, float f) { return v1 * f; }
glm::vec3 multiply(float, const glm::vec3& v1); glm::vec3 multiply(float f, const glm::vec3& v1) { return v1 * f; }
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v); glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v) { return q * v; }
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2) { return v1 + v2; }
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2); glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2) { return v1 - v2; }
float length(const glm::vec3& v); float length(const glm::vec3& v) { return glm::length(v); }
float distance(const glm::vec3& v1, const glm::vec3& v2); float distance(const glm::vec3& v1, const glm::vec3& v2) { return glm::distance(v1, v2); }
float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3); float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3);
glm::vec3 normalize(const glm::vec3& v); glm::vec3 normalize(const glm::vec3& v) { return glm::normalize(v); };
glm::vec3 mix(const glm::vec3& v1, const glm::vec3& v2, float m); glm::vec3 mix(const glm::vec3& v1, const glm::vec3& v2, float m) { return glm::mix(v1, v2, m); }
void print(const QString& lable, const glm::vec3& v); void print(const QString& label, const glm::vec3& v);
bool equal(const glm::vec3& v1, const glm::vec3& v2); bool equal(const glm::vec3& v1, const glm::vec3& v2) { return v1 == v2; }
bool withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon);
// FIXME misnamed, should be 'spherical' or 'euler' depending on the implementation
glm::vec3 toPolar(const glm::vec3& v); glm::vec3 toPolar(const glm::vec3& v);
glm::vec3 fromPolar(const glm::vec3& polar); glm::vec3 fromPolar(const glm::vec3& polar);
glm::vec3 fromPolar(float elevation, float azimuth); glm::vec3 fromPolar(float elevation, float azimuth);
const glm::vec3& UNIT_X() { return Vectors::UNIT_X; }
const glm::vec3& UNIT_Y() { return Vectors::UNIT_Y; }
const glm::vec3& UNIT_Z() { return Vectors::UNIT_Z; }
const glm::vec3& UNIT_NEG_X() { return Vectors::UNIT_NEG_X; }
const glm::vec3& UNIT_NEG_Y() { return Vectors::UNIT_NEG_Y; }
const glm::vec3& UNIT_NEG_Z() { return Vectors::UNIT_NEG_Z; }
const glm::vec3& UNIT_XY() { return Vectors::UNIT_XY; }
const glm::vec3& UNIT_XZ() { return Vectors::UNIT_XZ; }
const glm::vec3& UNIT_YZ() { return Vectors::UNIT_YZ; }
const glm::vec3& UNIT_XYZ() { return Vectors::UNIT_XYZ; }
const glm::vec3& FLOAT_MAX() { return Vectors::MAX; }
const glm::vec3& FLOAT_MIN() { return Vectors::MIN; }
const glm::vec3& ZERO() { return Vectors::ZERO; }
const glm::vec3& ONE() { return Vectors::ONE; }
const glm::vec3& TWO() { return Vectors::TWO; }
const glm::vec3& HALF() { return Vectors::HALF; }
const glm::vec3& RIGHT() { return Vectors::RIGHT; }
const glm::vec3& UP() { return Vectors::UNIT_X; }
const glm::vec3& FRONT() { return Vectors::FRONT; }
}; };
#endif // hifi_Vec3_h #endif // hifi_Vec3_h

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis on 2015/09/01
// Copyright 2013-2105 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
//
// Simulates a java finally block by executing a lambda when an instance leaves
// scope
#include <functional>
#pragma once
#ifndef hifi_Finally_h
#define hifi_Finally_h
class Finally {
public:
template <typename F>
Finally(F f) : _f(f) {}
~Finally() { _f(); }
private:
std::function<void()> _f;
};
#endif

View file

@ -27,6 +27,8 @@ const vec3 Vectors::MAX{ FLT_MAX };
const vec3 Vectors::MIN{ -FLT_MAX }; const vec3 Vectors::MIN{ -FLT_MAX };
const vec3 Vectors::ZERO{ 0.0f }; const vec3 Vectors::ZERO{ 0.0f };
const vec3 Vectors::ONE{ 1.0f }; const vec3 Vectors::ONE{ 1.0f };
const vec3 Vectors::TWO{ 2.0f };
const vec3 Vectors::HALF{ 0.5f };
const vec3& Vectors::RIGHT = Vectors::UNIT_X; const vec3& Vectors::RIGHT = Vectors::UNIT_X;
const vec3& Vectors::UP = Vectors::UNIT_Y; const vec3& Vectors::UP = Vectors::UNIT_Y;
const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z; const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z;

View file

@ -64,12 +64,13 @@ public:
static const vec3 UNIT_XY; static const vec3 UNIT_XY;
static const vec3 UNIT_XZ; static const vec3 UNIT_XZ;
static const vec3 UNIT_YZ; static const vec3 UNIT_YZ;
static const vec3 UNIT_ZX;
static const vec3 UNIT_XYZ; static const vec3 UNIT_XYZ;
static const vec3 MAX; static const vec3 MAX;
static const vec3 MIN; static const vec3 MIN;
static const vec3 ZERO; static const vec3 ZERO;
static const vec3 ONE; static const vec3 ONE;
static const vec3 TWO;
static const vec3 HALF;
static const vec3& RIGHT; static const vec3& RIGHT;
static const vec3& UP; static const vec3& UP;
static const vec3& FRONT; static const vec3& FRONT;

View file

@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
} }
void xColorFromScriptValue(const QScriptValue &object, xColor& color) { void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
color.red = object.property("red").toVariant().toInt(); if (!object.isValid()) {
color.green = object.property("green").toVariant().toInt(); return;
color.blue = object.property("blue").toVariant().toInt(); }
if (object.isNumber()) {
color.red = color.green = color.blue = (uint8_t)object.toUInt32();
} else if (object.isString()) {
QColor qcolor(object.toString());
if (qcolor.isValid()) {
color.red = (uint8_t)qcolor.red();
color.blue = (uint8_t)qcolor.blue();
color.green = (uint8_t)qcolor.green();
}
} else {
color.red = object.property("red").toVariant().toInt();
color.green = object.property("green").toVariant().toInt();
color.blue = object.property("blue").toVariant().toInt();
}
} }
QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) {