merge hand controller things

This commit is contained in:
James B. Pollack 2016-04-02 21:02:14 -07:00
commit 15e0b40a18
66 changed files with 3132 additions and 1095 deletions

View file

@ -82,11 +82,11 @@ void AssetServer::completeSetup() {
auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1);
if (maxBandwidthFloat > 0.0) {
const int BYTES_PER_MEGABITS = (1024 * 1024) / 8;
int maxBandwidth = maxBandwidthFloat * BYTES_PER_MEGABITS;
const int BITS_PER_MEGABITS = 1000 * 1000;
int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS;
nodeList->setConnectionMaxBandwidth(maxBandwidth);
qInfo() << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s."
" (" << maxBandwidth << "bytes/sec)";
" (" << maxBandwidth << "bits/s)";
}
// get the path to the asset folder from the domain server settings

View file

@ -34,6 +34,8 @@ var TRIGGER_OFF_VALUE = 0.15;
var BUMPER_ON_VALUE = 0.5;
var THUMB_ON_VALUE = 0.5;
var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move.
var PICK_WITH_HAND_RAY = true;
@ -121,7 +123,8 @@ var GRABBABLE_PROPERTIES = [
"parentID",
"parentJointIndex",
"density",
"dimensions"
"dimensions",
"userData"
];
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js
@ -153,20 +156,22 @@ var USE_POINTLIGHT = false;
// states for the state machine
var STATE_OFF = 0;
var STATE_SEARCHING = 1;
var STATE_DISTANCE_HOLDING = 2;
var STATE_CONTINUE_DISTANCE_HOLDING = 3;
var STATE_NEAR_GRABBING = 4;
var STATE_CONTINUE_NEAR_GRABBING = 5;
var STATE_NEAR_TRIGGER = 6;
var STATE_CONTINUE_NEAR_TRIGGER = 7;
var STATE_FAR_TRIGGER = 8;
var STATE_CONTINUE_FAR_TRIGGER = 9;
var STATE_RELEASE = 10;
var STATE_EQUIP_SEARCHING = 11;
var STATE_EQUIP = 12
var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down
var STATE_CONTINUE_EQUIP = 14;
var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
var STATE_HOLD_SEARCHING = 2;
var STATE_DISTANCE_HOLDING = 3;
var STATE_CONTINUE_DISTANCE_HOLDING = 4;
var STATE_NEAR_GRABBING = 5;
var STATE_CONTINUE_NEAR_GRABBING = 6;
var STATE_NEAR_TRIGGER = 7;
var STATE_CONTINUE_NEAR_TRIGGER = 8;
var STATE_FAR_TRIGGER = 9;
var STATE_CONTINUE_FAR_TRIGGER = 10;
var STATE_RELEASE = 11;
var STATE_EQUIP = 12;
var STATE_HOLD = 13;
var STATE_CONTINUE_HOLD = 14;
var STATE_CONTINUE_EQUIP = 15;
var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 16;
var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 17;
// "collidesWith" is specified by comma-separated list of group names
// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar
@ -182,6 +187,8 @@ function stateToName(state) {
return "off";
case STATE_SEARCHING:
return "searching";
case STATE_HOLD_SEARCHING:
return "hold_searching";
case STATE_DISTANCE_HOLDING:
return "distance_holding";
case STATE_CONTINUE_DISTANCE_HOLDING:
@ -200,16 +207,18 @@ function stateToName(state) {
return "continue_far_trigger";
case STATE_RELEASE:
return "release";
case STATE_EQUIP_SEARCHING:
return "equip_searching";
case STATE_EQUIP:
return "equip";
case STATE_CONTINUE_EQUIP_BD:
return "continue_equip_bd";
case STATE_HOLD:
return "hold";
case STATE_CONTINUE_HOLD:
return "continue_hold";
case STATE_CONTINUE_EQUIP:
return "continue_equip";
case STATE_WAITING_FOR_BUMPER_RELEASE:
return "waiting_for_bumper_release";
case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE:
return "waiting_for_equip_thumb_release";
case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE:
return "waiting_for_release_thumb_release";
}
return "unknown";
@ -260,9 +269,13 @@ function MyController(hand) {
this.grabbedEntity = null; // on this entity.
this.state = STATE_OFF;
this.pointer = null; // entity-id of line object
this.entityActivated = false;
this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0;
this.rawBumperValue = 0;
this.rawThumbValue = 0;
//for visualizations
this.overlayLine = null;
this.particleBeamObject = null;
@ -296,9 +309,7 @@ function MyController(hand) {
this.touchTest();
break;
case STATE_SEARCHING:
this.search();
break;
case STATE_EQUIP_SEARCHING:
case STATE_HOLD_SEARCHING:
this.search();
break;
case STATE_DISTANCE_HOLDING:
@ -309,13 +320,17 @@ function MyController(hand) {
break;
case STATE_NEAR_GRABBING:
case STATE_EQUIP:
case STATE_HOLD:
this.nearGrabbing();
break;
case STATE_WAITING_FOR_BUMPER_RELEASE:
this.waitingForBumperRelease();
case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE:
this.waitingForEquipThumbRelease();
break;
case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE:
this.waitingForReleaseThumbRelease();
break;
case STATE_CONTINUE_NEAR_GRABBING:
case STATE_CONTINUE_EQUIP_BD:
case STATE_CONTINUE_HOLD:
case STATE_CONTINUE_EQUIP:
this.continueNearGrabbing();
break;
@ -787,6 +802,26 @@ function MyController(hand) {
return _this.rawBumperValue < BUMPER_ON_VALUE;
};
// this.triggerOrBumperSqueezed = function() {
// return triggerSmoothedSqueezed() || bumperSqueezed();
// }
// this.triggerAndBumperReleased = function() {
// return triggerSmoothedReleased() && bumperReleased();
// }
this.thumbPress = function(value) {
_this.rawThumbValue = value;
}
this.thumbPressed = function() {
return _this.rawThumbValue > THUMB_ON_VALUE;
};
this.thumbReleased = function() {
return _this.rawThumbValue < THUMB_ON_VALUE;
};
this.off = function() {
if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) {
this.lastPickTime = 0;
@ -794,8 +829,8 @@ function MyController(hand) {
this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation;
if (this.triggerSmoothedSqueezed()) {
this.setState(STATE_SEARCHING);
} else {
this.setState(STATE_EQUIP_SEARCHING);
} else if (this.bumperSqueezed()) {
this.setState(STATE_HOLD_SEARCHING);
}
}
};
@ -807,7 +842,11 @@ function MyController(hand) {
this.checkForStrayChildren();
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
return;
}
if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) {
this.setState(STATE_RELEASE);
return;
}
@ -821,18 +860,24 @@ function MyController(hand) {
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation;
var currentControllerPosition = Controller.getPoseValue(controllerHandInput).position;
var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation));
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ?
Controller.Standard.RightHand : Controller.Standard.LeftHand);
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
var distantPickRay = {
origin: PICK_WITH_HAND_RAY ? handPosition : Camera.position,
direction: PICK_WITH_HAND_RAY ? Quat.getUp(this.getHandRotation()) : Vec3.mix(Quat.getUp(this.getHandRotation()),
direction: PICK_WITH_HAND_RAY ? Quat.getUp(controllerRotation) : Vec3.mix(Quat.getUp(controllerRotation),
Quat.getFront(Camera.orientation),
HAND_HEAD_MIX_RATIO),
length: PICK_MAX_DISTANCE
};
var searchVisualizationPickRay = {
origin: handPosition,
origin: currentControllerPosition,
direction: Quat.getUp(this.getHandRotation()),
length: PICK_MAX_DISTANCE
};
@ -939,7 +984,7 @@ function MyController(hand) {
}
continue;
}
if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_EQUIP_SEARCHING) {
if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_HOLD_SEARCHING) {
// don't allow a double-equip
if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) {
print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': it's a child");
@ -973,15 +1018,19 @@ function MyController(hand) {
this.setState(near ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER);
return;
}
// near grab or equip with action
// near grab with action or equip
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
var refCount = ("refCount" in grabData) ? grabData.refCount : 0;
if (near && (refCount < 1 || entityHasActions(this.grabbedEntity))) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
if (this.state == STATE_SEARCHING) {
this.setState(STATE_NEAR_GRABBING);
} else { // (this.state == STATE_HOLD_SEARCHING)
this.setState(STATE_HOLD);
}
return;
}
// far grab or equip with action
if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) {
// far grab
if (isPhysical && !near) {
if (entityIsGrabbedByOther(this.grabbedEntity)) {
// don't distance grab something that is already grabbed.
if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) {
@ -1003,8 +1052,9 @@ function MyController(hand) {
FAR_TO_NEAR_GRAB_PADDING_FACTOR);
}
this.setState(STATE_DISTANCE_HOLDING);
this.setState(this.state == STATE_SEARCHING ? STATE_DISTANCE_HOLDING : STATE_EQUIP);
this.searchSphereOff();
return;
}
@ -1012,7 +1062,11 @@ function MyController(hand) {
// else this thing isn't physical. grab it by reparenting it (but not if we've already
// grabbed it).
if (refCount < 1) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
if (this.state == STATE_SEARCHING) {
this.setState(STATE_NEAR_GRABBING);
} else { // this.state == STATE_HOLD_SEARCHING)
this.setState(STATE_HOLD);
}
return;
} else {
// it's not physical and it's already held via parenting. go ahead and grab it, but
@ -1021,7 +1075,11 @@ function MyController(hand) {
this.doubleParentGrab = true;
this.previousParentID = props.parentID;
this.previousParentJointIndex = props.parentJointIndex;
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
if (this.state == STATE_SEARCHING) {
this.setState(STATE_NEAR_GRABBING);
} else { // (this.state == STATE_HOLD_SEARCHING)
this.setState(STATE_HOLD);
}
return;
}
if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) {
@ -1056,10 +1114,12 @@ function MyController(hand) {
this.distanceHolding = function() {
// controller pose is in avatar frame
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
var avatarControllerPose =
Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
// transform it into world frame
var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
var controllerPosition = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
@ -1071,8 +1131,11 @@ function MyController(hand) {
this.currentObjectTime = now;
this.currentCameraOrientation = Camera.orientation;
this.grabRadius = Vec3.distance(this.currentObjectPosition, controllerPosition);
this.grabRadialVelocity = 0.0;
// compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object
this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, controllerPosition) + 1.0);
this.radiusScalar = Math.log(this.grabRadius + 1.0);
if (this.radiusScalar < 1.0) {
this.radiusScalar = 1.0;
}
@ -1109,7 +1172,7 @@ function MyController(hand) {
};
this.continueDistanceHolding = function() {
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("releaseGrab");
return;
@ -1118,27 +1181,21 @@ function MyController(hand) {
this.heartBeat(this.grabbedEntity);
// controller pose is in avatar frame
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ?
Controller.Standard.RightHand : Controller.Standard.LeftHand);
// transform it into world frame
var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
var controllerPosition = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() &&
this.hasPresetOffsets()) {
var saveGrabbedID = this.grabbedEntity;
this.release();
this.setState(STATE_EQUIP);
this.grabbedEntity = saveGrabbedID;
return;
}
var now = Date.now();
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
this.currentObjectTime = now;
// the action was set up on a previous call. update the targets.
// the action was set up when this.distanceHolding was called. update the targets.
var radius = Vec3.distance(this.currentObjectPosition, controllerPosition) *
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
if (radius < 1.0) {
@ -1156,7 +1213,7 @@ function MyController(hand) {
// update the currentObject position and rotation.
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
// this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
this.callEntityMethodOnGrabbed("continueDistantGrab");
@ -1166,6 +1223,25 @@ function MyController(hand) {
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
// Update radialVelocity
var lastVelocity = Vec3.subtract(controllerPosition, this.previousControllerPosition);
lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaTime);
var newRadialVelocity = Vec3.dot(lastVelocity,
Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition)));
var VELOCITY_AVERAGING_TIME = 0.016;
this.grabRadialVelocity = (deltaTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity +
(1.0 - (deltaTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity;
var RADIAL_GRAB_AMPLIFIER = 10.0;
if (Math.abs(this.grabRadialVelocity) > 0.0) {
this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER);
}
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(controllerRotation));
newTargetPosition = Vec3.sum(newTargetPosition, controllerPosition);
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
if (handControllerData.disableMoveWithHead !== true) {
// mix in head motion
@ -1227,7 +1303,7 @@ function MyController(hand) {
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: targetPosition,
targetPosition: newTargetPosition,
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
targetRotation: this.currentObjectRotation,
angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
@ -1339,24 +1415,30 @@ function MyController(hand) {
this.callEntityMethodOnGrabbed("releaseGrab");
return;
}
if (this.state == STATE_HOLD && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("releaseGrab");
return;
}
this.lineOff();
this.overlayLineOff();
if (this.entityActivated) {
var saveGrabbedID = this.grabbedEntity;
this.release();
this.grabbedEntity = saveGrabbedID;
}
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
this.activateEntity(this.grabbedEntity, grabbedProperties, false);
if (grabbedProperties.dynamic && NEAR_GRABBING_KINEMATIC) {
Entities.editEntity(this.grabbedEntity, {
dynamic: false
});
}
// var handRotation = this.getHandRotation();
var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation();
var handPosition = this.getHandPosition();
var hasPresetPosition = false;
if (this.state != STATE_NEAR_GRABBING && this.hasPresetOffsets()) {
if ((this.state == STATE_EQUIP || this.state == STATE_HOLD) && this.hasPresetOffsets()) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
// if an object is "equipped" and has a predefined offset, use it.
this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false;
@ -1372,9 +1454,9 @@ function MyController(hand) {
var currentObjectPosition = grabbedProperties.position;
var offset = Vec3.subtract(currentObjectPosition, handPosition);
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
if (this.temporaryPositionOffset && this.state != STATE_NEAR_GRABBING) {
if (this.temporaryPositionOffset && (this.state == STATE_EQUIP)) {
this.offsetPosition = this.temporaryPositionOffset;
hasPresetPosition = true;
// hasPresetPosition = true;
}
}
@ -1404,19 +1486,45 @@ function MyController(hand) {
}));
}
this.callEntityMethodOnGrabbed(this.state == STATE_NEAR_GRABBING ? "startNearGrab" : "startEquip");
Entities.editEntity(this.grabbedEntity, {
velocity: {
x: 0,
y: 0,
z: 0
},
angularVelocity: {
x: 0,
y: 0,
z: 0
},
dynamic: false
});
if (this.state == STATE_NEAR_GRABBING) {
this.callEntityMethodOnGrabbed("startNearGrab");
} else { // this.state == STATE_EQUIP || this.state == STATE_HOLD
this.callEntityMethodOnGrabbed("startEquip");
}
if (this.state == STATE_NEAR_GRABBING) {
// near grabbing
this.setState(STATE_CONTINUE_NEAR_GRABBING);
} else {
} else if (this.state == STATE_HOLD) {
// holding
this.setState(STATE_CONTINUE_HOLD);
} else { // (this.state == STATE_EQUIP)
// equipping
this.setState(STATE_CONTINUE_EQUIP_BD);
this.setState(STATE_CONTINUE_EQUIP);
}
this.currentHandControllerTipPosition =
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
this.currentObjectTime = Date.now();
this.currentObjectPosition = grabbedProperties.position;
this.currentObjectRotation = grabbedProperties.rotation;
this.currentVelocity = ZERO_VEC;
this.currentAngularVelocity = ZERO_VEC;
};
this.continueNearGrabbing = function() {
@ -1426,25 +1534,30 @@ function MyController(hand) {
this.callEntityMethodOnGrabbed("releaseGrab");
return;
}
if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) {
this.setState(STATE_CONTINUE_EQUIP);
return;
}
if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) {
this.setState(STATE_WAITING_FOR_BUMPER_RELEASE);
if (this.state == STATE_CONTINUE_HOLD && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("releaseEquip");
return;
}
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) {
this.setState(STATE_CONTINUE_EQUIP_BD);
if (this.state == STATE_CONTINUE_EQUIP && this.thumbPressed()) {
this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE);
this.callEntityMethodOnGrabbed("releaseEquip");
return;
}
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.thumbPressed()) {
this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE);
this.callEntityMethodOnGrabbed("releaseGrab");
this.callEntityMethodOnGrabbed("startEquip");
return;
}
if (this.state == STATE_CONTINUE_HOLD && this.thumbPressed()) {
this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE);
return;
}
this.heartBeat(this.grabbedEntity);
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position"]);
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]);
if (!props.position) {
// server may have reset, taking our equipped entity with it. move back to "off" stte
this.setState(STATE_RELEASE);
@ -1458,7 +1571,11 @@ function MyController(hand) {
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
props.parentID + " " + vec3toStr(props.position));
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed(this.state == STATE_NEAR_GRABBING ? "releaseGrab" : "releaseEquip");
if (this.state == STATE_CONTINUE_NEAR_GRABBING) {
this.callEntityMethodOnGrabbed("releaseGrab");
} else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD)
this.callEntityMethodOnGrabbed("releaseEquip");
}
return;
}
@ -1475,11 +1592,25 @@ function MyController(hand) {
var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
if (deltaTime > 0.0) {
var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition);
var previousEulers = Quat.safeEulerAngles(this.currentObjectRotation);
var newEulers = Quat.safeEulerAngles(props.rotation);
var worldDeltaRotation = Vec3.subtract(newEulers, previousEulers);
this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaTime);
this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaTime * 180.0));
this.currentObjectPosition = props.position;
this.currentObjectRotation = props.rotation;
}
this.currentHandControllerTipPosition = handControllerPosition;
this.currentObjectTime = now;
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
if (this.state === STATE_CONTINUE_EQUIP) {
if (this.state === STATE_CONTINUE_EQUIP || this.state === STATE_CONTINUE_HOLD) {
this.callEntityMethodOnGrabbed("continueEquip");
}
if (this.state == STATE_CONTINUE_NEAR_GRABBING) {
@ -1520,14 +1651,19 @@ function MyController(hand) {
}
};
this.waitingForBumperRelease = function() {
if (this.bumperReleased()) {
this.waitingForEquipThumbRelease = function() {
if (this.thumbReleased() && this.triggerSmoothedReleased()) {
this.setState(STATE_EQUIP);
}
};
this.waitingForReleaseThumbRelease = function() {
if (this.thumbReleased() && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
}
};
this.nearTrigger = function() {
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("stopNearTrigger");
return;
@ -1540,7 +1676,7 @@ function MyController(hand) {
};
this.farTrigger = function() {
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("stopFarTrigger");
return;
@ -1550,7 +1686,7 @@ function MyController(hand) {
};
this.continueNearTrigger = function() {
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("stopNearTrigger");
return;
@ -1624,7 +1760,7 @@ function MyController(hand) {
};
this.continueFarTrigger = function() {
if (this.triggerSmoothedReleased()) {
if (this.triggerSmoothedReleased() && this.bumperReleased()) {
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed("stopFarTrigger");
return;
@ -1722,7 +1858,6 @@ function MyController(hand) {
};
this.release = function() {
this.turnLightsOff();
this.turnOffVisualizations();
@ -1784,6 +1919,11 @@ function MyController(hand) {
};
this.activateEntity = function(entityID, grabbedProperties, wasLoaded) {
if (this.entityActivated) {
return;
}
this.entityActivated = true;
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
var now = Date.now();
@ -1858,6 +1998,11 @@ function MyController(hand) {
}
this.deactivateEntity = function(entityID, noVelocity) {
if (!this.entityActivated) {
return;
}
this.entityActivated = false;
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
if (data && data["refCount"]) {
data["refCount"] = data["refCount"] - 1;
@ -1873,19 +2018,33 @@ function MyController(hand) {
// things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If
// it looks like the dropped thing should fall, give it a little velocity.
var parentID = Entities.getEntityProperties(entityID, ["parentID"]).parentID;
var props = Entities.getEntityProperties(entityID, ["parentID", "velocity"])
var parentID = props.parentID;
var forceVelocity = false;
var doSetVelocity = false;
if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID) {
// TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up
// props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that
// is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab
// can be thrown.
doSetVelocity = true;
}
if (!noVelocity &&
!doSetVelocity &&
parentID == MyAvatar.sessionUUID &&
Vec3.length(data["gravity"]) > 0.0 &&
data["dynamic"] &&
data["parentID"] == NULL_UUID &&
!data["collisionless"]) {
deactiveProps["velocity"] = {
x: 0.0,
y: 0.1,
z: 0.0
};
doSetVelocity = false;
}
if (noVelocity) {
deactiveProps["velocity"] = {
@ -1898,9 +2057,23 @@ function MyController(hand) {
y: 0.0,
z: 0.0
};
doSetVelocity = false;
}
Entities.editEntity(entityID, deactiveProps);
if (doSetVelocity) {
// this is a continuation of the TODO above -- we shouldn't need to set this here.
// do this after the parent has been reset. setting this at the same time as
// the parent causes it to go off in the wrong direction. This is a bug that should
// be fixed.
Entities.editEntity(entityID, {
velocity: this.currentVelocity,
// angularVelocity: this.currentAngularVelocity
});
}
data = null;
} else if (this.doubleParentGrab) {
// we parent-grabbed this from another parent grab. try to put it back where we found it.
@ -1942,7 +2115,7 @@ function MyController(hand) {
this.checkNewlyLoaded = function(loadedEntityID) {
if (this.state == STATE_OFF ||
this.state == STATE_SEARCHING ||
this.state == STATE_EQUIP_SEARCHING) {
this.state == STATE_HOLD_SEARCHING) {
var loadedProps = Entities.getEntityProperties(loadedEntityID);
if (loadedProps.parentID != MyAvatar.sessionUUID) {
return;
@ -1974,6 +2147,9 @@ mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress);
mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress);
mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress);
mapping.from([Controller.Standard.LeftPrimaryThumb]).peek().to(leftController.thumbPress);
mapping.from([Controller.Standard.RightPrimaryThumb]).peek().to(rightController.thumbPress);
Controller.enableMapping(MAPPING_NAME);
//the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items

View file

@ -0,0 +1,68 @@
// cowEntityScript.js
// examples/cows
//
// Created by Eric Levin on 3/25/16
// Copyright 2016 High Fidelity, Inc.
//
// This entity script handles the logic for untipping a cow after it collides with something
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include("../libraries/utils.js");
var _this = this;
_this.COLLISION_COOLDOWN_TIME = 5000;
this.preload = function(entityID) {
print("EBL Preload!!");
_this.entityID = entityID;
_this.mooSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/moo.wav")
_this.mooSoundOptions = {volume: 0.7, loop: false};
_this.timeSinceLastCollision = 0;
_this.shouldUntipCow = true;
}
this.collisionWithEntity = function(myID, otherID, collisionInfo) {
if(_this.shouldUntipCow) {
Script.setTimeout(function() {
_this.untipCow();
_this.shouldUntipCow = true;
}, _this.COLLISION_COOLDOWN_TIME);
}
_this.shouldUntipCow = false;
}
this.untipCow = function() {
// keep yaw but reset pitch and roll
var cowProps = Entities.getEntityProperties(_this.entityID, ["rotation", "position"]);
var eulerRotation = Quat.safeEulerAngles(cowProps.rotation);
eulerRotation.x = 0;
eulerRotation.z = 0;
var newRotation = Quat.fromVec3Degrees(eulerRotation);
Entities.editEntity(_this.entityID, {
rotation: newRotation,
velocity: {x: 0, y: 0, z: 0},
angularVelocity: {x: 0, y: 0, z:0}
});
_this.mooSoundOptions.position = cowProps.position;
if (!_this.soundInjector) {
_this.soundInjector = Audio.playSound(_this.mooSound, _this.mooSoundOptions);
} else {
_this.soundInjector.setOptions(_this.mooSoundOptions);
_this.soundInjector.restart();
}
}
});

View file

@ -0,0 +1,53 @@
// cowSpawner.js
// examples/cows
//
// Created by Eric Levin on 3/25/16
// Copyright 2016 High Fidelity, Inc.
//
// This spawns a cow which will untip itself
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var orientation = MyAvatar.orientation;
orientation = Quat.safeEulerAngles(orientation);
orientation.x = 0;
orientation = Quat.fromVec3Degrees(orientation);
var center = Vec3.sum(MyAvatar.getHeadPosition(), Vec3.multiply(2, Quat.getFront(orientation)));
var SCRIPT_URL = Script.resolvePath("cowEntityScript.js?");
var cow = Entities.addEntity({
type: "Model",
modelURL: "http://hifi-content.s3.amazonaws.com/DomainContent/production/cow/newMooCow.fbx",
name: "playa_model_throwinCow",
position: center,
animation: {
currentFrame: 278,
running: true,
url: "http://hifi-content.s3.amazonaws.com/DomainContent/Junkyard/Playa/newMooCow.fbx"
},
dimensions: {
x: 0.739,
y: 1.613,
z: 2.529
},
dynamic: true,
gravity: {
x: 0,
y: -5,
z: 0
},
shapeType: "box",
script: SCRIPT_URL,
userData: "{\"grabbableKey\":{\"grabbable\":true}}"
});
function cleanup() {
Entities.deleteEntity(cow);
}
Script.scriptEnding.connect(cleanup);

View file

@ -20,5 +20,5 @@ Script.load("controllers/squeezeHands.js");
Script.load("grab.js");
Script.load("directory.js");
Script.load("dialTone.js");
Script.load("attachedEntitiesManager.js");
// Script.load("attachedEntitiesManager.js");
Script.load("depthReticle.js");

View file

@ -112,7 +112,7 @@ function autoHideReticle() {
function checkReticleDepth() {
var now = Date.now();
var timeSinceLastDepthCheck = now - lastDepthCheckTime;
if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) {
if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS && Reticle.visible) {
var newDesiredDepth = desiredDepth;
lastDepthCheckTime = now;
var reticlePosition = Reticle.position;
@ -160,7 +160,6 @@ function moveToDesiredDepth() {
} else {
newDepth = Reticle.depth + distanceToAdjustThisCycle;
}
Reticle.setDepth(newDepth);
}
}

View file

@ -0,0 +1,883 @@
/*
// edit-style.css
//
// Created by Ryan Huffman on 13 Nov 2014
// 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
*/
@font-face {
font-family: Raleway-Regular;
src: url(../../resources/fonts/Raleway-Regular.ttf), /* Production */
url(../../interface/resources/fonts/Raleway-Regular.ttf); /* Development */
}
@font-face {
font-family: Raleway-Light;
src: url(../../resources/fonts/Raleway-Light.ttf),
url(../../interface/resources/fonts/Raleway-Light.ttf);
}
@font-face {
font-family: Raleway-Bold;
src: url(../../resources/fonts/Raleway-Bold.ttf),
url(../../interface/resources/fonts/Raleway-Bold.ttf);
}
@font-face {
font-family: Raleway-SemiBold;
src: url(../../resources/fonts/Raleway-SemiBold.ttf),
url(../../interface/resources/fonts/Raleway-SemiBold.ttf);
}
@font-face {
font-family: FiraSans-SemiBold;
src: url(../../resources/fonts/FiraSans-SemiBold.ttf),
url(../../interface/resources/fonts/FiraSans-SemiBold.ttf);
}
@font-face {
font-family: AnonymousPro-Regular;
src: url(../../resources/fonts/AnonymousPro-Regular.ttf),
url(../../interface/resources/fonts/AnonymousPro-Regular.ttf);
}
@font-face {
font-family: HiFi-Glyphs;
src: url(../../resources/fonts/hifi-glyphs.ttf),
url(../../interface/resources/fonts/hifi-glyphs.ttf);
}
* {
margin: 0;
padding: 0;
}
body {
padding: 24px 12px 24px 12px;
color: #afafaf;
background-color: #404040;
font-family: Raleway-Regular;
font-size: 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
table {
font-family: FiraSans-SemiBold;
font-size: 15px;
color: #afafaf;
border-collapse: collapse;
width: 100%;
border: 2px solid #575757;
border-radius: 7px;
}
thead {
font-family: Raleway-Regular;
font-size: 12px;
text-transform: uppercase;
background-color: #1c1c1c;
padding: 1px 0px;
border-bottom: 1px solid #575757;
width: 100%;
}
tbody {
width: 100%;
}
tfoot {
font-family: Raleway-Light;
font-size: 13px;
background-color: #1c1c1c;
border-top: 1px solid #575757;
width: 100%;
}
tfoot tr {
background-color: #1c1cff;
}
thead tr {
height: 26px; /* 28px with thead padding */
}
thead th {
height: 26px;
background-color: #1c1c1c;
border-right: 1px solid #575757;
}
thead th:last-child {
border: none;
}
tbody td {
height: 26px;
}
tfoot td {
height: 18px;
width: 100%;
background-color: #1c1c1c;
margin-left: 12px;
}
tr {
width: 100%;
cursor: pointer;
}
tr:nth-child(odd) {
background-color: #2e2e2e;
}
tr:nth-child(even) {
background-color: #1c1c1c;
}
tr:focus {
outline: none;
}
tr.selected {
color: #000000;
background-color: #00b4ef;
}
th {
text-align: center;
word-wrap: nowrap;
white-space: nowrap;
padding-left: 12px;
padding-right: 12px;
}
td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: nowrap;
padding-left: 12px;
padding-right: 12px;
}
td.url {
white-space: nowrap;
overflow: hidden;
}
input[type="text"], input[type="number"], textarea {
margin: 0;
padding: 0 12px;
color: #afafaf;
background-color: #252525;
border: none;
font-family: FiraSans-SemiBold;
font-size: 15px;
}
textarea {
font-family: AnonymousPro-Regular;
font-size: 16px;
padding-top: 5px;
padding-bottom: 5px;
min-height: 64px;
width: 100%;
resize: vertical;
}
input::-webkit-input-placeholder {
font-style: italic;
}
input:focus, textarea:focus {
color: #fff;
background-color: #000;
outline: 1px solid #00b4ef;
outline-offset: -1px;
}
input::selection, textarea::selection {
color: #000000;
background-color: #00b4ef;
}
input.search {
border-radius: 14px;
}
input:disabled, textarea:disabled {
background-color: #383838;
color: #afafaf;
}
input[type="text"] {
height: 28px;
width: 100%;
}
input[type="number"] {
position: relative;
height: 28px;
width: 120px;
}
input[type=number] {
padding-right: 6px;
}
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
opacity: 1.0;
display: block;
position: relative;
width: 10px;
overflow: hidden;
font-family: hifi-glyphs;
font-size: 50px;
color: #afafaf;
cursor: pointer;
/*background-color: #000000;*/
}
input[type=number]::-webkit-inner-spin-button:before,
input[type=number]::-webkit-inner-spin-button:after {
position:absolute;
left: -21px;
line-height: 8px;
text-align: center;
}
input[type=number]::-webkit-inner-spin-button:before {
content: "6";
top: 5px;
}
input[type=number]::-webkit-inner-spin-button:after {
content: "5";
bottom: 6px;
}
input[type="number"]::-webkit-inner-spin-button:hover {
color: #ffffff;
}
input.no-spin::-webkit-outer-spin-button,
input.no-spin::-webkit-inner-spin-button {
display: none;
-webkit-appearance: none;
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
padding-right: 12px;
}
input[type=button] {
font-family: Raleway-Bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
padding: 0px 12px;
margin-right: 8px;
border-radius: 5px;
border: none;
color: #fff;
background-color: #000;
background: linear-gradient(#343434 20%, #000 100%);
cursor: pointer;
}
input[type=button].glyph {
font-family: HiFi-Glyphs;
font-size: 20px;
text-transform: none;
min-width: 32px;
padding: 0;
}
input[type=button].red {
color: #fff;
background-color: #94132e;
background: linear-gradient(#d42043 20%, #94132e 100%);
}
input[type=button].blue {
color: #fff;
background-color: #94132e;
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
}
input[type=button]:enabled:hover {
background: linear-gradient(#000, #000);
border: none;
}
input[type=button].red:enabled:hover {
background: linear-gradient(#d42043, #d42043);
border: none;
}
input[type=button].blue:enabled:hover {
background: linear-gradient(#00b4ef, #00b4ef);
border: none;
}
input[type=button]:active {
background: linear-gradient(#343434, #343434);
}
input[type=button].red:active {
background: linear-gradient(#94132e, #94132e);
}
input[type=button].blue:active {
background: linear-gradient(#1080b8, #1080b8);
}
input[type=button]:disabled {
color: #252525;
background: linear-gradient(#575757 20%, #252525 100%);
}
input[type=checkbox] {
display: none;
}
input[type=checkbox] + label {
padding-left: 24px;
background-position-y: 6px;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC);
}
input[type=checkbox]:enabled + label:hover {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=);
}
input[type=checkbox]:checked + label {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC);
}
input[type=checkbox]:checked + label:hover {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=);
}
.selectable {
-webkit-touch-callout: text;
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
cursor: text;
}
.color-box {
display: inline-block;
width: 15pt;
height: 15pt;
border: 0.75pt solid black;
margin: 1.5pt;
cursor: pointer;
}
.color-box.highlight {
width: 13.5pt;
height: 13.5pt;
border: 1.5pt solid black;
}
.section-header, .sub-section-header {
display: table;
width: 100%;
margin: 22px -12px 0 -12px;
padding: 14px 12px 0 12px;
font-family: Raleway-Regular;
font-size: 12px;
color: #afafaf;
height: 28px;
text-transform: uppercase;
outline: none;
}
.section-header {
position: relative;
background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAqCAIAAAAbNW1vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAmSURBVChTY1BFAgzhSIDBAQmMcoYHRwIJMCgjAQZ9JMBgBQdWVgBh5XmBV5A2FQAAAABJRU5ErkJggg==) repeat-x top left;
}
.sub-section-header, .no-collapse {
background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left;
}
.section-header:first-child {
margin-top: 0;
padding-top: 0;
background: none;
height: auto;
}
.sub-section-header {
margin-bottom: -10px;
}
.section-header span {
font-family: HiFi-Glyphs;
font-size: 30px;
float: right;
position: absolute;
top: 4px;
right: 6px;
}
.section-header[collapsed="true"] {
margin-bottom: -22px;
}
.text-group[collapsed="true"] ~ .text-group,
.zone-group[collapsed="true"] ~ .zone-group,
.web-group[collapsed="true"] ~ .web-group,
.hyperlink-group[collapsed="true"] ~ .hyperlink-group,
.spatial-group[collapsed="true"] ~ .spatial-group,
.physical-group[collapsed="true"] ~ .physical-group,
.behavior-group[collapsed="true"] ~ .behavior-group,
.model-group[collapsed="true"] ~ .model-group,
.light-group[collapsed="true"] ~ .light-group {
display: none !important;
}
.property {
display: table;
width: 100%;
margin-top: 22px;
min-height: 29px;
}
.property label {
display: table-cell;
vertical-align: middle;
font-family: Raleway-SemiBold;
font-size: 14px;
}
.value {
display: block;
min-height: 18px;
}
.value label {
display: inline-block;
vertical-align: top;
width: 48px;
}
.value span {
font-family: FiraSans-SemiBold;
font-size: 15px;
}
.checkbox + .checkbox {
margin-top: 0;
}
.checkbox-sub-props {
margin-top: 18px;
}
.property .number {
float: left;
}
.property .number + .number {
margin-left: 12px;
}
.text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label {
float: left;
margin-bottom: 4px;
}
.number > input {
clear: both;
float: left;
}
.number > span {
clear: both;
float: left;
}
.xyz > div, .pyr > div, .gen > div {
clear: both;
}
.unit {
padding-left: 4px;
vertical-align: top;
position: relative;
top: 5px;
}
.dropdown {
position: relative;
margin-bottom: -17px;
}
.dropdown select {
clear: both;
}
.dropdown dl {
clear: both;
}
.dropdown dl {
font-family: FiraSans-SemiBold;
font-size: 15px;
width: 172px;
height: 28px;
padding: 0 28px 0 12px;
color: #afafaf;
background: linear-gradient(#7d7d7d 20%, #686a68 100%);
position: relative;
}
.dropdown dl[dropped="true"] {
color: #404040;
background: linear-gradient(#afafaf, #afafaf);
}
.dropdown dt {
height: 100%;
box-sizing: border-box;
border-right: 1px solid #121212;
width: 100%;
}
.dropdown dt:hover {
color: #404040;
}
.dropdown dt:focus {
outline: none;
}
.dropdown dt span:first-child {
display: inline-block;
position: relative;
top: 5px;
}
.dropdown dt span:last-child {
font-family: HiFi-Glyphs;
font-size: 42px;
float: right;
margin-right: -48px;
position: relative;
left: -12px;
top: -11px;
}
.dropdown dd {
position: absolute;
top: 28px;
left: 3px;
display: none;
}
.dropdown dl[dropped="true"] dd {
display: block;
}
.dropdown li {
list-style-type: none;
padding: 3px 0 1px 12px;
width: 200px;
height: auto;
font-family: FiraSans-SemiBold;
font-size: 15px;
color: #404040;
background-color: #afafaf
}
.dropdown li:hover {
background-color: #00b4ef;
}
div.refresh {
box-sizing: border-box;
padding-right: 44px;
}
div.refresh input[type="button"] {
float: right;
margin-right: -44px;
}
.color-picker {
box-sizing: border-box;
float: left;
margin-bottom: 12px;
width: 36px;
height: 36px;
border: 4px solid #afafaf;
border-radius: 4px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVChTjcxBDoAwCETRwTs33sFjwB6uaCE1Ggvav5qQF7CSqu40dllHjYiOT3gh3yV8Ii+Fb+RNMEP9hm3sKENmBhG5P1aImWMH/EMerSAAOAFgTC/R8ZXSXAAAAABJRU5ErkJggg==);
background-position: bottom right;
background-repeat: no-repeat;
}
.color-picker:focus {
outline: none;
}
.color-picker[active="true"] {
border-color: #000;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABDSURBVChTjcoLCkAhCETRNq0tf97Y5xGZ1gVJ45TH6njThIO+xk2UwhWFcEdH6JCqOuiQiMDi/hcii3crRRb/7ggAPvIMVihQwvSXAAAAAElFTkSuQmCC);
}
.rgb label {
float: left;
margin-top: 10px;
margin-left: 12px;
}
.rgb label + * {
clear: both;
}
.tuple {
width: 100%;
text-align: center;
}
.tuple div {
display: inline-block;
position: relative;
min-width: 120px;
min-height: 1px;
}
.tuple div:nth-child(1) {
float: left;
}
.tuple div:nth-child(2) {
}
.tuple div:nth-child(3) {
float: right;
}
.rgb .tuple input {
padding-left: 65px;
}
.xyz .tuple input {
padding-left: 25px;
}
.pyr .tuple input {
padding-left: 45px;
}
.tuple div > label:first-child {
float: left;
}
.tuple div > label + input {
clear: both;
float: left;
}
.tuple div input + label {
display: inline !important;
float: none !important;
position: absolute;
margin-top: 8px;
margin-left: 6px;
left: 0;
font-family: FiraSans-SemiBold;
font-size: 12px;
}
.tuple .red + label, .tuple .x + label, .tuple .pitch + label {
color: #e2334d;
}
.tuple .green + label, .tuple .y + label, .tuple .yaw + label {
color: #1ac567;
}
.tuple .blue + label, .tuple .z + label, .tuple .roll + label {
color: #1080b8;
}
.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus {
outline-color: #e2334d;
}
.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus {
outline-color: #1ac567;
}
tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus {
outline-color: #1080b8;
}
.xyz .buttons input {
margin-top: 12px;
}
.xyz .buttons span {
word-wrap: nowrap;
white-space: nowrap;
}
.row input {
float: left;
}
.row input[type=button] {
margin-left: 8px;
margin-right: 0;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background-color: #2e2e2e;
}
::-webkit-scrollbar-thumb {
background-color: #696969;
border: 2px solid #2e2e2e;
border-radius: 8px;
}
/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and
have correct background color with and without scrollbars. */
textarea:enabled::-webkit-resizer {
background-size: 10px 10px;
background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right;
}
textarea:focus::-webkit-resizer {
background-size: 10px 10px;
background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right;
}
textarea:enabled[scrolling="true"]::-webkit-resizer {
background-size: 10px 10px;
background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right;
}
#entity-list-header {
margin-bottom: 24px;
}
#delete {
float: right;
margin-right: 0;
background-color: #ff0000;
}
#entity-list {
position: relative; /* New positioning context. */
}
#search-area {
padding-right: 148px;
padding-bottom: 24px;
}
#filter {
width: 98%;
}
#radius-and-unit {
float: right;
margin-right: -148px;
}
#entity-table-scroll {
/* Height is set by JavaScript. */
width: 100%;
overflow-x: hidden;
overflow-y: auto;
padding-top: 28px; /* Space for header and footer outside of scroll region. */
margin-top: 28px;
border-left: 2px solid #575757;
border-right: 2px solid #575757;
}
#entity-table-scroll, #entity-table {
background-color: #1c1c1c;
}
#entity-table {
margin-top: -28px;
margin-bottom: -18px;
table-layout: fixed;
border: none;
}
#entity-table thead {
border: 2px solid #575757;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
border-bottom: 1px solid #575757;
}
#entity-table tfoot {
border: 2px solid #575757;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
border-top: 1px solid #575757;
}
#entity-table thead tr, #entity-table thead tr th,
#entity-table tfoot tr, #entity-table tfoot tr td {
background: none;
}
#entity-table th:focus {
outline: none;
}
#col-type {
width: 16%;
}
#col-name {
width: 42%;
}
#col-url {
width: 42%;
}
#entity-table thead {
position: absolute;
top: 49px;
left: 0;
width: 100%;
}
#entity-table thead th {
padding: 0;
}
#entity-table tfoot {
position: absolute;
bottom: -21px;
left: 0;
width: 100%;
}
#no-entities {
display: none;
position: absolute;
top: 80px;
padding: 12px;
font-family: FiraSans-SemiBold;
font-size: 15px;
font-style: italic;
color: #afafaf;
}
#properties-list .property:first-child {
margin-top: 0;
}
#property-id::selection {
color: #000000;
background-color: #00b4ef;
}
input#dimension-rescale-button {
min-width: 50px;
margin-left: 6px;
}
.color-set label, .color-set span {
display: block;
}
.color-set span {
padding-top: 2px;
}

