Merge branch 'master' into 20855

This commit is contained in:
David Rowe 2016-03-25 09:06:20 +13:00
commit 9280443425
56 changed files with 994 additions and 319 deletions

View file

@ -8,14 +8,14 @@ like to get paid for your work, make sure you report the bug via a job on
[Worklist.net](https://worklist.net).
We're hiring! We're looking for skilled developers;
send your resume to hiring@highfidelity.io
send your resume to hiring@highfidelity.com
##### Chat with us
Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi!
Documentation
=========
Documentation is available at [docs.highfidelity.io](http://docs.highfidelity.io), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
Documentation is available at [docs.highfidelity.com](http://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
Build Instructions
=========

301
examples/airship/airship.js Normal file
View file

@ -0,0 +1,301 @@
//
// airship.js
//
// Animates a pirate airship that chases people and shoots cannonballs at them
//
// Created by Philip Rosedale on March 7, 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
//
(function () {
var entityID,
minimumDelay = 100, // milliseconds
distanceScale = 2.0, // distance at which 100% chance of hopping
timeScale = 300.0, // crash
hopStrength = 0.4, // meters / second
spotlight = null,
wantDebug = false,
timeoutID = undefined,
bullet = null,
particles = null,
nearbyAvatars = 0,
nearbyAvatarsInRange = 0,
averageAvatarLocation = { x: 0, y: 0, z: 0 },
properties,
lightTimer = 0,
lightTimeoutID = undefined,
audioInjector = null;
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var cannonSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/cannonShot.wav");
var explosionSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/explosion.wav");
var NO_SHOOT_COLOR = { red: 100, green: 100, blue: 100 };
var MAX_TARGET_RANGE = 200;
function printDebug(message) {
if (wantDebug) {
print(message);
}
}
var LIGHT_UPDATE_INTERVAL = 50;
var LIGHT_LIFETIME = 700;
function randomVector(size) {
return { x: (Math.random() - 0.5) * size,
y: (Math.random() - 0.5) * size,
z: (Math.random() - 0.5) * size };
}
function makeLight(parent, position, colorDivisor) {
// Create a flickering light somewhere for a while
if (spotlight !== null) {
// light still exists, do nothing
printDebug("light still there");
return;
}
printDebug("making light");
var colorIndex = 180 + Math.random() * 50;
spotlight = Entities.addEntity({
type: "Light",
name: "Test Light",
intensity: 50.0,
falloffRadius: 20.0,
dimensions: {
x: 150,
y: 150,
z: 150
},
position: position,
parentID: parent,
color: {
red: colorIndex,
green: colorIndex / colorDivisor,
blue: 0
},
lifetime: LIGHT_LIFETIME * 2
});
lightTimer = 0;
lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL);
};
function updateLight() {
lightTimer += LIGHT_UPDATE_INTERVAL;
if ((spotlight !== null) && (lightTimer > LIGHT_LIFETIME)) {
printDebug("deleting light!");
Entities.deleteEntity(spotlight);
spotlight = null;
} else {
Entities.editEntity(spotlight, {
intensity: 5 + Math.random() * 50,
falloffRadius: 5 + Math.random() * 10
});
lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL);
}
}
function move() {
var HOVER_DISTANCE = 30.0;
var RUN_TOWARD_STRENGTH = 0.002;
var VELOCITY_FOLLOW_RATE = 0.01;
var range = Vec3.distance(properties.position, averageAvatarLocation);
var impulse = { x: 0, y: 0, z: 0 };
// move in the XZ plane
var away = Vec3.subtract(properties.position, averageAvatarLocation);
away.y = 0.0;
if (range > HOVER_DISTANCE) {
impulse = Vec3.multiply(-RUN_TOWARD_STRENGTH, Vec3.normalize(away));
}
var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, properties.velocity);
Entities.editEntity(entityID, {velocity: Vec3.sum(properties.velocity, impulse), rotation: Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE)});
}
function countAvatars() {
var workQueue = AvatarList.getAvatarIdentifiers();
var averageLocation = {x: 0, y: 0, z: 0};
var summed = 0;
var inRange = 0;
for (var i = 0; i < workQueue.length; i++) {
var avatar = AvatarList.getAvatar(workQueue[i]), avatarPosition = avatar && avatar.position;
if (avatarPosition) {
averageLocation = Vec3.sum(averageLocation, avatarPosition);
summed++;
if (Vec3.distance(avatarPosition, properties.position) < MAX_TARGET_RANGE) {
inRange++;
}
}
}
if (summed > 0) {
averageLocation = Vec3.multiply(1 / summed, averageLocation);
}
nearbyAvatars = summed;
nearbyAvatarsInRange = inRange;
averageAvatarLocation = averageLocation;
//printDebug(" Avatars: " + summed + "in range: " + nearbyAvatarsInRange);
//Vec3.print(" location: ", averageAvatarLocation);
return;
}
function shoot() {
if (bullet !== null) {
return;
}
if (Vec3.distance(MyAvatar.position, properties.position) > MAX_TARGET_RANGE) {
return;
}
var SPEED = 7.5;
var GRAVITY = -2.0;
var DIAMETER = 0.5;
var direction = Vec3.subtract(MyAvatar.position, properties.position);
var range = Vec3.distance(MyAvatar.position, properties.position);
var timeOfFlight = range / SPEED;
var fall = 0.5 * -GRAVITY * (timeOfFlight * timeOfFlight);
var velocity = Vec3.multiply(SPEED, Vec3.normalize(direction));
velocity.y += 0.5 * fall / timeOfFlight * 2.0;
var DISTANCE_TO_DECK = 3;
var bulletStart = properties.position;
bulletStart.y -= DISTANCE_TO_DECK;
makeLight(entityID, Vec3.sum(properties.position, randomVector(10.0)), 2);
bullet = Entities.addEntity({
type: "Sphere",
name: "cannonball",
position: properties.position,
dimensions: { x: DIAMETER, y: DIAMETER, z: DIAMETER },
color: { red: 10, green: 10, blue: 10 },
velocity: velocity,
damping: 0.01,
dynamic: true,
ignoreForCollisions: true,
gravity: { x: 0, y: GRAVITY, z: 0 },
lifetime: timeOfFlight * 2
});
Audio.playSound(cannonSound, {
position: Vec3.sum(MyAvatar.position, velocity),
volume: 1.0
});
makeParticles(properties.position, bullet, timeOfFlight * 2);
Script.setTimeout(explode, timeOfFlight * 1000);
}
function explode() {
var properties = Entities.getEntityProperties(bullet);
var direction = Vec3.normalize(Vec3.subtract(MyAvatar.position, properties.position));
makeLight(null, properties.position, 10);
Audio.playSound(explosionSound, { position: Vec3.sum(MyAvatar.position, direction), volume: 1.0 });
bullet = null;
}
function maybe() { // every user checks their distance and tries to claim if close enough.
var PROBABILITY_OF_SHOOT = 0.015;
properties = Entities.getEntityProperties(entityID);
countAvatars();
if (nearbyAvatars && (Math.random() < 1 / nearbyAvatars)) {
move();
}
if (nearbyAvatarsInRange && (Math.random() < PROBABILITY_OF_SHOOT / nearbyAvatarsInRange)) {
shoot();
}
var TIME_TO_NEXT_CHECK = 33;
timeoutID = Script.setTimeout(maybe, TIME_TO_NEXT_CHECK);
}
this.preload = function (givenEntityID) {
printDebug("preload airship v1...");
entityID = givenEntityID;
properties = Entities.getEntityProperties(entityID);
timeoutID = Script.setTimeout(maybe, minimumDelay);
};
this.unload = function () {
printDebug("unload airship...");
if (timeoutID !== undefined) {
Script.clearTimeout(timeoutID);
}
if (lightTimeoutID !== undefined) {
Script.clearTimeout(lightTimeoutID);
}
if (spotlight !== null) {
Entities.deleteEntity(spotlight);
}
};
function makeParticles(position, parent, lifespan) {
particles = Entities.addEntity({
type: 'ParticleEffect',
position: position,
parentID: parent,
color: {
red: 70,
green: 70,
blue: 70
},
isEmitting: 1,
maxParticles: 1000,
lifetime: lifespan,
lifespan: lifespan / 3,
emitRate: 80,
emitSpeed: 0,
speedSpread: 1.0,
emitRadiusStart: 1,
polarStart: -Math.PI/8,
polarFinish: Math.PI/8,
azimuthStart: -Math.PI/4,
azimuthFinish: Math.PI/4,
emitAcceleration: { x: 0, y: 0, z: 0 },
particleRadius: 0.25,
radiusSpread: 0.1,
radiusStart: 0.3,
radiusFinish: 0.15,
colorSpread: {
red: 100,
green: 100,
blue: 0
},
colorStart: {
red: 125,
green: 125,
blue: 125
},
colorFinish: {
red: 10,
green: 10,
blue: 10
},
alpha: 0.5,
alphaSpread: 0,
alphaStart: 1,
alphaFinish: 0,
emitterShouldTrail: true,
textures: 'https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png'
});
}
})

