mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-04 20:37:11 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into stack-manager
This commit is contained in:
commit
7b748dc59a
21 changed files with 691 additions and 461 deletions
|
@ -37,9 +37,21 @@ var BUMPER_ON_VALUE = 0.5;
|
||||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object
|
var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object
|
||||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||||
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
|
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
|
||||||
var NO_INTERSECT_COLOR = { red: 10, green: 10, blue: 255}; // line color when pick misses
|
var NO_INTERSECT_COLOR = {
|
||||||
var INTERSECT_COLOR = { red: 250, green: 10, blue: 10}; // line color when pick hits
|
red: 10,
|
||||||
var LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000,z: 1000};
|
green: 10,
|
||||||
|
blue: 255
|
||||||
|
}; // line color when pick misses
|
||||||
|
var INTERSECT_COLOR = {
|
||||||
|
red: 250,
|
||||||
|
green: 10,
|
||||||
|
blue: 10
|
||||||
|
}; // line color when pick hits
|
||||||
|
var LINE_ENTITY_DIMENSIONS = {
|
||||||
|
x: 1000,
|
||||||
|
y: 1000,
|
||||||
|
z: 1000
|
||||||
|
};
|
||||||
var LINE_LENGTH = 500;
|
var LINE_LENGTH = 500;
|
||||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||||
|
|
||||||
|
@ -84,12 +96,13 @@ var ACTION_TTL_REFRESH = 5;
|
||||||
var PICKS_PER_SECOND_PER_HAND = 5;
|
var PICKS_PER_SECOND_PER_HAND = 5;
|
||||||
var MSECS_PER_SEC = 1000.0;
|
var MSECS_PER_SEC = 1000.0;
|
||||||
var GRABBABLE_PROPERTIES = ["position",
|
var GRABBABLE_PROPERTIES = ["position",
|
||||||
"rotation",
|
"rotation",
|
||||||
"gravity",
|
"gravity",
|
||||||
"ignoreForCollisions",
|
"ignoreForCollisions",
|
||||||
"collisionsWillMove",
|
"collisionsWillMove",
|
||||||
"locked",
|
"locked",
|
||||||
"name"];
|
"name"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js
|
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js
|
||||||
|
@ -100,7 +113,7 @@ var DEFAULT_GRABBABLE_DATA = {
|
||||||
invertSolidWhileHeld: false
|
invertSolidWhileHeld: false
|
||||||
};
|
};
|
||||||
|
|
||||||
var disabledHand ='none';
|
var disabledHand = 'none';
|
||||||
|
|
||||||
|
|
||||||
// states for the state machine
|
// states for the state machine
|
||||||
|
@ -125,40 +138,40 @@ var STATE_EQUIP_SPRING = 16;
|
||||||
|
|
||||||
function stateToName(state) {
|
function stateToName(state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_OFF:
|
case STATE_OFF:
|
||||||
return "off";
|
return "off";
|
||||||
case STATE_SEARCHING:
|
case STATE_SEARCHING:
|
||||||
return "searching";
|
return "searching";
|
||||||
case STATE_DISTANCE_HOLDING:
|
case STATE_DISTANCE_HOLDING:
|
||||||
return "distance_holding";
|
return "distance_holding";
|
||||||
case STATE_CONTINUE_DISTANCE_HOLDING:
|
case STATE_CONTINUE_DISTANCE_HOLDING:
|
||||||
return "continue_distance_holding";
|
return "continue_distance_holding";
|
||||||
case STATE_NEAR_GRABBING:
|
case STATE_NEAR_GRABBING:
|
||||||
return "near_grabbing";
|
return "near_grabbing";
|
||||||
case STATE_CONTINUE_NEAR_GRABBING:
|
case STATE_CONTINUE_NEAR_GRABBING:
|
||||||
return "continue_near_grabbing";
|
return "continue_near_grabbing";
|
||||||
case STATE_NEAR_TRIGGER:
|
case STATE_NEAR_TRIGGER:
|
||||||
return "near_trigger";
|
return "near_trigger";
|
||||||
case STATE_CONTINUE_NEAR_TRIGGER:
|
case STATE_CONTINUE_NEAR_TRIGGER:
|
||||||
return "continue_near_trigger";
|
return "continue_near_trigger";
|
||||||
case STATE_FAR_TRIGGER:
|
case STATE_FAR_TRIGGER:
|
||||||
return "far_trigger";
|
return "far_trigger";
|
||||||
case STATE_CONTINUE_FAR_TRIGGER:
|
case STATE_CONTINUE_FAR_TRIGGER:
|
||||||
return "continue_far_trigger";
|
return "continue_far_trigger";
|
||||||
case STATE_RELEASE:
|
case STATE_RELEASE:
|
||||||
return "release";
|
return "release";
|
||||||
case STATE_EQUIP_SEARCHING:
|
case STATE_EQUIP_SEARCHING:
|
||||||
return "equip_searching";
|
return "equip_searching";
|
||||||
case STATE_EQUIP:
|
case STATE_EQUIP:
|
||||||
return "equip";
|
return "equip";
|
||||||
case STATE_CONTINUE_EQUIP_BD:
|
case STATE_CONTINUE_EQUIP_BD:
|
||||||
return "continue_equip_bd";
|
return "continue_equip_bd";
|
||||||
case STATE_CONTINUE_EQUIP:
|
case STATE_CONTINUE_EQUIP:
|
||||||
return "continue_equip";
|
return "continue_equip";
|
||||||
case STATE_WAITING_FOR_BUMPER_RELEASE:
|
case STATE_WAITING_FOR_BUMPER_RELEASE:
|
||||||
return "waiting_for_bumper_release";
|
return "waiting_for_bumper_release";
|
||||||
case STATE_EQUIP_SPRING:
|
case STATE_EQUIP_SPRING:
|
||||||
return "state_equip_spring";
|
return "state_equip_spring";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "unknown";
|
return "unknown";
|
||||||
|
@ -187,7 +200,6 @@ function entityIsGrabbedByOther(entityID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function MyController(hand) {
|
function MyController(hand) {
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
if (this.hand === RIGHT_HAND) {
|
if (this.hand === RIGHT_HAND) {
|
||||||
|
@ -211,8 +223,17 @@ function MyController(hand) {
|
||||||
this.rawTriggerValue = 0;
|
this.rawTriggerValue = 0;
|
||||||
this.rawBumperValue = 0;
|
this.rawBumperValue = 0;
|
||||||
|
|
||||||
this.offsetPosition = { x: 0.0, y: 0.0, z: 0.0 };
|
this.offsetPosition = {
|
||||||
this.offsetRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0
|
||||||
|
};
|
||||||
|
this.offsetRotation = {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
w: 1.0
|
||||||
|
};
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
|
@ -277,7 +298,7 @@ function MyController(hand) {
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.debugLine = function(closePoint, farPoint, color){
|
this.debugLine = function(closePoint, farPoint, color) {
|
||||||
Entities.addEntity({
|
Entities.addEntity({
|
||||||
type: "Line",
|
type: "Line",
|
||||||
name: "Grab Debug Entity",
|
name: "Grab Debug Entity",
|
||||||
|
@ -321,16 +342,16 @@ function MyController(hand) {
|
||||||
this.pointer = null;
|
this.pointer = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.triggerPress = function (value) {
|
this.triggerPress = function(value) {
|
||||||
_this.rawTriggerValue = value;
|
_this.rawTriggerValue = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.bumperPress = function (value) {
|
this.bumperPress = function(value) {
|
||||||
_this.rawBumperValue = value;
|
_this.rawBumperValue = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.updateSmoothedTrigger = function () {
|
this.updateSmoothedTrigger = function() {
|
||||||
var triggerValue = this.rawTriggerValue;
|
var triggerValue = this.rawTriggerValue;
|
||||||
// smooth out trigger value
|
// smooth out trigger value
|
||||||
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) +
|
||||||
|
@ -401,7 +422,7 @@ function MyController(hand) {
|
||||||
this.lastPickTime = now;
|
this.lastPickTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index=0; index < pickRays.length; ++index) {
|
for (var index = 0; index < pickRays.length; ++index) {
|
||||||
var pickRay = pickRays[index];
|
var pickRay = pickRays[index];
|
||||||
var directionNormalized = Vec3.normalize(pickRay.direction);
|
var directionNormalized = Vec3.normalize(pickRay.direction);
|
||||||
var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE);
|
var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE);
|
||||||
|
@ -466,10 +487,9 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (! entityIsGrabbedByOther(intersection.entityID)) {
|
} else if (!entityIsGrabbedByOther(intersection.entityID)) {
|
||||||
// don't allow two people to distance grab the same object
|
// don't allow two people to distance grab the same object
|
||||||
if (intersection.properties.collisionsWillMove
|
if (intersection.properties.collisionsWillMove && !intersection.properties.locked) {
|
||||||
&& !intersection.properties.locked) {
|
|
||||||
// the hand is far from the intersected object. go into distance-holding mode
|
// the hand is far from the intersected object. go into distance-holding mode
|
||||||
this.grabbedEntity = intersection.entityID;
|
this.grabbedEntity = intersection.entityID;
|
||||||
if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) {
|
if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) {
|
||||||
|
@ -494,10 +514,18 @@ function MyController(hand) {
|
||||||
Entities.addEntity({
|
Entities.addEntity({
|
||||||
type: "Sphere",
|
type: "Sphere",
|
||||||
name: "Grab Debug Entity",
|
name: "Grab Debug Entity",
|
||||||
dimensions: {x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS},
|
dimensions: {
|
||||||
|
x: GRAB_RADIUS,
|
||||||
|
y: GRAB_RADIUS,
|
||||||
|
z: GRAB_RADIUS
|
||||||
|
},
|
||||||
visible: true,
|
visible: true,
|
||||||
position: handPosition,
|
position: handPosition,
|
||||||
color: { red: 0, green: 255, blue: 0},
|
color: {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
lifetime: 0.1
|
lifetime: 0.1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -604,6 +632,7 @@ function MyController(hand) {
|
||||||
} else {
|
} else {
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||||
}
|
}
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
|
Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,7 +668,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
// the action was set up on a previous call. update the targets.
|
// the action was set up on a previous call. update the targets.
|
||||||
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) *
|
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) *
|
||||||
DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
|
DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
|
||||||
// how far did avatar move this timestep?
|
// how far did avatar move this timestep?
|
||||||
var currentPosition = MyAvatar.position;
|
var currentPosition = MyAvatar.position;
|
||||||
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
|
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
|
||||||
|
@ -688,9 +717,9 @@ function MyController(hand) {
|
||||||
|
|
||||||
// this doubles hand rotation
|
// this doubles hand rotation
|
||||||
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation,
|
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation,
|
||||||
handRotation,
|
handRotation,
|
||||||
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
||||||
Quat.inverse(this.handPreviousRotation));
|
Quat.inverse(this.handPreviousRotation));
|
||||||
this.handPreviousRotation = handRotation;
|
this.handPreviousRotation = handRotation;
|
||||||
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||||
|
|
||||||
|
@ -773,6 +802,8 @@ function MyController(hand) {
|
||||||
this.setState(STATE_CONTINUE_NEAR_GRABBING);
|
this.setState(STATE_CONTINUE_NEAR_GRABBING);
|
||||||
} else {
|
} else {
|
||||||
// equipping
|
// equipping
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]);
|
||||||
|
this.startHandGrasp();
|
||||||
this.setState(STATE_CONTINUE_EQUIP_BD);
|
this.setState(STATE_CONTINUE_EQUIP_BD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,6 +812,9 @@ function MyController(hand) {
|
||||||
} else {
|
} else {
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
||||||
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
|
Entities.callEntityMethod(this.grabbedEntity, "startNearGrab");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -807,6 +841,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) {
|
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) {
|
||||||
this.setState(STATE_CONTINUE_EQUIP_BD);
|
this.setState(STATE_CONTINUE_EQUIP_BD);
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,6 +862,10 @@ function MyController(hand) {
|
||||||
this.currentObjectTime = now;
|
this.currentObjectTime = now;
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
|
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
|
||||||
|
|
||||||
|
if (this.state === STATE_CONTINUE_EQUIP_BD) {
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "continueEquip");
|
||||||
|
}
|
||||||
|
|
||||||
if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) {
|
if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) {
|
||||||
// if less than a 5 seconds left, refresh the actions ttl
|
// if less than a 5 seconds left, refresh the actions ttl
|
||||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||||
|
@ -846,6 +885,8 @@ function MyController(hand) {
|
||||||
if (this.bumperReleased()) {
|
if (this.bumperReleased()) {
|
||||||
this.setState(STATE_RELEASE);
|
this.setState(STATE_RELEASE);
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "unequip");
|
||||||
|
this.endHandGrasp();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -856,8 +897,17 @@ function MyController(hand) {
|
||||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||||
|
|
||||||
// use a spring to pull the object to where it will be when equipped
|
// use a spring to pull the object to where it will be when equipped
|
||||||
var relativeRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
|
var relativeRotation = {
|
||||||
var relativePosition = { x: 0.0, y: 0.0, z: 0.0 };
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
w: 1.0
|
||||||
|
};
|
||||||
|
var relativePosition = {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0
|
||||||
|
};
|
||||||
if (grabbableData.spatialKey.relativePosition) {
|
if (grabbableData.spatialKey.relativePosition) {
|
||||||
relativePosition = grabbableData.spatialKey.relativePosition;
|
relativePosition = grabbableData.spatialKey.relativePosition;
|
||||||
}
|
}
|
||||||
|
@ -913,6 +963,9 @@ function MyController(hand) {
|
||||||
} else {
|
} else {
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
||||||
|
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger");
|
Entities.callEntityMethod(this.grabbedEntity, "startNearTrigger");
|
||||||
this.setState(STATE_CONTINUE_NEAR_TRIGGER);
|
this.setState(STATE_CONTINUE_NEAR_TRIGGER);
|
||||||
};
|
};
|
||||||
|
@ -929,6 +982,7 @@ function MyController(hand) {
|
||||||
} else {
|
} else {
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
Entities.callEntityMethod(this.grabbedEntity, "setLeftHand");
|
||||||
}
|
}
|
||||||
|
Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]);
|
||||||
Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger");
|
Entities.callEntityMethod(this.grabbedEntity, "startFarTrigger");
|
||||||
this.setState(STATE_CONTINUE_FAR_TRIGGER);
|
this.setState(STATE_CONTINUE_FAR_TRIGGER);
|
||||||
};
|
};
|
||||||
|
@ -1040,7 +1094,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.release = function() {
|
this.release = function() {
|
||||||
|
|
||||||
if(this.hand !== disabledHand){
|
if (this.hand !== disabledHand) {
|
||||||
//release the disabled hand when we let go with the main one
|
//release the disabled hand when we let go with the main one
|
||||||
disabledHand = 'none';
|
disabledHand = 'none';
|
||||||
}
|
}
|
||||||
|
@ -1061,6 +1115,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.cleanup = function() {
|
this.cleanup = function() {
|
||||||
this.release();
|
this.release();
|
||||||
|
this.endHandGrasp();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.activateEntity = function(entityID, grabbedProperties) {
|
this.activateEntity = function(entityID, grabbedProperties) {
|
||||||
|
@ -1075,9 +1130,15 @@ function MyController(hand) {
|
||||||
data["gravity"] = grabbedProperties.gravity;
|
data["gravity"] = grabbedProperties.gravity;
|
||||||
data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions;
|
data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions;
|
||||||
data["collisionsWillMove"] = grabbedProperties.collisionsWillMove;
|
data["collisionsWillMove"] = grabbedProperties.collisionsWillMove;
|
||||||
var whileHeldProperties = {gravity: {x:0, y:0, z:0}};
|
var whileHeldProperties = {
|
||||||
|
gravity: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
if (invertSolidWhileHeld) {
|
if (invertSolidWhileHeld) {
|
||||||
whileHeldProperties["ignoreForCollisions"] = ! grabbedProperties.ignoreForCollisions;
|
whileHeldProperties["ignoreForCollisions"] = !grabbedProperties.ignoreForCollisions;
|
||||||
}
|
}
|
||||||
Entities.editEntity(entityID, whileHeldProperties);
|
Entities.editEntity(entityID, whileHeldProperties);
|
||||||
}
|
}
|
||||||
|
@ -1103,6 +1164,44 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//this is our handler, where we do the actual work of changing animation settings
|
||||||
|
this.graspHand = function(animationProperties) {
|
||||||
|
var result = {};
|
||||||
|
//full alpha on overlay for this hand
|
||||||
|
//set grab to true
|
||||||
|
//set idle to false
|
||||||
|
//full alpha on the blend btw open and grab
|
||||||
|
if (_this.hand === RIGHT_HAND) {
|
||||||
|
result['rightHandOverlayAlpha'] = 1.0;
|
||||||
|
result['isRightHandGrab'] = true;
|
||||||
|
result['isRightHandIdle'] = false;
|
||||||
|
result['rightHandGrabBlend'] = 1.0;
|
||||||
|
} else if (_this.hand === LEFT_HAND) {
|
||||||
|
result['leftHandOverlayAlpha'] = 1.0;
|
||||||
|
result['isLeftHandGrab'] = true;
|
||||||
|
result['isLeftHandIdle'] = false;
|
||||||
|
result['leftHandGrabBlend'] = 1.0;
|
||||||
|
}
|
||||||
|
//return an object with our updated settings
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.graspHandler = null
|
||||||
|
this.startHandGrasp = function() {
|
||||||
|
if (this.hand === RIGHT_HAND) {
|
||||||
|
this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isRightHandGrab']);
|
||||||
|
} else if (this.hand === LEFT_HAND) {
|
||||||
|
this.graspHandler = MyAvatar.addAnimationStateHandler(this.graspHand, ['isLeftHandGrab']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.endHandGrasp = function() {
|
||||||
|
// Tell the animation system we don't need any more callbacks.
|
||||||
|
MyAvatar.removeAnimationStateHandler(this.graspHandler);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rightController = new MyController(RIGHT_HAND);
|
var rightController = new MyController(RIGHT_HAND);
|
||||||
|
@ -1132,4 +1231,4 @@ function cleanup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
Script.scriptEnding.connect(cleanup);
|
||||||
Script.update.connect(update);
|
Script.update.connect(update);
|
|
@ -27,7 +27,6 @@ var toolBar = null;
|
||||||
var recordIcon;
|
var recordIcon;
|
||||||
var isRecording = false;
|
var isRecording = false;
|
||||||
var channel = "groupRecordingChannel";
|
var channel = "groupRecordingChannel";
|
||||||
Messages.subscribe(channel);
|
|
||||||
setupToolBar();
|
setupToolBar();
|
||||||
|
|
||||||
function setupToolBar() {
|
function setupToolBar() {
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
//
|
|
||||||
// synchronizerEntityScript.js
|
|
||||||
// examples/entityScripts
|
|
||||||
//
|
|
||||||
// Created by Alessandro Signa on 11/12/15.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
|
|
||||||
// This script shows how to create a synchronized event between avatars trhough an entity.
|
|
||||||
// It works using the entity's userData: the master change its value and every client checks it every frame
|
|
||||||
// This entity prints a message when the event starts and when it ends.
|
|
||||||
// The client running synchronizerMaster.js is the event master and it decides when the event starts/ends by pressing a button.
|
|
||||||
// All the avatars in the area when the master presses the button will receive a message.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
var insideArea = false;
|
|
||||||
var isJoiningTheEvent = false;
|
|
||||||
var _this;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function ParamsEntity() {
|
|
||||||
_this = this;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ParamsEntity.prototype = {
|
|
||||||
update: function(){
|
|
||||||
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData);
|
|
||||||
var valueToCheck = userData.myKey.valueToCheck;
|
|
||||||
if(valueToCheck && !isJoiningTheEvent){
|
|
||||||
_this.sendMessage();
|
|
||||||
}else if((!valueToCheck && isJoiningTheEvent) || (isJoiningTheEvent && !insideArea)){
|
|
||||||
_this.stopMessage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preload: function(entityID) {
|
|
||||||
print('entity loaded')
|
|
||||||
this.entityID = entityID;
|
|
||||||
Script.update.connect(_this.update);
|
|
||||||
},
|
|
||||||
enterEntity: function(entityID) {
|
|
||||||
print("enterEntity("+entityID+")");
|
|
||||||
var userData = JSON.parse(Entities.getEntityProperties(_this.entityID, ["userData"]).userData);
|
|
||||||
var valueToCheck = userData.myKey.valueToCheck;
|
|
||||||
if(!valueToCheck){
|
|
||||||
//i'm in the area in time (before the event starts)
|
|
||||||
insideArea = true;
|
|
||||||
}
|
|
||||||
change(entityID);
|
|
||||||
},
|
|
||||||
leaveEntity: function(entityID) {
|
|
||||||
print("leaveEntity("+entityID+")");
|
|
||||||
Entities.editEntity(entityID, { color: { red: 255, green: 190, blue: 20} });
|
|
||||||
insideArea = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
sendMessage: function(myID){
|
|
||||||
if(insideArea && !isJoiningTheEvent){
|
|
||||||
print("The event started");
|
|
||||||
isJoiningTheEvent = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stopMessage: function(myID){
|
|
||||||
if(isJoiningTheEvent){
|
|
||||||
print("The event ended");
|
|
||||||
isJoiningTheEvent = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clean: function(entityID) {
|
|
||||||
Script.update.disconnect(_this.update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function change(entityID) {
|
|
||||||
Entities.editEntity(entityID, { color: { red: 255, green: 100, blue: 220} });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return new ParamsEntity();
|
|
||||||
});
|
|
|
@ -1,117 +0,0 @@
|
||||||
//
|
|
||||||
// synchronizerMaster.js
|
|
||||||
// examples/entityScripts
|
|
||||||
//
|
|
||||||
// Created by Alessandro Signa on 11/12/15.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Run this script to spawn a box (synchronizer) and drive the start/end of the event for anyone who is inside the box
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
|
|
||||||
var PARAMS_SCRIPT_URL = Script.resolvePath('synchronizerEntityScript.js');
|
|
||||||
|
|
||||||
|
|
||||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
|
||||||
Script.include("../libraries/toolBars.js");
|
|
||||||
Script.include("../libraries/utils.js");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var rotation = Quat.safeEulerAngles(Camera.getOrientation());
|
|
||||||
rotation = Quat.fromPitchYawRollDegrees(0, rotation.y, 0);
|
|
||||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(1, Quat.getFront(rotation)));
|
|
||||||
|
|
||||||
var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/";
|
|
||||||
var ALPHA_ON = 1.0;
|
|
||||||
var ALPHA_OFF = 0.7;
|
|
||||||
var COLOR_TOOL_BAR = { red: 0, green: 0, blue: 0 };
|
|
||||||
|
|
||||||
var toolBar = null;
|
|
||||||
var recordIcon;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var isHappening = false;
|
|
||||||
|
|
||||||
var testEntity = Entities.addEntity({
|
|
||||||
name: 'paramsTestEntity',
|
|
||||||
dimensions: {
|
|
||||||
x: 2,
|
|
||||||
y: 1,
|
|
||||||
z: 2
|
|
||||||
},
|
|
||||||
type: 'Box',
|
|
||||||
position: center,
|
|
||||||
color: {
|
|
||||||
red: 255,
|
|
||||||
green: 255,
|
|
||||||
blue: 255
|
|
||||||
},
|
|
||||||
visible: true,
|
|
||||||
ignoreForCollisions: true,
|
|
||||||
script: PARAMS_SCRIPT_URL,
|
|
||||||
|
|
||||||
userData: JSON.stringify({
|
|
||||||
myKey: {
|
|
||||||
valueToCheck: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
setupToolBar();
|
|
||||||
|
|
||||||
function setupToolBar() {
|
|
||||||
if (toolBar != null) {
|
|
||||||
print("Multiple calls to setupToolBar()");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Tool.IMAGE_HEIGHT /= 2;
|
|
||||||
Tool.IMAGE_WIDTH /= 2;
|
|
||||||
|
|
||||||
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); //put the button in the up-left corner
|
|
||||||
|
|
||||||
toolBar.setBack(COLOR_TOOL_BAR, ALPHA_OFF);
|
|
||||||
|
|
||||||
recordIcon = toolBar.addTool({
|
|
||||||
imageURL: TOOL_ICON_URL + "recording-record.svg",
|
|
||||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
|
||||||
x: 0, y: 0,
|
|
||||||
width: Tool.IMAGE_WIDTH,
|
|
||||||
height: Tool.IMAGE_HEIGHT,
|
|
||||||
alpha: MyAvatar.isPlaying() ? ALPHA_OFF : ALPHA_ON,
|
|
||||||
visible: true
|
|
||||||
}, true, isHappening);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function mousePressEvent(event) {
|
|
||||||
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
|
||||||
if (recordIcon === toolBar.clicked(clickedOverlay, false)) {
|
|
||||||
if (!isHappening) {
|
|
||||||
print("I'm the event master. I want the event starts");
|
|
||||||
isHappening = true;
|
|
||||||
setEntityCustomData("myKey", testEntity, {valueToCheck: true});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print("I want the event stops");
|
|
||||||
isHappening = false;
|
|
||||||
setEntityCustomData("myKey", testEntity, {valueToCheck: false});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
toolBar.cleanup();
|
|
||||||
Entities.callEntityMethod(testEntity, 'clean'); //have to call this before deleting to avoid the JSON warnings
|
|
||||||
Entities.deleteEntity(testEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
|
||||||
Controller.mousePressEvent.connect(mousePressEvent);
|
|
11
examples/example/assetsExample.js
Normal file
11
examples/example/assetsExample.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
var data = "this is some data";
|
||||||
|
var extension = "txt";
|
||||||
|
var uploadedFile;
|
||||||
|
|
||||||
|
Assets.uploadData(data, extension, function (url) {
|
||||||
|
print("data uploaded to:" + url);
|
||||||
|
uploadedFile = url;
|
||||||
|
Assets.downloadData(url, function (data) {
|
||||||
|
print("data downloaded from:" + url + " the data is:" + data);
|
||||||
|
});
|
||||||
|
});
|
68
examples/example/avatarcontrol/graspHands.js
Normal file
68
examples/example/avatarcontrol/graspHands.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// graspHands.js
|
||||||
|
//
|
||||||
|
// Created by James B. Pollack @imgntn -- 11/19/2015
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Shows how to use the animation API to grasp an Avatar's hands.
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
//choose a hand. set it programatically if you'd like
|
||||||
|
var handToGrasp = 'LEFT_HAND';
|
||||||
|
|
||||||
|
//this is our handler, where we do the actual work of changing animation settings
|
||||||
|
function graspHand(animationProperties) {
|
||||||
|
var result = {};
|
||||||
|
//full alpha on overlay for this hand
|
||||||
|
//set grab to true
|
||||||
|
//set idle to false
|
||||||
|
//full alpha on the blend btw open and grab
|
||||||
|
if (handToGrasp === 'RIGHT_HAND') {
|
||||||
|
result['rightHandOverlayAlpha'] = 1.0;
|
||||||
|
result['isRightHandGrab'] = true;
|
||||||
|
result['isRightHandIdle'] = false;
|
||||||
|
result['rightHandGrabBlend'] = 1.0;
|
||||||
|
} else if (handToGrasp === 'LEFT_HAND') {
|
||||||
|
result['leftHandOverlayAlpha'] = 1.0;
|
||||||
|
result['isLeftHandGrab'] = true;
|
||||||
|
result['isLeftHandIdle'] = false;
|
||||||
|
result['leftHandGrabBlend'] = 1.0;
|
||||||
|
}
|
||||||
|
//return an object with our updated settings
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//keep a reference to this so we can clear it
|
||||||
|
var handler;
|
||||||
|
|
||||||
|
//register our handler with the animation system
|
||||||
|
function startHandGrasp() {
|
||||||
|
if (handToGrasp === 'RIGHT_HAND') {
|
||||||
|
handler = MyAvatar.addAnimationStateHandler(graspHand, ['isRightHandGrab']);
|
||||||
|
} else if (handToGrasp === 'LEFT_HAND') {
|
||||||
|
handler = MyAvatar.addAnimationStateHandler(graspHand, ['isLeftHandGrab']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function endHandGrasp() {
|
||||||
|
// Tell the animation system we don't need any more callbacks.
|
||||||
|
MyAvatar.removeAnimationStateHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
//make sure to clean this up when the script ends so we don't get stuck.
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
Script.clearInterval(graspInterval);
|
||||||
|
endHandGrasp();
|
||||||
|
})
|
||||||
|
|
||||||
|
//set an interval and toggle grasping
|
||||||
|
var isGrasping = false;
|
||||||
|
var graspInterval = Script.setInterval(function() {
|
||||||
|
if (isGrasping === false) {
|
||||||
|
startHandGrasp();
|
||||||
|
isGrasping = true;
|
||||||
|
} else {
|
||||||
|
endHandGrasp();
|
||||||
|
isGrasping = false
|
||||||
|
}
|
||||||
|
}, 1000)
|
|
@ -12,8 +12,8 @@
|
||||||
|
|
||||||
Script.include("../../libraries/utils.js");
|
Script.include("../../libraries/utils.js");
|
||||||
|
|
||||||
var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/wand.fbx';
|
var WAND_MODEL = 'http://hifi-content.s3.amazonaws.com/james/bubblewand/wand.fbx';
|
||||||
var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/models/bubblewand/actual_no_top_collision_hull.obj';
|
var WAND_COLLISION_SHAPE = 'http://hifi-content.s3.amazonaws.com/james/bubblewand/wand_collision_hull.obj';
|
||||||
|
|
||||||
var WAND_SCRIPT_URL = Script.resolvePath("wand.js");
|
var WAND_SCRIPT_URL = Script.resolvePath("wand.js");
|
||||||
|
|
||||||
|
@ -43,5 +43,18 @@ var wand = Entities.addEntity({
|
||||||
//must be enabled to be grabbable in the physics engine
|
//must be enabled to be grabbable in the physics engine
|
||||||
collisionsWillMove: true,
|
collisionsWillMove: true,
|
||||||
compoundShapeURL: WAND_COLLISION_SHAPE,
|
compoundShapeURL: WAND_COLLISION_SHAPE,
|
||||||
script: WAND_SCRIPT_URL
|
script: WAND_SCRIPT_URL,
|
||||||
|
userData: JSON.stringify({
|
||||||
|
grabbableKey: {
|
||||||
|
invertSolidWhileHeld: true,
|
||||||
|
spatialKey: {
|
||||||
|
relativePosition: {
|
||||||
|
x: 0,
|
||||||
|
y: 0.1,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 90)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||||
|
|
||||||
(function () {
|
(function() {
|
||||||
|
|
||||||
Script.include("../../libraries/utils.js");
|
Script.include("../../libraries/utils.js");
|
||||||
|
|
||||||
|
@ -58,23 +58,23 @@
|
||||||
BubbleWand.prototype = {
|
BubbleWand.prototype = {
|
||||||
timePassed: null,
|
timePassed: null,
|
||||||
currentBubble: null,
|
currentBubble: null,
|
||||||
preload: function (entityID) {
|
preload: function(entityID) {
|
||||||
this.entityID = entityID;
|
this.entityID = entityID;
|
||||||
},
|
},
|
||||||
getWandTipPosition: function (properties) {
|
getWandTipPosition: function(properties) {
|
||||||
//the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position
|
//the tip of the wand is going to be in a different place than the center, so we move in space relative to the model to find that position
|
||||||
var upVector = Quat.getUp(properties.rotation);
|
var upVector = Quat.getUp(properties.rotation);
|
||||||
var upOffset = Vec3.multiply(upVector, WAND_TIP_OFFSET);
|
var upOffset = Vec3.multiply(upVector, WAND_TIP_OFFSET);
|
||||||
var wandTipPosition = Vec3.sum(properties.position, upOffset);
|
var wandTipPosition = Vec3.sum(properties.position, upOffset);
|
||||||
return wandTipPosition;
|
return wandTipPosition;
|
||||||
},
|
},
|
||||||
addCollisionsToBubbleAfterCreation: function (bubble) {
|
addCollisionsToBubbleAfterCreation: function(bubble) {
|
||||||
//if the bubble collide immediately, we get weird effects. so we add collisions after release
|
//if the bubble collide immediately, we get weird effects. so we add collisions after release
|
||||||
Entities.editEntity(bubble, {
|
Entities.editEntity(bubble, {
|
||||||
collisionsWillMove: true
|
collisionsWillMove: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
randomizeBubbleGravity: function () {
|
randomizeBubbleGravity: function() {
|
||||||
//change up the gravity a little bit for variation in floating effects
|
//change up the gravity a little bit for variation in floating effects
|
||||||
var randomNumber = randFloat(BUBBLE_GRAVITY_MIN, BUBBLE_GRAVITY_MAX);
|
var randomNumber = randFloat(BUBBLE_GRAVITY_MIN, BUBBLE_GRAVITY_MAX);
|
||||||
var gravity = {
|
var gravity = {
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
};
|
};
|
||||||
return gravity;
|
return gravity;
|
||||||
},
|
},
|
||||||
growBubbleWithWandVelocity: function (properties, deltaTime) {
|
growBubbleWithWandVelocity: function(properties, deltaTime) {
|
||||||
//get the wand and tip position for calculations
|
//get the wand and tip position for calculations
|
||||||
var wandPosition = properties.position;
|
var wandPosition = properties.position;
|
||||||
this.getWandTipPosition(properties);
|
this.getWandTipPosition(properties);
|
||||||
|
@ -145,7 +145,7 @@
|
||||||
dimensions: dimensions
|
dimensions: dimensions
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
createBubbleAtTipOfWand: function () {
|
createBubbleAtTipOfWand: function() {
|
||||||
|
|
||||||
//create a new bubble at the tip of the wand
|
//create a new bubble at the tip of the wand
|
||||||
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||||
|
@ -162,24 +162,23 @@
|
||||||
position: this.getWandTipPosition(properties),
|
position: this.getWandTipPosition(properties),
|
||||||
dimensions: BUBBLE_INITIAL_DIMENSIONS,
|
dimensions: BUBBLE_INITIAL_DIMENSIONS,
|
||||||
collisionsWillMove: false,
|
collisionsWillMove: false,
|
||||||
ignoreForCollisions: false,
|
ignoreForCollisions: true,
|
||||||
linearDamping: BUBBLE_LINEAR_DAMPING,
|
linearDamping: BUBBLE_LINEAR_DAMPING,
|
||||||
shapeType: "sphere"
|
shapeType: "sphere"
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
startNearGrab: function () {
|
startNearGrab: function() {
|
||||||
//create a bubble to grow at the start of the grab
|
//create a bubble to grow at the start of the grab
|
||||||
if (this.currentBubble === null) {
|
if (this.currentBubble === null) {
|
||||||
this.createBubbleAtTipOfWand();
|
this.createBubbleAtTipOfWand();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
continueNearGrab: function () {
|
continueNearGrab: function() {
|
||||||
var deltaTime = checkInterval();
|
var deltaTime = checkInterval();
|
||||||
//only get the properties that we need
|
//only get the properties that we need
|
||||||
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
var properties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]);
|
||||||
|
|
||||||
|
|
||||||
var wandTipPosition = this.getWandTipPosition(properties);
|
var wandTipPosition = this.getWandTipPosition(properties);
|
||||||
|
|
||||||
//update the bubble to stay with the wand tip
|
//update the bubble to stay with the wand tip
|
||||||
|
@ -189,7 +188,7 @@
|
||||||
this.growBubbleWithWandVelocity(properties, deltaTime);
|
this.growBubbleWithWandVelocity(properties, deltaTime);
|
||||||
|
|
||||||
},
|
},
|
||||||
releaseGrab: function () {
|
releaseGrab: function() {
|
||||||
//delete the current buble and reset state when the wand is released
|
//delete the current buble and reset state when the wand is released
|
||||||
Entities.deleteEntity(this.currentBubble);
|
Entities.deleteEntity(this.currentBubble);
|
||||||
this.currentBubble = null;
|
this.currentBubble = null;
|
||||||
|
|
|
@ -365,3 +365,65 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssetScriptingInterface::uploadData(QString data, QString extension, QScriptValue callback) {
|
||||||
|
QByteArray dataByteArray = data.toUtf8();
|
||||||
|
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray, extension);
|
||||||
|
QObject::connect(upload, &AssetUpload::finished, this, [callback, extension](AssetUpload* upload, const QString& hash) mutable {
|
||||||
|
if (callback.isFunction()) {
|
||||||
|
QString url = "atp://" + hash + "." + extension;
|
||||||
|
QScriptValueList args { url };
|
||||||
|
callback.call(QScriptValue(), args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start the upload now
|
||||||
|
upload->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) {
|
||||||
|
const QString ATP_SCHEME { "atp://" };
|
||||||
|
|
||||||
|
if (!urlString.startsWith(ATP_SCHEME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request to atp
|
||||||
|
auto path = urlString.right(urlString.length() - ATP_SCHEME.length());
|
||||||
|
auto parts = path.split(".", QString::SkipEmptyParts);
|
||||||
|
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||||
|
auto extension = parts.length() > 1 ? parts[1] : "";
|
||||||
|
|
||||||
|
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto assetClient = DependencyManager::get<AssetClient>();
|
||||||
|
auto assetRequest = assetClient->createRequest(hash, extension);
|
||||||
|
|
||||||
|
if (!assetRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingRequests << assetRequest;
|
||||||
|
|
||||||
|
connect(assetRequest, &AssetRequest::finished, [this, callback](AssetRequest* request) mutable {
|
||||||
|
Q_ASSERT(request->getState() == AssetRequest::Finished);
|
||||||
|
|
||||||
|
if (request->getError() == AssetRequest::Error::NoError) {
|
||||||
|
if (callback.isFunction()) {
|
||||||
|
QString data = QString::fromUtf8(request->getData());
|
||||||
|
QScriptValueList args { data };
|
||||||
|
callback.call(QScriptValue(), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request->deleteLater();
|
||||||
|
_pendingRequests.remove(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
assetRequest->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#define hifi_AssetClient_h
|
#define hifi_AssetClient_h
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
#include "LimitedNodeList.h"
|
#include "LimitedNodeList.h"
|
||||||
#include "NLPacket.h"
|
#include "NLPacket.h"
|
||||||
#include "Node.h"
|
#include "Node.h"
|
||||||
|
#include "ResourceCache.h"
|
||||||
|
|
||||||
class AssetRequest;
|
class AssetRequest;
|
||||||
class AssetUpload;
|
class AssetUpload;
|
||||||
|
@ -68,4 +70,15 @@ private:
|
||||||
friend class AssetUpload;
|
friend class AssetUpload;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AssetScriptingInterface : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE void uploadData(QString data, QString extension, QScriptValue callback);
|
||||||
|
Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete);
|
||||||
|
protected:
|
||||||
|
QSet<AssetRequest*> _pendingRequests;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -4,6 +4,6 @@ set(TARGET_NAME recording)
|
||||||
setup_hifi_library(Script)
|
setup_hifi_library(Script)
|
||||||
|
|
||||||
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
|
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
|
||||||
link_hifi_libraries(shared)
|
link_hifi_libraries(shared networking)
|
||||||
|
|
||||||
GroupSources("src/recording")
|
GroupSources("src/recording")
|
||||||
|
|
40
libraries/recording/src/recording/ClipCache.cpp
Normal file
40
libraries/recording/src/recording/ClipCache.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/11/19
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
#include "ClipCache.h"
|
||||||
|
#include "impl/PointerClip.h"
|
||||||
|
|
||||||
|
using namespace recording;
|
||||||
|
NetworkClipLoader::NetworkClipLoader(const QUrl& url, bool delayLoad)
|
||||||
|
: Resource(url, delayLoad), _clip(std::make_shared<NetworkClip>(url))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void NetworkClip::init(const QByteArray& clipData) {
|
||||||
|
_clipData = clipData;
|
||||||
|
PointerClip::init((uchar*)_clipData.data(), _clipData.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClipLoader::downloadFinished(const QByteArray& data) {
|
||||||
|
_clip->init(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipCache& ClipCache::instance() {
|
||||||
|
static ClipCache _instance;
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) {
|
||||||
|
return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast<NetworkClipLoader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||||
|
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared);
|
||||||
|
}
|
||||||
|
|
57
libraries/recording/src/recording/ClipCache.h
Normal file
57
libraries/recording/src/recording/ClipCache.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2015/11/19
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_Recording_ClipCache_h
|
||||||
|
#define hifi_Recording_ClipCache_h
|
||||||
|
|
||||||
|
#include <ResourceCache.h>
|
||||||
|
|
||||||
|
#include "Forward.h"
|
||||||
|
#include "impl/PointerClip.h"
|
||||||
|
|
||||||
|
namespace recording {
|
||||||
|
|
||||||
|
class NetworkClip : public PointerClip {
|
||||||
|
public:
|
||||||
|
using Pointer = std::shared_ptr<NetworkClip>;
|
||||||
|
|
||||||
|
NetworkClip(const QUrl& url) : _url(url) {}
|
||||||
|
virtual void init(const QByteArray& clipData);
|
||||||
|
virtual QString getName() const override { return _url.toString(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QByteArray _clipData;
|
||||||
|
QUrl _url;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkClipLoader : public Resource {
|
||||||
|
public:
|
||||||
|
NetworkClipLoader(const QUrl& url, bool delayLoad);
|
||||||
|
virtual void downloadFinished(const QByteArray& data) override;
|
||||||
|
ClipPointer getClip() { return _clip; }
|
||||||
|
bool completed() { return _failedToLoad || isLoaded(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const NetworkClip::Pointer _clip;
|
||||||
|
};
|
||||||
|
|
||||||
|
using NetworkClipLoaderPointer = QSharedPointer<NetworkClipLoader>;
|
||||||
|
|
||||||
|
class ClipCache : public ResourceCache {
|
||||||
|
public:
|
||||||
|
static ClipCache& instance();
|
||||||
|
|
||||||
|
NetworkClipLoaderPointer getClipLoader(const QUrl& url);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -23,63 +23,6 @@
|
||||||
|
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize);
|
|
||||||
static const QString FRAME_TYPE_MAP = QStringLiteral("frameTypes");
|
|
||||||
static const QString FRAME_COMREPSSION_FLAG = QStringLiteral("compressed");
|
|
||||||
|
|
||||||
using FrameTranslationMap = QMap<FrameType, FrameType>;
|
|
||||||
|
|
||||||
FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) {
|
|
||||||
FrameTranslationMap results;
|
|
||||||
auto headerObj = doc.object();
|
|
||||||
if (headerObj.contains(FRAME_TYPE_MAP)) {
|
|
||||||
auto frameTypeObj = headerObj[FRAME_TYPE_MAP].toObject();
|
|
||||||
auto currentFrameTypes = Frame::getFrameTypes();
|
|
||||||
for (auto frameTypeName : frameTypeObj.keys()) {
|
|
||||||
qDebug() << frameTypeName;
|
|
||||||
if (!currentFrameTypes.contains(frameTypeName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
FrameType currentTypeEnum = currentFrameTypes[frameTypeName];
|
|
||||||
FrameType storedTypeEnum = static_cast<FrameType>(frameTypeObj[frameTypeName].toInt());
|
|
||||||
results[storedTypeEnum] = currentTypeEnum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FileFrameHeaderList parseFrameHeaders(uchar* const start, const qint64& size) {
|
|
||||||
FileFrameHeaderList results;
|
|
||||||
auto current = start;
|
|
||||||
auto end = current + size;
|
|
||||||
// Read all the frame headers
|
|
||||||
// FIXME move to Frame::readHeader?
|
|
||||||
while (end - current >= MINIMUM_FRAME_SIZE) {
|
|
||||||
FileFrameHeader header;
|
|
||||||
memcpy(&(header.type), current, sizeof(FrameType));
|
|
||||||
current += sizeof(FrameType);
|
|
||||||
memcpy(&(header.timeOffset), current, sizeof(Frame::Time));
|
|
||||||
current += sizeof(Frame::Time);
|
|
||||||
memcpy(&(header.size), current, sizeof(FrameSize));
|
|
||||||
current += sizeof(FrameSize);
|
|
||||||
header.fileOffset = current - start;
|
|
||||||
if (end - current < header.size) {
|
|
||||||
current = end;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current += header.size;
|
|
||||||
results.push_back(header);
|
|
||||||
}
|
|
||||||
qDebug() << "Parsed source data into " << results.size() << " frames";
|
|
||||||
// int i = 0;
|
|
||||||
// for (const auto& frameHeader : results) {
|
|
||||||
// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type;
|
|
||||||
// }
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FileClip::FileClip(const QString& fileName) : _file(fileName) {
|
FileClip::FileClip(const QString& fileName) : _file(fileName) {
|
||||||
auto size = _file.size();
|
auto size = _file.size();
|
||||||
qDebug() << "Opening file of size: " << size;
|
qDebug() << "Opening file of size: " << size;
|
||||||
|
@ -88,58 +31,8 @@ FileClip::FileClip(const QString& fileName) : _file(fileName) {
|
||||||
qCWarning(recordingLog) << "Unable to open file " << fileName;
|
qCWarning(recordingLog) << "Unable to open file " << fileName;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_map = _file.map(0, size, QFile::MapPrivateOption);
|
auto mappedFile = _file.map(0, size, QFile::MapPrivateOption);
|
||||||
if (!_map) {
|
init(mappedFile, size);
|
||||||
qCWarning(recordingLog) << "Unable to map file " << fileName;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parsedFrameHeaders = parseFrameHeaders(_map, size);
|
|
||||||
|
|
||||||
// Verify that at least one frame exists and that the first frame is a header
|
|
||||||
if (0 == parsedFrameHeaders.size()) {
|
|
||||||
qWarning() << "No frames found, invalid file";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the file header
|
|
||||||
{
|
|
||||||
auto fileHeaderFrameHeader = *parsedFrameHeaders.begin();
|
|
||||||
parsedFrameHeaders.pop_front();
|
|
||||||
if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) {
|
|
||||||
qWarning() << "Missing header frame, invalid file";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray fileHeaderData((char*)_map + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size);
|
|
||||||
_fileHeader = QJsonDocument::fromBinaryData(fileHeaderData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for compression
|
|
||||||
{
|
|
||||||
_compressed = _fileHeader.object()[FRAME_COMREPSSION_FLAG].toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the type enum translation map and fix up the frame headers
|
|
||||||
{
|
|
||||||
FrameTranslationMap translationMap = parseTranslationMap(_fileHeader);
|
|
||||||
if (translationMap.empty()) {
|
|
||||||
qWarning() << "Header missing frame type map, invalid file";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qDebug() << translationMap;
|
|
||||||
|
|
||||||
// Update the loaded headers with the frame data
|
|
||||||
_frames.reserve(parsedFrameHeaders.size());
|
|
||||||
for (auto& frameHeader : parsedFrameHeaders) {
|
|
||||||
if (!translationMap.contains(frameHeader.type)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
frameHeader.type = translationMap[frameHeader.type];
|
|
||||||
_frames.push_back(frameHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,31 +121,9 @@ bool FileClip::write(const QString& fileName, Clip::Pointer clip) {
|
||||||
|
|
||||||
FileClip::~FileClip() {
|
FileClip::~FileClip() {
|
||||||
Locker lock(_mutex);
|
Locker lock(_mutex);
|
||||||
_file.unmap(_map);
|
_file.unmap(_data);
|
||||||
_map = nullptr;
|
|
||||||
if (_file.isOpen()) {
|
if (_file.isOpen()) {
|
||||||
_file.close();
|
_file.close();
|
||||||
}
|
}
|
||||||
}
|
reset();
|
||||||
|
|
||||||
// Internal only function, needs no locking
|
|
||||||
FrameConstPointer FileClip::readFrame(size_t frameIndex) const {
|
|
||||||
FramePointer result;
|
|
||||||
if (frameIndex < _frames.size()) {
|
|
||||||
result = std::make_shared<Frame>();
|
|
||||||
const auto& header = _frames[frameIndex];
|
|
||||||
result->type = header.type;
|
|
||||||
result->timeOffset = header.timeOffset;
|
|
||||||
if (header.size) {
|
|
||||||
result->data.insert(0, reinterpret_cast<char*>(_map)+header.fileOffset, header.size);
|
|
||||||
if (_compressed) {
|
|
||||||
result->data = qUncompress(result->data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileClip::addFrame(FrameConstPointer) {
|
|
||||||
throw std::runtime_error("File clips are read only");
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,27 +10,13 @@
|
||||||
#ifndef hifi_Recording_Impl_FileClip_h
|
#ifndef hifi_Recording_Impl_FileClip_h
|
||||||
#define hifi_Recording_Impl_FileClip_h
|
#define hifi_Recording_Impl_FileClip_h
|
||||||
|
|
||||||
#include "ArrayClip.h"
|
#include "PointerClip.h"
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QJsonDocument>
|
|
||||||
|
|
||||||
#include "../Frame.h"
|
|
||||||
|
|
||||||
namespace recording {
|
namespace recording {
|
||||||
|
|
||||||
struct FileFrameHeader : public FrameHeader {
|
class FileClip : public PointerClip {
|
||||||
FrameType type;
|
|
||||||
Frame::Time timeOffset;
|
|
||||||
uint16_t size;
|
|
||||||
quint64 fileOffset;
|
|
||||||
};
|
|
||||||
|
|
||||||
using FileFrameHeaderList = std::list<FileFrameHeader>;
|
|
||||||
|
|
||||||
class FileClip : public ArrayClip<FileFrameHeader> {
|
|
||||||
public:
|
public:
|
||||||
using Pointer = std::shared_ptr<FileClip>;
|
using Pointer = std::shared_ptr<FileClip>;
|
||||||
|
|
||||||
|
@ -38,20 +24,11 @@ public:
|
||||||
virtual ~FileClip();
|
virtual ~FileClip();
|
||||||
|
|
||||||
virtual QString getName() const override;
|
virtual QString getName() const override;
|
||||||
virtual void addFrame(FrameConstPointer) override;
|
|
||||||
|
|
||||||
const QJsonDocument& getHeader() {
|
|
||||||
return _fileHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool write(const QString& filePath, Clip::Pointer clip);
|
static bool write(const QString& filePath, Clip::Pointer clip);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual FrameConstPointer readFrame(size_t index) const override;
|
|
||||||
QJsonDocument _fileHeader;
|
|
||||||
QFile _file;
|
QFile _file;
|
||||||
uchar* _map { nullptr };
|
|
||||||
bool _compressed { true };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
163
libraries/recording/src/recording/impl/PointerClip.cpp
Normal file
163
libraries/recording/src/recording/impl/PointerClip.cpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/11/04
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "PointerClip.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
#include <QtCore/QJsonObject>
|
||||||
|
|
||||||
|
#include <Finally.h>
|
||||||
|
|
||||||
|
#include "../Frame.h"
|
||||||
|
#include "../Logging.h"
|
||||||
|
#include "BufferClip.h"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace recording;
|
||||||
|
|
||||||
|
const QString PointerClip::FRAME_TYPE_MAP = QStringLiteral("frameTypes");
|
||||||
|
const QString PointerClip::FRAME_COMREPSSION_FLAG = QStringLiteral("compressed");
|
||||||
|
|
||||||
|
using FrameTranslationMap = QMap<FrameType, FrameType>;
|
||||||
|
|
||||||
|
FrameTranslationMap parseTranslationMap(const QJsonDocument& doc) {
|
||||||
|
FrameTranslationMap results;
|
||||||
|
auto headerObj = doc.object();
|
||||||
|
if (headerObj.contains(PointerClip::FRAME_TYPE_MAP)) {
|
||||||
|
auto frameTypeObj = headerObj[PointerClip::FRAME_TYPE_MAP].toObject();
|
||||||
|
auto currentFrameTypes = Frame::getFrameTypes();
|
||||||
|
for (auto frameTypeName : frameTypeObj.keys()) {
|
||||||
|
qDebug() << frameTypeName;
|
||||||
|
if (!currentFrameTypes.contains(frameTypeName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FrameType currentTypeEnum = currentFrameTypes[frameTypeName];
|
||||||
|
FrameType storedTypeEnum = static_cast<FrameType>(frameTypeObj[frameTypeName].toInt());
|
||||||
|
results[storedTypeEnum] = currentTypeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PointerFrameHeaderList parseFrameHeaders(uchar* const start, const size_t& size) {
|
||||||
|
PointerFrameHeaderList results;
|
||||||
|
auto current = start;
|
||||||
|
auto end = current + size;
|
||||||
|
// Read all the frame headers
|
||||||
|
// FIXME move to Frame::readHeader?
|
||||||
|
while (end - current >= PointerClip::MINIMUM_FRAME_SIZE) {
|
||||||
|
PointerFrameHeader header;
|
||||||
|
memcpy(&(header.type), current, sizeof(FrameType));
|
||||||
|
current += sizeof(FrameType);
|
||||||
|
memcpy(&(header.timeOffset), current, sizeof(Frame::Time));
|
||||||
|
current += sizeof(Frame::Time);
|
||||||
|
memcpy(&(header.size), current, sizeof(FrameSize));
|
||||||
|
current += sizeof(FrameSize);
|
||||||
|
header.fileOffset = current - start;
|
||||||
|
if (end - current < header.size) {
|
||||||
|
current = end;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current += header.size;
|
||||||
|
results.push_back(header);
|
||||||
|
}
|
||||||
|
qDebug() << "Parsed source data into " << results.size() << " frames";
|
||||||
|
// int i = 0;
|
||||||
|
// for (const auto& frameHeader : results) {
|
||||||
|
// qDebug() << "Frame " << i++ << " time " << frameHeader.timeOffset << " Type " << frameHeader.type;
|
||||||
|
// }
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointerClip::reset() {
|
||||||
|
_frames.clear();
|
||||||
|
_data = nullptr;
|
||||||
|
_size = 0;
|
||||||
|
_header = QJsonDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointerClip::init(uchar* data, size_t size) {
|
||||||
|
reset();
|
||||||
|
|
||||||
|
_data = data;
|
||||||
|
_size = size;
|
||||||
|
|
||||||
|
auto parsedFrameHeaders = parseFrameHeaders(data, size);
|
||||||
|
// Verify that at least one frame exists and that the first frame is a header
|
||||||
|
if (0 == parsedFrameHeaders.size()) {
|
||||||
|
qWarning() << "No frames found, invalid file";
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the file header
|
||||||
|
{
|
||||||
|
auto fileHeaderFrameHeader = *parsedFrameHeaders.begin();
|
||||||
|
parsedFrameHeaders.pop_front();
|
||||||
|
if (fileHeaderFrameHeader.type != Frame::TYPE_HEADER) {
|
||||||
|
qWarning() << "Missing header frame, invalid file";
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray fileHeaderData((char*)_data + fileHeaderFrameHeader.fileOffset, fileHeaderFrameHeader.size);
|
||||||
|
_header = QJsonDocument::fromBinaryData(fileHeaderData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for compression
|
||||||
|
{
|
||||||
|
_compressed = _header.object()[FRAME_COMREPSSION_FLAG].toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the type enum translation map and fix up the frame headers
|
||||||
|
{
|
||||||
|
FrameTranslationMap translationMap = parseTranslationMap(_header);
|
||||||
|
if (translationMap.empty()) {
|
||||||
|
qWarning() << "Header missing frame type map, invalid file";
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the loaded headers with the frame data
|
||||||
|
_frames.reserve(parsedFrameHeaders.size());
|
||||||
|
for (auto& frameHeader : parsedFrameHeaders) {
|
||||||
|
if (!translationMap.contains(frameHeader.type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
frameHeader.type = translationMap[frameHeader.type];
|
||||||
|
_frames.push_back(frameHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal only function, needs no locking
|
||||||
|
FrameConstPointer PointerClip::readFrame(size_t frameIndex) const {
|
||||||
|
FramePointer result;
|
||||||
|
if (frameIndex < _frames.size()) {
|
||||||
|
result = std::make_shared<Frame>();
|
||||||
|
const auto& header = _frames[frameIndex];
|
||||||
|
result->type = header.type;
|
||||||
|
result->timeOffset = header.timeOffset;
|
||||||
|
if (header.size) {
|
||||||
|
result->data.insert(0, reinterpret_cast<char*>(_data)+header.fileOffset, header.size);
|
||||||
|
if (_compressed) {
|
||||||
|
result->data = qUncompress(result->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointerClip::addFrame(FrameConstPointer) {
|
||||||
|
throw std::runtime_error("Pointer clips are read only, use duplicate to create a read/write clip");
|
||||||
|
}
|
61
libraries/recording/src/recording/impl/PointerClip.h
Normal file
61
libraries/recording/src/recording/impl/PointerClip.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis 2015/11/05
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef hifi_Recording_Impl_PointerClip_h
|
||||||
|
#define hifi_Recording_Impl_PointerClip_h
|
||||||
|
|
||||||
|
#include "ArrayClip.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
|
#include "../Frame.h"
|
||||||
|
|
||||||
|
namespace recording {
|
||||||
|
|
||||||
|
struct PointerFrameHeader : public FrameHeader {
|
||||||
|
FrameType type;
|
||||||
|
Frame::Time timeOffset;
|
||||||
|
uint16_t size;
|
||||||
|
quint64 fileOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
using PointerFrameHeaderList = std::list<PointerFrameHeader>;
|
||||||
|
|
||||||
|
class PointerClip : public ArrayClip<PointerFrameHeader> {
|
||||||
|
public:
|
||||||
|
using Pointer = std::shared_ptr<PointerClip>;
|
||||||
|
|
||||||
|
PointerClip() {};
|
||||||
|
PointerClip(uchar* data, size_t size) { init(data, size); }
|
||||||
|
|
||||||
|
void init(uchar* data, size_t size);
|
||||||
|
virtual void addFrame(FrameConstPointer) override;
|
||||||
|
const QJsonDocument& getHeader() const {
|
||||||
|
return _header;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME move to frame?
|
||||||
|
static const qint64 MINIMUM_FRAME_SIZE = sizeof(FrameType) + sizeof(Frame::Time) + sizeof(FrameSize);
|
||||||
|
static const QString FRAME_TYPE_MAP;
|
||||||
|
static const QString FRAME_COMREPSSION_FLAG;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void reset();
|
||||||
|
virtual FrameConstPointer readFrame(size_t index) const override;
|
||||||
|
QJsonDocument _header;
|
||||||
|
uchar* _data { nullptr };
|
||||||
|
size_t _size { 0 };
|
||||||
|
bool _compressed { true };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -8,14 +8,15 @@
|
||||||
|
|
||||||
#include "RecordingScriptingInterface.h"
|
#include "RecordingScriptingInterface.h"
|
||||||
|
|
||||||
#include <QThread>
|
#include <QtCore/QThread>
|
||||||
|
|
||||||
|
#include <NumericalConstants.h>
|
||||||
|
#include <Transform.h>
|
||||||
#include <recording/Deck.h>
|
#include <recording/Deck.h>
|
||||||
#include <recording/Recorder.h>
|
#include <recording/Recorder.h>
|
||||||
#include <recording/Clip.h>
|
#include <recording/Clip.h>
|
||||||
#include <recording/Frame.h>
|
#include <recording/Frame.h>
|
||||||
#include <NumericalConstants.h>
|
#include <recording/ClipCache.h>
|
||||||
#include <Transform.h>
|
|
||||||
|
|
||||||
#include "ScriptEngineLogging.h"
|
#include "ScriptEngineLogging.h"
|
||||||
|
|
||||||
|
@ -43,20 +44,17 @@ float RecordingScriptingInterface::playerLength() const {
|
||||||
return _player->length();
|
return _player->length();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordingScriptingInterface::loadRecording(const QString& filename) {
|
void RecordingScriptingInterface::loadRecording(const QString& url) {
|
||||||
using namespace recording;
|
using namespace recording;
|
||||||
|
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
|
QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection,
|
||||||
Q_ARG(QString, filename));
|
Q_ARG(QString, url));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipPointer clip = Clip::fromFile(filename);
|
// FIXME make blocking and force off main thread?
|
||||||
if (!clip) {
|
_player->queueClip(ClipCache::instance().getClipLoader(url)->getClip());
|
||||||
qWarning() << "Unable to load clip data from " << filename;
|
|
||||||
}
|
|
||||||
_player->queueClip(clip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordingScriptingInterface::startPlaying() {
|
void RecordingScriptingInterface::startPlaying() {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <recording/Forward.h>
|
#include <recording/Forward.h>
|
||||||
|
@ -25,7 +25,7 @@ public:
|
||||||
RecordingScriptingInterface();
|
RecordingScriptingInterface();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void loadRecording(const QString& filename);
|
void loadRecording(const QString& url);
|
||||||
|
|
||||||
void startPlaying();
|
void startPlaying();
|
||||||
void pausePlayer();
|
void pausePlayer();
|
||||||
|
|
|
@ -381,6 +381,9 @@ void ScriptEngine::init() {
|
||||||
|
|
||||||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||||
registerGlobalObject("Recording", recordingInterface.data());
|
registerGlobalObject("Recording", recordingInterface.data());
|
||||||
|
|
||||||
|
registerGlobalObject("Assets", &_assetScriptingInterface);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
|
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
#include <AnimationCache.h>
|
#include <AnimationCache.h>
|
||||||
#include <AnimVariant.h>
|
#include <AnimVariant.h>
|
||||||
|
#include <AssetClient.h>
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
#include <AvatarHashMap.h>
|
#include <AvatarHashMap.h>
|
||||||
#include <LimitedNodeList.h>
|
#include <LimitedNodeList.h>
|
||||||
|
@ -195,6 +196,8 @@ private:
|
||||||
|
|
||||||
ArrayBufferClass* _arrayBufferClass;
|
ArrayBufferClass* _arrayBufferClass;
|
||||||
|
|
||||||
|
AssetScriptingInterface _assetScriptingInterface;
|
||||||
|
|
||||||
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
QHash<EntityItemID, RegisteredEventHandlers> _registeredHandlers;
|
||||||
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs);
|
||||||
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
|
||||||
|
|
Loading…
Reference in a new issue