View file

@ -1,6 +1,16 @@
<!--
// entityList.html
//
// Created by Ryan Huffman on 19 Nov 2014
// 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
-->
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="edit-style.css">
<script src="list.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
@ -26,8 +36,10 @@
elDelete = document.getElementById("delete");
elTeleport = document.getElementById("teleport");
elRadius = document.getElementById("radius");
elFooter = document.getElementById("footer-text");
elNoEntitiesMessage = document.getElementById("no-entities");
elNoEntitiesRadius = document.getElementById("no-entities-radius");
elEntityTableScroll = document.getElementById("entity-table-scroll");
document.getElementById("entity-name").onclick = function() {
setSortColumn('name');
@ -168,6 +180,17 @@
notFound = true;
}
}
if (selectedEntities.length > 1) {
elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected";
} else if (selectedEntities.length === 1) {
elFooter.firstChild.nodeValue = "1 entity selected";
} else if (entityList.visibleItems.length === 1) {
elFooter.firstChild.nodeValue = "1 entity found";
} else {
elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found";
}
return notFound;
}
@ -214,57 +237,90 @@
} else if (data.type == "update") {
var newEntities = data.entities;
if (newEntities.length == 0) {
elEntityTable.style.display = "none";
elNoEntitiesMessage.style.display = "block";
} else {
elEntityTable.style.display = "table";
elNoEntitiesMessage.style.display = "none";
for (var i = 0; i < newEntities.length; i++) {
var id = newEntities[i].id;
addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url);
}
updateSelectedEntities(data.selectedIDs);
resize();
}
}
});
setTimeout(refreshEntities, 1000);
}
function resize() {
// Take up available window space
elEntityTableScroll.style.height = window.innerHeight - 232;
// Update the widths of the header cells to match the body
var tds = document.querySelectorAll("#entity-table-body tr:first-child td");
var ths = document.querySelectorAll("#entity-table thead th");
if (tds.length >= ths.length) {
for (var i = 0; i < ths.length; i++) {
ths[i].style.width = tds[i].offsetWidth;
}
}
};
window.onresize = resize;
resize();
});
}
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
}, false);
}
</script>
</head>
<body onload='loaded();'>
<div id="entity-list-header">
<input type="button" id="refresh" value="Refresh" />
<input type="button" id="teleport" value="Teleport" />
<input type="button" id="delete" style="background-color: rgb(244, 64, 64); float: right" value="Delete" />
<input type="button" class="glyph" id="refresh" value="F" />
<input type="button" id="teleport" value="Jump To Selection" />
<input type="button" class="red" id="delete" value="Delete" />
</div>
<div id="entity-list">
<div id="search-area">
<input type="text" class="search" id="filter" placeholder="Filter" />
<span id="radius-and-unit"><input type="number" id="radius" value="100" />&nbsp;m</span>
<span id="radius-and-unit"><input type="number" id="radius" value="100" /><span class="unit">m</span></span>
</div>
<div id="entity-table-scroll">
<table id="entity-table">
<colgroup>
<col span="1" id="col-type" />
<col span="1" id="col-name" />
<col span="1" id="col-url" />
</colgroup>
<thead>
<tr>
<th id="entity-type" data-sort="type">Type <span class="sort-order" style="display: inline">&nbsp;&#x25BE;</span></th>
<th id="entity-name" data-sort="type">Name <span class="sort-order" style="display: none">&nbsp;&#x25BE;</span></th>
<th id="entity-url" data-sort="url">File <span class="sort-order" style="display: none">&nbsp;&#x25BE;</span></th>
</tr>
</thead>
<tbody class="list" id="entity-table-body">
<tr>
<td class="type">Type</td>
<td class="name">Name</td>
<td class="url"><div class='outer'><div class='inner'>URL</div></div></td>
<td class="id" style="display: none">Type</td>
</tr>
</tbody>
<tfoot>
<tr>
<td id="footer-text" colspan="3">Footer text</td>
</tr>
</tfoot>
</table>
<div id="no-entities">
No entities found within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing.
</div>
</div>
<table id="entity-table">
<thead>
<tr>
<th id="entity-type" data-sort="type">Type <span class="sort-order" style="display: inline">&nbsp;&#x25BE;</span></th>
<th id="entity-name" data-sort="type">Name <span class="sort-order" style="display: inline">&nbsp;&#x25BE;</span></th>
<th id="entity-url" data-sort="url">URL <span class="sort-order" style="display: none">&nbsp;&#x25BE;</span></th>
</tr>
</thead>
<tbody class="list" id="entity-table-body">
<tr>
<td class="id" style="display: none">Type</td>
<td class="type">Type</td>
<td class="name">Name</td>
<td class="url"><div class='outer'><div class='inner'>URL</div></div></td>
</tr>
</tbody>
</table>
</div>
<div id="no-entities">
No entities found within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing.
</div>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,16 @@
<!--
// gridControls.html
//
// Created by Ryan Huffman on 6 Nov 2014
// 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
-->
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="edit-style.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script>
@ -109,61 +119,63 @@
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
});
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
}, false);
}
</script>
</head>
<body onload='loaded();'>
<div class="grid-section">
<div id="grid-section">
<div class="property-section">
<label>Visible</label>
<div class="section-header">
<label>Editing Grid</label>
</div>
<div class="property checkbox">
<input type='checkbox' id="horiz-grid-visible">
<label for="horiz-grid-visible">Visible</label>
</div>
<div class="property checkbox">
<input type="checkbox" id="snap-to-grid">
<label for="snap-to-grid">Snap entities to grid</label>
</div>
<div class="property">
<div class="number">
<label>Major grid size</label>
<span>
<input type="number" id="major-spacing" min="1" step="1" /><span class="unit">m</span>
</span>
</div>
<div class="number">
<label>Minor grid size</label>
<span>
<input type="number" id="minor-spacing" min="0.2" step="0.2" /><span class="unit">m</span>
</span>
</div>
</div>
<div class="property number">
<label>Position (Y axis)</label>
<span>
<input type='checkbox' id="horiz-grid-visible">
<input type="number" id="horiz-y" step="0.1" /><span class="unit">m</span>
</span>
</div>
<div class="property-section">
<label>Snap to grid</label>
<span>
<input type='checkbox' id="snap-to-grid">
</span>
</div>
<div id="horizontal-position" class="property-section">
<label>Position (Y Axis)</label>
<span>
<input type='number' id="horiz-y" class="number" step="0.1"></input>
</span>
</div>
<div class="property-section">
<label>Minor Grid Size</label>
<span>
<input type='number' id="minor-spacing" min="0.2" step="0.2"></input>
</span>
</div>
<div class="property-section">
<label>Major Grid Every</label>
<span>
<input type='number' id="major-spacing" min="1" step="1", ></input>
</span>
</div>
<div class="property-section">
<label>Grid Color</label>
<div class="property color-set">
<label>Grid line color</label>
<span id="grid-colors"></span>
</div>
<div class="property-section">
<div class="property">
<span>
<input type="button" id="move-to-selection" value="Move to Selection">
</span>
</div>
<div class="property-section">
<span>
<input type="button" id="move-to-avatar" value="Move to Avatar">
</span>
<input type="button" id="move-to-selection" value="Align To Selection">
<input type="button" id="move-to-avatar" value="Align To Avatar">
</span>
</div>
</div>
</body>