View file

@ -0,0 +1,59 @@
// makeAirship.js
//
// // Created by Philip Rosedale on March 7, 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
//
var SIZE = 0.2;
var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere"
var MODEL_URL = "https://s3.amazonaws.com/hifi-public/philip/airship_compact.fbx"
var MODEL_DIMENSION = { x: 19.257, y: 24.094, z: 40.3122 };
var ENTITY_URL = "https://s3.amazonaws.com/hifi-public/scripts/airship/airship.js";
var LIFETIME = 3600 * 48;
var GRAVITY = { x: 0, y: 0, z: 0 };
var DAMPING = 0.05;
var ANGULAR_DAMPING = 0.01;
var collidable = true;
var gravity = true;
var HOW_FAR_IN_FRONT_OF_ME = 30;
var HOW_FAR_ABOVE_ME = 15;
var leaveBehind = true;
var shipLocation = Vec3.sum(MyAvatar.position, Vec3.multiply(HOW_FAR_IN_FRONT_OF_ME, Quat.getFront(Camera.orientation)));
shipLocation.y += HOW_FAR_ABOVE_ME;
var airship = Entities.addEntity({
type: TYPE,
modelURL: MODEL_URL,
name: "airship",
position: shipLocation,
dimensions: (TYPE == "Model") ? MODEL_DIMENSION : { x: SIZE, y: SIZE, z: SIZE },
damping: DAMPING,
angularDamping: ANGULAR_DAMPING,
gravity: (gravity ? GRAVITY : { x: 0, y: 0, z: 0}),
dynamic: collidable,
lifetime: LIFETIME,
animation: {url: MODEL_URL, running: true, currentFrame: 0, loop: true},
script: ENTITY_URL
});
function scriptEnding() {
if (!leaveBehind) {
Entities.deleteEntity(airship);
}
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -198,6 +198,16 @@ function createColorPicker(key) {
settings[key] = colorArray;
var controller = gui.addColor(settings, key);
controller.onChange(function(value) {
// Handle hex colors
if(_.isString(value) && value[0] === '#') {
const BASE_HEX = 16;
var colorRegExResult = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value);
value = [
parseInt(colorRegExResult[1], BASE_HEX),
parseInt(colorRegExResult[2], BASE_HEX),
parseInt(colorRegExResult[3], BASE_HEX)
];
}
var obj = {};
obj[key] = convertColorArrayToObject(value);
writeVec3ToInterface(obj);

24
interface/resources/qml/controls-uit/ComboBox.qml Normal file → Executable file
View file

@ -162,6 +162,30 @@ FocusScope {
height: 480
width: root.width + 4
style: ScrollViewStyle {
decrementControl: Item {
visible: false
}
incrementControl: Item {
visible: false
}
scrollBarBackground: Rectangle{
implicitWidth: 14
color: hifi.colors.baseGray
}
handle:
Rectangle {
implicitWidth: 8
anchors.left: parent.left
anchors.leftMargin: 3
anchors.top: parent.top
anchors.bottom: parent.bottom
radius: 3
color: hifi.colors.lightGrayText
}
}
ListView {
id: listView
height: textField.height * count * 1.4

24
interface/resources/qml/controls-uit/SpinBox.qml Normal file → Executable file
View file

@ -35,7 +35,6 @@ SpinBox {
style: SpinBoxStyle {
id: spinStyle
background: Rectangle {
id: backgrondRec
color: isLightColorScheme
? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow)
@ -91,4 +90,27 @@ SpinBox {
color: spinBox.colorLabelInside
visible: spinBox.labelInside != ""
}
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onWheel: {
if(spinBox.focus)
wheel.accepted = false
else
wheel.accepted = true
}
onPressed: {
mouse.accepted = false
}
onReleased: {
mouse.accepted = false
}
onClicked: {
mouse.accepted = false
}
onDoubleClicked: {
mouse.accepted = false
}
}
}

View file

@ -4,18 +4,14 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.4
//import "../../windows"
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "attachments"
Window {
id: root
title: "Attachments Dialog"
title: "Attachments"
objectName: "AttachmentsDialog"
width: 600
height: 600
@ -43,6 +39,7 @@ Window {
listView.model.append({});
}
}
Column {
width: pane.contentWidth
@ -55,14 +52,22 @@ Window {
Rectangle {
id: attachmentsBackground
anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 }
color: hifi.colors.lightGrayText
color: hifi.colors.baseGrayShadow
radius: 4
ScrollView{
ScrollView {
id: scrollView
anchors.fill: parent
anchors.margins: 4
style: ScrollViewStyle {
padding {
top: 0
right: 0
bottom: 0
}
decrementControl: Item {
visible: false
}
@ -72,16 +77,29 @@ Window {
scrollBarBackground: Rectangle{
implicitWidth: 14
color: hifi.colors.baseGray
}
radius: 4
Rectangle {
// Make top left corner of scrollbar appear square
width: 8
height: 4
color: hifi.colors.baseGray
anchors.top: parent.top
anchors.horizontalCenter: parent.left
}
}
handle:
Rectangle {
implicitWidth: 8
anchors.left: parent.left
anchors.leftMargin: 3
anchors.top: parent.top
anchors.bottom: parent.bottom
radius: 3
anchors {
left: parent.left
leftMargin: 3
top: parent.top
topMargin: 3
bottom: parent.bottom
bottomMargin: 4
}
radius: 4
color: hifi.colors.lightGrayText
}
}
@ -90,8 +108,9 @@ Window {
id: listView
model: ListModel {}
delegate: Item {
id: attachmentDelegate
implicitHeight: attachmentView.height + 8;
implicitWidth: attachmentView.width;
implicitWidth: attachmentView.width
Attachment {
id: attachmentView
width: scrollView.width
@ -113,7 +132,7 @@ Window {
anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 }
text: "New Attachment"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
colorScheme: hifi.colorSchemes.dark
onClicked: {
var template = {
modelUrl: "",
@ -133,15 +152,15 @@ Window {
id: buttonRow
spacing: 8
anchors { right: parent.right; bottom: parent.bottom; margins: 8 }
HifiControls.Button {
action: cancelAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
}
HifiControls.Button {
action: okAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
colorScheme: hifi.colorSchemes.dark
}
HifiControls.Button {
action: cancelAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
}
}

View file

@ -4,11 +4,8 @@ import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0
//import "../../../windows"
//import "../../../controls" as VrControls
import "."
import ".."
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows-uit"
@ -33,13 +30,13 @@ Item {
Column {
y: 8
id: column
anchors { left: parent.left; right: parent.right; margins: 8 }
anchors { left: parent.left; right: parent.right; margins: 20 }
spacing: 8
Item {
height: modelChooserButton.height + urlLabel.height
height: modelChooserButton.height + urlLabel.height + 4
anchors { left: parent.left; right: parent.right;}
Text { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL:"; width: 80; anchors.top: parent.top;}
HifiControls.Label { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL"; anchors.top: parent.top;}
HifiControls.TextField {
id: modelUrl;
height: jointChooser.height;
@ -60,12 +57,12 @@ Item {
colorScheme: hifi.colorSchemes.dark
anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter }
Component {
id: modelBrowserBuiler;
id: modelBrowserBuilder;
ModelBrowserDialog {}
}
onClicked: {
var browser = modelBrowserBuiler.createObject(desktop);
var browser = modelBrowserBuilder.createObject(desktop);
browser.selected.connect(function(newModelUrl){
modelUrl.text = newModelUrl;
})
@ -74,12 +71,11 @@ Item {
}
Item {
height: jointChooser.height + jointLabel.height
height: jointChooser.height + jointLabel.height + 4
anchors { left: parent.left; right: parent.right; }
Text {
HifiControls.Label {
id: jointLabel;
width: 80;
text: "Joint:";
text: "Joint";
color: hifi.colors.lightGrayText;
anchors.top: parent.top
}
@ -99,9 +95,9 @@ Item {
}
Item {
height: translation.height + translationLabel.height
height: translation.height + translationLabel.height + 4
anchors { left: parent.left; right: parent.right; }
Text { id: translationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Translation:"; anchors.top: parent.top; }
HifiControls.Label { id: translationLabel; color: hifi.colors.lightGrayText; text: "Translation"; anchors.top: parent.top; }
Translation {
id: translation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom}
@ -116,9 +112,9 @@ Item {
}
Item {
height: rotation.height + rotationLabel.height
height: rotation.height + rotationLabel.height + 4
anchors { left: parent.left; right: parent.right; }
Text { id: rotationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Rotation:"; anchors.top: parent.top; }
HifiControls.Label { id: rotationLabel; color: hifi.colors.lightGrayText; text: "Rotation"; anchors.top: parent.top; }
Rotation {
id: rotation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
@ -133,45 +129,58 @@ Item {
}
Item {
height: scaleSpinner.height + scaleLabel.height
height: scaleItem.height
anchors { left: parent.left; right: parent.right; }
Text { id: scaleLabel; width: 80; color: hifi.colors.lightGrayText; text: "Scale:"; anchors.top: parent.top; }
HifiControls.SpinBox {
id: scaleSpinner;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
decimals: 1;
minimumValue: 0.1
maximumValue: 10
stepSize: 0.1;
value: attachment ? attachment.scale : 1.0
colorScheme: hifi.colorSchemes.dark
onValueChanged: {
if (completed && attachment && attachment.scale !== value) {
attachment.scale = value;
updateAttachment();
Item {
id: scaleItem
height: scaleSpinner.height + scaleLabel.height + 4
width: parent.width / 3 - 8
anchors { right: parent.right; }
HifiControls.Label { id: scaleLabel; color: hifi.colors.lightGrayText; text: "Scale"; anchors.top: parent.top; }
HifiControls.SpinBox {
id: scaleSpinner;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
decimals: 1;
minimumValue: 0.1
maximumValue: 10
stepSize: 0.1;
value: attachment ? attachment.scale : 1.0
colorScheme: hifi.colorSchemes.dark
onValueChanged: {
if (completed && attachment && attachment.scale !== value) {
attachment.scale = value;
updateAttachment();
}
}
}
}
Item {
id: isSoftItem
height: scaleSpinner.height
anchors {
left: parent.left
bottom: parent.bottom
}
HifiControls.CheckBox {
id: soft
text: "Is soft"
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
checked: attachment ? attachment.soft : false
colorScheme: hifi.colorSchemes.dark
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
}
}
}
}
Item {
height: soft.height
anchors { left: parent.left; right: parent.right; }
Text { id: softLabel; width: 80; color: hifi.colors.lightGrayText; text: "Is soft"; anchors.left: soft.right; anchors.leftMargin: 8; }
HifiControls.CheckBox {
id: soft;
anchors { left: parent.left; bottom: parent.bottom;}
checked: attachment ? attachment.soft : false
colorScheme: hifi.colorSchemes.dark
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
}
}
}
HifiControls.Button {
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark

View file

@ -1,7 +1,7 @@
import "."
Vector3 {
decimals: 2;
decimals: 3;
stepSize: 0.01;
maximumValue: 10
minimumValue: -10

View file

@ -195,6 +195,7 @@ static const QString FBX_EXTENSION = ".fbx";
static const QString OBJ_EXTENSION = ".obj";
static const QString AVA_JSON_EXTENSION = ".ava.json";
static const int MSECS_PER_SEC = 1000;
static const int MIRROR_VIEW_TOP_PADDING = 5;
static const int MIRROR_VIEW_LEFT_PADDING = 10;
static const int MIRROR_VIEW_WIDTH = 265;
@ -239,7 +240,7 @@ class DeadlockWatchdogThread : public QThread {
public:
static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1;
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 10 * USECS_PER_SECOND;
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 15 * USECS_PER_SECOND;
// Set the heartbeat on launch
DeadlockWatchdogThread() {
@ -365,7 +366,7 @@ bool setupEssentials(int& argc, char** argv) {
Setting::preInit();
CrashHandler::checkForAndHandleCrash();
bool previousSessionCrashed = CrashHandler::checkForAndHandleCrash();
CrashHandler::writeRunningMarkerFiler();
qAddPostRoutine(CrashHandler::deleteRunningMarkerFile);
@ -427,7 +428,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<InterfaceParentFinder>();
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
DependencyManager::set<CompositorHelper>();
return true;
return previousSessionCrashed;
}
// FIXME move to header, or better yet, design some kind of UI manager
@ -448,10 +449,13 @@ PluginContainer* _pluginContainer;
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv),
_window(new MainWindow(desktop())),
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
_sessionRunTimer(startupTimer),
_previousSessionCrashed(setupEssentials(argc, argv)),
_undoStackScriptingInterface(&_undoStack),
_frameCount(0),
_fps(60.0f),
@ -601,7 +605,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
// 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;
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC;
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
@ -623,7 +627,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// connect to appropriate slots on AccountManager
AccountManager& accountManager = AccountManager::getInstance();
const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000;
const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * MSECS_PER_SEC;
connect(&balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance);
balanceUpdateTimer.start(BALANCE_UPDATE_INTERVAL_MSECS);
@ -638,7 +642,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
accountManager.setIsAgent(true);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get());
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
@ -2804,6 +2810,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
void Application::loadSettings() {
sessionRunTime.set(0); // Just clean living. We're about to saveSettings, which will update value.
DependencyManager::get<AudioClient>()->loadSettings();
DependencyManager::get<LODManager>()->loadSettings();
@ -2817,6 +2824,7 @@ void Application::loadSettings() {
}
void Application::saveSettings() {
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC);
DependencyManager::get<AudioClient>()->saveSettings();
DependencyManager::get<LODManager>()->saveSettings();
@ -3228,7 +3236,9 @@ void Application::update(float deltaTime) {
controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);
auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
myAvatar->setHandControllerPosesInWorldFrame(leftHandPose.transform(myAvatarMatrix), rightHandPose.transform(myAvatarMatrix));
auto worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix());
auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix));
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
updateDialogs(deltaTime); // update various stats dialogs if present
@ -3315,6 +3325,9 @@ void Application::update(float deltaTime) {
qApp->updateMyAvatarLookAtPosition();
// update sensorToWorldMatrix for camera and hand controllers
myAvatar->updateSensorToWorldMatrix();
avatarManager->updateMyAvatar(deltaTime);
}
@ -3374,9 +3387,6 @@ void Application::update(float deltaTime) {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
}
}
// update sensorToWorldMatrix for rendering camera.
myAvatar->updateSensorToWorldMatrix();
}

View file

@ -377,8 +377,9 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event);
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;
bool _dependencyManagerIsSetup;
bool _previousSessionCrashed;
OffscreenGLCanvas* _offscreenContext { nullptr };
DisplayPluginPointer _displayPlugin;

View file

@ -27,7 +27,7 @@
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
void CrashHandler::checkForAndHandleCrash() {
bool CrashHandler::checkForAndHandleCrash() {
QFile runningMarkerFile(runningMarkerFilePath());
if (runningMarkerFile.exists()) {
QSettings::setDefaultFormat(QSettings::IniFormat);
@ -42,7 +42,9 @@ void CrashHandler::checkForAndHandleCrash() {
handleCrash(action);
}
}
return true;
}
return false;
}
CrashHandler::Action CrashHandler::promptUserForAction() {

View file

@ -17,7 +17,7 @@
class CrashHandler {
public:
static void checkForAndHandleCrash();
static bool checkForAndHandleCrash();
static void writeRunningMarkerFiler();
static void deleteRunningMarkerFile();

View file

@ -107,7 +107,7 @@ Menu::Menu() {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
// Edit > Stop All Scripts... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0,
addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0,
scriptEngines.data(), SLOT(stopAllScripts()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
@ -140,7 +140,7 @@ Menu::Menu() {
// 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,
@ -153,7 +153,7 @@ Menu::Menu() {
auto audioIO = DependencyManager::get<AudioClient>();
// Audio > Mute
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false,
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false,
audioIO.data(), SLOT(toggleMute()));
// Audio > Show Level Meter
@ -458,7 +458,7 @@ Menu::Menu() {
avatar, SLOT(setEnableMeshVisible(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false,
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true,
avatar, SLOT(setUseAnimPreAndPostRotations(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true,
avatar, SLOT(setEnableInverseKinematics(bool)));
@ -534,7 +534,7 @@ Menu::Menu() {
// Developer > Audio >>>
MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true,
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true,
audioIO.data(), SLOT(toggleAudioNoiseReduction()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false,
audioIO.data(), SLOT(toggleServerEcho()));
@ -617,7 +617,7 @@ Menu::Menu() {
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true,
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true,
NULL, NULL, UNSPECIFIED_POSITION, "Advanced");
#endif
}
@ -651,7 +651,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
} else if (properties.isCheckable) {
menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName,
properties.shortcutKeySequence, properties.isChecked,
MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()),
MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()),
requestedPosition, properties.grouping);
} else {
menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence,

View file

@ -418,7 +418,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation);
}
// best called at end of main loop, just before rendering.
// best called at end of main loop, after physics.
// update sensor to world matrix from current body position and hmd sensor.
// This is so the correct camera can be used for rendering.
void MyAvatar::updateSensorToWorldMatrix() {
@ -1087,24 +1087,32 @@ static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose,
return finalPose;
}
void MyAvatar::setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right) {
void MyAvatar::setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) {
if (controller::InputDevice::getLowVelocityFilter()) {
auto oldLeftPose = getLeftHandControllerPoseInWorldFrame();
auto oldRightPose = getRightHandControllerPoseInWorldFrame();
_leftHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldLeftPose, left));
_rightHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldRightPose, right));
auto oldLeftPose = getLeftHandControllerPoseInSensorFrame();
auto oldRightPose = getRightHandControllerPoseInSensorFrame();
_leftHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left));
_rightHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right));
} else {
_leftHandControllerPoseInWorldFrameCache.set(left);
_rightHandControllerPoseInWorldFrameCache.set(right);
_leftHandControllerPoseInSensorFrameCache.set(left);
_rightHandControllerPoseInSensorFrameCache.set(right);
}
}
controller::Pose MyAvatar::getLeftHandControllerPoseInSensorFrame() const {
return _leftHandControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getRightHandControllerPoseInSensorFrame() const {
return _rightHandControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getLeftHandControllerPoseInWorldFrame() const {
return _leftHandControllerPoseInWorldFrameCache.get();
return _leftHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getRightHandControllerPoseInWorldFrame() const {
return _rightHandControllerPoseInWorldFrameCache.get();
return _rightHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getLeftHandControllerPoseInAvatarFrame() const {

View file

@ -247,7 +247,9 @@ public:
virtual void rebuildCollisionShape() override;
void setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right);
void setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
controller::Pose getLeftHandControllerPoseInSensorFrame() const;
controller::Pose getRightHandControllerPoseInSensorFrame() const;
controller::Pose getLeftHandControllerPoseInWorldFrame() const;
controller::Pose getRightHandControllerPoseInWorldFrame() const;
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
@ -451,9 +453,9 @@ private:
bool _hoverReferenceCameraFacingIsCaptured { false };
glm::vec3 _hoverReferenceCameraFacing { 0.0f, 0.0f, -1.0f }; // hmd sensor space
// These are stored in WORLD frame
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInWorldFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInWorldFrameCache { controller::Pose() };
// These are stored in SENSOR frame
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
float AUDIO_ENERGY_CONSTANT { 0.000001f };

View file

@ -13,7 +13,7 @@
#include "AnimationLogging.h"
#include "AnimUtil.h"
bool AnimClip::usePreAndPostPoseFromAnim = false;
bool AnimClip::usePreAndPostPoseFromAnim = true;
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
AnimNode(AnimNode::Type::Clip, id),

View file

@ -38,12 +38,6 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
virtual void somethingChangedNotification() override {
// FIX ME: this is overly aggressive. We only really need to simulate() if something about
// the world space transform has changed and/or if some animation is occurring.
_needsInitialSimulation = true;
}
virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr);
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;

View file

@ -516,7 +516,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
EntityTreePointer tree = getTree();
if (tree && tree->isDeletedEntity(_id)) {
#ifdef WANT_DEBUG
qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. "
qDebug() << "Received packet for previously deleted entity [" << _id << "] ignoring. "
"(inside " << __FUNCTION__ << ")";
#endif
ignoreServerPacket = true;

View file

@ -956,8 +956,10 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItem->recordCreationTime();
}
} else {
qDebug() << "Recieved packet for previously deleted entity [" <<
#ifdef WANT_DEBUG
qDebug() << "Received packet for previously deleted entity [" <<
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
#endif
}
}
}

View file

@ -1102,8 +1102,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("tex_color_map")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("transparentcolor")) { // it should be TransparentColor...
// THis is how Maya assign a texture that affect diffuse color AND transparency ?
} else if (type.contains("transparentcolor")) { // Maya way of passing TransparentMap
transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("transparencyfactor")) { // Blender way of passing TransparentMap
transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("bump")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));

View file

@ -102,16 +102,20 @@ void FBXReader::consolidateFBXMaterials() {
detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity());
}
FBXTexture transparentTexture;
QString transparentTextureID = transparentTextures.value(material.materialID);
// If PBS Material, systematically bind the albedo texture as transparency texture and check for the alpha channel
if (material.isPBSMaterial) {
transparentTextureID = diffuseTextureID;
}
if (!transparentTextureID.isNull()) {
transparentTexture = getTexture(transparentTextureID);
material.opacityTexture = transparentTexture;
detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity());
}
FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(material.materialID);
QString normalTextureID = normalTextures.value(material.materialID);

View file

@ -23,6 +23,7 @@
#include <PerfStat.h>
#include <DependencyManager.h>
#include <NumericalConstants.h>
#include <Finally.h>
#include "OffscreenGLCanvas.h"
#include "GLEscrow.h"
@ -84,6 +85,7 @@ protected:
Queue _queue;
QMutex _mutex;
QWaitCondition _waitCondition;
std::atomic<bool> _rendering { false };
private:
// Event-driven methods
@ -271,15 +273,25 @@ void OffscreenQmlRenderThread::resize() {
}
void OffscreenQmlRenderThread::render() {
if (_surface->_paused) {
// Ensure we always release the main thread
Finally releaseMainThread([this] {
_waitCondition.wakeOne();
});
if (_surface->_paused) {
return;
}
QMutexLocker locker(&_mutex);
_renderControl->sync();
_waitCondition.wakeOne();
locker.unlock();
_rendering = true;
Finally unmarkRenderingFlag([this] {
_rendering = false;
});
{
QMutexLocker locker(&_mutex);
_renderControl->sync();
releaseMainThread.trigger();
}
using namespace oglplus;
@ -292,6 +304,7 @@ void OffscreenQmlRenderThread::render() {
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
_fbo->Complete(Framebuffer::Target::Draw);
{
PROFILE_RANGE("qml_render->rendercontrol")
_renderControl->render();
// FIXME The web browsers seem to be leaving GL in an error state.
// Need a debug context with sync logging to figure out why.
@ -380,8 +393,6 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) {
std::max(static_cast<int>(scale * newSize.height()), 10));
}
QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) {
return;
@ -492,7 +503,12 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
}
void OffscreenQmlSurface::updateQuick() {
if (!_renderer || !_renderer->allowNewFrame(_maxFps)) {
// If we're
// a) not set up
// b) already rendering a frame
// c) rendering too fast
// then skip this
if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) {
return;
}
@ -502,11 +518,11 @@ void OffscreenQmlSurface::updateQuick() {
}
if (_render) {
PROFILE_RANGE(__FUNCTION__);
// Lock the GUI size while syncing
QMutexLocker locker(&(_renderer->_mutex));
_renderer->_queue.add(RENDER);
_renderer->_waitCondition.wait(&(_renderer->_mutex));
_render = false;
}

View file

@ -139,7 +139,7 @@ bool NetworkGeometry::isLoadedWithTextures() const {
}
if (!_isLoadedWithTextures) {
_hasTransparentTextures = true;
_hasTransparentTextures = false;
for (auto&& material : _materials) {
if ((material->albedoTexture && !material->albedoTexture->isLoaded()) ||
@ -152,12 +152,11 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return false;
}
if (material->albedoTexture && material->albedoTexture->getGPUTexture()) {
// Reset the materialKey transparentTexture key only, as it is albedoTexture-dependent
// Reassign the texture to make sure that itsalbedo alpha channel material key is detected correctly
material->_material->setTextureMap(model::MaterialKey::ALBEDO_MAP, material->_material->getTextureMap(model::MaterialKey::ALBEDO_MAP));
const auto& usage = material->albedoTexture->getGPUTexture()->getUsage();
bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask();
material->_material->setTransparentTexture(isTransparentTexture);
// FIXME: Materials with *some* transparent textures seem to give all *other* textures alphas of 0.
_hasTransparentTextures = isTransparentTexture && _hasTransparentTextures;
_hasTransparentTextures |= isTransparentTexture;
}
}
@ -177,9 +176,9 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u
auto albedoMap = model::TextureMapPointer(new model::TextureMap());
albedoMap->setTextureSource(material->albedoTexture->_textureSource);
albedoMap->setTextureTransform(
oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform());
albedoMap->setTextureTransform(oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform());
// when reassigning the albedo texture we also check for the alpha channel used as opacity
albedoMap->setUseAlphaChannel(true);
networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
} else if (material->normalTextureName == name) {
material->normalTexture = textureCache->getTexture(url);
@ -212,10 +211,10 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u
networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
} else if (material->lightmapTextureName == name) {
material->emissiveTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE);
material->lightmapTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE);
auto lightmapMap = model::TextureMapPointer(new model::TextureMap());
lightmapMap->setTextureSource(material->emissiveTexture->_textureSource);
lightmapMap->setTextureSource(material->lightmapTexture->_textureSource);
lightmapMap->setTextureTransform(
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform());
glm::vec2 oldOffsetScale =
@ -380,9 +379,20 @@ static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FB
auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE,
networkMaterial->albedoTexture, networkMaterial->albedoTextureName);
albedoMap->setTextureTransform(material.albedoTexture.transform);
if (!material.opacityTexture.filename.isEmpty()) {
if (material.albedoTexture.filename == material.opacityTexture.filename) {
// Best case scenario, just indicating that the albedo map contains transparency
albedoMap->setUseAlphaChannel(true);
} else {
// Opacity Map is different from the Abledo map, not supported
}
}
material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
}
if (!material.normalTexture.filename.isEmpty()) {
auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture,
(material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE),

View file

@ -199,7 +199,6 @@ public:
QSharedPointer<NetworkTexture> occlusionTexture;
QString lightmapTextureName;
QSharedPointer<NetworkTexture> lightmapTexture;
};

View file

@ -51,7 +51,7 @@ void Material::setEmissive(const Color& emissive, bool isSRGB) {
}
void Material::setOpacity(float opacity) {
_key.setTransparent((opacity < 1.0f));
_key.setTranslucentFactor((opacity < 1.0f));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._opacity = opacity;
}
@ -80,19 +80,52 @@ void Material::setMetallic(float metallic) {
_schemaBuffer.edit<Schema>()._metallic = metallic;
}
void Material::setTransparentTexture(bool isTransparent) {
_key.setTransparentTexture(isTransparent);
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
if (textureMap) {
_key.setMapChannel(channel, (true));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
if (channel == MaterialKey::ALBEDO_MAP) {
// clear the previous flags whatever they were:
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(false);
if (textureMap->useAlphaChannel() && textureMap->isDefined() && textureMap->getTextureView().isValid()) {
auto usage = textureMap->getTextureView()._texture->getUsage();
if (usage.isAlpha()) {
// Texture has alpha, is not just a mask or a true transparent channel
if (usage.isAlphaMask()) {
_key.setOpacityMaskMap(true);
_key.setTranslucentMap(false);
} else {
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(true);
}
}
}
}
_textureMaps[channel] = textureMap;
} else {
_key.setMapChannel(channel, (false));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
if (channel == MaterialKey::ALBEDO_MAP) {
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(false);
}
_textureMaps.erase(channel);
}
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
auto result = _textureMaps.find(channel);
if (result != _textureMaps.end()) {
return (result->second);
} else {
return TextureMapPointer();
}
}

View file

@ -31,14 +31,15 @@ public:
ALBEDO_VAL_BIT,
METALLIC_VAL_BIT,
GLOSSY_VAL_BIT,
TRANSPARENT_VAL_BIT,
TRANSPARENT_TEX_VAL_BIT,
OPACITY_VAL_BIT,
OPACITY_MASK_MAP_BIT, // OPacity Map and Opacity MASK map are mutually exclusive
OPACITY_TRANSLUCENT_MAP_BIT,
// THe map bits must be in the smae sequence as the enum names for the map channels
EMISSIVE_MAP_BIT,
ALBEDO_MAP_BIT,
METALLIC_MAP_BIT,
ROUGHNESS_MAP_BIT,
TRANSPARENT_MAP_BIT,
NORMAL_MAP_BIT,
OCCLUSION_MAP_BIT,
LIGHTMAP_MAP_BIT,
@ -52,7 +53,6 @@ public:
ALBEDO_MAP,
METALLIC_MAP,
ROUGHNESS_MAP,
TRANSPARENT_MAP,
NORMAL_MAP,
OCCLUSION_MAP,
LIGHTMAP_MAP,
@ -77,13 +77,15 @@ public:
Builder& withAlbedo() { _flags.set(ALBEDO_VAL_BIT); return (*this); }
Builder& withMetallic() { _flags.set(METALLIC_VAL_BIT); return (*this); }
Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); }
Builder& withTransparent() { _flags.set(TRANSPARENT_VAL_BIT); return (*this); }
Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); }
Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); }
Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); }
Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); }
Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); }
Builder& withTransparentMap() { _flags.set(TRANSPARENT_MAP_BIT); return (*this); }
Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); }
Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); }
@ -102,9 +104,6 @@ public:
void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); }
bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; }
void setTransparentTexture(bool value) { _flags.set(TRANSPARENT_TEX_VAL_BIT, value); }
bool isTransparentTexture() const { return _flags[TRANSPARENT_TEX_VAL_BIT]; }
void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); }
bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; }
@ -121,13 +120,15 @@ public:
void setRoughnessMap(bool value) { _flags.set(ROUGHNESS_MAP_BIT, value); }
bool isRoughnessMap() const { return _flags[ROUGHNESS_MAP_BIT]; }
void setTransparent(bool value) { _flags.set(TRANSPARENT_VAL_BIT, value); }
bool isTransparent() const { return _flags[TRANSPARENT_VAL_BIT]; }
bool isOpaque() const { return !_flags[TRANSPARENT_VAL_BIT]; }
void setTranslucentFactor(bool value) { _flags.set(OPACITY_VAL_BIT, value); }
bool isTranslucentFactor() const { return _flags[OPACITY_VAL_BIT]; }
void setTransparentMap(bool value) { _flags.set(TRANSPARENT_MAP_BIT, value); }
bool isTransparentMap() const { return _flags[TRANSPARENT_MAP_BIT]; }
void setTranslucentMap(bool value) { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT, value); }
bool isTranslucentMap() const { return _flags[OPACITY_TRANSLUCENT_MAP_BIT]; }
void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); }
bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; }
void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); }
bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; }
@ -140,6 +141,12 @@ public:
void setMapChannel(MapChannel channel, bool value) { _flags.set(EMISSIVE_MAP_BIT + channel, value); }
bool isMapChannel(MapChannel channel) const { return _flags[EMISSIVE_MAP_BIT + channel]; }
// Translucency and Opacity Heuristics are combining several flags:
bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); }
bool isOpaque() const { return !isTranslucent(); }
bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); }
bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); }
};
@ -168,9 +175,6 @@ public:
Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
Builder& withoutTransparentTexture() { _value.reset(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
Builder& withTransparentTexture() { _value.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
@ -186,11 +190,14 @@ public:
Builder& withoutRoughnessMap() { _value.reset(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); }
Builder& withRoughnessMap() { _value.set(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); }
Builder& withoutTransparent() { _value.reset(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); }
Builder& withTransparent() { _value.set(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); }
Builder& withoutTranslucentFactor() { _value.reset(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); }
Builder& withTranslucentFactor() { _value.set(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); }
Builder& withoutTransparentMap() { _value.reset(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); }
Builder& withTransparentMap() { _value.set(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); }
Builder& withoutTranslucentMap() { _value.reset(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withTranslucentMap() { _value.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withoutMaskMap() { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withMaskMap() { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
@ -202,7 +209,7 @@ public:
Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); }
// Convenient standard keys that we will keep on using all over the place
static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTransparent().build(); }
static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTranslucentFactor().build(); }
};
// Item Filter operator testing if a key pass the filter
@ -255,7 +262,6 @@ public:
void setRoughness(float roughness);
float getRoughness() const { return _schemaBuffer.get<Schema>()._roughness; }
void setTransparentTexture(bool isTransparent);
// Schema to access the attribute values of the material
class Schema {
@ -283,6 +289,7 @@ public:
// The texture map to channel association
void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap);
const TextureMaps& getTextureMaps() const { return _textureMaps; }
const TextureMapPointer getTextureMap(MapChannel channel) const;
// conversion from legacy material properties to PBR equivalent
static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }

View file

@ -39,21 +39,21 @@ float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); }
int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); }
const int EMISSIVE_VAL_BIT = 0x00000001;
const int ALBEDO_VAL_BIT = 0x00000002;
const int METALLIC_VAL_BIT = 0x00000004;
const int GLOSSY_VAL_BIT = 0x00000008;
const int TRANSPARENT_VAL_BIT = 0x00000010;
const int TRANSPARENT_TEX_VAL_BIT = 0x00000020;
const int EMISSIVE_VAL_BIT = 0x00000001;
const int ALBEDO_VAL_BIT = 0x00000002;
const int METALLIC_VAL_BIT = 0x00000004;
const int GLOSSY_VAL_BIT = 0x00000008;
const int OPACITY_VAL_BIT = 0x00000010;
const int OPACITY_MASK_MAP_BIT = 0x00000020;
const int OPACITY_TRANSLUCENT_MAP_BIT = 0x00000040;
const int EMISSIVE_MAP_BIT = 0x00000040;
const int ALBEDO_MAP_BIT = 0x00000080;
const int METALLIC_MAP_BIT = 0x00000100;
const int ROUGHNESS_MAP_BIT = 0x00000200;
const int TRANSPARENT_MAP_BIT = 0x00000400;
const int NORMAL_MAP_BIT = 0x00000800;
const int OCCLUSION_MAP_BIT = 0x00001000;
const int EMISSIVE_MAP_BIT = 0x00000080;
const int ALBEDO_MAP_BIT = 0x00000100;
const int METALLIC_MAP_BIT = 0x00000200;
const int ROUGHNESS_MAP_BIT = 0x00000400;
const int NORMAL_MAP_BIT = 0x00000800;
const int OCCLUSION_MAP_BIT = 0x00001000;
const int LIGHTMAP_MAP_BIT = 0x00002000;
const int LIGHTMAP_MAP_BIT = 0x00002000;
<@endif@>

