Merge branch 'master' of https://github.com/highfidelity/hifi into better-head-joint-setup

This commit is contained in:
howard-stearns 2016-02-01 09:35:30 -08:00
commit 277dbdbc37
18 changed files with 472 additions and 98 deletions

View file

@ -680,11 +680,11 @@ void AudioMixer::domainSettingsRequestComplete() {
void AudioMixer::broadcastMixes() {
auto nodeList = DependencyManager::get<NodeList>();
int nextFrame = 0;
int64_t nextFrame = 0;
QElapsedTimer timer;
timer.start();
int usecToSleep = AudioConstants::NETWORK_FRAME_USECS;
int64_t usecToSleep = AudioConstants::NETWORK_FRAME_USECS;
const int TRAILING_AVERAGE_FRAMES = 100;
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
@ -826,12 +826,7 @@ void AudioMixer::broadcastMixes() {
break;
}
usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us
if (usecToSleep > int(USECS_PER_SECOND)) {
qDebug() << "DANGER: amount to sleep is" << usecToSleep;
qDebug() << "NextFrame is" << nextFrame << "and timer nsecs elapsed is" << timer.nsecsElapsed();
}
usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - (timer.nsecsElapsed() / 1000);
if (usecToSleep > 0) {
usleep(usecToSleep);

182
examples/FlockOfFish.js Normal file
View file

@ -0,0 +1,182 @@
//
// flockOfFish.js
// examples
//
// Philip Rosedale
// Copyright 2016 High Fidelity, Inc.
// Fish smimming around in a space in front of you
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var LIFETIME = 300; // Fish live for 5 minutes
var NUM_FISH = 20;
var TANK_WIDTH = 3.0;
var TANK_HEIGHT = 1.0;
var FISH_WIDTH = 0.03;
var FISH_LENGTH = 0.15;
var MAX_SIGHT_DISTANCE = 0.8;
var MIN_SEPARATION = 0.15;
var AVOIDANCE_FORCE = 0.2;
var COHESION_FORCE = 0.05;
var ALIGNMENT_FORCE = 0.05;
var SWIMMING_FORCE = 0.05;
var SWIMMING_SPEED = 1.5;
var fishLoaded = false;
var fish = [];
var lowerCorner = { x: 0, y: 0, z: 0 };
var upperCorner = { x: 0, y: 0, z: 0 };
function randomVector(scale) {
return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 };
}
function updateFish(deltaTime) {
if (!Entities.serversExist() || !Entities.canRez()) {
return;
}
if (!fishLoaded) {
loadFish(NUM_FISH);
fishLoaded = true;
return;
}
var averageVelocity = { x: 0, y: 0, z: 0 };
var averagePosition = { x: 0, y: 0, z: 0 };
var birdPositionsCounted = 0;
var birdVelocitiesCounted = 0;
// First pre-load an array with properties on all the other fish so our per-fish loop
// isn't doing it.
var flockProperties = [];
for (var i = 0; i < fish.length; i++) {
var otherProps = Entities.getEntityProperties(fish[i].entityId, ["position", "velocity", "rotation"]);
flockProperties.push(otherProps);
}
for (var i = 0; i < fish.length; i++) {
if (fish[i].entityId) {
// Get only the properties we need, because that is faster
var properties = flockProperties[i];
// If fish has been deleted, bail
if (properties.id != fish[i].entityId) {
fish[i].entityId = false;
return;
}
// Store old values so we can check if they have changed enough to update
var velocity = { x: properties.velocity.x, y: properties.velocity.y, z: properties.velocity.z };
var position = { x: properties.position.x, y: properties.position.y, z: properties.position.z };
averageVelocity = { x: 0, y: 0, z: 0 };
averagePosition = { x: 0, y: 0, z: 0 };
var othersCounted = 0;
for (var j = 0; j < fish.length; j++) {
if (i != j) {
// Get only the properties we need, because that is faster
var otherProps = flockProperties[j];
var separation = Vec3.distance(properties.position, otherProps.position);
if (separation < MAX_SIGHT_DISTANCE) {
averageVelocity = Vec3.sum(averageVelocity, otherProps.velocity);
averagePosition = Vec3.sum(averagePosition, otherProps.position);
othersCounted++;
}
if (separation < MIN_SEPARATION) {
var pushAway = Vec3.multiply(Vec3.normalize(Vec3.subtract(properties.position, otherProps.position)), AVOIDANCE_FORCE);
velocity = Vec3.sum(velocity, pushAway);
}
}
}
if (othersCounted > 0) {
averageVelocity = Vec3.multiply(averageVelocity, 1.0 / othersCounted);
averagePosition = Vec3.multiply(averagePosition, 1.0 / othersCounted);
// Alignment: Follow group's direction and speed
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(averageVelocity), Vec3.length(velocity)), ALIGNMENT_FORCE);
// Cohesion: Steer towards center of flock
var towardCenter = Vec3.subtract(averagePosition, position);
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(towardCenter), Vec3.length(velocity)), COHESION_FORCE);
}
// Try to swim at a constant speed
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(velocity), SWIMMING_SPEED), SWIMMING_FORCE);
// Keep fish in their 'tank'
if (position.x < lowerCorner.x) {
position.x = lowerCorner.x;
velocity.x *= -1.0;
} else if (position.x > upperCorner.x) {
position.x = upperCorner.x;
velocity.x *= -1.0;
}
if (position.y < lowerCorner.y) {
position.y = lowerCorner.y;
velocity.y *= -1.0;
} else if (position.y > upperCorner.y) {
position.y = upperCorner.y;
velocity.y *= -1.0;
}
if (position.z < lowerCorner.z) {
position.z = lowerCorner.z;
velocity.z *= -1.0;
} else if (position.z > upperCorner.z) {
position.z = upperCorner.z;
velocity.z *= -1.0;
}
// Orient in direction of velocity
var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, velocity);
var VELOCITY_FOLLOW_RATE = 0.30;
// Only update properties if they have changed, to save bandwidth
var MIN_POSITION_CHANGE_FOR_UPDATE = 0.001;
if (Vec3.distance(properties.position, position) < MIN_POSITION_CHANGE_FOR_UPDATE) {
Entities.editEntity(fish[i].entityId, { velocity: velocity, rotation: Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE) });
} else {
Entities.editEntity(fish[i].entityId, { position: position, velocity: velocity, rotation: Quat.slerp(properties.rotation, rotation, VELOCITY_FOLLOW_RATE) });
}
}
}
}
// Connect a call back that happens every frame
Script.update.connect(updateFish);
// Delete our little friends if script is stopped
Script.scriptEnding.connect(function() {
for (var i = 0; i < fish.length; i++) {
Entities.deleteEntity(fish[i].entityId);
}
});
var STARTING_FRACTION = 0.25;
function loadFish(howMany) {
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 2 * TANK_WIDTH));
lowerCorner = { x: center.x - TANK_WIDTH / 2, y: center.y, z: center.z - TANK_WIDTH / 2 };
upperCorner = { x: center.x + TANK_WIDTH / 2, y: center.y + TANK_HEIGHT, z: center.z + TANK_WIDTH / 2 };
for (var i = 0; i < howMany; i++) {
var position = {
x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION,
y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION,
z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION
};
fish.push({
entityId: Entities.addEntity({
type: "Box",
position: position,
rotation: { x: 0, y: 0, z: 0, w: 1 },
dimensions: { x: FISH_WIDTH, y: FISH_WIDTH, z: FISH_LENGTH },
velocity: { x: SWIMMING_SPEED, y: SWIMMING_SPEED, z: SWIMMING_SPEED },
damping: 0.0,
dynamic: false,
lifetime: LIFETIME,
color: { red: 0, green: 255, blue: 255 }
})
});
}
}