View file

@ -5,7 +5,7 @@ EntityListTool = function(opts) {
var url = ENTITY_LIST_HTML_URL;
var webView = new OverlayWebWindow({
title: 'Entities', source: url, toolWindow: true
title: 'Entity List', source: url, toolWindow: true
});

View file

@ -20,6 +20,12 @@
<script type="text/javascript" src="../html/eventBridgeLoader.js"></script>
<script type="text/javascript" src="particleExplorer.js?v42"></script>
<script>
function loaded() {
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
}, false);
}
</script>
<style>
@ -60,7 +66,7 @@ body{
</style>
</head>
<body>
<body onload="loaded();">
<div class="importer">
<input type='text' id="importer-input" placeholder="Import: Paste JSON here." onkeypress="handleInputKeyPress(event)">
<div class = "exported-props-section">

View file

@ -120,27 +120,95 @@ function menuItemEvent(menuItem) {
if (menuItem.endsWith(" for Output")) {
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Output");
print("output audio selection..." + selectedDevice);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedOutputMenu, false);
selectedOutputMenu = menuItem;
Menu.setIsOptionChecked(selectedOutputMenu, true);
if (AudioDevice.setOutputDevice(selectedDevice)) {
Settings.setValue(OUTPUT_DEVICE_SETTING, selectedDevice);
}
Menu.menuItemEvent.connect(menuItemEvent);
Menu.menuItemEvent.connect(menuItemEvent);
} else if (menuItem.endsWith(" for Input")) {
var selectedDevice = menuItem.trimStartsWith("Use ").trimEndsWith(" for Input");
print("input audio selection..." + selectedDevice);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.menuItemEvent.disconnect(menuItemEvent);
Menu.setIsOptionChecked(selectedInputMenu, false);
selectedInputMenu = menuItem;
Menu.setIsOptionChecked(selectedInputMenu, true);
if (AudioDevice.setInputDevice(selectedDevice)) {
Settings.setValue(INPUT_DEVICE_SETTING, selectedDevice);
}
Menu.menuItemEvent.connect(menuItemEvent);
Menu.menuItemEvent.connect(menuItemEvent);
}
}
}
Menu.menuItemEvent.connect(menuItemEvent);
// Some HMDs (like Oculus CV1) have a built in audio device. If they
// do, then this function will handle switching to that device automatically
// when you goActive with the HMD active.
var wasHmdMounted = false; // assume it's un-mounted to start
var switchedAudioInputToHMD = false;
var switchedAudioOutputToHMD = false;
var previousSelectedInputAudioDevice = "";
var previousSelectedOutputAudioDevice = "";
function restoreAudio() {
if (switchedAudioInputToHMD) {
print("switching back from HMD preferred audio input to:" + previousSelectedInputAudioDevice);
menuItemEvent("Use " + previousSelectedInputAudioDevice + " for Input");
}
if (switchedAudioOutputToHMD) {
print("switching back from HMD preferred audio output to:" + previousSelectedOutputAudioDevice);
menuItemEvent("Use " + previousSelectedOutputAudioDevice + " for Output");
}
}
function checkHMDAudio() {
// Mounted state is changing... handle switching
if (HMD.mounted != wasHmdMounted) {
print("HMD mounted changed...");
// We're putting the HMD on... switch to those devices
if (HMD.mounted) {
print("NOW mounted...");
var hmdPreferredAudioInput = HMD.preferredAudioInput();
var hmdPreferredAudioOutput = HMD.preferredAudioOutput();
print("hmdPreferredAudioInput:" + hmdPreferredAudioInput);
print("hmdPreferredAudioOutput:" + hmdPreferredAudioOutput);
var hmdHasPreferredAudio = (hmdPreferredAudioInput !== "") || (hmdPreferredAudioOutput !== "");
if (hmdHasPreferredAudio) {
print("HMD has preferred audio!");
previousSelectedInputAudioDevice = Settings.getValue(INPUT_DEVICE_SETTING);
previousSelectedOutputAudioDevice = Settings.getValue(OUTPUT_DEVICE_SETTING);
print("previousSelectedInputAudioDevice:" + previousSelectedInputAudioDevice);
print("previousSelectedOutputAudioDevice:" + previousSelectedOutputAudioDevice);
if (hmdPreferredAudioInput != previousSelectedInputAudioDevice && hmdPreferredAudioInput !== "") {
print("switching to HMD preferred audio input to:" + hmdPreferredAudioInput);
switchedAudioInputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioInput + " for Input");
}
if (hmdPreferredAudioOutput != previousSelectedOutputAudioDevice && hmdPreferredAudioOutput !== "") {
print("switching to HMD preferred audio output to:" + hmdPreferredAudioOutput);
switchedAudioOutputToHMD = true;
menuItemEvent("Use " + hmdPreferredAudioOutput + " for Output");
}
}
} else {
print("HMD NOW un-mounted...");
restoreAudio();
}
}
wasHmdMounted = HMD.mounted;
}
Script.update.connect(checkHMDAudio);
Script.scriptEnding.connect(function () {
restoreAudio();
Menu.menuItemEvent.disconnect(menuItemEvent);
Script.update.disconnect(checkHMDAudio);
});