View file

@ -56,6 +56,9 @@ public:
void setTextureTransform(const Transform& texcoordTransform);
const Transform& getTextureTransform() const { return _texcoordTransform; }
void setUseAlphaChannel(bool useAlpha) { _useAlphaChannel = useAlpha; }
bool useAlphaChannel() const { return _useAlphaChannel; }
void setLightmapOffsetScale(float offset, float scale);
const glm::vec2& getLightmapOffsetScale() const { return _lightmapOffsetScale; }
@ -64,6 +67,8 @@ protected:
Transform _texcoordTransform;
glm::vec2 _lightmapOffsetScale{ 0.0f, 1.0f };
bool _useAlphaChannel{ false };
};
typedef std::shared_ptr< TextureMap > TextureMapPointer;

View file

@ -216,17 +216,13 @@ Resource* ResourceCacheSharedItems::getHighestPendingRequest() {
bool ResourceCache::attemptRequest(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
// Disable request limiting for ATP
if (resource->getURL().scheme() != URL_SCHEME_ATP) {
if (_requestsActive >= _requestLimit) {
// wait until a slot becomes available
sharedItems->appendPendingRequest(resource);
return false;
}
++_requestsActive;
if (_requestsActive >= _requestLimit) {
// wait until a slot becomes available
sharedItems->appendPendingRequest(resource);
return false;
}
++_requestsActive;
sharedItems->appendActiveRequest(resource);
resource->makeRequest();
return true;
@ -235,9 +231,7 @@ bool ResourceCache::attemptRequest(Resource* resource) {
void ResourceCache::requestCompleted(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->removeRequest(resource);
if (resource->getURL().scheme() != URL_SCHEME_ATP) {
--_requestsActive;
}
--_requestsActive;
attemptHighestPriorityRequest();
}
@ -380,6 +374,7 @@ void Resource::finishedLoading(bool success) {
_failedToLoad = true;
}
_loadPriorities.clear();
emit finished(success);
}
void Resource::reinsert() {

View file

@ -201,6 +201,9 @@ signals:
/// This can be used instead of downloadFinished to access data before it is processed.
void loaded(const QByteArray& request);
/// Fired when the resource has finished loading.
void finished(bool success);
/// Fired when the resource failed to load.
void failed(QNetworkReply::NetworkError error);
@ -224,9 +227,6 @@ protected:
/// This should be called by subclasses that override downloadFinished to mark the end of processing.
Q_INVOKABLE void finishedLoading(bool success);
/// Reinserts this resource into the cache.
virtual void reinsert();
QUrl _url;
QUrl _activeUrl;
bool _startedLoading = false;
@ -246,6 +246,7 @@ private:
void makeRequest();
void retry();
void reinsert();
friend class ResourceCache;

View file

@ -78,11 +78,15 @@ void UserActivityLogger::requestError(QNetworkReply& errorReply) {
qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString();
}
void UserActivityLogger::launch(QString applicationVersion) {
void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) {
const QString ACTION_NAME = "launch";
QJsonObject actionDetails;
QString VERSION_KEY = "version";
QString CRASH_KEY = "previousSessionCrashed";
QString RUNTIME_KEY = "previousSessionRuntime";
actionDetails.insert(VERSION_KEY, applicationVersion);
actionDetails.insert(CRASH_KEY, previousSessionCrashed);
actionDetails.insert(RUNTIME_KEY, previousSessionRuntime);
logAction(ACTION_NAME, actionDetails);
}

View file

@ -29,7 +29,7 @@ public slots:
void disable(bool disable);
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
void launch(QString applicationVersion);
void launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime);
void changedDisplayName(QString displayName);
void changedModel(QString typeOfModel, QString modelURL);

View file

@ -201,7 +201,7 @@ void DefaultCC::onTimeout() {
void DefaultCC::stopSlowStart() {
_slowStart = false;
if (_receiveRate > 0) {
// Set the sending rate to the receiving rate.
setPacketSendPeriod(USECS_PER_SECOND / _receiveRate);

View file

@ -103,6 +103,7 @@ SendQueue& Connection::getSendQueue() {
QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission);
QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive);
QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout);
QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss);
// set defaults on the send queue from our congestion control object and estimatedTimeout()
_sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod);
@ -140,6 +141,12 @@ void Connection::queueTimeout() {
});
}
void Connection::queueShortCircuitLoss(quint32 sequenceNumber) {
updateCongestionControlAndSendQueue([this, sequenceNumber]{
_congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber });
});
}
void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) {
Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably.");
getSendQueue().queuePacket(std::move(packet));

View file

@ -87,6 +87,7 @@ private slots:
void recordRetransmission();
void queueInactive();
void queueTimeout();
void queueShortCircuitLoss(quint32 sequenceNumber);
private:
void sendACK(bool wasCausedBySyncTimeout = true);

View file

@ -128,13 +128,13 @@ void SendQueue::stop() {
_emptyCondition.notify_one();
}
void SendQueue::sendPacket(const Packet& packet) {
_socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
int SendQueue::sendPacket(const Packet& packet) {
return _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
}
void SendQueue::ack(SequenceNumber ack) {
// this is a response from the client, re-set our timeout expiry and our last response time
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
_lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
if (_lastACKSequenceNumber == (uint32_t) ack) {
return;
@ -164,7 +164,7 @@ void SendQueue::ack(SequenceNumber ack) {
void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
// this is a response from the client, re-set our timeout expiry
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
_lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
{
std::lock_guard<std::mutex> nakLocker(_naksLock);
@ -177,8 +177,8 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) {
// this is a response from the client, re-set our timeout expiry
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch());
_lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
{
std::lock_guard<std::mutex> nakLocker(_naksLock);
_naks.clear();
@ -232,15 +232,16 @@ SequenceNumber SendQueue::getNextSequenceNumber() {
return _currentSequenceNumber;
}
void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
// write the sequence number and send the packet
newPacket->writeSequenceNumber(sequenceNumber);
sendPacket(*newPacket);
// Save packet/payload size before we move it
auto packetSize = newPacket->getDataSize();
auto payloadSize = newPacket->getPayloadSize();
auto bytesWritten = sendPacket(*newPacket);
{
// Insert the packet we have just sent in the sent list
QWriteLocker locker(&_sentLock);
@ -249,8 +250,24 @@ void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket,
entry.second.swap(newPacket);
}
Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list");
emit packetSent(packetSize, payloadSize);
if (bytesWritten < 0) {
// this is a short-circuit loss - we failed to put this packet on the wire
// so immediately add it to the loss list
{
std::lock_guard<std::mutex> nakLocker(_naksLock);
_naks.append(sequenceNumber);
}
emit shortCircuitLoss(quint32(sequenceNumber));
return false;
} else {
return true;
}
}
void SendQueue::run() {
@ -285,12 +302,14 @@ void SendQueue::run() {
auto nextPacketTimestamp = p_high_resolution_clock::now();
while (_state == State::Running) {
bool sentAPacket = maybeResendPacket();
bool attemptedToSendPacket = maybeResendPacket();
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
// (this is according to the current flow window size) then we send out a new packet
if (!sentAPacket) {
sentAPacket = maybeSendNewPacket();
auto newPacketCount = 0;
if (!attemptedToSendPacket) {
newPacketCount = maybeSendNewPacket();
attemptedToSendPacket = (newPacketCount > 0);
}
// since we're a while loop, give the thread a chance to process events
@ -300,12 +319,13 @@ void SendQueue::run() {
// If the send queue has been innactive, skip the sleep for
// Either _isRunning will have been set to false and we'll break
// Or something happened and we'll keep going
if (_state != State::Running || isInactive(sentAPacket)) {
if (_state != State::Running || isInactive(attemptedToSendPacket)) {
return;
}
// push the next packet timestamp forwards by the current packet send period
nextPacketTimestamp += std::chrono::microseconds(_packetSendPeriod);
auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod;
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
// sleep as long as we need until next packet send, if we can
const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now());
@ -314,7 +334,7 @@ void SendQueue::run() {
}
}
bool SendQueue::maybeSendNewPacket() {
int SendQueue::maybeSendNewPacket() {
if (!isFlowWindowFull()) {
// we didn't re-send a packet, so time to send a new one
@ -324,38 +344,43 @@ bool SendQueue::maybeSendNewPacket() {
// grab the first packet we will send
std::unique_ptr<Packet> firstPacket = _packets.takePacket();
Q_ASSERT(firstPacket);
std::unique_ptr<Packet> secondPacket;
bool shouldSendPairTail = false;
if (((uint32_t) nextNumber & 0xF) == 0) {
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
// pull off a second packet if we can before we unlock
shouldSendPairTail = true;
secondPacket = _packets.takePacket();
// attempt to send the first packet
if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) {
std::unique_ptr<Packet> secondPacket;
bool shouldSendPairTail = false;
if (((uint32_t) nextNumber & 0xF) == 0) {
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
// pull off a second packet if we can before we unlock
shouldSendPairTail = true;
secondPacket = _packets.takePacket();
}
// do we have a second in a pair to send as well?
if (secondPacket) {
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
} else if (shouldSendPairTail) {
// we didn't get a second packet to send in the probe pair
// send a control packet of type ProbePairTail so the receiver can still do
// proper bandwidth estimation
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
_socket->writeBasePacket(*pairTailPacket, _destination);
}
// we attempted to send two packets, return 2
return 2;
} else {
// we attempted to send a single packet, return 1
return 1;
}
// definitely send the first packet
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
// do we have a second in a pair to send as well?
if (secondPacket) {
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
} else if (shouldSendPairTail) {
// we didn't get a second packet to send in the probe pair
// send a control packet of type ProbePairTail so the receiver can still do
// proper bandwidth estimation
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
_socket->writeBasePacket(*pairTailPacket, _destination);
}
// We sent our packet(s), return here
return true;
}
}
// No packets were sent
return false;
return 0;
}
bool SendQueue::maybeResendPacket() {
@ -375,8 +400,9 @@ bool SendQueue::maybeResendPacket() {
// see if we can find the packet to re-send
auto it = _sentPackets.find(resendNumber);
if (it != _sentPackets.end()) {
auto& entry = it->second;
// we found the packet - grab it
auto& resendPacket = *(entry.second);
@ -437,7 +463,7 @@ bool SendQueue::maybeResendPacket() {
return false;
}
bool SendQueue::isInactive(bool sentAPacket) {
bool SendQueue::isInactive(bool attemptedToSendPacket) {
// check for connection timeout first
// that will be the case if we have had 16 timeouts since hearing back from the client, and it has been
@ -447,7 +473,8 @@ bool SendQueue::isInactive(bool sentAPacket) {
auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse);
if (sinceLastResponse >= quint64(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) &&
if (sinceLastResponse > 0 &&
sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) &&
_lastReceiverResponse > 0 &&
sinceLastResponse > MIN_MS_BEFORE_INACTIVE) {
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
@ -462,7 +489,7 @@ bool SendQueue::isInactive(bool sentAPacket) {
return true;
}
if (!sentAPacket) {
if (!attemptedToSendPacket) {
// During our processing above we didn't send any packets
// If that is still the case we should use a condition_variable_any to sleep until we have data to handle.

View file

@ -79,6 +79,7 @@ signals:
void queueInactive();
void shortCircuitLoss(quint32 sequenceNumber);
void timeout();
private slots:
@ -91,13 +92,13 @@ private:
void sendHandshake();
void sendPacket(const Packet& packet);
void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
int sendPacket(const Packet& packet);
bool sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
bool maybeSendNewPacket(); // Figures out what packet to send next
int maybeSendNewPacket(); // Figures out what packet to send next
bool maybeResendPacket(); // Determines whether to resend a packet and which one
bool isInactive(bool sentAPacket);
bool isInactive(bool attemptedToSendPacket);
void deactivate(); // makes the queue inactive and cleans it up
bool isFlowWindowFull() const;
@ -122,7 +123,7 @@ private:
std::atomic<int> _estimatedTimeout { 0 }; // Estimated timeout, set from CC
std::atomic<int> _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC
std::atomic<uint64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK)
std::atomic<int64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK)
std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC

View file

@ -120,3 +120,21 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
}
return shape;
}
void ShapeFactory::deleteShape(btCollisionShape* shape) {
assert(shape);
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
btCollisionShape* childShape = compoundShape->getChildShape(i);
if (childShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
// recurse
ShapeFactory::deleteShape(childShape);
} else {
delete childShape;
}
}
}
delete shape;
}

View file

@ -22,6 +22,7 @@
namespace ShapeFactory {
btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape);
};
#endif // hifi_ShapeFactory_h

View file

@ -23,7 +23,7 @@ ShapeManager::~ShapeManager() {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
delete shapeRef->shape;
ShapeFactory::deleteShape(shapeRef->shape);
}
_shapeMap.clear();
}
@ -32,13 +32,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) {
return NULL;
}
// Very small or large objects are not supported.
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
//const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED /* || diagonal > MAX_SHAPE_DIAGONAL_SQUARED*/ ) {
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
if (info.getType() != SHAPE_TYPE_COMPOUND) {
// Very small or large non-compound objects are not supported.
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) {
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
}
DoubleHashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key);
@ -58,14 +59,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
}
// private helper method
bool ShapeManager::releaseShape(const DoubleHashKey& key) {
bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) {
if (shapeRef->refCount > 0) {
shapeRef->refCount--;
if (shapeRef->refCount == 0) {
_pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 127;
const int MAX_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
collectGarbage();
}
@ -82,16 +83,12 @@ bool ShapeManager::releaseShape(const DoubleHashKey& key) {
return false;
}
bool ShapeManager::releaseShape(const ShapeInfo& info) {
return releaseShape(info.getHash());
}
bool ShapeManager::releaseShape(const btCollisionShape* shape) {
int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
if (shape == shapeRef->shape) {
return releaseShape(shapeRef->key);
return releaseShapeByKey(shapeRef->key);
}
}
return false;
@ -103,17 +100,7 @@ void ShapeManager::collectGarbage() {
DoubleHashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->refCount == 0) {
// if the shape we're about to delete is compound, delete the children first.
if (shapeRef->shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE) {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shapeRef->shape);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
delete childShape;
}
}
delete shapeRef->shape;
ShapeFactory::deleteShape(shapeRef->shape);
_shapeMap.remove(key);
}
}

View file

@ -29,7 +29,6 @@ public:
btCollisionShape* getShape(const ShapeInfo& info);
/// \return true if shape was found and released
bool releaseShape(const ShapeInfo& info);
bool releaseShape(const btCollisionShape* shape);
/// delete shapes that have zero references
@ -39,10 +38,10 @@ public:
int getNumShapes() const { return _shapeMap.size(); }
int getNumReferences(const ShapeInfo& info) const;
int getNumReferences(const btCollisionShape* shape) const;
bool hasShape(const btCollisionShape* shape) const;
bool hasShape(const btCollisionShape* shape) const;
private:
bool releaseShape(const DoubleHashKey& key);
bool releaseShapeByKey(const DoubleHashKey& key);
struct ShapeReference {
int refCount;

View file

@ -59,7 +59,7 @@ float fetchOcclusionMap(vec2 uv) {
<@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@>
<@if albedo@>
vec4 <$albedo$> = (((<$matKey$> & ALBEDO_MAP_BIT) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0));
vec4 <$albedo$> = (((<$matKey$> & (ALBEDO_MAP_BIT | OPACITY_MASK_MAP_BIT | OPACITY_TRANSLUCENT_MAP_BIT)) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0));
<@endif@>
<@if roughness@>
float <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? fetchRoughnessMap(<$texcoord0$>) : 1.0);
@ -112,6 +112,23 @@ vec3 fetchLightmapMap(vec2 uv) {
}
<@endfunc@>
<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@>
{
const float OPACITY_MASK_THRESHOLD = 0.5;
<$opacity$> = (((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0) ?
(((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0) ? step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>) : <$fetchedOpacity$>) :
1.0) * <$materialOpacity$>;
}
<@endfunc@>
<@func $discardTransparent(opacity)@>
{
if (<$opacity$> < 1.0) {
discard;
}
}
<@endfunc@>
<@func evalMaterialRoughness(fetchedRoughness, materialRoughness, matKey, roughness)@>
{
<$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? <$fetchedRoughness$> : <$materialRoughness$>);

View file

@ -81,7 +81,7 @@ ItemKey MeshPartPayload::getKey() const {
if (_drawMaterial) {
auto matKey = _drawMaterial->getKey();
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) {
if (matKey.isTranslucent()) {
builder.withTransparent();
}
}
@ -100,7 +100,7 @@ ShapeKey MeshPartPayload::getShapeKey() const {
}
ShapeKey::Builder builder;
if (drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap()) {
if (drawMaterialKey.isTranslucent()) {
builder.withTranslucent();
}
if (drawMaterialKey.isNormalMap()) {
@ -365,7 +365,7 @@ ItemKey ModelMeshPartPayload::getKey() const {
if (_drawMaterial) {
auto matKey = _drawMaterial->getKey();
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) {
if (matKey.isTranslucent()) {
builder.withTransparent();
}
}
@ -412,8 +412,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
drawMaterialKey = _drawMaterial->getKey();
}
bool isTranslucent =
drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap();
bool isTranslucent = drawMaterialKey.isTranslucent();
bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty();
bool hasSpecular = drawMaterialKey.isMetallicMap();
bool hasLightmap = drawMaterialKey.isLightmapMap();

View file

@ -29,6 +29,10 @@ void main(void) {
int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
@ -41,7 +45,7 @@ void main(void) {
packDeferredFragment(
normalize(_normal.xyz),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a),
opacity,
albedo,
roughness,
getMaterialMetallic(mat),

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
@ -45,7 +49,7 @@ void main(void) {
packDeferredFragment(
viewNormal,
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a),
opacity,
albedo,
roughness,
getMaterialMetallic(mat),

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
@ -49,7 +53,7 @@ void main(void) {
packDeferredFragment(
normalize(viewNormal.xyz),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a),
opacity,
albedo,
roughness,
metallic,

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
@ -45,7 +49,7 @@ void main(void) {
packDeferredFragment(
normalize(_normal),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a),
opacity,
albedo,
roughness,
metallic,

View file

@ -36,6 +36,10 @@ void main(void) {
int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = getMaterialOpacity(mat) * _alpha;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color;
@ -52,8 +56,6 @@ void main(void) {
vec3 fragPosition = _position.xyz;
vec3 fragNormal = normalize(_normal);
float fragOpacity = getMaterialOpacity(mat) * albedoTex.a * _alpha;
TransformCamera cam = getTransformCamera();
_fragColor = vec4(evalAmbientSphereGlobalColor(
@ -66,5 +68,5 @@ void main(void) {
metallic,
emissive,
roughness),
fragOpacity);
opacity);
}

View file

@ -20,6 +20,10 @@ public:
template <typename F>
Finally(F f) : _f(f) {}
~Finally() { _f(); }
void trigger() {
_f();
_f = [] {};
}
private:
std::function<void()> _f;
};

View file

@ -63,7 +63,7 @@ public:
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
float computeVolume() const;
/// Returns whether point is inside the shape
/// For compound shapes it will only return whether it is inside the bounding box
bool contains(const glm::vec3& point) const;

View file

@ -14,6 +14,8 @@
#include <BulletUtil.h>
#include <functional>
// Implements functionality in QTestExtensions.h for glm types
// There are 3 functions in here (which need to be defined for all types that use them):
//

View file

@ -33,7 +33,7 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() {
// verity we can compute the inertia tensor of a box in two different ways:
// (a) as one box
// (b) as a combination of two partial boxes.
btScalar bigBoxX = 7.0f;
btScalar bigBoxY = 9.0f;
btScalar bigBoxZ = 11.0f;
@ -62,9 +62,9 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() {
}
void MeshMassPropertiesTests::testTetrahedron(){
// given the four vertices of a tetrahedron verify the analytic formula for inertia
// given the four vertices of a tetrahedron verify the analytic formula for inertia
// agrees with expected results
// these numbers from the Tonon paper:
btVector3 points[4];
points[0] = btVector3(8.33220f, -11.86875f, 0.93355f);
@ -102,14 +102,14 @@ void MeshMassPropertiesTests::testTetrahedron(){
}
btMatrix3x3 inertia;
computeTetrahedronInertia(volume, points, inertia);
QCOMPARE_WITH_ABS_ERROR(volume, expectedVolume, acceptableRelativeError * volume);
QCOMPARE_WITH_RELATIVE_ERROR(inertia, expectedInertia, acceptableRelativeError);
}
void MeshMassPropertiesTests::testOpenTetrahedonMesh() {
// given the simplest possible mesh (open, with one triangle)
// given the simplest possible mesh (open, with one triangle)
// verify MeshMassProperties computes the right nubers
// these numbers from the Tonon paper:
@ -155,7 +155,7 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() {
void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
// given a tetrahedron as a closed mesh of four tiangles
// verify MeshMassProperties computes the right nubers
// these numbers from the Tonon paper:
VectorOfPoints points;
points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f));
@ -186,7 +186,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
// compute mass properties
MeshMassProperties mesh(points, triangles);
// verify
QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume);
QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError);
@ -210,7 +210,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
void MeshMassPropertiesTests::testBoxAsMesh() {
// verify that a mesh box produces the same mass properties as the analytic box.
// build a box:
// /
// y
@ -265,7 +265,7 @@ void MeshMassPropertiesTests::testBoxAsMesh() {
MeshMassProperties mesh(points, triangles);
// verify
QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume);
QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError);

View file

@ -21,7 +21,7 @@ void ShapeManagerTests::testShapeAccounting() {
ShapeManager shapeManager;
ShapeInfo info;
info.setBox(glm::vec3(1.0f, 1.0f, 1.0f));
int numReferences = shapeManager.getNumReferences(info);
QCOMPARE(numReferences, 0);
@ -42,10 +42,10 @@ void ShapeManagerTests::testShapeAccounting() {
QCOMPARE(numReferences, expectedNumReferences);
// release all references
bool released = shapeManager.releaseShape(info);
bool released = shapeManager.releaseShape(shape);
numReferences--;
while (numReferences > 0) {
released = shapeManager.releaseShape(info) && released;
released = shapeManager.releaseShape(shape) && released;
numReferences--;
}
QCOMPARE(released, true);
@ -69,7 +69,7 @@ void ShapeManagerTests::testShapeAccounting() {
QCOMPARE(numReferences, 1);
// release reference and verify that it is collected as garbage
released = shapeManager.releaseShape(info);
released = shapeManager.releaseShape(shape);
shapeManager.collectGarbage();
QCOMPARE(shapeManager.getNumShapes(), 0);
QCOMPARE(shapeManager.hasShape(shape), false);
@ -183,3 +183,58 @@ void ShapeManagerTests::addCapsuleShape() {
QCOMPARE(shape, otherShape);
*/
}
void ShapeManagerTests::addCompoundShape() {
// initialize some points for generating tetrahedral convex hulls
QVector<glm::vec3> tetrahedron;
tetrahedron.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
tetrahedron.push_back(glm::vec3(1.0f, -1.0f, -1.0f));
tetrahedron.push_back(glm::vec3(-1.0f, 1.0f, -1.0f));
tetrahedron.push_back(glm::vec3(-1.0f, -1.0f, 1.0f));
int numHullPoints = tetrahedron.size();
// compute the points of the hulls
QVector< QVector<glm::vec3> > hulls;
int numHulls = 5;
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
for (int i = 0; i < numHulls; ++i) {
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
QVector<glm::vec3> hull;
float radius = (float)(i + 1);
for (int j = 0; j < numHullPoints; ++j) {
glm::vec3 point = radius * tetrahedron[j] + offset;
hull.push_back(point);
}
hulls.push_back(hull);
}
// create the ShapeInfo
ShapeInfo info;
info.setConvexHulls(hulls);
// create the shape
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
QVERIFY(shape != nullptr);
// verify the shape is correct type
QCOMPARE(shape->getShapeType(), (int)COMPOUND_SHAPE_PROXYTYPE);
// verify the shape has correct number of children
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
QCOMPARE(compoundShape->getNumChildShapes(), numHulls);
// verify manager has only one shape
QCOMPARE(shapeManager.getNumShapes(), 1);
QCOMPARE(shapeManager.getNumReferences(info), 1);
// release the shape
shapeManager.releaseShape(shape);
QCOMPARE(shapeManager.getNumShapes(), 1);
QCOMPARE(shapeManager.getNumReferences(info), 0);
// collect garbage
shapeManager.collectGarbage();
QCOMPARE(shapeManager.getNumShapes(), 0);
QCOMPARE(shapeManager.getNumReferences(info), 0);
}

View file

@ -16,7 +16,7 @@
class ShapeManagerTests : public QObject {
Q_OBJECT
private slots:
void testShapeAccounting();
void addManyShapes();
@ -24,6 +24,7 @@ private slots:
void addSphereShape();
void addCylinderShape();
void addCapsuleShape();
void addCompoundShape();
};
#endif // hifi_ShapeManagerTests_h

View file

@ -77,7 +77,7 @@ UDTTest::UDTTest(int& argc, char** argv) :
// randomize the seed for packet size randomization
srand(time(NULL));
_socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt());
qDebug() << "Test socket is listening on" << _socket.localPort();