View file

@ -12,7 +12,7 @@
// The rectangular area in the domain where the flock will fly
var lowerCorner = { x: 0, y: 0, z: 0 };
var upperCorner = { x: 10, y: 10, z: 10 };
var upperCorner = { x: 30, y: 10, z: 30 };
var STARTING_FRACTION = 0.25;
var NUM_BIRDS = 50;
@ -36,7 +36,7 @@ var ALIGNMENT_FORCE = 1.5;
var COHESION_FORCE = 1.0;
var MAX_COHESION_VELOCITY = 0.5;
var followBirds = true;
var followBirds = false;
var AVATAR_FOLLOW_RATE = 0.001;
var AVATAR_FOLLOW_VELOCITY_TIMESCALE = 2.0;
var AVATAR_FOLLOW_ORIENTATION_RATE = 0.005;

View file

@ -0,0 +1,103 @@
// entitySpawnerAC
//
// Created by James B. Pollack @imgntn on 1/29/2016
//
// This script shows how to use an AC to create entities, and delete and recreate those entities if the AC gets restarted.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var basePosition = {
x: 0,
y: 0,
z: 0
};
var NUMBER_OF_BOXES = 4;
Agent.isAvatar = true;
function makeBoxes() {
var i;
for (i = 0; i < NUMBER_OF_BOXES; i++) {
createBox();
}
Script.clearInterval(octreeQueryInterval);
}
function createBox() {
var boxProps = {
dimensions: {
x: 1,
y: 1,
z: 1
},
color: {
red: 0,
green: 255,
blue: 0
},
type: 'Box',
name: 'TestBox',
position: {
x: basePosition.x + Math.random() * 5,
y: basePosition.y + Math.random() * 5,
z: basePosition.z + Math.random() * 5
}
}
Entities.addEntity(boxProps)
}
var secondaryInit = false;
function deleteBoxes() {
if (secondaryInit === true) {
return;
}
if (EntityViewer.getOctreeElementsCount() <= 1) {
Script.setTimeout(function() {
deleteBoxes();
}, 1000)
return;
}
var results = Entities.findEntities(basePosition, 2000);
results.forEach(function(r) {
var name = Entities.getEntityProperties(r, 'name').name;
if (name === "TestBox") {
Entities.deleteEntity(r);
}
})
makeBoxes();
secondaryInit = true;
}
var initialized = false;
function update(deltaTime) {
if (!initialized) {
if (Entities.serversExist() && Entities.canRez()) {
Entities.setPacketsPerSecond(6000);
deleteBoxes()
initialized = true;
Script.update.disconnect(update);
}
return;
}
}
EntityViewer.setPosition({
x: 0,
y: 0,
z: 0
});
EntityViewer.setKeyholeRadius(60000);
var octreeQueryInterval = Script.setInterval(function() {
EntityViewer.queryOctree();
}, 1000);
Script.update.connect(update);

