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

View file

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

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

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

View file

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

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
SCALE = 0.5;
@ -16,60 +6,9 @@ STICK_LENGTH = 0.24 * SCALE;
DEBUG_MAGSTICKS = true;
CUSTOM_DATA_NAME = "magBalls";
BALL_NAME = "MagBall";
EDGE_NAME = "MagStick";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,
green: 255,
blue: 255,
},
BLACK: {
red: 0,
green: 0,
blue: 0,
},
GREY: {
red: 128,
green: 128,
blue: 128,
},
RED: {
red: 255,
green: 0,
blue: 0
},
BLUE: {
red: 0,
green: 0,
blue: 255
},
GREEN: {
red: 0,
green: 255,
blue: 0
},
CYAN: {
red: 0,
green: 255,
blue: 255
},
YELLOW: {
red: 255,
green: 255,
blue: 0
},
MAGENTA: {
red: 255,
green: 0,
blue: 255
}
}
BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
@ -136,5 +75,3 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
// ignoreCollisions: true,
// collisionsWillMove: false
// }

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -31,10 +31,6 @@
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
const float MIN_ROTATION_DOT = 0.9999999f;
using namespace std;
const glm::vec3 DEFAULT_LOCAL_AABOX_CORNER(-0.5f);
@ -145,7 +141,7 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) {
_handPosition = glm::inverse(getOrientation()) * (handPosition - _position);
}
QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
// TODO: DRY this up to a shared method
// that can pack any type given the number of bytes
// and return the number of bytes to push the pointer
@ -244,11 +240,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
_lastSentJointData.resize(_jointData.size());
// foreach (const JointData& data, _jointData) {
for (int i=0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i);
if (_lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges || fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= MIN_ROTATION_DOT) {
if (sendAll || _lastSentJointData[i].rotation != data.rotation) {
if (sendAll ||
!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
validity |= (1 << validityBit);
}
}
@ -267,7 +264,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
const JointData& data = _jointData[ i ];
if (validity & (1 << validityBit)) {
destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation);
_lastSentJointData[i].rotation = data.rotation;
}
if (++validityBit == BITS_IN_BYTE) {
validityBit = 0;
@ -278,6 +274,20 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges) {
return avatarDataByteArray.left(destinationBuffer - startPosition);
}
void AvatarData::doneEncoding(bool cullSmallChanges) {
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
_lastSentJointData.resize(_jointData.size());
for (int i = 0; i < _jointData.size(); i ++) {
const JointData& data = _jointData[ i ];
if (_lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
_lastSentJointData[i].rotation = data.rotation;
}
}
}
}
bool AvatarData::shouldLogError(const quint64& now) {
if (now > _errorLogExpiry) {
_errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
@ -1083,7 +1093,11 @@ void AvatarData::setJointMappingsFromNetworkReply() {
void AvatarData::sendAvatarDataPacket() {
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());
avatarPacket->write(avatarByteArray);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,6 +18,26 @@
#include "ScriptEngineLogging.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) {
return q1 * q2;

View file

@ -25,6 +25,10 @@ class Quat : public QObject {
public slots:
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 fromVec3Radians(const glm::vec3& vec3); // radians
glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees

View file

@ -599,7 +599,8 @@ void ScriptEngine::run() {
/ (1000 * 1000)) + 0.5);
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());
avatarPacket->write(avatarByteArray);

View file

@ -13,66 +13,26 @@
#include <QDebug>
#include <GLMHelpers.h>
#include "ScriptEngineLogging.h"
#include "NumericalConstants.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) {
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) {
qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z;
}
bool Vec3::equal(const glm::vec3& v1, const glm::vec3& v2) {
return v1 == v2;
bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) {
float distanceSquared = glm::length2(v1 - v2);
return (epsilon*epsilon) >= distanceSquared;
}
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
//
#pragma once
#ifndef hifi_Vec3_h
#define hifi_Vec3_h
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QObject>
#include <QString>
#include "GLMHelpers.h"
/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
class Vec3 : public QObject {
Q_OBJECT
public slots:
glm::vec3 reflect(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2);
float dot(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 multiply(const glm::vec3& v1, float f);
glm::vec3 multiply(float, const glm::vec3& v1);
glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v);
glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2);
float length(const glm::vec3& v);
float distance(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) { return glm::cross(v1, 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) { return v1 * f; }
glm::vec3 multiply(float f, const glm::vec3& v1) { return v1 * f; }
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) { return v1 + v2; }
glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2) { return v1 - v2; }
float length(const glm::vec3& v) { return glm::length(v); }
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);
glm::vec3 normalize(const glm::vec3& v);
glm::vec3 mix(const glm::vec3& v1, const glm::vec3& v2, float m);
void print(const QString& lable, const glm::vec3& v);
bool equal(const glm::vec3& v1, const glm::vec3& v2);
glm::vec3 normalize(const glm::vec3& v) { return glm::normalize(v); };
glm::vec3 mix(const glm::vec3& v1, const glm::vec3& v2, float m) { return glm::mix(v1, v2, m); }
void print(const QString& label, const glm::vec3& v);
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 fromPolar(const glm::vec3& polar);
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

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::ZERO{ 0.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::UP = Vectors::UNIT_Y;
const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z;

View file

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

View file

@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
}
void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
color.red = object.property("red").toVariant().toInt();
color.green = object.property("green").toVariant().toInt();
color.blue = object.property("blue").toVariant().toInt();
if (!object.isValid()) {
return;
}
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) {