View file

@ -106,6 +106,13 @@ elseif(WIN32)
# add an executable that also has the icon itself and the configured rc file as resources
add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT})
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND "mt.exe" -manifest "${CMAKE_CURRENT_SOURCE_DIR}/interface.exe.manifest" -inputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1 -outputresource:"$<TARGET_FILE:${TARGET_NAME}>"\;\#1
COMMENT "Adding OS version support manifest to exe"
)
else()
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
endif()

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--This Id value indicates the application supports Windows 7 functionality-->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--This Id value indicates the application supports Windows 8 functionality-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!--This Id value indicates the application supports Windows 8.1 functionality-->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!--This Id value indicates the application supports Windows 10 functionality-->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View file

@ -21,7 +21,7 @@ import "dialogs"
Window {
id: root
objectName: "AssetServer"
title: "My Asset Server"
title: "Asset Browser"
resizable: true
destroyOnInvisible: true
x: 40; y: 40
@ -125,6 +125,10 @@ Window {
}, false);
}
function clear() {
Assets.mappingModel.clear();
}
function reload() {
Assets.mappingModel.refresh();
treeView.selection.clear();
@ -416,7 +420,6 @@ Window {
anchors.top: assetDirectory.bottom
anchors.bottom: uploadSection.top
anchors.margins: 12
anchors.bottomMargin: 0
anchors.left: parent.left
anchors.right: parent.right
@ -445,7 +448,7 @@ Window {
name: "Upload A File"
spacing: hifi.dimensions.contentSpacing.y
anchors.bottom: parent.bottom
height: 130
height: 92
Item {
height: parent.height

View file

@ -1,13 +1,25 @@
//
// ToolWindow.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtWebEngine 1.1
import QtWebChannel 1.0
import Qt.labs.settings 1.0
import "windows" as Windows
import "controls" as Controls
import "windows-uit"
import "controls-uit"
import "styles-uit"
Windows.Window {
Window {
id: toolWindow
resizable: true
objectName: "ToolWindow"
@ -15,9 +27,13 @@ Windows.Window {
destroyOnInvisible: false
closable: true
visible: false
width: 384; height: 640;
title: "Tools"
title: "Edit"
property alias tabView: tabView
implicitWidth: 520; implicitHeight: 695
minSize: Qt.vector2d(400, 500)
HifiConstants { id: hifi }
onParentChanged: {
if (parent) {
x = 120;
@ -32,8 +48,11 @@ Windows.Window {
}
TabView {
anchors.fill: parent
id: tabView;
width: pane.contentWidth
height: pane.scrollHeight // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
property int tabCount: 0
Repeater {
model: 4
Tab {
@ -43,7 +62,7 @@ Windows.Window {
enabled: false
property string originalUrl: "";
Controls.WebView {
WebView {
id: webView;
anchors.fill: parent
enabled: false
@ -60,6 +79,61 @@ Windows.Window {
}
}
}
style: TabViewStyle {
frame: Rectangle { // Background shown before content loads.
anchors.fill: parent
color: hifi.colors.baseGray
}
frameOverlap: 0
tab: Rectangle {
implicitWidth: text.width
implicitHeight: 3 * text.height
color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
RalewayRegular {
id: text
text: styleData.title
font.capitalization: Font.AllUppercase
size: hifi.fontSizes.tabName
width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
elide: Text.ElideRight
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
Rectangle { // Separator.
width: 1
height: parent.height
color: hifi.colors.black
anchors.left: parent.left
anchors.top: parent.top
visible: styleData.index > 0
Rectangle {
width: 1
height: 1
color: hifi.colors.baseGray
anchors.left: parent.left
anchors.bottom: parent.bottom
}
}
Rectangle { // Active underline.
width: parent.width - (styleData.index > 0 ? 1 : 0)
height: 1
anchors.right: parent.right
anchors.bottom: parent.bottom
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
}
}
tabOverlap: 0
}
}
function updateVisiblity() {
@ -129,6 +203,7 @@ Windows.Window {
tab.originalUrl = "";
tab.item.url = "about:blank";
tab.item.enabled = false;
tabView.tabCount--;
}
function addWebTab(properties) {
@ -150,6 +225,7 @@ Windows.Window {
return;
}
if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
}
@ -169,6 +245,7 @@ Windows.Window {
var result = tab.item;
result.enabled = true;
tabView.tabCount++;
console.log("Setting event bridge: " + eventBridge);
result.eventBridgeWrapper.eventBridge = eventBridge;
result.url = properties.source;

View file

@ -46,7 +46,7 @@ Column {
Item {
id: leadingSpace
width: 1
height: isFirst ? hifi.dimensions.contentSpacing.y : hifi.dimensions.controlInterlineHeight
height: isFirst ? hifi.dimensions.contentSpacing.y : 0
anchors.top: parent.top
}
@ -97,11 +97,11 @@ Column {
HiFiGlyphs {
anchors {
verticalCenter: title.verticalCenter
top: title.top
topMargin: -9
right: parent.right
rightMargin: -4
}
y: -2
size: hifi.fontSizes.disclosureButton
text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse
color: hifi.colors.lightGrayText

View file

@ -0,0 +1,74 @@
//
// WebView.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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
//
import QtQuick 2.5
import QtWebEngine 1.1
WebEngineView {
id: root
property var newUrl;
Component.onCompleted: {
console.log("Connecting JS messaging to Hifi Logging")
// Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
}
// FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6
Timer {
id: urlReplacementTimer
running: false
repeat: false
interval: 50
onTriggered: url = newUrl;
}
onUrlChanged: {
console.log("Url changed to " + url);
var originalUrl = url.toString();
newUrl = urlHandler.fixupUrl(originalUrl).toString();
if (newUrl !== originalUrl) {
root.stop();
if (urlReplacementTimer.running) {
console.warn("Replacement timer already running");
return;
}
urlReplacementTimer.start();
}
}
onFeaturePermissionRequested: {
grantFeaturePermission(securityOrigin, feature, true);
}
onLoadingChanged: {
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
root.stop();
}
}
}
}
onNewViewRequested:{
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView)
}
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
// See https://bugreports.qt.io/browse/QTBUG-49521
//profile: desktop.browserProfile
}

View file

@ -44,7 +44,7 @@ Preference {
children: [ contentContainer ]
height: contentContainer.height + (root.isLast ? 2 * hifi.dimensions.contentSpacing.y : 0)
height: contentContainer.height + (contentContainer.isCollapsed ? 0 : hifi.dimensions.controlInterlineHeight)
Component.onCompleted: d.buildPreferences();
@ -111,7 +111,6 @@ Preference {
case Preference.Checkbox:
checkBoxCount++;
console.log("####### checkBoxCount = " + checkBoxCount);
builder = checkboxBuilder;
break;

View file

@ -120,7 +120,7 @@ Window {
}
HifiControls.VerticalSpacer {
height: 2 // Table view draws a little taller than it's height.
height: hifi.dimensions.controlInterlineHeight + 2 // Table view draws a little taller than it's height.
}
}

View file

@ -58,6 +58,7 @@ Item {
readonly property color lightGrayText: "#afafaf"
readonly property color faintGray: "#e3e3e3"
readonly property color primaryHighlight: "#00b4ef"
readonly property color blueHighlight: "#00b4ef"
readonly property color blueAccent: "#1080b8"
readonly property color redHighlight: "#e2334d"
readonly property color redAccent: "#b70a37"
@ -75,12 +76,12 @@ Item {
readonly property color lightGrayText80: "#ccafafaf"
readonly property color faintGray80: "#cce3e3e3"
readonly property color faintGray50: "#80e3e3e3"
readonly property color locked: "#252525"
// Other colors
readonly property color white: "#ffffff"
readonly property color gray: "#808080"
readonly property color black: "#000000"
readonly property color locked: "#252525"
// Semitransparent
readonly property color white50: "#80ffffff"
readonly property color white30: "#4dffffff"
@ -115,6 +116,8 @@ Item {
readonly property color dropDownDarkStart: "#7d7d7d"
readonly property color dropDownDarkFinish: "#6b6a6b"
readonly property color textFieldLightBackground: "#d4d4d4"
readonly property color tabBackgroundDark: "#252525"
readonly property color tabBackgroundLight: "#d4d4d4"
}
Item {
@ -152,6 +155,7 @@ Item {
readonly property real sectionName: dimensions.largeScreen ? 12 : 10
readonly property real inputLabel: dimensions.largeScreen ? 14 : 10
readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12
readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9
readonly property real tableText: dimensions.largeScreen ? 15 : 12
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
readonly property real iconButton: dimensions.largeScreen ? 13 : 9
@ -164,7 +168,7 @@ Item {
readonly property real menuItem: dimensions.largeScreen ? 15 : 11
readonly property real shortcutText: dimensions.largeScreen ? 13 : 9
readonly property real carat: dimensions.largeScreen ? 38 : 30
readonly property real disclosureButton: dimensions.largeScreen ? 20 : 15
readonly property real disclosureButton: dimensions.largeScreen ? 30 : 22
}
Item {

View file

@ -53,7 +53,7 @@ Fadable {
property bool resizable: false
property vector2d minSize: Qt.vector2d(100, 100)
property vector2d maxSize: Qt.vector2d(1280, 720)
property vector2d maxSize: Qt.vector2d(1280, 800)
// The content to place inside the window, determined by the client
default property var content

View file

@ -680,6 +680,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated);
connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID);
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
@ -3215,6 +3216,25 @@ void Application::update(float deltaTime) {
updateLOD();
if (!_physicsEnabled && _processOctreeStatsCounter > 0) {
// process octree stats packets are sent in between full sends of a scene.
// We keep physics disabled until we've recieved a full scene and everything near the avatar in that
// scene is ready to compute its collision shape.
if (nearbyEntitiesAreReadyForPhysics()) {
_physicsEnabled = true;
getMyAvatar()->updateMotionBehaviorFromMenu();
} else {
auto characterController = getMyAvatar()->getCharacterController();
if (characterController) {
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
// a non-loading collision hull.
characterController->setEnabled(false);
}
}
}
{
PerformanceTimer perfTimer("devices");
DeviceTracker::updateAll();
@ -4124,6 +4144,27 @@ void Application::nodeAdded(SharedNodePointer node) {
}
}
void Application::nodeActivated(SharedNodePointer node) {
if (node->getType() == NodeType::AssetServer) {
// asset server just connected - check if we have the asset browser showing
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer");
if (assetDialog) {
auto nodeList = DependencyManager::get<NodeList>();
if (nodeList->getThisNodeCanRez()) {
// call reload on the shown asset browser dialog to get the mappings (if permissions allow)
QMetaObject::invokeMethod(assetDialog, "reload");
} else {
// we switched to an Asset Server that we can't modify, hide the Asset Browser
assetDialog->setVisible(false);
}
}
}
}
void Application::nodeKilled(SharedNodePointer node) {
// These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work:
@ -4136,10 +4177,7 @@ void Application::nodeKilled(SharedNodePointer node) {
if (node->getType() == NodeType::AudioMixer) {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled");
}
if (node->getType() == NodeType::EntityServer) {
} else if (node->getType() == NodeType::EntityServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
_entityServerJurisdictions.withReadLock([&] {
@ -4170,6 +4208,16 @@ void Application::nodeKilled(SharedNodePointer node) {
} else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
} else if (node->getType() == NodeType::AssetServer) {
// asset server going away - check if we have the asset browser showing
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer");
if (assetDialog) {
// call reload on the shown asset browser dialog
QMetaObject::invokeMethod(assetDialog, "clear");
}
}
}
void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) {
@ -4260,22 +4308,7 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
});
});
if (!_physicsEnabled) {
if (nearbyEntitiesAreReadyForPhysics()) {
// These stats packets are sent in between full sends of a scene.
// We keep physics disabled until we've recieved a full scene and everything near the avatar in that
// scene is ready to compute its collision shape.
_physicsEnabled = true;
getMyAvatar()->updateMotionBehaviorFromMenu();
} else {
auto characterController = getMyAvatar()->getCharacterController();
if (characterController) {
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
// a non-loading collision hull.
characterController->setEnabled(false);
}
}
}
_processOctreeStatsCounter++;
return statsMessageLength;
}

View file

@ -312,6 +312,7 @@ private slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
void nodeAdded(SharedNodePointer node);
void nodeActivated(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void packetSent(quint64 length);
void updateDisplayMode();
@ -515,6 +516,8 @@ private:
std::map<void*, std::function<void()>> _preRenderLambdas;
std::mutex _preRenderLambdasLock;
std::atomic<uint32_t> _processOctreeStatsCounter { 0 };
};
#endif // hifi_Application_h

View file

@ -137,16 +137,15 @@ Menu::Menu() {
QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled);
assetServerAction->setEnabled(nodeList->getThisNodeCanRez());
// Edit > Reload All Content [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
// Edit > Package Model... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
qApp, SLOT(packageModel()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
// Edit > Reload All Content [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
// Audio menu ----------------------------------
MenuWrapper* audioMenu = addMenu("Audio");

View file

@ -34,7 +34,7 @@ namespace MenuOption {
const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose";
const QString AnimDebugDrawPosition= "Debug Draw Position";
const QString AssetMigration = "ATP Asset Migration";
const QString AssetServer = "My Asset Server";
const QString AssetServer = "Asset Browser";
const QString Attachments = "Attachments...";
const QString AudioNetworkStats = "Audio Network Stats";
const QString AudioNoiseReduction = "Audio Noise Reduction";

View file

@ -105,13 +105,18 @@ bool ModelPackager::loadModel() {
qWarning() << QString("ModelPackager::loadModel(): Could not open FBX file %1").arg(_fbxInfo.filePath());
return false;
}
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
try {
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
} catch (const QString& error) {
qCDebug(interfaceapp) << "Error reading " << _fbxInfo.filePath() << ": " << error;
return false;
}
return true;
}

View file

@ -141,7 +141,7 @@ AABox Avatar::getBounds() const {
if (!_skeletonModel->isRenderable()) {
return AABox(getPosition(), getUniformScale()); // approximately 2m tall, scaled to user request.
}
return _skeletonModel->getPartBounds(0, 0, getPosition(), getOrientation());
return _skeletonModel->getRenderableMeshBound();
}
void Avatar::animateScaleChanges(float deltaTime) {

View file

@ -31,6 +31,7 @@
#include <BugSplat.h>
#endif
int main(int argc, const char* argv[]) {
disableQtBearerPoll(); // Fixes wifi ping spikes
@ -180,6 +181,8 @@ int main(int argc, const char* argv[]) {
mpSender.sendAdditionalFile(qPrintable(logPath));
#endif
printSystemInformation();
QTranslator translator;
translator.load("i18n/interface_en");
app.installTranslator(&translator);

View file

@ -13,6 +13,7 @@
#include <QtScript/QScriptEngine>
#include <QtCore/QFile>
#include <QtCore/QThread>
#include <AssetRequest.h>
#include <AssetUpload.h>
@ -20,6 +21,19 @@
#include <NetworkLogging.h>
#include <OffscreenUi.h>
void AssetMappingModel::clear() {
// make sure we are on the same thread before we touch the hash
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "clear");
return;
}
qDebug() << "Clearing loaded asset mappings for Asset Browser";
_pathToItemMap.clear();
QStandardItemModel::clear();
}
AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() {
_proxyModel.setSourceModel(&_assetMappingModel);
_proxyModel.setSortRole(Qt::DisplayRole);

View file

@ -21,7 +21,6 @@
#include <QSortFilterProxyModel>
class AssetMappingModel : public QStandardItemModel {
Q_OBJECT
public:
@ -30,6 +29,9 @@ public:
bool isKnownMapping(QString path) const { return _pathToItemMap.contains(path); }
bool isKnownFolder(QString path) const;
public slots:
void clear();
signals:
void errorGettingMappings(QString errorString);

View file

@ -17,6 +17,7 @@
#include <QtCore/QDebug>
#include <udt/PacketHeaders.h>
#include <LogHandler.h>
#include "AudioLogging.h"
@ -129,7 +130,10 @@ int AudioRingBuffer::writeData(const char* data, int maxSize) {
int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++;
qCDebug(audio) << "Overflowed ring buffer! Overwriting old data";
const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." };
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG);
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
}
if (_endOfLastWrite + samplesToCopy <= _buffer + _bufferLength) {

View file

@ -47,6 +47,9 @@ template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
position += compressedLength;
QByteArray uncompressed = qUncompress(compressed);
if (uncompressed.isEmpty()) { // answers empty byte array if corrupt
throw QString("corrupt fbx file");
}
QDataStream uncompressedIn(uncompressed);
uncompressedIn.setByteOrder(QDataStream::LittleEndian);
uncompressedIn.setVersion(QDataStream::Qt_4_5); // for single/double precision switch

View file

@ -29,6 +29,10 @@ public:
const QUrl& textureBaseUrl;
};
QUrl resolveTextureBaseUrl(const QUrl& url, const QUrl& textureBaseUrl) {
return textureBaseUrl.isValid() ? textureBaseUrl : url;
}
class GeometryMappingResource : public GeometryResource {
Q_OBJECT
public:
@ -52,18 +56,17 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
finishedLoading(false);
} else {
QUrl url = _url.resolved(filename);
QUrl textureBaseUrl;
QString texdir = mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
textureBaseUrl = _url.resolved(texdir);
_textureBaseUrl = resolveTextureBaseUrl(url, _url.resolved(texdir));
}
auto modelCache = DependencyManager::get<ModelCache>();
GeometryExtra extra{ mapping, textureBaseUrl };
GeometryExtra extra{ mapping, _textureBaseUrl };
// Get the raw GeometryResource, not the wrapped NetworkGeometry
_geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast<GeometryResource>();
@ -160,7 +163,7 @@ class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
GeometryResource(url, textureBaseUrl.isValid() ? textureBaseUrl : url), _mapping(mapping) {}
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping) {}
virtual void downloadFinished(const QByteArray& data) override;

View file

@ -95,7 +95,8 @@ class GeometryResource : public Resource, public Geometry {
public:
using Pointer = QSharedPointer<GeometryResource>;
GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : Resource(url) {}
GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) :
Resource(url), _textureBaseUrl(textureBaseUrl) {}
virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); }

View file

@ -200,7 +200,7 @@ AssetUpload* AssetClient::createUpload(const QByteArray& data) {
return upload;
}
bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end,
MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end,
ReceivedAssetCallback callback, ProgressCallback progressCallback) {
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
qCWarning(asset_client) << "Invalid hash size";
@ -230,17 +230,16 @@ bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end
_pendingRequests[assetServer][messageID] = { QSharedPointer<ReceivedMessage>(), callback, progressCallback };
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QByteArray());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -257,10 +256,10 @@ bool AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
_pendingInfoRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, { "", 0 });
return false;
return INVALID_MESSAGE_ID;
}
}
@ -352,7 +351,7 @@ void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, S
}
}
bool AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallback callback) {
MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -370,14 +369,14 @@ bool AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallbac
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -393,14 +392,14 @@ bool AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) {
MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -422,14 +421,14 @@ bool AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperati
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) {
MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -448,14 +447,14 @@ bool AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, Ma
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) {
MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -474,15 +473,53 @@ bool AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath&
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
return INVALID_MESSAGE_ID;
}
}
bool AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callback) {
bool AssetClient::cancelMappingRequest(MessageID id) {
for (auto& kv : _pendingMappingRequests) {
if (kv.second.erase(id)) {
return true;
}
}
return false;
}
bool AssetClient::cancelGetAssetInfoRequest(MessageID id) {
for (auto& kv : _pendingInfoRequests) {
if (kv.second.erase(id)) {
return true;
}
}
return false;
}
bool AssetClient::cancelGetAssetRequest(MessageID id) {
// Search through each pending mapping request for id `id`
for (auto& kv : _pendingRequests) {
if (kv.second.erase(id)) {
return true;
}
}
return false;
}
bool AssetClient::cancelUploadAssetRequest(MessageID id) {
// Search through each pending mapping request for id `id`
for (auto& kv : _pendingUploads) {
if (kv.second.erase(id)) {
return true;
}
}
return false;
}
MessageID AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
@ -500,10 +537,10 @@ bool AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callb
_pendingUploads[assetServer][messageID] = callback;
return true;
return messageID;
} else {
callback(false, AssetServerError::NoError, QString());
return false;
return INVALID_MESSAGE_ID;
}
}

View file

@ -60,6 +60,8 @@ public:
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
static const MessageID INVALID_MESSAGE_ID = 0;
public slots:
void init();
@ -75,16 +77,21 @@ private slots:
void handleNodeKilled(SharedNodePointer node);
private:
bool getAssetMapping(const AssetHash& hash, MappingOperationCallback callback);
bool getAllAssetMappings(MappingOperationCallback callback);
bool setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback);
bool deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback);
bool renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback);
MessageID getAssetMapping(const AssetHash& hash, MappingOperationCallback callback);
MessageID getAllAssetMappings(MappingOperationCallback callback);
MessageID setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback);
MessageID deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback);
MessageID renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback);
bool getAssetInfo(const QString& hash, GetInfoCallback callback);
bool getAsset(const QString& hash, DataOffset start, DataOffset end,
MessageID getAssetInfo(const QString& hash, GetInfoCallback callback);
MessageID getAsset(const QString& hash, DataOffset start, DataOffset end,
ReceivedAssetCallback callback, ProgressCallback progressCallback);
bool uploadAsset(const QByteArray& data, UploadResultCallback callback);
MessageID uploadAsset(const QByteArray& data, UploadResultCallback callback);
bool cancelMappingRequest(MessageID id);
bool cancelGetAssetInfoRequest(MessageID id);
bool cancelGetAssetRequest(MessageID id);
bool cancelUploadAssetRequest(MessageID id);
struct GetAssetRequestData {
QSharedPointer<ReceivedMessage> message;
@ -100,6 +107,7 @@ private:
friend class AssetRequest;
friend class AssetUpload;
friend class MappingRequest;
friend class GetMappingRequest;
friend class GetAllMappingsRequest;
friend class SetMappingRequest;

View file

@ -25,6 +25,16 @@ AssetRequest::AssetRequest(const QString& hash) :
{
}
AssetRequest::~AssetRequest() {
auto assetClient = DependencyManager::get<AssetClient>();
if (_assetRequestID) {
assetClient->cancelGetAssetRequest(_assetRequestID);
}
if (_assetInfoRequestID) {
assetClient->cancelGetAssetInfoRequest(_assetInfoRequestID);
}
}
void AssetRequest::start() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "start", Qt::AutoConnection);
@ -60,7 +70,11 @@ void AssetRequest::start() {
_state = WaitingForInfo;
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAssetInfo(_hash, [this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
_assetInfoRequestID = assetClient->getAssetInfo(_hash,
[this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
_assetInfoRequestID = AssetClient::INVALID_MESSAGE_ID;
_info = info;
if (!responseReceived) {
@ -92,8 +106,11 @@ void AssetRequest::start() {
int start = 0, end = _info.size;
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAsset(_hash, start, end, [this, start, end](bool responseReceived, AssetServerError serverError,
const QByteArray& data) {
_assetRequestID = assetClient->getAsset(_hash, start, end,
[this, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) {
_assetRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else if (serverError != AssetServerError::NoError) {

View file

@ -41,6 +41,7 @@ public:
};
AssetRequest(const QString& hash);
virtual ~AssetRequest() override;
Q_INVOKABLE void start();
@ -61,6 +62,8 @@ private:
QString _hash;
QByteArray _data;
int _numPendingRequests { 0 };
MessageID _assetRequestID { AssetClient::INVALID_MESSAGE_ID };
MessageID _assetInfoRequestID { AssetClient::INVALID_MESSAGE_ID };
};
#endif

View file

@ -21,7 +21,7 @@ class AssetResourceRequest : public ResourceRequest {
Q_OBJECT
public:
AssetResourceRequest(const QUrl& url) : ResourceRequest(url) { }
~AssetResourceRequest();
virtual ~AssetResourceRequest() override;
protected:
virtual void doSend() override;

View file

@ -15,7 +15,12 @@
#include <DependencyManager.h>
#include "AssetClient.h"
MappingRequest::~MappingRequest() {
auto assetClient = DependencyManager::get<AssetClient>();
if (_mappingRequestID) {
assetClient->cancelMappingRequest(_mappingRequestID);
}
}
void MappingRequest::start() {
if (QThread::currentThread() != thread()) {
@ -60,7 +65,10 @@ void GetMappingRequest::doStart() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAssetMapping(_path, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = assetClient->getAssetMapping(_path,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -89,7 +97,11 @@ GetAllMappingsRequest::GetAllMappingsRequest() {
void GetAllMappingsRequest::doStart() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAllAssetMappings([this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = assetClient->getAllAssetMappings(
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -137,7 +149,10 @@ void SetMappingRequest::doStart() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->setAssetMapping(_path, _hash, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = assetClient->setAssetMapping(_path, _hash,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -177,7 +192,10 @@ void DeleteMappingsRequest::doStart() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->deleteAssetMappings(_paths, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = assetClient->deleteAssetMappings(_paths,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {
@ -216,9 +234,10 @@ void RenameMappingRequest::doStart() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->renameAssetMapping(_oldPath, _newPath, [this, assetClient](bool responseReceived,
AssetServerError error,
QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = assetClient->renameAssetMapping(_oldPath, _newPath,
[this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
_mappingRequestID = AssetClient::INVALID_MESSAGE_ID;
if (!responseReceived) {
_error = NetworkError;
} else {

View file

@ -17,6 +17,7 @@
#include <QtCore/QObject>
#include "AssetUtils.h"
#include "AssetClient.h"
class MappingRequest : public QObject {
Q_OBJECT
@ -31,12 +32,15 @@ public:
UnknownError
};
virtual ~MappingRequest();
Q_INVOKABLE void start();
Error getError() const { return _error; }
Q_INVOKABLE QString getErrorString() const;
protected:
Error _error { NoError };
MessageID _mappingRequestID { AssetClient::INVALID_MESSAGE_ID };
private:
virtual void doStart() = 0;

View file

@ -21,6 +21,7 @@ class ResourceRequest : public QObject {
Q_OBJECT
public:
ResourceRequest(const QUrl& url);
virtual ~ResourceRequest() = default;
enum State {
NotStarted = 0,

View file

@ -10,10 +10,13 @@
//
#include "SequenceNumberStats.h"
#include "NetworkLogging.h"
#include <limits>
#include <LogHandler.h>
#include "NetworkLogging.h"
float PacketStreamStats::getLostRate() const {
return (_expectedReceived == 0) ? 0.0f : (float)_lost / (float)_expectedReceived;
}
@ -63,6 +66,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
arrivalInfo._status = OnTime;
_lastReceivedSequence = incoming;
_stats._expectedReceived++;
} else { // out of order
if (wantExtraDebugging) {
@ -85,6 +89,9 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
arrivalInfo._status = Unreasonable;
static const QString UNREASONABLE_SEQUENCE_REGEX { "unreasonable sequence number: \\d+ previous: \\d+" };
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(UNREASONABLE_SEQUENCE_REGEX);
qCDebug(networking) << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence;
_stats._unreasonable++;
@ -147,6 +154,9 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
arrivalInfo._status = Unreasonable;
static const QString UNREASONABLE_SEQUENCE_REGEX { "unreasonable sequence number: \\d+ \\(possible duplicate\\)" };
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(UNREASONABLE_SEQUENCE_REGEX);
qCDebug(networking) << "unreasonable sequence number:" << incoming << "(possible duplicate)";
_stats._unreasonable++;

View file

@ -19,6 +19,7 @@ using namespace udt;
using namespace std::chrono;
static const double USECS_PER_SECOND = 1000000.0;
static const int BITS_PER_BYTE = 8;
void CongestionControl::setMaxBandwidth(int maxBandwidth) {
_maxBandwidth = maxBandwidth;
@ -28,11 +29,11 @@ void CongestionControl::setMaxBandwidth(int maxBandwidth) {
void CongestionControl::setPacketSendPeriod(double newSendPeriod) {
Q_ASSERT_X(newSendPeriod >= 0, "CongestionControl::setPacketPeriod", "Can not set a negative packet send period");
auto maxBandwidth = _maxBandwidth.load();
if (maxBandwidth > 0) {
auto packetsPerSecond = (double)_maxBandwidth / (BITS_PER_BYTE * _mss);
if (packetsPerSecond > 0.0) {
// anytime the packet send period is about to be increased, make sure it stays below the minimum period,
// calculated based on the maximum desired bandwidth
double minPacketSendPeriod = USECS_PER_SECOND / (((double) maxBandwidth) / _mss);
double minPacketSendPeriod = USECS_PER_SECOND / packetsPerSecond;
_packetSendPeriod = std::max(newSendPeriod, minPacketSendPeriod);
} else {
_packetSendPeriod = newSendPeriod;
@ -145,17 +146,23 @@ void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {
}
_loss = true;
++_nakCount;
static const double INTER_PACKET_ARRIVAL_INCREASE = 1.125;
static const int MAX_DECREASES_PER_CONGESTION_EPOCH = 5;
// check if this NAK starts a new congestion period - this will be the case if the
// NAK received occured for a packet sent after the last decrease
if (rangeStart > _lastDecreaseMaxSeq) {
_delayedDecrease = (rangeStart == rangeEnd);
_lastDecreasePeriod = _packetSendPeriod;
setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE));
if (!_delayedDecrease) {
setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE));
} else {
_loss = false;
}
// use EWMA to update the average number of NAKs per congestion
static const double NAK_EWMA_ALPHA = 0.125;
@ -177,10 +184,12 @@ void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {
_randomDecreaseThreshold = distribution(generator);
}
} else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((++_nakCount % _randomDecreaseThreshold) == 0)) {
} else if (_delayedDecrease && _nakCount == 2) {
setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE));
} else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((_nakCount % _randomDecreaseThreshold) == 0)) {
// there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we
// decided we would decrease the packet send period
setPacketSendPeriod(ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE));
_lastDecreaseMaxSeq = _sendCurrSeqNum;
}

View file

@ -15,7 +15,6 @@
#include <atomic>
#include <memory>
#include <vector>
#include <memory>
#include <PortableHighResolutionClock.h>
@ -61,7 +60,7 @@ protected:
double _congestionWindowSize { 16.0 }; // Congestion window size, in packets
int _bandwidth { 0 }; // estimated bandwidth, packets per second
std::atomic<int> _maxBandwidth { -1 }; // Maximum desired bandwidth, bytes per second
std::atomic<int> _maxBandwidth { -1 }; // Maximum desired bandwidth, bits per second
double _maxCongestionWindowSize { 0.0 }; // maximum cwnd size, in packets
int _mss { 0 }; // Maximum Packet Size, including all packet headers
@ -124,6 +123,7 @@ private:
int _randomDecreaseThreshold { 1 }; // random threshold on decrease by number of loss events
int _avgNAKNum { 0 }; // average number of NAKs per congestion
int _decreaseCount { 0 }; // number of decreases in a congestion epoch
bool _delayedDecrease { false };
};
}

View file

@ -354,7 +354,7 @@ void Socket::setCongestionControlFactory(std::unique_ptr<CongestionControlVirtua
void Socket::setConnectionMaxBandwidth(int maxBandwidth) {
qInfo() << "Setting socket's maximum bandwith to" << maxBandwidth << ". ("
qInfo() << "Setting socket's maximum bandwith to" << maxBandwidth << "bps. ("
<< _connectionsHash.size() << "live connections)";
_maxBandwidth = maxBandwidth;
for (auto& pair : _connectionsHash) {

View file

@ -392,20 +392,14 @@ void AnimDebugDraw::update() {
assert(numVerts == (v - verts));
// The RenderItem culling is not working correctly
// Workaround this issue by using the default constructed
// item._bound which is a 16 km cube.
/*
render::Item::Bound theBound;
for (int i = 0; i < numVerts; i++) {
theBound += verts[i].pos;
}
data._bound = theBound;
*/
data._isVisible = (numVerts > 0);
data._indexBuffer->resize(sizeof(uint16_t) * numVerts);
uint16_t* indices = (uint16_t*)data._indexBuffer->editData();
for (int i = 0; i < numVerts; i++) {

View file

@ -520,26 +520,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
return;
}
// render the part bounding box
#ifdef DEBUG_BOUNDING_PARTS
{
AABox partBounds = getPartBounds(_meshIndex, partIndex);
glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
if (isSkinned) {
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
} else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
}
Transform transform;
transform.setTranslation(partBounds.calcCenter());
transform.setScale(partBounds.getDimensions());
batch.setModelTransform(transform);
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif //def DEBUG_BOUNDING_PARTS
auto locations = args->_pipeline->locations;
assert(locations);

View file

@ -89,12 +89,12 @@ bool Model::needsFixupInScene() const {
void Model::setTranslation(const glm::vec3& translation) {
_translation = translation;
enqueueLocationChange();
updateRenderItems();
}
void Model::setRotation(const glm::quat& rotation) {
_rotation = rotation;
enqueueLocationChange();
updateRenderItems();
}
void Model::setScale(const glm::vec3& scale) {
@ -124,7 +124,7 @@ void Model::setOffset(const glm::vec3& offset) {
_snappedToRegistrationPoint = false;
}
void Model::enqueueLocationChange() {
void Model::updateRenderItems() {
_needsUpdateClusterMatrices = true;
@ -552,50 +552,33 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
bool somethingAdded = false;
if (_modelMeshRenderItems.size()) {
for (auto item : _modelMeshRenderItems.keys()) {
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
}
} else {
for (auto renderItem : _modelMeshRenderItemsSet) {
if (_modelMeshRenderItems.empty()) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
}
if (_collisionRenderItems.size()) {
for (auto item : _collisionRenderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}
} else {
for (auto renderItem : _collisionRenderItemsSet) {
if (_collisionRenderItems.empty()) {
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
}
updateRenderItems();
_readyWhenAdded = readyToAddToScene();
return somethingAdded;
@ -1169,38 +1152,17 @@ void Model::deleteGeometry() {
_blendedBlendshapeCoefficients.clear();
}
AABox Model::getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition, glm::quat modelOrientation) const {
AABox Model::getRenderableMeshBound() const {
if (!isLoaded()) {
return AABox();
}
if (meshIndex < _meshStates.size()) {
const MeshState& state = _meshStates.at(meshIndex);
bool isSkinned = state.clusterMatrices.size() > 1;
if (isSkinned) {
// if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us
return calculateScaledOffsetAABox(getFBXGeometry().meshExtents, modelPosition, modelOrientation);
} else {
// Build a bound using the last known bound from all the renderItems.
AABox totalBound;
for (auto& renderItem : _modelMeshRenderItemsSet) {
totalBound += renderItem->getBound();
}
return totalBound;
}
if (getFBXGeometry().meshes.size() > meshIndex) {
// FIX ME! - This is currently a hack because for some mesh parts our efforts to calculate the bounding
// box of the mesh part fails. It seems to create boxes that are not consistent with where the
// geometry actually renders. If instead we make all the parts share the bounds of the entire subMesh
// things will render properly.
//
// return calculateScaledOffsetAABox(_calculatedMeshPartBoxes[QPair<int,int>(meshIndex, partIndex)]);
//
// NOTE: we also don't want to use the _calculatedMeshBoxes[] because they don't handle avatar moving correctly
// without recalculating them...
// return _calculatedMeshBoxes[meshIndex];
//
// If we not skinned use the bounds of the subMesh for all it's parts
const FBXMesh& mesh = getFBXGeometry().meshes.at(meshIndex);
return calculateScaledOffsetExtents(mesh.meshExtents, modelPosition, modelOrientation);
}
return AABox();
}
void Model::segregateMeshGroups() {
@ -1292,20 +1254,15 @@ bool Model::initWhenReady(render::ScenePointer scene) {
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
_modelMeshRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
_collisionRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}
scene->enqueuePendingChanges(pendingChanges);
updateRenderItems();
_readyWhenAdded = true;
return true;

View file

@ -102,7 +102,7 @@ public:
bool isVisible() const { return _isVisible; }
void updateRenderItems();
AABox getPartBounds(int meshIndex, int partIndex, glm::vec3 modelPosition, glm::quat modelOrientation) const;
AABox getRenderableMeshBound() const;
bool maybeStartBlender();
@ -213,8 +213,6 @@ public:
void setScale(const glm::vec3& scale);
const glm::vec3& getScale() const { return _scale; }
void enqueueLocationChange();
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
glm::vec3 getScaleToFitDimensions() const; /// the dimensions model is scaled to, including inferred y/z

View file

@ -220,7 +220,7 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
return;
}
QUrl url = expandScriptUrl(normalizeScriptURL(scriptURL));
QUrl url = expandScriptUrl(scriptURL);
_fileNameString = url.toString();
_isReloading = reload;
@ -847,7 +847,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
QUrl url(include);
// first lets check to see if it's already a full URL
if (!url.scheme().isEmpty()) {
return expandScriptUrl(normalizeScriptURL(url));
return expandScriptUrl(url);
}
// we apparently weren't a fully qualified url, so, let's assume we're relative
@ -864,7 +864,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
}
// at this point we should have a legitimate fully qualified URL for our parent
url = expandScriptUrl(normalizeScriptURL(parentURL.resolved(url)));
url = expandScriptUrl(parentURL.resolved(url));
return url;
}

View file

@ -47,13 +47,6 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
QUrl fullNormal = rawScriptURL;
QUrl defaultScriptLoc = defaultScriptsLocation();
#ifdef Q_OS_LINUX
#else
// Force lowercase on file scripts because of drive letter weirdness.
if (rawScriptURL.isLocalFile()) {
fullNormal.setPath(fullNormal.path().toLower());
}
#endif
// if this url is something "beneath" the default script url, replace the local path with ~
if (fullNormal.scheme() == defaultScriptLoc.scheme() &&
fullNormal.host() == defaultScriptLoc.host() &&
@ -69,7 +62,8 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
}
}
QUrl expandScriptUrl(const QUrl& normalizedScriptURL) {
QUrl expandScriptUrl(const QUrl& rawScriptURL) {
QUrl normalizedScriptURL = normalizeScriptURL(rawScriptURL);
if (normalizedScriptURL.scheme() == "http" ||
normalizedScriptURL.scheme() == "https" ||
normalizedScriptURL.scheme() == "atp") {
@ -230,7 +224,7 @@ QVariantList ScriptEngines::getRunning() {
}
QVariantMap resultNode;
resultNode.insert("name", runningScriptURL.fileName());
QUrl displayURL = expandScriptUrl(QUrl(runningScriptURL));
QUrl displayURL = expandScriptUrl(runningScriptURL);
QString displayURLString;
if (displayURL.isLocalFile()) {
displayURLString = displayURL.toLocalFile();
@ -251,7 +245,7 @@ static const QString SETTINGS_KEY = "Settings";
void ScriptEngines::loadDefaultScripts() {
QUrl defaultScriptsLoc = defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "/scripts/defaultScripts.js");
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "/defaultScripts.js");
loadScript(defaultScriptsLoc.toString());
}
@ -268,7 +262,7 @@ void ScriptEngines::loadScripts() {
loadDefaultScripts();
_firstRun.set(false);
return;
}
}
// loads all saved scripts
Settings settings;
@ -310,7 +304,12 @@ void ScriptEngines::saveScripts() {
QStringList ScriptEngines::getRunningScripts() {
QReadLocker lock(&_scriptEnginesHashLock);
return _scriptEnginesHash.keys();
QList<QUrl> urls = _scriptEnginesHash.keys();
QStringList result;
for (auto url : urls) {
result.append(url.toString());
}
return result;
}
void ScriptEngines::stopAllScripts(bool restart) {
@ -318,7 +317,7 @@ void ScriptEngines::stopAllScripts(bool restart) {
if (restart) {
// Delete all running scripts from cache so that they are re-downloaded when they are restarted
auto scriptCache = DependencyManager::get<ScriptCache>();
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
for (QHash<QUrl, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
it != _scriptEnginesHash.constEnd(); it++) {
if (!it.value()->isFinished()) {
scriptCache->deleteScript(it.key());
@ -327,7 +326,7 @@ void ScriptEngines::stopAllScripts(bool restart) {
}
// Stop and possibly restart all currently running scripts
for (QHash<QString, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
for (QHash<QUrl, ScriptEngine*>::const_iterator it = _scriptEnginesHash.constBegin();
it != _scriptEnginesHash.constEnd(); it++) {
if (it.value()->isFinished()) {
continue;
@ -350,21 +349,20 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) {
if (!scriptURL.isValid()) {
scriptURL = normalizeScriptURL(QUrl::fromLocalFile(rawScriptURL));
}
const QString scriptURLString = scriptURL.toString();
QReadLocker lock(&_scriptEnginesHashLock);
if (_scriptEnginesHash.contains(scriptURLString)) {
ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURLString];
if (_scriptEnginesHash.contains(scriptURL)) {
ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURL];
if (restart) {
auto scriptCache = DependencyManager::get<ScriptCache>();
scriptCache->deleteScript(QUrl(scriptURLString));
scriptCache->deleteScript(scriptURL);
connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) {
reloadScript(scriptName);
});
}
scriptEngine->stop();
stoppedScript = true;
qCDebug(scriptengine) << "stopping script..." << scriptURLString;
qCDebug(scriptengine) << "stopping script..." << scriptURL;
}
}
return stoppedScript;
@ -379,7 +377,7 @@ void ScriptEngines::setScriptsLocation(const QString& scriptsLocation) {
_scriptsModel.updateScriptsLocation(scriptsLocation);
}
void ScriptEngines::reloadAllScripts() {
void ScriptEngines::reloadAllScripts() {
DependencyManager::get<ScriptCache>()->clearCache();
emit scriptsReloading();
stopAllScripts(true);
@ -409,7 +407,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
scriptUrl = normalizeScriptURL(scriptFilename);
}
auto scriptEngine = getScriptEngine(scriptUrl.toString());
auto scriptEngine = getScriptEngine(scriptUrl);
if (scriptEngine) {
return scriptEngine;
}
@ -435,12 +433,12 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
return scriptEngine;
}
ScriptEngine* ScriptEngines::getScriptEngine(const QString& rawScriptURL) {
ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) {
ScriptEngine* result = nullptr;
{
QReadLocker lock(&_scriptEnginesHashLock);
const QString scriptURLString = normalizeScriptURL(QUrl(rawScriptURL)).toString();
auto it = _scriptEnginesHash.find(scriptURLString);
const QUrl scriptURL = normalizeScriptURL(rawScriptURL);
auto it = _scriptEnginesHash.find(scriptURL);
if (it != _scriptEnginesHash.end()) {
result = it.value();
}
@ -459,8 +457,7 @@ void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) {
QWriteLocker lock(&_scriptEnginesHashLock);
QUrl url = QUrl(rawScriptURL);
QUrl normalized = normalizeScriptURL(url);
const QString scriptURLString = normalized.toString();
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_scriptEnginesHash.insertMulti(normalized, scriptEngine);
}
emit scriptCountChanged();
}
@ -486,8 +483,8 @@ void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEngine*
bool removed = false;
{
QWriteLocker lock(&_scriptEnginesHashLock);
const QString scriptURLString = normalizeScriptURL(QUrl(rawScriptURL)).toString();
for (auto it = _scriptEnginesHash.find(scriptURLString); it != _scriptEnginesHash.end(); ++it) {
const QUrl scriptURL = normalizeScriptURL(QUrl(rawScriptURL));
for (auto it = _scriptEnginesHash.find(scriptURL); it != _scriptEnginesHash.end(); ++it) {
if (it.value() == engine) {
_scriptEnginesHash.erase(it);
removed = true;

View file

@ -45,7 +45,7 @@ public:
void loadDefaultScripts();
void setScriptsLocation(const QString& scriptsLocation);
QStringList getRunningScripts();
ScriptEngine* getScriptEngine(const QString& scriptHash);
ScriptEngine* getScriptEngine(const QUrl& scriptHash);
ScriptsModel* scriptsModel() { return &_scriptsModel; };
ScriptsModelFilter* scriptsModelFilter() { return &_scriptsModelFilter; };
@ -65,12 +65,12 @@ public:
// Called at shutdown time
void shutdownScripting();
signals:
signals:
void scriptCountChanged();
void scriptsReloading();
void scriptLoadError(const QString& filename, const QString& error);
protected slots:
protected slots:
void onScriptFinished(const QString& fileNameString, ScriptEngine* engine);
protected:
@ -86,7 +86,7 @@ protected:
Setting::Handle<bool> _firstRun { "firstRun", true };
QReadWriteLock _scriptEnginesHashLock;
QHash<QString, ScriptEngine*> _scriptEnginesHash;
QHash<QUrl, ScriptEngine*> _scriptEnginesHash;
QSet<ScriptEngine*> _allKnownScriptEngines;
QMutex _allScriptsMutex;
std::atomic<bool> _stoppingAllScripts { false };
@ -97,6 +97,6 @@ protected:
};
QUrl normalizeScriptURL(const QUrl& rawScriptURL);
QUrl expandScriptUrl(const QUrl& normalizedScriptURL);
QUrl expandScriptUrl(const QUrl& rawScriptURL);
#endif // hifi_ScriptEngine_h

View file

@ -25,7 +25,6 @@
#define __STR1__(x) __STR2__(x)
#define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning Msg: "
static const QString MODELS_LOCATION = "scripts/";
static const QString PREFIX_PARAMETER_NAME = "prefix";
static const QString MARKER_PARAMETER_NAME = "marker";
static const QString IS_TRUNCATED_NAME = "IsTruncated";
@ -41,7 +40,7 @@ TreeNodeBase::TreeNodeBase(TreeNodeFolder* parent, const QString& name, TreeNode
TreeNodeScript::TreeNodeScript(const QString& localPath, const QString& fullPath, ScriptOrigin origin) :
TreeNodeBase(NULL, localPath.split("/").last(), TREE_NODE_TYPE_SCRIPT),
_localPath(localPath),
_fullPath(fullPath),
_fullPath(expandScriptUrl(QUrl(fullPath)).toString()),
_origin(origin) {
};
@ -159,9 +158,11 @@ void ScriptsModel::requestDefaultFiles(QString marker) {
if (url.isLocalFile()) {
// if the url indicates a local directory, use QDirIterator
// QString localDir = url.toLocalFile() + "/scripts";
QString localDir = expandScriptUrl(url).toLocalFile() + "/scripts";
QString localDir = expandScriptUrl(url).toLocalFile();
int localDirPartCount = localDir.split("/").size();
if (localDir.endsWith("/")) {
localDirPartCount--;
}
#ifdef Q_OS_WIN
localDirPartCount++; // one for the drive letter
#endif
@ -176,7 +177,7 @@ void ScriptsModel::requestDefaultFiles(QString marker) {
} else {
// the url indicates http(s), use QNetworkRequest
QUrlQuery query;
query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION);
query.addQueryItem(PREFIX_PARAMETER_NAME, ".");
if (!marker.isEmpty()) {
query.addQueryItem(MARKER_PARAMETER_NAME, marker);
}
@ -240,7 +241,7 @@ bool ScriptsModel::parseXML(QByteArray xmlFile) {
if (jsRegex.exactMatch(xml.text().toString())) {
QString localPath = lastKey.split("/").mid(1).join("/");
QUrl fullPath = defaultScriptsLocation();
fullPath.setPath(fullPath.path() + "/" + lastKey);
fullPath.setPath(fullPath.path() + lastKey);
const QString fullPathStr = normalizeScriptURL(fullPath).toString();
_treeNodes.append(new TreeNodeScript(localPath, fullPathStr, SCRIPT_ORIGIN_DEFAULT));
}

View file

@ -0,0 +1,75 @@
//
// CPUID.cpp
//
// Created by Ryan Huffman on 3/25/16.
// Copyright 2016 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 "CPUID.h"
#ifdef Q_OS_WIN
const CPUID::CPUID_Internal CPUID::CPU_Rep;
std::vector<CPUID::Feature> CPUID::getAllFeatures() {
std::vector<CPUID::Feature> features;
features.push_back({ "3DNOW", CPUID::_3DNOW() });
features.push_back({ "3DNOWEXT", CPUID::_3DNOWEXT() });
features.push_back({ "ABM", CPUID::ABM() });
features.push_back({ "ADX", CPUID::ADX() });
features.push_back({ "AES", CPUID::AES() });
features.push_back({ "AVX", CPUID::AVX() });
features.push_back({ "AVX2", CPUID::AVX2() });
features.push_back({ "AVX512CD", CPUID::AVX512CD() });
features.push_back({ "AVX512ER", CPUID::AVX512ER() });
features.push_back({ "AVX512F", CPUID::AVX512F() });
features.push_back({ "AVX512PF", CPUID::AVX512PF() });
features.push_back({ "BMI1", CPUID::BMI1() });
features.push_back({ "BMI2", CPUID::BMI2() });
features.push_back({ "CLFSH", CPUID::CLFSH() });
features.push_back({ "CMPXCHG16B", CPUID::CMPXCHG16B() });
features.push_back({ "CX8", CPUID::CX8() });
features.push_back({ "ERMS", CPUID::ERMS() });
features.push_back({ "F16C", CPUID::F16C() });
features.push_back({ "FMA", CPUID::FMA() });
features.push_back({ "FSGSBASE", CPUID::FSGSBASE() });
features.push_back({ "FXSR", CPUID::FXSR() });
features.push_back({ "HLE", CPUID::HLE() });
features.push_back({ "INVPCID", CPUID::INVPCID() });
features.push_back({ "LAHF", CPUID::LAHF() });
features.push_back({ "LZCNT", CPUID::LZCNT() });
features.push_back({ "MMX", CPUID::MMX() });
features.push_back({ "MMXEXT", CPUID::MMXEXT() });
features.push_back({ "MONITOR", CPUID::MONITOR() });
features.push_back({ "MOVBE", CPUID::MOVBE() });
features.push_back({ "MSR", CPUID::MSR() });
features.push_back({ "OSXSAVE", CPUID::OSXSAVE() });
features.push_back({ "PCLMULQDQ", CPUID::PCLMULQDQ() });
features.push_back({ "POPCNT", CPUID::POPCNT() });
features.push_back({ "PREFETCHWT1", CPUID::PREFETCHWT1() });
features.push_back({ "RDRAND", CPUID::RDRAND() });
features.push_back({ "RDSEED", CPUID::RDSEED() });
features.push_back({ "RDTSCP", CPUID::RDTSCP() });
features.push_back({ "RTM", CPUID::RTM() });
features.push_back({ "SEP", CPUID::SEP() });
features.push_back({ "SHA", CPUID::SHA() });
features.push_back({ "SSE", CPUID::SSE() });
features.push_back({ "SSE2", CPUID::SSE2() });
features.push_back({ "SSE3", CPUID::SSE3() });
features.push_back({ "SSE4.1", CPUID::SSE41() });
features.push_back({ "SSE4.2", CPUID::SSE42() });
features.push_back({ "SSE4a", CPUID::SSE4a() });
features.push_back({ "SSSE3", CPUID::SSSE3() });
features.push_back({ "SYSCALL", CPUID::SYSCALL() });
features.push_back({ "TBM", CPUID::TBM() });
features.push_back({ "XOP", CPUID::XOP() });
features.push_back({ "XSAVE", CPUID::XSAVE() });
return features;
};
#endif

View file

@ -0,0 +1,212 @@
//
// CPUID.h
//
// Adapted from Microsoft's example for using the cpuid intrinsic,
// found at https://msdn.microsoft.com/en-us/library/hskdteyh.aspx
//
// Provides acccess to information provided by the CPUID opcode
//
// TODO: Generalize to work outside of Windows.
//
// Created by Ryan Huffman on 3/25/16.
// Copyright 2016 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_CPUID_h
#define hifi_CPUID_h
#include <QtCore/QtGlobal>
#include <vector>
#include <bitset>
#include <array>
#include <string>
#ifdef Q_OS_WIN
#include <intrin.h>
class CPUID
{
// forward declarations
class CPUID_Internal;
public:
struct Feature {
std::string name;
bool supported;
};
static std::vector<Feature> getAllFeatures();
static std::string Vendor(void) { return CPU_Rep.vendor_; }
static std::string Brand(void) { return CPU_Rep.brand_; }
static bool SSE3(void) { return CPU_Rep.f_1_ECX_[0]; }
static bool PCLMULQDQ(void) { return CPU_Rep.f_1_ECX_[1]; }
static bool MONITOR(void) { return CPU_Rep.f_1_ECX_[3]; }
static bool SSSE3(void) { return CPU_Rep.f_1_ECX_[9]; }
static bool FMA(void) { return CPU_Rep.f_1_ECX_[12]; }
static bool CMPXCHG16B(void) { return CPU_Rep.f_1_ECX_[13]; }
static bool SSE41(void) { return CPU_Rep.f_1_ECX_[19]; }
static bool SSE42(void) { return CPU_Rep.f_1_ECX_[20]; }
static bool MOVBE(void) { return CPU_Rep.f_1_ECX_[22]; }
static bool POPCNT(void) { return CPU_Rep.f_1_ECX_[23]; }
static bool AES(void) { return CPU_Rep.f_1_ECX_[25]; }
static bool XSAVE(void) { return CPU_Rep.f_1_ECX_[26]; }
static bool OSXSAVE(void) { return CPU_Rep.f_1_ECX_[27]; }
static bool AVX(void) { return CPU_Rep.f_1_ECX_[28]; }
static bool F16C(void) { return CPU_Rep.f_1_ECX_[29]; }
static bool RDRAND(void) { return CPU_Rep.f_1_ECX_[30]; }
static bool MSR(void) { return CPU_Rep.f_1_EDX_[5]; }
static bool CX8(void) { return CPU_Rep.f_1_EDX_[8]; }
static bool SEP(void) { return CPU_Rep.f_1_EDX_[11]; }
static bool CMOV(void) { return CPU_Rep.f_1_EDX_[15]; }
static bool CLFSH(void) { return CPU_Rep.f_1_EDX_[19]; }
static bool MMX(void) { return CPU_Rep.f_1_EDX_[23]; }
static bool FXSR(void) { return CPU_Rep.f_1_EDX_[24]; }
static bool SSE(void) { return CPU_Rep.f_1_EDX_[25]; }
static bool SSE2(void) { return CPU_Rep.f_1_EDX_[26]; }
static bool FSGSBASE(void) { return CPU_Rep.f_7_EBX_[0]; }
static bool BMI1(void) { return CPU_Rep.f_7_EBX_[3]; }
static bool HLE(void) { return CPU_Rep.isIntel_ && CPU_Rep.f_7_EBX_[4]; }
static bool AVX2(void) { return CPU_Rep.f_7_EBX_[5]; }
static bool BMI2(void) { return CPU_Rep.f_7_EBX_[8]; }
static bool ERMS(void) { return CPU_Rep.f_7_EBX_[9]; }
static bool INVPCID(void) { return CPU_Rep.f_7_EBX_[10]; }
static bool RTM(void) { return CPU_Rep.isIntel_ && CPU_Rep.f_7_EBX_[11]; }
static bool AVX512F(void) { return CPU_Rep.f_7_EBX_[16]; }
static bool RDSEED(void) { return CPU_Rep.f_7_EBX_[18]; }
static bool ADX(void) { return CPU_Rep.f_7_EBX_[19]; }
static bool AVX512PF(void) { return CPU_Rep.f_7_EBX_[26]; }
static bool AVX512ER(void) { return CPU_Rep.f_7_EBX_[27]; }
static bool AVX512CD(void) { return CPU_Rep.f_7_EBX_[28]; }
static bool SHA(void) { return CPU_Rep.f_7_EBX_[29]; }
static bool PREFETCHWT1(void) { return CPU_Rep.f_7_ECX_[0]; }
static bool LAHF(void) { return CPU_Rep.f_81_ECX_[0]; }
static bool LZCNT(void) { return CPU_Rep.isIntel_ && CPU_Rep.f_81_ECX_[5]; }
static bool ABM(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_ECX_[5]; }
static bool SSE4a(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_ECX_[6]; }
static bool XOP(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_ECX_[11]; }
static bool TBM(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_ECX_[21]; }
static bool SYSCALL(void) { return CPU_Rep.isIntel_ && CPU_Rep.f_81_EDX_[11]; }
static bool MMXEXT(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_EDX_[22]; }
static bool RDTSCP(void) { return CPU_Rep.isIntel_ && CPU_Rep.f_81_EDX_[27]; }
static bool _3DNOWEXT(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_EDX_[30]; }
static bool _3DNOW(void) { return CPU_Rep.isAMD_ && CPU_Rep.f_81_EDX_[31]; }
private:
static const CPUID_Internal CPU_Rep;
class CPUID_Internal
{
public:
CPUID_Internal()
: nIds_ { 0 },
nExIds_ { 0 },
isIntel_ { false },
isAMD_ { false },
f_1_ECX_ { 0 },
f_1_EDX_ { 0 },
f_7_EBX_ { 0 },
f_7_ECX_ { 0 },
f_81_ECX_ { 0 },
f_81_EDX_ { 0 },
data_ {},
extdata_ {}
{
//int cpuInfo[4] = {-1};
std::array<int, 4> cpui;
// Calling __cpuid with 0x0 as the function_id argument
// gets the number of the highest valid function ID.
__cpuid(cpui.data(), 0);
nIds_ = cpui[0];
for (int i = 0; i <= nIds_; ++i) {
__cpuidex(cpui.data(), i, 0);
data_.push_back(cpui);
}
// Capture vendor string
char vendor[0x20];
memset(vendor, 0, sizeof(vendor));
*reinterpret_cast<int*>(vendor) = data_[0][1];
*reinterpret_cast<int*>(vendor + 4) = data_[0][3];
*reinterpret_cast<int*>(vendor + 8) = data_[0][2];
vendor_ = vendor;
if (vendor_ == "GenuineIntel") {
isIntel_ = true;
} else if (vendor_ == "AuthenticAMD") {
isAMD_ = true;
}
// load bitset with flags for function 0x00000001
if (nIds_ >= 1) {
f_1_ECX_ = data_[1][2];
f_1_EDX_ = data_[1][3];
}
// load bitset with flags for function 0x00000007
if (nIds_ >= 7) {
f_7_EBX_ = data_[7][1];
f_7_ECX_ = data_[7][2];
}
// Calling __cpuid with 0x80000000 as the function_id argument
// gets the number of the highest valid extended ID.
__cpuid(cpui.data(), 0x80000000);
nExIds_ = cpui[0];
char brand[0x40];
memset(brand, 0, sizeof(brand));
for (int i = 0x80000000; i <= nExIds_; ++i) {
__cpuidex(cpui.data(), i, 0);
extdata_.push_back(cpui);
}
// load bitset with flags for function 0x80000001
if (nExIds_ >= 0x80000001) {
f_81_ECX_ = extdata_[1][2];
f_81_EDX_ = extdata_[1][3];
}
// Interpret CPU brand string if reported
if (nExIds_ >= 0x80000004) {
memcpy(brand, extdata_[2].data(), sizeof(cpui));
memcpy(brand + 16, extdata_[3].data(), sizeof(cpui));
memcpy(brand + 32, extdata_[4].data(), sizeof(cpui));
brand_ = brand;
}
};
int nIds_;
int nExIds_;
std::string vendor_;
std::string brand_;
bool isIntel_;
bool isAMD_;
std::bitset<32> f_1_ECX_;
std::bitset<32> f_1_EDX_;
std::bitset<32> f_7_EBX_;
std::bitset<32> f_7_ECX_;
std::bitset<32> f_81_ECX_;
std::bitset<32> f_81_EDX_;
std::vector<std::array<int, 4>> data_;
std::vector<std::array<int, 4>> extdata_;
};
};
#endif
#endif // hifi_CPUID_h

View file

@ -57,11 +57,11 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector<QSt
QUrl defaultScriptsLocation() {
#ifdef Q_OS_WIN
return QUrl(("file:///" + QCoreApplication::applicationDirPath()).toLower());
return QUrl(("file:///" + QCoreApplication::applicationDirPath()).toLower() + "/scripts");
#elif defined(Q_OS_OSX)
return QUrl(("file://" + QCoreApplication::applicationDirPath() + "/../Resources").toLower());
return QUrl(("file://" + QCoreApplication::applicationDirPath() + "/../Resources/scripts").toLower());
#else
// return "http://s3.amazonaws.com/hifi-public";
return QUrl("file://" + QCoreApplication::applicationDirPath());
return QUrl("file://" + QCoreApplication::applicationDirPath() + "/scripts");
#endif
}

View file

@ -23,6 +23,11 @@
#include <windows.h>
#endif
#ifdef Q_OS_WIN
#include "CPUID.h"
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif
@ -30,6 +35,8 @@
#include <QtCore/QDebug>
#include <QDateTime>
#include <QElapsedTimer>
#include <QProcess>
#include <QSysInfo>
#include <QThread>
#include "NumericalConstants.h"
@ -692,3 +699,82 @@ void disableQtBearerPoll() {
qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT);
}
void printSystemInformation() {
// Write system information to log
qDebug() << "Build Information";
qDebug().noquote() << "\tBuild ABI: " << QSysInfo::buildAbi();
qDebug().noquote() << "\tBuild CPU Architecture: " << QSysInfo::buildCpuArchitecture();
qDebug().noquote() << "System Information";
qDebug().noquote() << "\tProduct Name: " << QSysInfo::prettyProductName();
qDebug().noquote() << "\tCPU Architecture: " << QSysInfo::currentCpuArchitecture();
qDebug().noquote() << "\tKernel Type: " << QSysInfo::kernelType();
qDebug().noquote() << "\tKernel Version: " << QSysInfo::kernelVersion();
auto macVersion = QSysInfo::macVersion();
if (macVersion != QSysInfo::MV_None) {
qDebug() << "\tMac Version: " << macVersion;
}
auto windowsVersion = QSysInfo::windowsVersion();
if (windowsVersion != QSysInfo::WV_None) {
qDebug() << "\tWindows Version: " << windowsVersion;
}
#ifdef Q_OS_WIN
SYSTEM_INFO si;
GetNativeSystemInfo(&si);
qDebug() << "SYSTEM_INFO";
qDebug().noquote() << "\tOEM ID: " << si.dwOemId;
qDebug().noquote() << "\tProcessor Architecture: " << si.wProcessorArchitecture;
qDebug().noquote() << "\tProcessor Type: " << si.dwProcessorType;
qDebug().noquote() << "\tProcessor Level: " << si.wProcessorLevel;
qDebug().noquote() << "\tProcessor Revision: "
<< QString("0x%1").arg(si.wProcessorRevision, 4, 16, QChar('0'));
qDebug().noquote() << "\tNumber of Processors: " << si.dwNumberOfProcessors;
qDebug().noquote() << "\tPage size: " << si.dwPageSize << " Bytes";
qDebug().noquote() << "\tMin Application Address: "
<< QString("0x%1").arg(qulonglong(si.lpMinimumApplicationAddress), 16, 16, QChar('0'));
qDebug().noquote() << "\tMax Application Address: "
<< QString("0x%1").arg(qulonglong(si.lpMaximumApplicationAddress), 16, 16, QChar('0'));
const double BYTES_TO_MEGABYTE = 1.0 / (1024 * 1024);
qDebug() << "MEMORYSTATUSEX";
MEMORYSTATUSEX ms;
ms.dwLength = sizeof(ms);
if (GlobalMemoryStatusEx(&ms)) {
qDebug().noquote() << QString("\tCurrent System Memory Usage: %1%").arg(ms.dwMemoryLoad);
qDebug().noquote() << QString("\tAvail Physical Memory: %1 MB").arg(ms.ullAvailPhys * BYTES_TO_MEGABYTE, 20, 'f', 2);
qDebug().noquote() << QString("\tTotal Physical Memory: %1 MB").arg(ms.ullTotalPhys * BYTES_TO_MEGABYTE, 20, 'f', 2);
qDebug().noquote() << QString("\tAvail in Page File: %1 MB").arg(ms.ullAvailPageFile * BYTES_TO_MEGABYTE, 20, 'f', 2);
qDebug().noquote() << QString("\tTotal in Page File: %1 MB").arg(ms.ullTotalPageFile * BYTES_TO_MEGABYTE, 20, 'f', 2);
qDebug().noquote() << QString("\tAvail Virtual Memory: %1 MB").arg(ms.ullAvailVirtual * BYTES_TO_MEGABYTE, 20, 'f', 2);
qDebug().noquote() << QString("\tTotal Virtual Memory: %1 MB").arg(ms.ullTotalVirtual * BYTES_TO_MEGABYTE, 20, 'f', 2);
} else {
qDebug() << "\tFailed to retrieve memory status: " << GetLastError();
}
qDebug() << "CPUID";
qDebug() << "\tCPU Vendor: " << CPUID::Vendor().c_str();
qDebug() << "\tCPU Brand: " << CPUID::Brand().c_str();
for (auto& feature : CPUID::getAllFeatures()) {
qDebug().nospace().noquote() << "\t[" << (feature.supported ? "x" : " ") << "] " << feature.name.c_str();
}
#endif
qDebug() << "Environment Variables";
// List of env variables to include in the log. For privacy reasons we don't send all env variables.
const QStringList envWhitelist = {
"QTWEBENGINE_REMOTE_DEBUGGING"
};
auto envVariables = QProcessEnvironment::systemEnvironment();
for (auto& env : envWhitelist)
{
qDebug().noquote().nospace() << "\t" <<
(envVariables.contains(env) ? " = " + envVariables.value(env) : " NOT FOUND");
}
}

View file

@ -198,4 +198,6 @@ uint qHash(const std::shared_ptr<T>& ptr, uint seed = 0)
void disableQtBearerPoll();
void printSystemInformation();
#endif // hifi_SharedUtil_h

View file

@ -130,8 +130,13 @@ void QmlWindowClass::initQml(QVariantMap properties) {
}
void QmlWindowClass::qmlToScript(const QVariant& message) {
QJSValue js = qvariant_cast<QJSValue>(message);
emit fromQml(js.toVariant());
if (message.canConvert<QJSValue>()) {
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
} else if (message.canConvert<QString>()) {
emit fromQml(message.toString());
} else {
qWarning() << "Unsupported message type " << message;
}
}
void QmlWindowClass::sendToQml(const QVariant& message) {

View file

@ -220,6 +220,24 @@ ApplicationWindow {
}
}
Button {
text: "Add Tab 2"
onClicked: {
console.log(desktop.toolWindow);
desktop.toolWindow.addWebTab({ source: "Foo 2" });
desktop.toolWindow.showTabForUrl("Foo 2", true);
}
}
Button {
text: "Add Tab 3"
onClicked: {
console.log(desktop.toolWindow);
desktop.toolWindow.addWebTab({ source: "Foo 3" });
desktop.toolWindow.showTabForUrl("Foo 3", true);
}
}
Button {
text: "Destroy Tab"
onClicked: {

View file

@ -57,13 +57,13 @@ const QCommandLineOption STATS_INTERVAL {
};
const QStringList CLIENT_STATS_TABLE_HEADERS {
"Send (P/s)", "Est. Max (P/s)", "RTT (ms)", "CW (P)", "Period (us)",
"Send (Mb/s)", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)", "Period (us)",
"Recv ACK", "Procd ACK", "Recv LACK", "Recv NAK", "Recv TNAK",
"Sent ACK2", "Sent Packets", "Re-sent Packets"
};
const QStringList SERVER_STATS_TABLE_HEADERS {
" Mb/s ", "Recv P/s", "Est. Max (P/s)", "RTT (ms)", "CW (P)",
" Mb/s ", "Recv Mb/s", "Est. Max (Mb/s)", "RTT (ms)", "CW (P)",
"Sent ACK", "Sent LACK", "Sent NAK", "Sent TNAK",
"Recv ACK2", "Duplicates (P)"
};
@ -364,7 +364,11 @@ void UDTTest::handleMessage(std::unique_ptr<Message> message) {
void UDTTest::sampleStats() {
static bool first = true;
static const double USECS_PER_MSEC = 1000.0;
static const double MEGABITS_PER_BYTE = 8.0 / 1000000.0;
static const double MS_PER_SECOND = 1000.0;
static const double PPS_TO_MBPS = 1500.0 * MEGABITS_PER_BYTE;
if (!_target.isNull()) {
if (first) {
// output the headers for stats for our table
@ -378,8 +382,8 @@ void UDTTest::sampleStats() {
// setup a list of left justified values
QStringList values {
QString::number(stats.sendRate).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.estimatedBandwith).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.sendRate * PPS_TO_MBPS).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.estimatedBandwith * PPS_TO_MBPS).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.congestionWindowSize).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.packetSendPeriod).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()),
@ -408,16 +412,13 @@ void UDTTest::sampleStats() {
int headerIndex = -1;
static const double MEGABITS_PER_BYTE = 8.0 / 1000000.0;
static const double MS_PER_SECOND = 1000.0;
double megabitsPerSecond = (stats.receivedBytes * MEGABITS_PER_BYTE * MS_PER_SECOND) / _statsInterval;
// setup a list of left justified values
QStringList values {
QString::number(megabitsPerSecond, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.receiveRate).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.estimatedBandwith).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.receiveRate * PPS_TO_MBPS).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.estimatedBandwith * PPS_TO_MBPS).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.congestionWindowSize).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),
QString::number(stats.events[udt::ConnectionStats::Stats::SentACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()),

View file

@ -34,20 +34,24 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) {
return false;
}
std::cout << "Reading FBX.....\n";
try {
QByteArray fbxContents = fbx.readAll();
FBXGeometry* geom;
if (filename.toLower().endsWith(".obj")) {
geom = OBJReader().readOBJ(fbxContents, QVariantHash());
} else if (filename.toLower().endsWith(".fbx")) {
geom = readFBX(fbxContents, QVariantHash(), filename);
} else {
qDebug() << "unknown file extension";
return false;
}
result = *geom;
QByteArray fbxContents = fbx.readAll();
FBXGeometry* geom;
if (filename.toLower().endsWith(".obj")) {
geom = OBJReader().readOBJ(fbxContents, QVariantHash());
} else if (filename.toLower().endsWith(".fbx")) {
geom = readFBX(fbxContents, QVariantHash(), filename);
} else {
qDebug() << "unknown file extension";
reSortFBXGeometryMeshes(result);
} catch (const QString& error) {
qDebug() << "Error reading " << filename << ": " << error;
return false;
}
result = *geom;
reSortFBXGeometryMeshes(result);
return true;
}