View file

@ -13,12 +13,11 @@
//
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
// See MAIN CONTROL, below, for what "paused" actually does.
var OVERLAY_RATIO = 1920 / 1080;
var OVERLAY_DATA = {
text: "Paused:\npress any key to continue",
font: {size: 75},
color: {red: 200, green: 255, blue: 255},
alpha: 0.9
imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
};
// ANIMATION
@ -64,10 +63,24 @@ function stopAwayAnimation() {
}
// OVERLAY
var overlay = Overlays.addOverlay("text", OVERLAY_DATA);
var overlay = Overlays.addOverlay("image", OVERLAY_DATA);
function showOverlay() {
var screen = Controller.getViewportDimensions();
Overlays.editOverlay(overlay, {visible: true, x: screen.x / 4, y: screen.y / 4});
var properties = {visible: true},
// Update for current screen size, keeping overlay proportions constant.
screen = Controller.getViewportDimensions(),
screenRatio = screen.x / screen.y;
if (screenRatio < OVERLAY_RATIO) {
properties.width = screen.x;
properties.height = screen.x / OVERLAY_RATIO;
properties.x = 0;
properties.y = (screen.y - properties.height) / 2;
} else {
properties.height = screen.y;
properties.width = screen.y * OVERLAY_RATIO;
properties.y = 0;
properties.x = (screen.x - properties.width) / 2;
}
Overlays.editOverlay(overlay, properties);
}
function hideOverlay() {
Overlays.editOverlay(overlay, {visible: false});

View file

@ -41,6 +41,8 @@ var PICK_WITH_HAND_RAY = true;
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects
var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2;
@ -114,7 +116,9 @@ var GRABBABLE_PROPERTIES = [
"name",
"shapeType",
"parentID",
"parentJointIndex"
"parentJointIndex",
"density",
"dimensions"
];
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js
@ -295,7 +299,7 @@ function MyController(hand) {
this.rawBumperValue = 0;
//for visualizations
this.overlayLine = null;
this.particleBeam = null;
this.particleBeamObject = null;
//for lights
this.spotlight = null;
@ -479,34 +483,32 @@ function MyController(hand) {
this.handleDistantParticleBeam = function(handPosition, objectPosition, color) {
var handToObject = Vec3.subtract(objectPosition, handPosition);
var finalRotation = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject);
var finalRotationObject = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject);
var distance = Vec3.distance(handPosition, objectPosition);
var speed = 5;
var speed = distance * 3;
var spread = 0;
var lifespan = distance / speed;
if (this.particleBeam === null) {
this.createParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan);
if (this.particleBeamObject === null) {
this.createParticleBeam(objectPosition, finalRotationObject, color, speed, spread, lifespan);
} else {
this.updateParticleBeam(objectPosition, finalRotation, color, speed, spread, lifespan);
this.updateParticleBeam(objectPosition, finalRotationObject, color, speed, spread, lifespan);
}
};
this.createParticleBeam = function(position, orientation, color, speed, spread, lifespan) {
this.createParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) {
var particleBeamProperties = {
var particleBeamPropertiesObject = {
type: "ParticleEffect",
isEmitting: true,
position: position,
position: positionObject,
visible: false,
lifetime: 60,
"name": "Particle Beam",
"color": color,
"maxParticles": 2000,
"lifespan": lifespan,
"emitRate": 50,
"emitRate": 1000,
"emitSpeed": speed,
"speedSpread": spread,
"emitOrientation": {
@ -544,26 +546,25 @@ function MyController(hand) {
"additiveBlending": 0,
"textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png"
}
this.particleBeam = Entities.addEntity(particleBeamProperties);
this.particleBeamObject = Entities.addEntity(particleBeamPropertiesObject);
};
this.updateParticleBeam = function(position, orientation, color, speed, spread, lifespan) {
Entities.editEntity(this.particleBeam, {
rotation: orientation,
position: position,
this.updateParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) {
Entities.editEntity(this.particleBeamObject, {
rotation: orientationObject,
position: positionObject,
visible: true,
color: color,
emitSpeed: speed,
speedSpread: spread,
lifespan: lifespan
})
};
this.renewParticleBeamLifetime = function() {
var props = Entities.getEntityProperties(this.particleBeam, "age");
Entities.editEntity(this.particleBeam, {
var props = Entities.getEntityProperties(this.particleBeamObject, "age");
Entities.editEntity(this.particleBeamObject, {
lifetime: TEMPORARY_PARTICLE_BEAM_LIFETIME + props.age // renew lifetime
})
}
@ -684,9 +685,9 @@ function MyController(hand) {
};
this.particleBeamOff = function() {
if (this.particleBeam !== null) {
Entities.deleteEntity(this.particleBeam);
this.particleBeam = null;
if (this.particleBeamObject !== null) {
Entities.deleteEntity(this.particleBeamObject);
this.particleBeamObject = null;
}
}
@ -864,6 +865,7 @@ function MyController(hand) {
// too far away, don't grab
continue;
}
if (distance < minDistance) {
this.grabbedEntity = candidateEntities[i];
minDistance = distance;
@ -932,6 +934,18 @@ function MyController(hand) {
}
};
this.distanceGrabTimescale = function(mass, distance) {
var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_DISTANCE;
if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {
timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;
}
return timeScale;
}
this.getMass = function(dimensions, density) {
return (dimensions.x * dimensions.y * dimensions.z) * density;
}
this.distanceHolding = function() {
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
@ -953,12 +967,17 @@ function MyController(hand) {
this.radiusScalar = 1.0;
}
// compute the mass for the purpose of energy and how quickly to move object
this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density);
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position));
var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject);
this.actionID = NULL_UUID;
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
targetPosition: this.currentObjectPosition,
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
linearTimeScale: timeScale,
targetRotation: this.currentObjectRotation,
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
angularTimeScale: timeScale,
tag: getTag(),
ttl: ACTION_TTL
});
@ -1135,11 +1154,12 @@ function MyController(hand) {
this.handleSpotlight(this.grabbedEntity);
}
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: targetPosition,
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
targetRotation: this.currentObjectRotation,
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
ttl: ACTION_TTL
});
if (success) {
@ -1607,7 +1627,7 @@ function MyController(hand) {
this.cleanup = function() {
this.release();
Entities.deleteEntity(this.particleBeam);
Entities.deleteEntity(this.particleBeamObject);
Entities.deleteEntity(this.spotLight);
Entities.deleteEntity(this.pointLight);
};

View file

@ -1277,9 +1277,12 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem == "Import Entities" || menuItem == "Import Entities from URL") {
var importURL;
var importURL = null;
if (menuItem == "Import Entities") {
importURL = "file:///" + Window.browse("Select models to import", "", "*.json");
var fullPath = Window.browse("Select models to import", "", "*.json");
if (fullPath) {
importURL = "file:///" + fullPath;
}
} else {
importURL = Window.prompt("URL of SVO to import", "");
}

View file

@ -976,6 +976,42 @@
</div>
</div>
<div class="section-header text-section">
<label>Text</label>
</div>
<div class="text-section property">
<div class="label">Text Content</div>
<div class="value">
<input type="text" id="property-text-text">
</div>
</div>
<div class="text-section property">
<div class="label">Line Height</div>
<div class="value">
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
</div>
</div>
<div class="text-section property">
<div class="label">Text Color</div>
<div class="value">
<div class='color-picker' id="property-text-text-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
</div>
</div>
<div class="text-section property">
<div class="label">Background Color</div>
<div class="value">
<div class='color-picker' id="property-text-background-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
</div>
</div>
<div class="section-header zone-section">
<label>Zone</label>
</div>
@ -1088,8 +1124,6 @@
</select>
</div>
</div>
<div class="sub-section-header zone-section skybox-section">
<label>Skybox</label>
</div>
@ -1103,10 +1137,13 @@
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
</div>
</div>
<div class="zone-section skybox-section property">
<div class="label">Skybox URL</div>
<div class="value">
<input type="text" id="property-zone-skybox-url" class="url">
</div>
</div>
<div class="section-header web-section">
<label>Web</label>
@ -1497,41 +1534,7 @@
</div>
<div class="section-header text-section">
<label>Text</label>
</div>
<div class="text-section property">
<div class="label">Text Content</div>
<div class="value">
<input type="text" id="property-text-text">
</div>
</div>
<div class="text-section property">
<div class="label">Line Height</div>
<div class="value">
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
</div>
</div>
<div class="text-section property">
<div class="label">Text Color</div>
<div class="value">
<div class='color-picker' id="property-text-text-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
</div>
</div>
<div class="text-section property">
<div class="label">Background Color</div>
<div class="value">
<div class='color-picker' id="property-text-background-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
</div>
</div>
<div class="section-header light-section">
<label>Light</label>

View file

@ -540,14 +540,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
connect(&domainHandler, &DomainHandler::hostnameChanged,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
connect(&locationUpdateTimer, &QTimer::timeout,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
// if we get a domain change, immediately attempt update location in metaverse server
@ -590,6 +590,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
// Save avatar location immediately after a teleport.
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
scriptEngines->registerScriptInitializer([this](ScriptEngine* engine){
@ -1691,9 +1695,8 @@ void Application::resizeGL() {
}
bool Application::importSVOFromURL(const QString& urlString) {
QUrl url(urlString);
emit svoImportRequested(url.url());
return true; // assume it's accepted
emit svoImportRequested(urlString);
return true;
}
bool Application::event(QEvent* event) {
@ -5003,7 +5006,7 @@ void Application::setPalmData(Hand* hand, const controller::Pose& pose, float de
palm.setActive(pose.isValid());
// transform from sensor space, to world space, to avatar model space.
glm::mat4 poseMat = createMatFromQuatAndPos(pose.getRotation(), pose.getTranslation());
glm::mat4 poseMat = createMatFromQuatAndPos(pose.getRotation(), pose.getTranslation() * myAvatar->getScale());
glm::mat4 sensorToWorldMat = myAvatar->getSensorToWorldMatrix();
glm::mat4 modelMat = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
glm::mat4 objectPose = glm::inverse(modelMat) * sensorToWorldMat * poseMat;

View file

@ -285,6 +285,7 @@ void MyAvatar::update(float deltaTime) {
// However, render/MyAvatar::update/Application::update don't always match (e.g., when using the separate avatar update thread),
// so we update now. It's ok if it updates again in the normal way.
updateSensorToWorldMatrix();
emit positionGoneTo();
}
Head* head = getHead();
@ -1629,7 +1630,7 @@ void MyAvatar::decreaseSize() {
void MyAvatar::resetSize() {
_targetScale = 1.0f;
qCDebug(interfaceapp, "Reseted scale to %f", (double)_targetScale);
qCDebug(interfaceapp, "Reset scale to %f", (double)_targetScale);
}
void MyAvatar::goToLocation(const glm::vec3& newPosition,

View file

@ -272,6 +272,7 @@ signals:
void transformChanged();
void newCollisionSoundURL(const QUrl& url);
void collisionWithEntity(const Collision& collision);
void positionGoneTo();
private:

View file

@ -34,8 +34,19 @@ WindowScriptingInterface::WindowScriptingInterface() :
{
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
connect(qApp, &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
static const QMetaMethod svoImportRequestedSignal =
QMetaMethod::fromSignal(&WindowScriptingInterface::svoImportRequested);
if (isSignalConnected(svoImportRequestedSignal)) {
QUrl url(urlString);
emit svoImportRequested(url.url());
} else {
OffscreenUi::warning("Import SVO Error", "You need to be running edit.js to import entities.");
}
});
}
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {

View file

@ -102,6 +102,11 @@ void AudioInjector::restart() {
// reset the current send offset to zero
_currentSendOffset = 0;
// reset state to start sending from beginning again
_nextFrame = 0;
_frameTimer->invalidate();
_hasSentFirstFrame = false;
// check our state to decide if we need extra handling for the restart request
if (_state == State::Finished) {
// we finished playing, need to reset state so we can get going again
@ -246,6 +251,11 @@ int64_t AudioInjector::injectNextFrame() {
}
}
if (!_frameTimer->isValid()) {
// in the case where we have been restarted, the frame timer will be invalid and we need to start it back over here
_frameTimer->restart();
}
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
if (!_options.loop) {
// If we aren't looping, let's make sure we don't read past the end

View file

@ -79,6 +79,12 @@ void AudioInjectorManager::run() {
if (_injectors.size() > 0) {
// loop through the injectors in the map and send whatever frames need to go out
auto front = _injectors.top();
// create an InjectorQueue to hold injectors to be queued
// this allows us to call processEvents even if a single injector wants to be re-queued immediately
std::vector<TimeInjectorPointerPair> heldInjectors;
heldInjectors.reserve(_injectors.size());
while (_injectors.size() > 0 && front.first <= usecTimestampNow()) {
// either way we're popping this injector off - get a copy first
auto injector = front.second;
@ -89,8 +95,8 @@ void AudioInjectorManager::run() {
auto nextCallDelta = injector->injectNextFrame();
if (nextCallDelta >= 0 && !injector->isFinished()) {
// re-enqueue the injector with the correct timing
_injectors.emplace(usecTimestampNow() + nextCallDelta, injector);
// enqueue the injector with the correct timing in our holding queue
heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector);
}
}
@ -101,6 +107,12 @@ void AudioInjectorManager::run() {
break;
}
}
// if there are injectors in the holding queue, push them to our persistent queue now
while (!heldInjectors.empty()) {
_injectors.push(heldInjectors.back());
heldInjectors.pop_back();
}
}
} else {

View file

@ -490,6 +490,9 @@ void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message)
// this is a packet from the domain server, reset the count of un-replied check-ins
_numNoReplyDomainCheckIns = 0;
// emit our signal so listeners know we just heard from the DS
emit receivedDomainServerList();
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList);
QDataStream packetStream(message->getMessage());

View file

@ -87,6 +87,7 @@ public slots:
signals:
void limitOfSilentDomainCheckInsReached();
void receivedDomainServerList();
private slots:
void stopKeepalivePingTimer();
void sendPendingDSPathQuery();

View file

@ -30,6 +30,10 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) :
connect(&_domainServerTimer, &QTimer::timeout, this, &ThreadedAssignment::checkInWithDomainServerOrExit);
_domainServerTimer.setInterval(DOMAIN_SERVER_CHECK_IN_MSECS);
// if the NL tells us we got a DS response, clear our member variable of queued check-ins
auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::receivedDomainServerList, this, &ThreadedAssignment::clearQueuedCheckIns);
}
void ThreadedAssignment::setFinished(bool isFinished) {
@ -103,11 +107,18 @@ void ThreadedAssignment::sendStatsPacket() {
}
void ThreadedAssignment::checkInWithDomainServerOrExit() {
if (DependencyManager::get<NodeList>()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// verify that the number of queued check-ins is not >= our max
// the number of queued check-ins is cleared anytime we get a response from the domain-server
if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
qDebug() << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server"
<< "Stopping the current assignment";
setFinished(true);
} else {
auto nodeList = DependencyManager::get<NodeList>();
QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn");
// increase the number of queued check ins
_numQueuedCheckIns++;
}
}

View file

@ -33,6 +33,7 @@ public slots:
virtual void run() = 0;
Q_INVOKABLE virtual void stop() { setFinished(true); }
virtual void sendStatsPacket();
void clearQueuedCheckIns() { _numQueuedCheckIns = 0; }
signals:
void finished();
@ -42,6 +43,7 @@ protected:
bool _isFinished;
QTimer _domainServerTimer;
QTimer _statsTimer;
int _numQueuedCheckIns { 0 };
protected slots:
void domainSettingsRequestFailed();