Merge branch 'master' of https://github.com/worklist/hifi into 20638

This commit is contained in:
Thijs Wenker 2015-08-21 19:29:38 +02:00
commit fd82f05307
83 changed files with 1341 additions and 975 deletions

View file

@ -213,3 +213,12 @@ endif ()
if (ANDROID OR DESKTOP_GVR)
add_subdirectory(gvr-interface)
endif ()
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
SET( HIFI_MEMORY_DEBUGGING true )
endif ()
if (HIFI_MEMORY_DEBUGGING)
if (UNIX)
MESSAGE("-- Memory debugging is enabled")
endif (UNIX)
endif ()

View file

@ -18,4 +18,7 @@ if (UNIX)
endif (UNIX)
include_application_version()
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -0,0 +1,21 @@
#
# MemoryDebugger.cmake
#
# 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
#
macro(SETUP_MEMORY_DEBUGGER)
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
SET( HIFI_MEMORY_DEBUGGING true )
endif ()
if (HIFI_MEMORY_DEBUGGING)
if (UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address")
endif (UNIX)
endif ()
endmacro(SETUP_MEMORY_DEBUGGER)

View file

@ -1,5 +1,7 @@
set(TARGET_NAME domain-server)
setup_memory_debugger()
if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32)
set(_SHOULD_SYMLINK_RESOURCES TRUE)
else ()

View file

@ -31,7 +31,7 @@
<td><%- node.username %></td>
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
<td><%- node.uptime %></td>
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
</tr>

View file

@ -9,9 +9,9 @@ $(document).ready(function(){
json.nodes.sort(function(a, b){
if (a.type === b.type) {
if (a.wake_timestamp < b.wake_timestamp) {
if (a.uptime < b.uptime) {
return 1;
} else if (a.wake_timestamp > b.wake_timestamp) {
} else if (a.uptime > b.uptime) {
return -1;
} else {
return 0;

View file

@ -1480,7 +1480,7 @@ const char JSON_KEY_PUBLIC_SOCKET[] = "public";
const char JSON_KEY_LOCAL_SOCKET[] = "local";
const char JSON_KEY_POOL[] = "pool";
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
const char JSON_KEY_UPTIME[] = "uptime";
const char JSON_KEY_USERNAME[] = "username";
const char JSON_KEY_VERSION[] = "version";
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
@ -1502,7 +1502,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket());
// add the node uptime in our list
nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp());
nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0);
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());

View file

@ -0,0 +1,82 @@
//
// basketball.js
// examples
//
// Created by Philip Rosedale on August 20, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx";
var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav";
var basketball = null;
var originalPosition = null;
var hasMoved = false;
var GRAVITY = -9.8;
var DISTANCE_IN_FRONT_OF_ME = 1.0;
var START_MOVE = 0.01;
var DIAMETER = 0.30;
function makeBasketball() {
var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation,
{ x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME }));
var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0));
basketball = Entities.addEntity({
type: "Model",
position: position,
rotation: rotation,
dimensions: { x: DIAMETER,
y: DIAMETER,
z: DIAMETER },
collisionsWillMove: true,
collisionSoundURL: collisionSoundURL,
modelURL: basketballURL,
restitution: 1.0,
linearDamping: 0.00001,
shapeType: "sphere"
});
originalPosition = position;
}
function update() {
if (!basketball) {
makeBasketball();
} else {
var newProperties = Entities.getEntityProperties(basketball);
var moved = Vec3.length(Vec3.subtract(originalPosition, newProperties.position));
if (!hasMoved && (moved > START_MOVE)) {
hasMoved = true;
Entities.editEntity(basketball, { gravity: {x: 0, y: GRAVITY, z: 0 }});
}
var MAX_DISTANCE = 10.0;
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, newProperties.position));
if (distance > MAX_DISTANCE) {
deleteStuff();
}
}
}
function scriptEnding() {
deleteStuff();
}
function deleteStuff() {
if (basketball != null) {
Entities.deleteEntity(basketball);
basketball = null;
hasMoved = false;
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

201
examples/toys/grenade.js Normal file
View file

@ -0,0 +1,201 @@
//
// Grenade.js
// examples
//
// Created by Philip Rosedale on August 20, 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var grenadeURL = HIFI_PUBLIC_BUCKET + "models/props/grenade/grenade.fbx";
var fuseSoundURL = HIFI_PUBLIC_BUCKET + "sounds/burningFuse.wav";
var boomSoundURL = HIFI_PUBLIC_BUCKET + "sounds/explosion.wav";
var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0);
var audioOptions = {
volume: 0.5,
loop: true
}
var injector = null;
var fuseSound = SoundCache.getSound(fuseSoundURL, audioOptions.isStereo);
var boomSound = SoundCache.getSound(boomSoundURL, audioOptions.isStereo);
var grenade = null;
var particles = null;
var properties = null;
var originalPosition = null;
var isGrenade = false;
var isBurning = false;
var animationSettings = JSON.stringify({
running: true,
loop: true
});
var explodeAnimationSettings = JSON.stringify({
running: true,
loop: false
});
var GRAVITY = -9.8;
var TIME_TO_EXPLODE = 2500;
var DISTANCE_IN_FRONT_OF_ME = 1.0;
function makeGrenade() {
var position = Vec3.sum(MyAvatar.position,
Vec3.multiplyQbyV(MyAvatar.orientation,
{ x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME }));
var rotation = Quat.multiply(MyAvatar.orientation,
Quat.fromPitchYawRollDegrees(0, -90, 0));
grenade = Entities.addEntity({
type: "Model",
position: position,
rotation: rotation,
dimensions: { x: 0.09,
y: 0.20,
z: 0.09 },
collisionsWillMove: true,
modelURL: grenadeURL,
shapeType: "box"
});
properties = Entities.getEntityProperties(grenade);
audioOptions.position = position;
audioOptions.orientation = rotation;
originalPosition = position;
}
function update() {
if (!grenade) {
makeGrenade();
} else {
var newProperties = Entities.getEntityProperties(grenade);
if (!isBurning) {
// If moved, start fuse
var FUSE_START_MOVE = 0.01;
if (Vec3.length(Vec3.subtract(newProperties.position, originalPosition)) > FUSE_START_MOVE) {
isBurning = true;
// Create fuse particles
particles = Entities.addEntity({
type: "ParticleEffect",
animationSettings: animationSettings,
position: newProperties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 100,
emitStrength: 2.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
color: { red: 200, green: 0, blue: 0 },
lifespan: 10.0,
visible: true,
locked: false
});
// Start fuse sound
injector = Audio.playSound(fuseSound, audioOptions);
// Start explosion timer
Script.setTimeout(boom, TIME_TO_EXPLODE);
originalPosition = newProperties.position;
// Add gravity
Entities.editEntity(grenade, { gravity: {x: 0, y: GRAVITY, z: 0 }});
}
}
if (newProperties.type === "Model") {
if (newProperties.position != properties.position) {
audioOptions.position = newProperties.position;
}
if (newProperties.orientation != properties.orientation) {
audioOptions.orientation = newProperties.orientation;
}
properties = newProperties;
// Update sound location if playing
if (injector) {
injector.options = audioOptions;
}
if (particles) {
Entities.editEntity(particles, { position: newProperties.position });
}
} else {
grenade = null;
Script.update.disconnect(update);
Script.scriptEnding.connect(scriptEnding);
scriptEnding();
Script.stop();
}
}
}
function boom() {
injector.stop();
isBurning = false;
var audioOptions = {
position: properties.position,
volume: 0.75,
loop: false
}
Audio.playSound(boomSound, audioOptions);
Entities.addEntity({
type: "ParticleEffect",
animationSettings: explodeAnimationSettings,
position: properties.position,
textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png',
emitRate: 200,
emitStrength: 3.0,
emitDirection: { x: 0.0, y: 1.0, z: 0.0 },
color: { red: 255, green: 255, blue: 0 },
lifespan: 2.0,
visible: true,
lifetime: 2,
locked: false
});
var BLAST_RADIUS = 20.0;
var LIFT_DEPTH = 2.0;
var epicenter = properties.position;
epicenter.y -= LIFT_DEPTH;
blowShitUp(epicenter, BLAST_RADIUS);
deleteStuff();
}
function blowShitUp(position, radius) {
var stuff = Entities.findEntities(position, radius);
var numMoveable = 0;
var STRENGTH = 3.5;
var SPIN_RATE = 20.0;
for (var i = 0; i < stuff.length; i++) {
var properties = Entities.getEntityProperties(stuff[i]);
if (properties.collisionsWillMove) {
var diff = Vec3.subtract(properties.position, position);
var distance = Vec3.length(diff);
var velocity = Vec3.sum(properties.velocity, Vec3.multiply(STRENGTH * 1.0 / distance, Vec3.normalize(diff)));
var angularVelocity = { x: Math.random() * SPIN_RATE, y: Math.random() * SPIN_RATE, z: Math.random() * SPIN_RATE };
angularVelocity = Vec3.multiply( 1.0 / distance, angularVelocity);
Entities.editEntity(stuff[i], { velocity: velocity,
angularVelocity: angularVelocity });
}
}
}
function scriptEnding() {
deleteStuff();
}
function deleteStuff() {
if (grenade != null) {
Entities.deleteEntity(grenade);
grenade = null;
}
if (particles != null) {
Entities.deleteEntity(particles);
particles = null;
}
}
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);

View file

@ -183,6 +183,24 @@ var CHECK_MARK_COLOR = {
this.onValueChanged(resetValue);
};
Slider.prototype.setMinValue = function(minValue) {
var currentValue = this.getValue();
this.minValue = minValue;
this.setValue(currentValue);
};
Slider.prototype.getMinValue = function() {
return this.minValue;
};
Slider.prototype.setMaxValue = function(maxValue) {
var currentValue = this.getValue();
this.maxValue = maxValue;
this.setValue(currentValue);
};
Slider.prototype.getMaxValue = function() {
return this.maxValue;
};
Slider.prototype.onValueChanged = function(value) {};
Slider.prototype.getHeight = function() {
@ -1396,6 +1414,14 @@ var CHECK_MARK_COLOR = {
return null;
};
Panel.prototype.getWidget = function(name) {
var item = this.items[name];
if (item != null) {
return item.widget;
}
return null;
};
Panel.prototype.update = function(name) {
var item = this.items[name];
if (item != null) {

View file

@ -12,59 +12,55 @@ Script.include("cookies.js");
var panel = new Panel(10, 100);
panel.newSlider("Num Feed Opaques", 0, 1000,
function(value) { },
function() { return Scene.getEngineNumFeedOpaqueItems(); },
function(value) { return (value); }
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
this.subPanel = panel.newSubPanel(name);
this.subPanel.newSlider("Num Feed", 0, 1,
function(value) { },
feedGetter,
function(value) { return (value); });
this.subPanel.newSlider("Num Drawn", 0, 1,
function(value) { },
drawGetter,
function(value) { return (value); });
this.subPanel.newSlider("Max Drawn", -1, 1,
capSetter,
capGetter,
function(value) { return (value); });
this.update = function () {
var numFeed = this.subPanel.get("Num Feed");
this.subPanel.set("Num Feed", numFeed);
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
var numMax = Math.max(numFeed, 1);
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques",
function () { return Scene.getEngineNumFeedOpaqueItems(); },
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
);
panel.newSlider("Num Drawn Opaques", 0, 1000,
function(value) { },
function() { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { return (value); }
var transparentsCounter = new CounterWidget(panel, "Transparents",
function () { return Scene.getEngineNumFeedTransparentItems(); },
function () { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
);
panel.newSlider("Max Drawn Opaques", -1, 1000,
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function() { return Scene.getEngineMaxDrawnOpaqueItems(); },
function(value) { return (value); }
var overlaysCounter = new CounterWidget(panel, "Overlays",
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
);
panel.newSlider("Num Feed Transparents", 0, 100,
function(value) { },
function() { return Scene.getEngineNumFeedTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Drawn Transparents", 0, 100,
function(value) { },
function() { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Max Drawn Transparents", -1, 100,
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function() { return Scene.getEngineMaxDrawnTransparentItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Feed Overlay3Ds", 0, 100,
function(value) { },
function() { return Scene.getEngineNumFeedOverlay3DItems(); },
function(value) { return (value); }
);
panel.newSlider("Num Drawn Overlay3Ds", 0, 100,
function(value) { },
function() { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { return (value); }
);
panel.newSlider("Max Drawn Overlay3Ds", -1, 100,
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function() { return Scene.getEngineMaxDrawnOverlay3DItems(); },
function(value) { return (value); }
);
panel.newCheckbox("Display status",
function(value) { Scene.setEngineDisplayItemStatus(value); },
@ -75,31 +71,9 @@ panel.newCheckbox("Display status",
var tickTackPeriod = 500;
function updateCounters() {
var numFeedOpaques = panel.get("Num Feed Opaques");
var numFeedTransparents = panel.get("Num Feed Transparents");
var numFeedOverlay3Ds = panel.get("Num Feed Overlay3Ds");
panel.set("Num Feed Opaques", numFeedOpaques);
panel.set("Num Drawn Opaques", panel.get("Num Drawn Opaques"));
panel.set("Num Feed Transparents", numFeedTransparents);
panel.set("Num Drawn Transparents", panel.get("Num Drawn Transparents"));
panel.set("Num Feed Overlay3Ds", numFeedOverlay3Ds);
panel.set("Num Drawn Overlay3Ds", panel.get("Num Drawn Overlay3Ds"));
var numMax = Math.max(numFeedOpaques * 1.2, 1);
panel.getWidget("Num Feed Opaques").setMaxValue(numMax);
panel.getWidget("Num Drawn Opaques").setMaxValue(numMax);
panel.getWidget("Max Drawn Opaques").setMaxValue(numMax);
numMax = Math.max(numFeedTransparents * 1.2, 1);
panel.getWidget("Num Feed Transparents").setMaxValue(numMax);
panel.getWidget("Num Drawn Transparents").setMaxValue(numMax);
panel.getWidget("Max Drawn Transparents").setMaxValue(numMax);
numMax = Math.max(numFeedOverlay3Ds * 1.2, 1);
panel.getWidget("Num Feed Overlay3Ds").setMaxValue(numMax);
panel.getWidget("Num Drawn Overlay3Ds").setMaxValue(numMax);
panel.getWidget("Max Drawn Overlay3Ds").setMaxValue(numMax);
opaquesCounter.update();
transparentsCounter.update();
overlaysCounter.update();
}
Script.setInterval(updateCounters, tickTackPeriod);

View file

@ -88,4 +88,6 @@ if (ANDROID)
endif (ANDROID)
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -1,9 +1,11 @@
set(TARGET_NAME ice-server)
setup_memory_debugger()
# setup the project and link required Qt modules
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(embedded-webserver networking shared)
copy_dlls_beside_windows_executable()
copy_dlls_beside_windows_executable()

View file

@ -205,4 +205,6 @@ else (APPLE)
endif()
endif (APPLE)
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -737,6 +737,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
});
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
setVSyncEnabled(); // make sure VSync is set properly at startup
}
void Application::aboutToQuit() {
@ -1744,6 +1746,27 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
return;
}
#ifndef Q_OS_MAC
// If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow
// then show the menubar.
if (_window->isFullScreen()) {
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
static const int MENU_TOGGLE_AREA = 10;
if (!menuBar->isVisible()) {
if (event->pos().y() <= MENU_TOGGLE_AREA) {
menuBar->setVisible(true);
}
} else {
if (event->pos().y() > MENU_TOGGLE_AREA) {
menuBar->setVisible(false);
}
}
}
}
#endif
_entities.mouseMoveEvent(event, deviceID);
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
@ -4985,6 +5008,14 @@ void Application::setFullscreen(const QScreen* target) {
#endif
_window->windowHandle()->setScreen((QScreen*)target);
_window->showFullScreen();
#ifndef Q_OS_MAC
// also hide the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(false);
}
#endif
}
void Application::unsetFullscreen(const QScreen* avoid) {
@ -5015,6 +5046,14 @@ void Application::unsetFullscreen(const QScreen* avoid) {
#else
_window->setGeometry(targetGeometry);
#endif
#ifndef Q_OS_MAC
// also show the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(true);
}
#endif
}

View file

@ -444,6 +444,7 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu,
MenuOption::Connexion,
0, false,

View file

@ -168,6 +168,7 @@ namespace MenuOption {
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable Entity NACK Packets";
const QString DiskCacheEditor = "Disk Cache Editor";

View file

@ -106,16 +106,17 @@ bool ModelPackager::loadModel() {
}
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
_geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath());
_geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()));
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry);
return true;
}
bool ModelPackager::editProperties() {
// open the dialog to configure the rest
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry);
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry);
if (properties.exec() == QDialog::Rejected) {
return false;
}
@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
void ModelPackager::listTextures() {
_textures.clear();
foreach (FBXMesh mesh, _geometry.meshes) {
foreach (FBXMesh mesh, _geometry->meshes) {
foreach (FBXMeshPart part, mesh.parts) {
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
!_textures.contains(part.diffuseTexture.filename)) {

View file

@ -39,11 +39,11 @@ private:
QString _texDir;
QVariantHash _mapping;
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
QStringList _textures;
};
#endif // hifi_ModelPackager_h
#endif // hifi_ModelPackager_h

View file

@ -196,7 +196,6 @@ void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("hand");
getHand()->simulate(deltaTime, false);
}
_skeletonModel.setLODDistance(getLODDistance());
if (!_shouldRenderBillboard && inViewFrustum) {
{
@ -562,24 +561,22 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
}
void Avatar::fixupModelsInScene() {
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
return;
}
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
render::ScenePointer scene = Application::getInstance()->getMain3DScene();
render::PendingChanges pendingChanges;
if (_skeletonModel.needsFixupInScene()) {
if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) {
_skeletonModel.removeFromScene(scene, pendingChanges);
_skeletonModel.addToScene(scene, pendingChanges);
}
if (getHead()->getFaceModel().needsFixupInScene()) {
getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
getHead()->getFaceModel().addToScene(scene, pendingChanges);
Model& faceModel = getHead()->getFaceModel();
if (faceModel.isRenderable() && faceModel.needsFixupInScene()) {
faceModel.removeFromScene(scene, pendingChanges);
faceModel.addToScene(scene, pendingChanges);
}
for (auto attachmentModel : _attachmentModels) {
if (attachmentModel->needsFixupInScene()) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
attachmentModel->removeFromScene(scene, pendingChanges);
attachmentModel->addToScene(scene, pendingChanges);
}
@ -621,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) {
int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition;
glm::quat jointRotation;
if (!isMyAvatar()) {
model->setLODDistance(getLODDistance());
}
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
_skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
model->setRotation(jointRotation * attachment.rotation);
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
@ -978,12 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
AvatarData::setFaceModelURL(faceModelURL);
getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
getHead()->getFaceModel().setURL(_faceModelURL);
}
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar());
_skeletonModel.setURL(_skeletonModelURL);
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {

View file

@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_saccade = glm::vec3();
}
if (!isMine) {
_faceModel.setLODDistance(static_cast<Avatar*>(_owningAvatar)->getLODDistance());
}
_leftEyePosition = _rightEyePosition = getPosition();
if (!billboard) {
_faceModel.simulate(deltaTime);
@ -277,6 +274,10 @@ void Head::calculateMouthShapes() {
void Head::applyEyelidOffset(glm::quat headOrientation) {
// Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches.
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableEyelidAdjustment)) {
return;
}
glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getLookAtPosition() - _eyePosition);
eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head
float eyePitch = safeEulerAngles(eyeRotation).x;

View file

@ -843,10 +843,11 @@ void MyAvatar::sendKillAvatar() {
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer);
}
static int counter = 0;
void MyAvatar::updateLookAtTargetAvatar() {
//
// Look at the avatar whose eyes are closest to the ray in direction of my avatar's head
//
// And set the correctedLookAt for all (nearby) avatars that are looking at me.
_lookAtTargetAvatar.reset();
_targetAvatarPosition = glm::vec3(0.0f);
@ -870,14 +871,51 @@ void MyAvatar::updateLookAtTargetAvatar() {
smallestAngleTo = angleTo;
}
if (Application::getInstance()->isLookingAtMyAvatar(avatar)) {
// Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face.
// Offset their gaze according to whether they're looking at one of my eyes or my mouth.
glm::vec3 gazeOffset = avatar->getHead()->getLookAtPosition() - getHead()->getEyePosition();
const float HUMAN_EYE_SEPARATION = 0.065f;
float myEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition());
gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation;
avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition()
+ gazeOffset);
glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar.
// The camera isn't at the point midway between the avatar eyes. (Even without an HMD, the head can be offset a bit.)
// Let's get everything to world space:
glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition();
glm::vec3 avatarRightEye = getHead()->getRightEyePosition();
// When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok.
// By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.)
// This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space.
glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left);
glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right);
glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]);
glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]);
auto humanSystem = Application::getInstance()->getViewFrustum();
glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal);
glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal);
// First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point.
// (We will be adding that offset to the camera position, after making some other adjustments.)
glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition();
// Scale by proportional differences between avatar and human.
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye);
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
// If the camera is also not oriented with the head, adjust by getting the offset in head-space...
/* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday.
glm::quat avatarHeadOrientation = getHead()->getOrientation();
glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset;
// ... and treat that as though it were in camera space, bringing it back to world space.
// But camera is fudged to make the picture feel like the avatar's orientation.
glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ?
gazeOffset = humanOrientation * gazeOffsetLocalToHead;
glm::vec3 corrected = humanSystem->getPosition() + gazeOffset;
*/
// And now we can finally add that offset to the camera.
glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset;
avatar->getHead()->setCorrectedLookAtPosition(corrected);
} else {
avatar->getHead()->clearCorrectedLookAtPosition();
}
@ -1114,6 +1152,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
getHead()->render(renderArgs, 1.0f, renderFrustum);
}
// This is drawing the lookat vectors from our avatar to wherever we're looking.
if (qApp->isHMDMode()) {
glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition();

View file

@ -3,4 +3,6 @@ set(TARGET_NAME animation)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)
link_hifi_libraries(shared gpu model fbx)
setup_memory_debugger()
link_hifi_libraries(shared gpu model fbx)

View file

@ -13,6 +13,7 @@
#include <QThreadPool>
#include "AnimationCache.h"
#include "AnimationLogging.h"
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
@ -62,11 +63,15 @@ void AnimationReader::run() {
QSharedPointer<Resource> animation = _animation.toStrongRef();
if (!animation.isNull()) {
QMetaObject::invokeMethod(animation.data(), "setGeometry",
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString())));
}
_reply->deleteLater();
}
bool Animation::isLoaded() const {
return _loaded && _geometry;
}
QStringList Animation::getJointNames() const {
if (QThread::currentThread() != thread()) {
QStringList result;
@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const {
return result;
}
QStringList names;
foreach (const FBXJoint& joint, _geometry.joints) {
foreach (const FBXJoint& joint, _geometry->joints) {
names.append(joint.name);
}
return names;
@ -88,15 +93,15 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
return result;
}
return _geometry.animationFrames;
return _geometry->animationFrames;
}
const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
return _geometry.animationFrames;
return _geometry->animationFrames;
}
void Animation::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
void Animation::setGeometry(FBXGeometry* geometry) {
_geometry.reset(geometry);
finishedLoading(true);
}

View file

@ -52,7 +52,10 @@ public:
Animation(const QUrl& url);
const FBXGeometry& getGeometry() const { return _geometry; }
const FBXGeometry& getGeometry() const { return *_geometry; }
virtual bool isLoaded() const override;
Q_INVOKABLE QStringList getJointNames() const;
@ -62,13 +65,13 @@ public:
protected:
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
Q_INVOKABLE void setGeometry(FBXGeometry* geometry);
virtual void downloadFinished(QNetworkReply* reply);
private:
FBXGeometry _geometry;
std::unique_ptr<FBXGeometry> _geometry;
};

View file

@ -1,5 +1,7 @@
set(TARGET_NAME audio-client)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Multimedia)
@ -25,4 +27,4 @@ if (APPLE)
find_library(CoreAudio CoreAudio)
find_library(CoreFoundation CoreFoundation)
target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation})
endif ()
endif ()

View file

@ -1,5 +1,7 @@
set(TARGET_NAME audio)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network)

View file

@ -77,9 +77,9 @@ void AudioInjector::injectAudio() {
int byteOffset = (int) floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f));
byteOffset *= sizeof(int16_t);
_currentSendPosition = byteOffset;
_currentSendOffset = byteOffset;
} else {
_currentSendPosition = 0;
_currentSendOffset = 0;
}
if (_options.localOnly) {
@ -119,7 +119,7 @@ void AudioInjector::injectLocally() {
_localBuffer->setVolume(_options.volume);
// give our current send position to the local buffer
_localBuffer->setCurrentOffset(_currentSendPosition);
_localBuffer->setCurrentOffset(_currentSendOffset);
success = _localAudioInterface->outputLocalInjector(_options.stereo, this);
@ -144,9 +144,9 @@ void AudioInjector::injectLocally() {
const uchar MAX_INJECTOR_VOLUME = 0xFF;
void AudioInjector::injectToMixer() {
if (_currentSendPosition < 0 ||
_currentSendPosition >= _audioData.size()) {
_currentSendPosition = 0;
if (_currentSendOffset < 0 ||
_currentSendOffset >= _audioData.size()) {
_currentSendOffset = 0;
}
auto nodeList = DependencyManager::get<NodeList>();
@ -203,15 +203,15 @@ void AudioInjector::injectToMixer() {
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
quint16 outgoingInjectedAudioSequenceNumber = 0;
while (_currentSendPosition < _audioData.size() && !_shouldStop) {
while (_currentSendOffset < _audioData.size() && !_shouldStop) {
int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL,
_audioData.size() - _currentSendPosition);
_audioData.size() - _currentSendOffset);
// Measure the loudness of this frame
_loudness = 0.0f;
for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) {
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendPosition + i)) /
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + _currentSendOffset + i)) /
(AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
}
_loudness /= (float)(bytesToCopy / sizeof(int16_t));
@ -220,7 +220,7 @@ void AudioInjector::injectToMixer() {
// pack the sequence number
audioPacket->writePrimitive(outgoingInjectedAudioSequenceNumber);
audioPacket->seek(positionOptionOffset);
audioPacket->writePrimitive(_options.position);
audioPacket->writePrimitive(_options.orientation);
@ -232,7 +232,7 @@ void AudioInjector::injectToMixer() {
audioPacket->seek(audioDataOffset);
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
audioPacket->write(_audioData.data() + _currentSendPosition, bytesToCopy);
audioPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy);
// set the correct size used for this packet
audioPacket->setPayloadSize(audioPacket->pos());
@ -246,11 +246,11 @@ void AudioInjector::injectToMixer() {
outgoingInjectedAudioSequenceNumber++;
}
_currentSendPosition += bytesToCopy;
_currentSendOffset += bytesToCopy;
// send two packets before the first sleep so the mixer can start playback right away
if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) {
if (_currentSendOffset != bytesToCopy && _currentSendOffset < _audioData.size()) {
// process events in case we have been told to stop and be deleted
QCoreApplication::processEvents();
@ -268,8 +268,8 @@ void AudioInjector::injectToMixer() {
}
}
if (shouldLoop && _currentSendPosition >= _audioData.size()) {
_currentSendPosition = 0;
if (shouldLoop && _currentSendOffset >= _audioData.size()) {
_currentSendOffset = 0;
}
}
}

View file

@ -31,7 +31,6 @@ class AbstractAudioInterface;
class AudioInjector : public QObject {
Q_OBJECT
Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions)
public:
AudioInjector(QObject* parent);
AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions);
@ -39,7 +38,8 @@ public:
bool isFinished() const { return _isFinished; }
int getCurrentSendPosition() const { return _currentSendPosition; }
int getCurrentSendOffset() const { return _currentSendOffset; }
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
bool isLocalOnly() const { return _options.localOnly; }
@ -58,9 +58,8 @@ public slots:
void stopAndDeleteLater();
const AudioInjectorOptions& getOptions() const { return _options; }
void setOptions(const AudioInjectorOptions& options) { _options = options; }
void setOptions(const AudioInjectorOptions& options) { _options = options; }
void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; }
float getLoudness() const { return _loudness; }
bool isPlaying() const { return _isPlaying; }
void restartPortionAfterFinished();
@ -82,7 +81,7 @@ private:
bool _isStarted = false;
bool _isFinished = false;
bool _shouldDeleteAfterFinish = false;
int _currentSendPosition = 0;
int _currentSendOffset = 0;
AbstractAudioInterface* _localAudioInterface = NULL;
AudioInjectorLocalBuffer* _localBuffer = NULL;
};

View file

@ -1,3 +1,6 @@
set(TARGET_NAME auto-updater)
setup_memory_debugger()
setup_hifi_library(Network)
link_hifi_libraries(shared networking)

View file

@ -1,5 +1,7 @@
set(TARGET_NAME avatars)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)

View file

@ -371,7 +371,7 @@ void Player::setAudioInjectorPosition() {
int MSEC_PER_SEC = 1000;
int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel();
int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC);
_injector->setCurrentSendPosition(currentAudioFrame);
_injector->setCurrentSendOffset(currentAudioFrame);
}
void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) {

View file

@ -1,5 +1,7 @@
set(TARGET_NAME display-plugins)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(OpenGL)
@ -31,4 +33,4 @@ if (WIN32)
find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
endif()
endif()

View file

@ -1,4 +1,6 @@
set(TARGET_NAME embedded-webserver)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network)
setup_hifi_library(Network)

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
@ -19,16 +20,19 @@
#include "EmbeddedWebserverLogging.h"
#include "HTTPManager.h"
const int SOCKET_ERROR_EXIT_CODE = 2;
const int SOCKET_CHECK_INTERVAL_IN_MS = 30000;
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
QTcpServer(parent),
_documentRoot(documentRoot),
_requestHandler(requestHandler)
_requestHandler(requestHandler),
_port(port)
{
// start listening on the passed port
if (!listen(QHostAddress("0.0.0.0"), port)) {
qCDebug(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString();
return;
}
bindSocket();
_isListeningTimer = new QTimer(this);
connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening);
_isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS);
}
void HTTPManager::incomingConnection(qintptr socketDescriptor) {
@ -157,3 +161,19 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
return _requestHandler && _requestHandler->handleHTTPRequest(connection, url);
}
void HTTPManager::isTcpServerListening() {
if (!isListening()) {
qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening";
bindSocket();
}
}
bool HTTPManager::bindSocket() {
qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port);
if (!listen(QHostAddress::Any, _port)) {
qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue";
QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE);
}
return true;
}

View file

@ -17,6 +17,7 @@
#define hifi_HTTPManager_h
#include <QtNetwork/QTcpServer>
#include <QtCore/QTimer>
class HTTPConnection;
class HTTPSConnection;
@ -35,14 +36,22 @@ public:
HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0);
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
private slots:
void isTcpServerListening();
private:
bool bindSocket();
protected:
/// Accepts all pending connections
virtual void incomingConnection(qintptr socketDescriptor);
virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url);
protected:
QString _documentRoot;
HTTPRequestHandler* _requestHandler;
QTimer* _isListeningTimer;
const quint16 _port;
};
#endif // hifi_HTTPManager_h

View file

@ -26,4 +26,6 @@ find_package(PolyVox REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES})
setup_memory_debugger()
link_hifi_libraries(shared gpu script-engine render render-utils)

View file

@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) {
_model = getModel();
_needsInitialSimulation = true;
_model->setURL(getCompoundShapeURL(), QUrl(), true, true);
_model->setURL(getCompoundShapeURL());
}
if (oldPosition != getPosition() ||
oldRotation != getRotation() ||
@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() {
void RenderableZoneEntityItem::updateGeometry() {
if (_model && !_model->isActive() && hasCompoundShapeURL()) {
// Since we have a delayload, we need to update the geometry if it has been downloaded
_model->setURL(getCompoundShapeURL(), QUrl(), true);
_model->setURL(getCompoundShapeURL());
}
if (_model && _model->isActive() && _needsInitialSimulation) {
initialSimulation();

View file

@ -1,5 +1,7 @@
set(TARGET_NAME entities)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network Script)

View file

@ -7,4 +7,6 @@ add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
link_hifi_libraries(shared networking)
setup_memory_debugger()
link_hifi_libraries(shared networking)

View file

@ -7,4 +7,6 @@ add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
link_hifi_libraries(shared gpu model networking octree)
setup_memory_debugger()
link_hifi_libraries(shared gpu model networking octree)

View file

@ -1373,12 +1373,12 @@ FBXLight extractLight(const FBXNode& object) {
#if USE_MODEL_MESH
void buildModelMesh(ExtractedMesh& extracted) {
void buildModelMesh(ExtractedMesh& extracted, const QString& url) {
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
if (extracted.mesh.vertices.size() == 0) {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no vertices";
qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url;
return;
}
FBXMesh& fbxMesh = extracted.mesh;
@ -1465,7 +1465,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
if (! totalIndices) {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no indices";
qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url;
return;
}
@ -1505,7 +1505,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
mesh.setPartBuffer(pbv);
} else {
extracted.mesh._mesh = model::Mesh();
qCDebug(modelformat) << "buildModelMesh failed -- no parts";
qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url;
return;
}
@ -1530,7 +1530,7 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) {
return filename;
}
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
QHash<QString, ExtractedMesh> meshes;
QHash<QString, QString> modelIDsToNames;
QHash<QString, int> meshIDsToMeshIndices;
@ -1615,7 +1615,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
#if defined(DEBUG_FBXREADER)
int unknown = 0;
#endif
FBXGeometry geometry;
FBXGeometry* geometryPtr = new FBXGeometry;
FBXGeometry& geometry = *geometryPtr;
float unitScaleFactor = 1.0f;
glm::vec3 ambientColor;
QString hifiGlobalNodeID;
@ -2680,7 +2682,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
# if USE_MODEL_MESH
buildModelMesh(extracted);
buildModelMesh(extracted, url);
# endif
if (extracted.mesh.isEye) {
@ -2761,15 +2763,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping,
}
}
return geometry;
return geometryPtr;
}
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
}
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) {
return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel);
}

View file

@ -272,10 +272,10 @@ Q_DECLARE_METATYPE(FBXGeometry)
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f);
#endif // hifi_FBXReader_h

View file

@ -399,15 +399,16 @@ done:
}
FBXGeometry OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
return readOBJ(&buffer, mapping, nullptr);
}
FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
FBXGeometry geometry;
FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) {
FBXGeometry* geometryPtr = new FBXGeometry();
FBXGeometry& geometry = *geometryPtr;
OBJTokenizer tokenizer(device);
float scaleGuess = 1.0f;
@ -545,7 +546,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q
qCDebug(modelformat) << "OBJ reader fail: " << e.what();
}
return geometry;
return geometryPtr;
}

View file

@ -71,8 +71,8 @@ public:
QHash<QString, OBJMaterial> materials;
QNetworkReply* request(QUrl& url, bool isTest);
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping);
FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url);
private:
QUrl* _url = nullptr;

View file

@ -1,5 +1,7 @@
set(TARGET_NAME gpu)
setup_memory_debugger()
AUTOSCRIBE_SHADER_LIB(gpu)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules

View file

@ -33,6 +33,8 @@ endif()
#target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
#target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
setup_memory_debugger()
# perform standard include and linking for found externals
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
@ -69,4 +71,4 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\")
endif ()
endif ()
endforeach()
endforeach()

View file

@ -1,7 +1,9 @@
set(TARGET_NAME model)
AUTOSCRIBE_SHADER_LIB(gpu model)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library()

View file

@ -1,5 +1,7 @@
set(TARGET_NAME networking)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Network)
@ -29,4 +31,4 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES})
# append tbb includes to our list of includes to bubble
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS})
include_application_version()
include_application_version()

View file

@ -320,7 +320,6 @@ void Resource::attemptRequest() {
void Resource::finishedLoading(bool success) {
if (success) {
_loaded = true;
emit loaded();
} else {
_failedToLoad = true;
}
@ -333,91 +332,26 @@ void Resource::reinsert() {
static const int REPLY_TIMEOUT_MS = 5000;
void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (!_reply->isFinished()) {
_bytesReceived = bytesReceived;
_bytesTotal = bytesTotal;
_replyTimer->start(REPLY_TIMEOUT_MS);
return;
}
_reply->disconnect(this);
_replyTimer->disconnect(this);
QNetworkReply* reply = _reply;
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
downloadFinished(reply);
_bytesReceived = bytesReceived;
_bytesTotal = bytesTotal;
_replyTimer->start(REPLY_TIMEOUT_MS);
}
void Resource::handleReplyError() {
handleReplyError(_reply->error(), qDebug() << _reply->errorString());
handleReplyErrorInternal(_reply->error());
}
void Resource::handleReplyTimeout() {
handleReplyError(QNetworkReply::TimeoutError, qDebug() << "Timed out loading" << _reply->url() <<
"received" << _bytesReceived << "total" << _bytesTotal);
}
void Resource::maybeRefresh() {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader);
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
if (variant.isValid() && variant.canConvert<QDateTime>() && metaData.isValid()) {
QDateTime lastModified = variant.value<QDateTime>();
QDateTime lastModifiedOld = metaData.lastModified();
if (lastModified.isValid() && lastModifiedOld.isValid() &&
lastModifiedOld >= lastModified) { // With >=, cache won't thrash in eventually-consistent cdn.
qCDebug(networking) << "Using cached version of" << _url.fileName();
// We don't need to update, return
return;
}
} else if (!variant.isValid() || !variant.canConvert<QDateTime>() ||
!variant.value<QDateTime>().isValid() || variant.value<QDateTime>().isNull()) {
qCDebug(networking) << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated";
return;
}
qCDebug(networking) << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing.";
refresh();
}
handleReplyErrorInternal(QNetworkReply::TimeoutError);
}
void Resource::makeRequest() {
_reply = NetworkAccessManager::getInstance().get(_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished()));
if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
// If the file as been updated since it was cached, refresh it
QNetworkRequest request(_request);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh);
} else {
if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) {
QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url);
bool needUpdate = false;
if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) {
// If the expiration date is NULL or in the past,
// put one far enough away that it won't be an issue.
metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100));
needUpdate = true;
}
if (metaData.lastModified().isNull()) {
// If the lastModified date is NULL, set it to now.
metaData.setLastModified(QDateTime::currentDateTime());
needUpdate = true;
}
if (needUpdate) {
NetworkAccessManager::getInstance().cache()->updateMetaData(metaData);
}
}
}
_replyTimer = new QTimer(this);
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
_replyTimer->setSingleShot(true);
@ -425,7 +359,8 @@ void Resource::makeRequest() {
_bytesReceived = _bytesTotal = 0;
}
void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) {
void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) {
_reply->disconnect(this);
_replyTimer->disconnect(this);
_reply->deleteLater();
@ -433,7 +368,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
// retry for certain types of failures
switch (error) {
case QNetworkReply::RemoteHostClosedError:
@ -444,26 +379,46 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug)
case QNetworkReply::UnknownNetworkError:
case QNetworkReply::UnknownProxyError:
case QNetworkReply::UnknownContentError:
case QNetworkReply::ProtocolFailure: {
case QNetworkReply::ProtocolFailure: {
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest()));
debug << "-- retrying...";
qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")";
return;
}
// fall through to final failure
}
}
default:
qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error;
emit failed(error);
finishedLoading(false);
break;
}
}
void Resource::handleReplyFinished() {
qCDebug(networking) << "Got finished without download progress/error?" << _url;
handleDownloadProgress(0, 0);
bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : "");
_reply->disconnect(this);
_replyTimer->disconnect(this);
QNetworkReply* reply = _reply;
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
finishedLoading(true);
emit loaded(*reply);
downloadFinished(reply);
}
void Resource::downloadFinished(QNetworkReply* reply) {
;
}
uint qHash(const QPointer<QObject>& value, uint seed) {

View file

@ -150,7 +150,7 @@ public:
float getLoadPriority();
/// Checks whether the resource has loaded.
bool isLoaded() const { return _loaded; }
virtual bool isLoaded() const { return _loaded; }
/// For loading resources, returns the number of bytes received.
qint64 getBytesReceived() const { return _bytesReceived; }
@ -174,21 +174,22 @@ public:
signals:
/// Fired when the resource has been loaded.
void loaded();
void loaded(QNetworkReply& request);
/// Fired when resource failed to load.
void failed(QNetworkReply::NetworkError error);
/// Fired when resource is refreshed.
void onRefresh();
protected slots:
void attemptRequest();
/// Refreshes the resource if the last modified date on the network
/// is greater than the last modified date in the cache.
void maybeRefresh();
protected:
virtual void init();
/// Called when the download has finished. The recipient should delete the reply when done with it.
virtual void downloadFinished(QNetworkReply* reply) = 0;
virtual void downloadFinished(QNetworkReply* reply);
/// Should be called by subclasses when all the loading that will be done has been done.
Q_INVOKABLE void finishedLoading(bool success);
@ -216,7 +217,7 @@ private:
void makeRequest();
void handleReplyError(QNetworkReply::NetworkError error, QDebug debug);
void handleReplyErrorInternal(QNetworkReply::NetworkError error);
friend class ResourceCache;

View file

@ -1,5 +1,7 @@
set(TARGET_NAME octree)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library()

View file

@ -1,5 +1,7 @@
set(TARGET_NAME physics)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library()

View file

@ -7,6 +7,7 @@ link_hifi_libraries(shared)
add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
setup_memory_debugger()
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})

View file

@ -40,4 +40,6 @@ add_dependency_external_projects(oglplus)
find_package(OGLPLUS REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS})
setup_memory_debugger()
link_hifi_libraries(animation fbx shared gpu model render environment)

View file

@ -14,7 +14,6 @@
#include <cmath>
#include <QNetworkReply>
#include <QRunnable>
#include <QThreadPool>
#include <FSTReader.h>
@ -50,6 +49,13 @@ GeometryCache::~GeometryCache() {
#endif //def WANT_DEBUG
}
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
// NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual.
assert(false);
return QSharedPointer<Resource>();
}
const int NUM_VERTICES_PER_TRIANGLE = 3;
const int NUM_TRIANGLES_PER_QUAD = 2;
const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD;
@ -1643,19 +1649,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm
batch.draw(gpu::LINES, 2, 0);
}
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
return getResource(url, fallback, delayLoad, NULL).staticCast<NetworkGeometry>();
}
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad),
&Resource::allReferencesCleared);
geometry->setLODParent(geometry);
return geometry.staticCast<Resource>();
}
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
if (!_standardDrawPipeline) {
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)));
@ -1685,33 +1678,82 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
}
}
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) :
_url(url),
_reply(reply),
_mapping(mapping) {
}
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping, const QUrl& textureBase) :
Resource(url, delayLoad),
_mapping(mapping),
_textureBase(textureBase.isValid() ? textureBase : url),
_fallback(fallback)
{
if (url.isEmpty()) {
// make the minimal amount of dummy geometry to satisfy Model
FBXJoint joint = { false, QVector<int>(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(),
glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(),
glm::mat4(), QString(""), false};
_geometry.joints.append(joint);
_geometry.leftEyeJointIndex = -1;
_geometry.rightEyeJointIndex = -1;
_geometry.neckJointIndex = -1;
_geometry.rootJointIndex = -1;
_geometry.leanJointIndex = -1;
_geometry.headJointIndex = -1;
_geometry.leftHandJointIndex = -1;
_geometry.rightHandJointIndex = -1;
void GeometryReader::run() {
try {
if (!_reply) {
throw QString("Reply is NULL ?!");
}
QString urlname = _url.path().toLower();
bool urlValid = true;
urlValid &= !urlname.isEmpty();
urlValid &= !_url.path().isEmpty();
urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj");
if (urlValid) {
// Let's read the binaries from the network
FBXGeometry* fbxgeo = nullptr;
if (_url.path().toLower().endsWith(".fbx")) {
const bool grabLightmaps = true;
const float lightmapLevel = 1.0f;
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
} else {
QString errorStr("usupported format");
emit onError(NetworkGeometry::ModelParseError, errorStr);
}
emit onSuccess(fbxgeo);
} else {
throw QString("url is invalid");
}
} catch (const QString& error) {
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
emit onError(NetworkGeometry::ModelParseError, error);
}
connect(this, &Resource::loaded, this, &NetworkGeometry::replaceTexturesWithPendingChanges);
_reply->deleteLater();
}
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
_url(url),
_mapping(mapping),
_textureBaseUrl(textureBaseUrl) {
if (delayLoad) {
_state = DelayState;
} else {
attemptRequestInternal();
}
}
NetworkGeometry::~NetworkGeometry() {
if (_resource) {
_resource->deleteLater();
}
}
void NetworkGeometry::attemptRequest() {
if (_state == DelayState) {
attemptRequestInternal();
}
}
void NetworkGeometry::attemptRequestInternal() {
if (_url.path().toLower().endsWith(".fst")) {
requestMapping(_url);
} else {
requestModel(_url);
}
}
bool NetworkGeometry::isLoaded() const {
return _state == SuccessState;
}
bool NetworkGeometry::isLoadedWithTextures() const {
@ -1719,12 +1761,12 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return false;
}
if (!_isLoadedWithTextures) {
foreach (const NetworkMesh& mesh, _meshes) {
foreach (const NetworkMeshPart& part, mesh.parts) {
if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) ||
(part.normalTexture && !part.normalTexture->isLoaded()) ||
(part.specularTexture && !part.specularTexture->isLoaded()) ||
(part.emissiveTexture && !part.emissiveTexture->isLoaded())) {
for (auto&& mesh : _meshes) {
for (auto && part : mesh->_parts) {
if ((part->diffuseTexture && !part->diffuseTexture->isLoaded()) ||
(part->normalTexture && !part->normalTexture->isLoaded()) ||
(part->specularTexture && !part->specularTexture->isLoaded()) ||
(part->emissiveTexture && !part->emissiveTexture->isLoaded())) {
return false;
}
}
@ -1734,183 +1776,38 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return true;
}
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const {
if (_lodParent.data() != this) {
return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad);
}
if (_failedToLoad && _fallback) {
return _fallback;
}
QSharedPointer<NetworkGeometry> lod = _lodParent;
float lodDistance = 0.0f;
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
if (it != _lods.constBegin()) {
it = it - 1;
lod = it.value();
lodDistance = it.key();
}
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
// if we previously selected a different distance, make sure we've moved far enough to justify switching
const float HYSTERESIS_PROPORTION = 0.1f;
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
lod = _lodParent;
lodDistance = 0.0f;
it = _lods.upperBound(hysteresis);
if (it != _lods.constBegin()) {
it = it - 1;
lod = it.value();
lodDistance = it.key();
}
}
}
if (lod && lod->isLoaded()) {
hysteresis = lodDistance;
return lod;
}
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
if (!delayLoad) {
lod->ensureLoading();
}
float closestDistance = FLT_MAX;
if (isLoaded()) {
lod = _lodParent;
closestDistance = distance;
}
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
float distanceToLOD = glm::abs(distance - it.key());
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
lod = it.value();
closestDistance = distanceToLOD;
}
}
hysteresis = NO_HYSTERESIS;
return lod;
}
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
return qHash(animation.data(), seed);
}
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
QVector<int> mappings = _jointMappings.value(animation);
if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) {
const FBXGeometry& animationGeometry = animation->getGeometry();
for (int i = 0; i < animationGeometry.joints.size(); i++) {
mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1);
}
_jointMappings.insert(animation, mappings);
}
return mappings;
}
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
Resource::setLoadPriority(owner, priority);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriority(owner, priority);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriority(owner, priority);
}
if (part.specularTexture) {
part.specularTexture->setLoadPriority(owner, priority);
}
if (part.emissiveTexture) {
part.emissiveTexture->setLoadPriority(owner, priority);
}
}
}
}
void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) {
Resource::setLoadPriorities(priorities);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriorities(priorities);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriorities(priorities);
}
if (part.specularTexture) {
part.specularTexture->setLoadPriorities(priorities);
}
if (part.emissiveTexture) {
part.emissiveTexture->setLoadPriorities(priorities);
}
}
}
}
void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) {
Resource::clearLoadPriority(owner);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->clearLoadPriority(owner);
}
if (part.normalTexture) {
part.normalTexture->clearLoadPriority(owner);
}
if (part.specularTexture) {
part.specularTexture->clearLoadPriority(owner);
}
if (part.emissiveTexture) {
part.emissiveTexture->clearLoadPriority(owner);
}
}
}
}
void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) {
if (_meshes.size() > 0) {
auto textureCache = DependencyManager::get<TextureCache>();
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
for (size_t i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = *(_meshes[i].get());
for (size_t j = 0; j < mesh._parts.size(); j++) {
NetworkMeshPart& part = *(mesh._parts[j].get());
QSharedPointer<NetworkTexture> matchingTexture = QSharedPointer<NetworkTexture>();
if (part.diffuseTextureName == name) {
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry.meshes[i].isEye);
part.diffuseTexture->setLoadPriorities(_loadPriorities);
part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry->meshes[i].isEye);
} else if (part.normalTextureName == name) {
part.normalTexture = textureCache->getTexture(url);
part.normalTexture->setLoadPriorities(_loadPriorities);
} else if (part.specularTextureName == name) {
part.specularTexture = textureCache->getTexture(url);
part.specularTexture->setLoadPriorities(_loadPriorities);
} else if (part.emissiveTextureName == name) {
part.emissiveTexture = textureCache->getTexture(url);
part.emissiveTexture->setLoadPriorities(_loadPriorities);
}
}
}
} else {
qCDebug(renderutils) << "Adding a name url pair to pending" << name << url;
// we don't have meshes downloaded yet, so hold this texture as pending
_pendingTextureChanges.insert(name, url);
qCWarning(renderutils) << "Ignoring setTextureWirthNameToURL() geometry not ready." << name << url;
}
_isLoadedWithTextures = false;
}
QStringList NetworkGeometry::getTextureNames() const {
QStringList result;
for (int i = 0; i < _meshes.size(); i++) {
const NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
const NetworkMeshPart& part = mesh.parts[j];
for (size_t i = 0; i < _meshes.size(); i++) {
const NetworkMesh& mesh = *(_meshes[i].get());
for (size_t j = 0; j < mesh._parts.size(); j++) {
const NetworkMeshPart& part = *(mesh._parts[j].get());
if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) {
QString textureURL = part.diffuseTexture->getURL().toString();
result << part.diffuseTextureName + ":" + textureURL;
@ -1935,320 +1832,259 @@ QStringList NetworkGeometry::getTextureNames() const {
return result;
}
void NetworkGeometry::replaceTexturesWithPendingChanges() {
QHash<QString, QUrl>::Iterator it = _pendingTextureChanges.begin();
while (it != _pendingTextureChanges.end()) {
setTextureWithNameToURL(it.key(), it.value());
it = _pendingTextureChanges.erase(it);
void NetworkGeometry::requestMapping(const QUrl& url) {
_state = RequestMappingState;
if (_resource) {
_resource->deleteLater();
}
_resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&)));
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError)));
}
/// Reads geometry in a worker thread.
class GeometryReader : public QRunnable {
public:
GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
QNetworkReply* reply, const QVariantHash& mapping);
virtual void run();
private:
QWeakPointer<Resource> _geometry;
QUrl _url;
QNetworkReply* _reply;
QVariantHash _mapping;
};
GeometryReader::GeometryReader(const QWeakPointer<Resource>& geometry, const QUrl& url,
QNetworkReply* reply, const QVariantHash& mapping) :
_geometry(geometry),
_url(url),
_reply(reply),
_mapping(mapping) {
void NetworkGeometry::requestModel(const QUrl& url) {
_state = RequestModelState;
if (_resource) {
_resource->deleteLater();
}
_resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&)));
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError)));
}
void GeometryReader::run() {
QSharedPointer<Resource> geometry = _geometry.toStrongRef();
if (geometry.isNull()) {
_reply->deleteLater();
return;
}
try {
if (!_reply) {
throw QString("Reply is NULL ?!");
}
QString urlname = _url.path().toLower();
bool urlValid = true;
urlValid &= !urlname.isEmpty();
urlValid &= !_url.path().isEmpty();
urlValid &= _url.path().toLower().endsWith(".fbx")
|| _url.path().toLower().endsWith(".obj")
|| _url.path().toLower().endsWith(".svo");
void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) {
assert(_state == RequestMappingState);
if (urlValid) {
// Let's read the binaries from the network
FBXGeometry fbxgeo;
if (_url.path().toLower().endsWith(".fbx")) {
bool grabLightmaps = true;
float lightmapLevel = 1.0f;
// HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber...
if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) {
grabLightmaps = false;
} else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) {
lightmapLevel = 4.0f;
} else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) {
lightmapLevel = 3.5f;
}
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url);
// parse the mapping file
_mapping = FSTReader::readMapping(reply.readAll());
QUrl replyUrl = reply.url();
QString modelUrlStr = _mapping.value("filename").toString();
if (modelUrlStr.isNull()) {
qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry";
emit onFailure(*this, MissingFilenameInMapping);
} else {
// read _textureBase from mapping file, if present
QString texdir = _mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo));
} else {
throw QString("url is invalid");
_textureBaseUrl = replyUrl.resolved(texdir);
}
} catch (const QString& error) {
qCDebug(renderutils) << "Error reading " << _url << ": " << error;
QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false));
}
_reply->deleteLater();
}
void NetworkGeometry::init() {
_mapping = QVariantHash();
_geometry = FBXGeometry();
_meshes.clear();
_lods.clear();
_pendingTextureChanges.clear();
_request.setUrl(_url);
Resource::init();
}
void NetworkGeometry::downloadFinished(QNetworkReply* reply) {
QUrl url = reply->url();
if (url.path().toLower().endsWith(".fst")) {
// it's a mapping file; parse it and get the mesh filename
_mapping = FSTReader::readMapping(reply->readAll());
reply->deleteLater();
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {
qCDebug(renderutils) << "Mapping file " << url << " has no filename.";
finishedLoading(false);
} else {
QString texdir = _mapping.value("texdir").toString();
if (!texdir.isNull()) {
if (!texdir.endsWith('/')) {
texdir += '/';
}
_textureBase = url.resolved(texdir);
}
QVariantHash lods = _mapping.value("lod").toHash();
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
auto geometry = QSharedPointer<NetworkGeometry>::create(url.resolved(it.key()),
QSharedPointer<NetworkGeometry>(), true, _mapping, _textureBase);
geometry->setSelf(geometry.staticCast<Resource>());
geometry->setLODParent(_lodParent);
_lods.insert(it.value().toFloat(), geometry);
}
_request.setUrl(url.resolved(filename));
// make the request immediately only if we have no LODs to switch between
_startedLoading = false;
if (_lods.isEmpty()) {
attemptRequest();
}
}
return;
}
// send the reader off to the thread pool
QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping));
}
void NetworkGeometry::reinsert() {
Resource::reinsert();
_lodParent = qWeakPointerCast<NetworkGeometry, Resource>(_self);
foreach (const QSharedPointer<NetworkGeometry>& lod, _lods) {
lod->setLODParent(_lodParent);
QUrl modelUrl = replyUrl.resolved(modelUrlStr);
requestModel(modelUrl);
}
}
void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
_geometry = geometry;
void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
assert(_state == RequestMappingState);
_state = ErrorState;
emit onFailure(*this, MappingRequestError);
}
void NetworkGeometry::modelRequestDone(QNetworkReply& reply) {
assert(_state == RequestModelState);
_state = ParsingModelState;
// asynchronously parse the model file.
GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping);
connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*)));
connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString)));
QThreadPool::globalInstance()->start(geometryReader);
}
void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) {
assert(_state == RequestModelState);
_state = ErrorState;
emit onFailure(*this, ModelRequestError);
}
static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) {
auto textureCache = DependencyManager::get<TextureCache>();
foreach (const FBXMesh& mesh, _geometry.meshes) {
NetworkMesh networkMesh;
int totalIndices = 0;
bool checkForTexcoordLightmap = false;
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart networkPart;
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart.diffuseTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
mesh.isEye, part.diffuseTexture.content);
networkPart.diffuseTextureName = part.diffuseTexture.name;
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
}
if (!part.normalTexture.filename.isEmpty()) {
networkPart.normalTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
false, part.normalTexture.content);
networkPart.normalTextureName = part.normalTexture.name;
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
}
if (!part.specularTexture.filename.isEmpty()) {
networkPart.specularTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
false, part.specularTexture.content);
networkPart.specularTextureName = part.specularTexture.name;
networkPart.specularTexture->setLoadPriorities(_loadPriorities);
}
if (!part.emissiveTexture.filename.isEmpty()) {
networkPart.emissiveTexture = textureCache->getTexture(
_textureBase.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
false, part.emissiveTexture.content);
networkPart.emissiveTextureName = part.emissiveTexture.name;
networkPart.emissiveTexture->setLoadPriorities(_loadPriorities);
checkForTexcoordLightmap = true;
}
networkMesh.parts.append(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
NetworkMesh* networkMesh = new NetworkMesh();
int totalIndices = 0;
bool checkForTexcoordLightmap = false;
// process network parts
foreach (const FBXMeshPart& part, mesh.parts) {
NetworkMeshPart* networkPart = new NetworkMeshPart();
if (!part.diffuseTexture.filename.isEmpty()) {
networkPart->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE,
mesh.isEye, part.diffuseTexture.content);
networkPart->diffuseTextureName = part.diffuseTexture.name;
}
{
networkMesh._indexBuffer = std::make_shared<gpu::Buffer>();
networkMesh._indexBuffer->resize(totalIndices * sizeof(int));
int offset = 0;
foreach(const FBXMeshPart& part, mesh.parts) {
networkMesh._indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
(gpu::Byte*) part.quadIndices.constData());
offset += part.quadIndices.size() * sizeof(int);
networkMesh._indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
(gpu::Byte*) part.triangleIndices.constData());
offset += part.triangleIndices.size() * sizeof(int);
}
if (!part.normalTexture.filename.isEmpty()) {
networkPart->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE,
false, part.normalTexture.content);
networkPart->normalTextureName = part.normalTexture.name;
}
{
networkMesh._vertexBuffer = std::make_shared<gpu::Buffer>();
// if we don't need to do any blending, the positions/normals can be static
if (mesh.blendshapes.isEmpty()) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh._vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
networkMesh._vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
networkMesh._vertexBuffer->setSubData(tangentsOffset,
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh._vertexBuffer->setSubData(texCoords1Offset,
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
// otherwise, at least the cluster indices/weights can be static
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.normals.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, normalsOffset, sizeof(glm::vec3));
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, tangentsOffset, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.texCoords1.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.texCoords1.size()) {
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
}
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh._vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh._vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh._vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh._vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
networkMesh._vertexStream = std::make_shared<gpu::BufferStream>();
if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh._vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
if (!part.specularTexture.filename.isEmpty()) {
networkPart->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE,
false, part.specularTexture.content);
networkPart->specularTextureName = part.specularTexture.name;
}
_meshes.append(networkMesh);
if (!part.emissiveTexture.filename.isEmpty()) {
networkPart->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE,
false, part.emissiveTexture.content);
networkPart->emissiveTextureName = part.emissiveTexture.name;
checkForTexcoordLightmap = true;
}
networkMesh->_parts.emplace_back(networkPart);
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
}
finishedLoading(true);
// initialize index buffer
{
networkMesh->_indexBuffer = std::make_shared<gpu::Buffer>();
networkMesh->_indexBuffer->resize(totalIndices * sizeof(int));
int offset = 0;
foreach(const FBXMeshPart& part, mesh.parts) {
networkMesh->_indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int),
(gpu::Byte*) part.quadIndices.constData());
offset += part.quadIndices.size() * sizeof(int);
networkMesh->_indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int),
(gpu::Byte*) part.triangleIndices.constData());
offset += part.triangleIndices.size() * sizeof(int);
}
}
// initialize vertex buffer
{
networkMesh->_vertexBuffer = std::make_shared<gpu::Buffer>();
// if we don't need to do any blending, the positions/normals can be static
if (mesh.blendshapes.isEmpty()) {
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh->_vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
networkMesh->_vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
networkMesh->_vertexBuffer->setSubData(tangentsOffset,
mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh->_vertexBuffer->setSubData(texCoords1Offset,
mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData());
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
// otherwise, at least the cluster indices/weights can be static
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.normals.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, normalsOffset, sizeof(glm::vec3));
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, tangentsOffset, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.texCoords1.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoords1Offset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.texCoords1.size()) {
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
} else if (checkForTexcoordLightmap && mesh.texCoords.size()) {
// need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel
networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
}
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
else {
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
networkMesh->_vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData());
networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData());
networkMesh->_vertexBuffer->setSubData(texCoordsOffset,
mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData());
networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset,
mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData());
networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset,
mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData());
networkMesh->_vertexStream = std::make_shared<gpu::BufferStream>();
if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3));
if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3));
if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2));
if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4));
if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4));
int channelNum = 0;
networkMesh->_vertexFormat = std::make_shared<gpu::Stream::Format>();
networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB));
if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW));
}
}
return networkMesh;
}
void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) {
// assume owner ship of geometry pointer
_geometry.reset(geometry);
foreach(const FBXMesh& mesh, _geometry->meshes) {
_meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl));
}
_state = SuccessState;
emit onSuccess(*this, *_geometry.get());
delete _resource;
_resource = nullptr;
}
void NetworkGeometry::modelParseError(int error, QString str) {
_state = ErrorState;
emit onFailure(*this, (NetworkGeometry::Error)error);
delete _resource;
_resource = nullptr;
}
bool NetworkMeshPart::isTranslucent() const {
return diffuseTexture && diffuseTexture->isTranslucent();
}
bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const {
assert(partIndex >= 0);
assert(partIndex < parts.size());
return (parts.at(partIndex).isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
assert((size_t)partIndex < _parts.size());
return (_parts.at(partIndex)->isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f);
}
int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const {
int count = 0;
for (int i = 0; i < parts.size(); i++) {
for (size_t i = 0; i < _parts.size(); i++) {
if (isPartTranslucent(fbxMesh, i)) {
count++;
}

View file

@ -13,6 +13,7 @@
#define hifi_GeometryCache_h
#include <QMap>
#include <QRunnable>
#include <DependencyManager.h>
#include <ResourceCache.h>
@ -129,6 +130,9 @@ public:
int allocateID() { return _nextID++; }
static const int UNKNOWN_ID;
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra);
void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID)
{ renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); }
@ -208,11 +212,6 @@ public:
/// Set a batch to the simple pipeline, returning the previous pipeline
void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
GeometryCache();
virtual ~GeometryCache();
@ -305,70 +304,104 @@ private:
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
};
/// Geometry loaded from the network.
class NetworkGeometry : public Resource {
class NetworkGeometry : public QObject {
Q_OBJECT
public:
/// A hysteresis value indicating that we have no state memory.
static const float NO_HYSTERESIS;
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback, bool delayLoad,
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
// mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file.
// if delayLoad is true, the url will not be immediately downloaded.
// use the attemptRequest method to initiate the download.
NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl());
~NetworkGeometry();
/// Checks whether the geometry and its textures are loaded.
const QUrl& getURL() const { return _url; }
void attemptRequest();
// true when the geometry is loaded (but maybe not it's associated textures)
bool isLoaded() const;
// true when the requested geometry and its textures are loaded.
bool isLoadedWithTextures() const;
/// Returns a pointer to the geometry appropriate for the specified distance.
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const;
// WARNING: only valid when isLoaded returns true.
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
const FBXGeometry& getFBXGeometry() const { return _geometry; }
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
QVector<int> getJointMappings(const AnimationPointer& animation);
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
virtual void clearLoadPriority(const QPointer<QObject>& owner);
void setTextureWithNameToURL(const QString& name, const QUrl& url);
QStringList getTextureNames() const;
enum Error {
MissingFilenameInMapping = 0,
MappingRequestError,
ModelRequestError,
ModelParseError
};
signals:
// Fired when everything has downloaded and parsed successfully.
void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry);
// Fired when something went wrong.
void onFailure(NetworkGeometry& networkGeometry, Error error);
protected slots:
void mappingRequestDone(QNetworkReply& reply);
void mappingRequestError(QNetworkReply::NetworkError error);
void modelRequestDone(QNetworkReply& reply);
void modelRequestError(QNetworkReply::NetworkError error);
void modelParseSuccess(FBXGeometry* geometry);
void modelParseError(int error, QString str);
protected:
void attemptRequestInternal();
void requestMapping(const QUrl& url);
void requestModel(const QUrl& url);
virtual void init();
virtual void downloadFinished(QNetworkReply* reply);
virtual void reinsert();
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
private slots:
void replaceTexturesWithPendingChanges();
private:
friend class GeometryCache;
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
enum State { DelayState,
RequestMappingState,
RequestModelState,
ParsingModelState,
SuccessState,
ErrorState };
State _state;
QUrl _url;
QVariantHash _mapping;
QUrl _textureBase;
QSharedPointer<NetworkGeometry> _fallback;
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
FBXGeometry _geometry;
QVector<NetworkMesh> _meshes;
QWeakPointer<NetworkGeometry> _lodParent;
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
QHash<QString, QUrl> _pendingTextureChanges;
QUrl _textureBaseUrl;
Resource* _resource = nullptr;
std::unique_ptr<FBXGeometry> _geometry;
std::vector<std::unique_ptr<NetworkMesh>> _meshes;
// cache for isLoadedWithTextures()
mutable bool _isLoadedWithTextures = false;
};
/// Reads geometry in a worker thread.
class GeometryReader : public QObject, public QRunnable {
Q_OBJECT
public:
GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping);
virtual void run();
signals:
void onSuccess(FBXGeometry* geometry);
void onError(int error, QString str);
private:
QWeakPointer<Resource> _geometry;
QUrl _url;
QNetworkReply* _reply;
QVariantHash _mapping;
};
/// The state associated with a single mesh part.
class NetworkMeshPart {
public:
@ -394,9 +427,9 @@ public:
gpu::BufferStreamPointer _vertexStream;
gpu::Stream::FormatPointer _vertexFormat;
QVector<NetworkMeshPart> parts;
std::vector<std::unique_ptr<NetworkMeshPart>> _parts;
int getTranslucentPartCount(const FBXMesh& fbxMesh) const;
bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const;
};

View file

@ -48,6 +48,8 @@
#include "model_lightmap_specular_map_frag.h"
#include "model_translucent_frag.h"
#include "RenderUtilsLogging.h"
using namespace std;
static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
@ -66,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_snappedToRegistrationPoint(false),
_showTrueJointTransforms(true),
_cauterizeBones(false),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url(HTTP_INVALID_COM),
_urlAsString(HTTP_INVALID_COM),
@ -234,7 +235,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
};
void Model::initJointTransforms() {
if (!_geometry) {
if (!_geometry || !_geometry->isLoaded()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -368,8 +369,10 @@ void Model::init() {
}
void Model::reset() {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
_rig->reset(geometry.joints);
if (_geometry && _geometry->isLoaded()) {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
_rig->reset(geometry.joints);
}
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
@ -378,68 +381,23 @@ void Model::reset() {
bool Model::updateGeometry() {
PROFILE_RANGE(__FUNCTION__);
bool needFullUpdate = false;
bool needToRebuild = false;
if (_nextGeometry) {
_nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis);
_nextGeometry->setLoadPriority(this, -_lodDistance);
_nextGeometry->ensureLoading();
if (_nextGeometry->isLoaded()) {
applyNextGeometry();
needToRebuild = true;
}
}
if (!_geometry) {
if (!_geometry || !_geometry->isLoaded()) {
// geometry is not ready
return false;
}
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis);
if (_geometry != geometry) {
_needsReload = false;
// NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above.
// Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry.
const FBXGeometry& newGeometry = geometry->getFBXGeometry();
QVector<JointState> newJointStates = createJointStates(newGeometry);
if (! _rig->jointStatesEmpty()) {
// copy the existing joint states
const FBXGeometry& oldGeometry = _geometry->getFBXGeometry();
for (QHash<QString, int>::const_iterator it = oldGeometry.jointIndices.constBegin();
it != oldGeometry.jointIndices.constEnd(); it++) {
int oldIndex = it.value() - 1;
int newIndex = newGeometry.getJointIndex(it.key());
if (newIndex != -1) {
newJointStates[newIndex].copyState(_rig->getJointState(oldIndex));
}
}
}
deleteGeometry();
_dilatedTextures.clear();
if (!geometry) {
std::cout << "WARNING: no geometry in Model::updateGeometry\n";
}
setGeometry(geometry);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
initJointStates(newJointStates);
needToRebuild = true;
} else if (_rig->jointStatesEmpty()) {
QSharedPointer<NetworkGeometry> geometry = _geometry;
if (_rig->jointStatesEmpty()) {
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
if (fbxGeometry.joints.size() > 0) {
initJointStates(createJointStates(fbxGeometry));
needToRebuild = true;
}
} else if (!geometry->isLoaded()) {
deleteGeometry();
_dilatedTextures.clear();
}
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
if (needToRebuild) {
const FBXGeometry& fbxGeometry = geometry->getFBXGeometry();
@ -454,7 +412,7 @@ bool Model::updateGeometry() {
buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3));
buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData());
buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3),
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData());
}
_blendedVertexBuffers.push_back(buffer);
}
@ -1069,53 +1027,36 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
}
void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) {
void Model::setURL(const QUrl& url) {
// don't recreate the geometry if it's the same URL
if (_url == url && _geometry && _geometry->getURL() == url) {
return;
}
_readyWhenAdded = false; // reset out render items.
_needsReload = true;
invalidCalculatedMeshBoxes();
_url = url;
_urlAsString = _url.toString();
{
render::PendingChanges pendingChanges;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
removeFromScene(scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges);
}
_needsReload = true;
_meshGroupsKnown = false;
invalidCalculatedMeshBoxes();
deleteGeometry();
_geometry.reset(new NetworkGeometry(url, false, QVariantHash()));
onInvalidate();
// if so instructed, keep the current geometry until the new one is loaded
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
if (!retainCurrent || !isActive() || (_nextGeometry && _nextGeometry->isLoaded())) {
applyNextGeometry();
}
}
void Model::geometryRefreshed() {
QObject* sender = QObject::sender();
if (sender == _geometry) {
_readyWhenAdded = false; // reset out render items.
_needsReload = true;
invalidCalculatedMeshBoxes();
onInvalidate();
// if so instructed, keep the current geometry until the new one is loaded
_nextGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_url);
_nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS;
applyNextGeometry();
} else {
sender->disconnect(this, SLOT(geometryRefreshed()));
}
}
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
{
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_collisionUrl, QUrl(), delayLoad);
_collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash()));
}
if (_collisionGeometry && _collisionGeometry->isLoaded()) {
@ -1130,7 +1071,7 @@ void Model::setCollisionModelURL(const QUrl& url) {
return;
}
_collisionUrl = url;
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, QUrl(), true);
_collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash()));
}
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
@ -1461,46 +1402,23 @@ void Model::setGeometry(const QSharedPointer<NetworkGeometry>& newGeometry) {
if (_geometry == newGeometry) {
return;
}
if (_geometry) {
_geometry->disconnect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
}
_geometry = newGeometry;
QObject::connect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed);
}
void Model::applyNextGeometry() {
// delete our local geometry and custom textures
deleteGeometry();
_dilatedTextures.clear();
_lodHysteresis = _nextLODHysteresis;
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
setGeometry(_nextGeometry);
_meshGroupsKnown = false;
_readyWhenAdded = false; // in case any of our users are using scenes
_needsReload = false; // we are loaded now!
invalidCalculatedMeshBoxes();
_nextGeometry.reset();
}
void Model::deleteGeometry() {
_blendedVertexBuffers.clear();
_rig->clearJointStates();
_meshStates.clear();
_rig->deleteAnimations();
if (_geometry) {
_geometry->clearLoadPriority(this);
}
_blendedBlendshapeCoefficients.clear();
}
AABox Model::getPartBounds(int meshIndex, int partIndex) {
if (!_geometry || !_geometry->isLoaded()) {
return AABox();
}
if (meshIndex < _meshStates.size()) {
const MeshState& state = _meshStates.at(meshIndex);
bool isSkinned = state.clusterMatrices.size() > 1;
@ -1531,7 +1449,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) {
}
void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) {
// PROFILE_RANGE(__FUNCTION__);
// PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("Model::renderPart");
if (!_readyWhenAdded) {
return; // bail asap
@ -1557,14 +1475,14 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
// guard against partially loaded meshes
if (meshIndex >= networkMeshes.size() || meshIndex >= geometry.meshes.size() || meshIndex >= _meshStates.size() ) {
if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) {
return;
}
const NetworkMesh& networkMesh = networkMeshes.at(meshIndex);
const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get());
const FBXMesh& mesh = geometry.meshes.at(meshIndex);
const MeshState& state = _meshStates.at(meshIndex);
@ -1614,8 +1532,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
// to false to rebuild out mesh groups.
if (meshIndex < 0 || meshIndex >= networkMeshes.size() || meshIndex > geometry.meshes.size()) {
if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) {
_meshGroupsKnown = false; // regenerate these lists next time around.
_readyWhenAdded = false; // in case any of our users are using scenes
invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
@ -1669,11 +1586,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
}
// guard against partially loaded meshes
if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) {
if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) {
return;
}
const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex);
const NetworkMeshPart& networkPart = *(networkMesh._parts.at(partIndex).get());
const FBXMeshPart& part = mesh.parts.at(partIndex);
model::MaterialPointer material = part._material;
@ -1790,10 +1707,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran
void Model::segregateMeshGroups() {
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
const std::vector<std::unique_ptr<NetworkMesh>>& networkMeshes = _geometry->getMeshes();
// all of our mesh vectors must match in size
if (networkMeshes.size() != geometry.meshes.size() ||
if ((int)networkMeshes.size() != geometry.meshes.size() ||
geometry.meshes.size() != _meshStates.size()) {
qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
return;
@ -1803,12 +1720,12 @@ void Model::segregateMeshGroups() {
_opaqueRenderItems.clear();
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
for (int i = 0; i < networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = networkMeshes.at(i);
for (int i = 0; i < (int)networkMeshes.size(); i++) {
const NetworkMesh& networkMesh = *(networkMeshes.at(i).get());
const FBXMesh& mesh = geometry.meshes.at(i);
const MeshState& state = _meshStates.at(i);
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size();
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == (int)networkMesh._parts.size();
bool hasTangents = !mesh.tangents.isEmpty();
bool hasSpecular = mesh.hasSpecularTexture();
bool hasLightmap = mesh.hasEmissiveTexture();

View file

@ -68,11 +68,7 @@ public:
/// Sets the URL of the model to render.
/// \param fallback the URL of a fallback model to render if the requested model fails to load
/// \param retainCurrent if true, keep rendering the current model until the new one is loaded
/// \param delayLoad if true, don't load the model immediately; wait until actually requested
Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(),
bool retainCurrent = false, bool delayLoad = false);
Q_INVOKABLE void setURL(const QUrl& url);
const QUrl& getURL() const { return _url; }
const QString& getURLAsString() const { return _urlAsString; }
@ -89,7 +85,7 @@ public:
render::Item::Status::Getters& statusGetters);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
void renderSetup(RenderArgs* args);
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); }
bool isVisible() const { return _isVisible; }
@ -141,14 +137,11 @@ public:
const QUrl& getCollisionURL() const { return _collisionUrl; }
/// Returns a reference to the shared collision geometry.
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = false);
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }
/// Sets the distance parameter used for LOD computations.
void setLODDistance(float distance) { _lodDistance = distance; }
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
@ -309,20 +302,12 @@ protected:
// hook for derived classes to be notified when setUrl invalidates the current model.
virtual void onInvalidate() {};
void geometryRefreshed();
private:
void applyNextGeometry();
void deleteGeometry();
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
QSharedPointer<NetworkGeometry> _nextGeometry;
float _lodDistance;
float _lodHysteresis;
float _nextLODHysteresis;
QSharedPointer<NetworkGeometry> _collisionGeometry;
float _pupilDilation;

View file

@ -1,5 +1,7 @@
set(TARGET_NAME render)
setup_memory_debugger()
AUTOSCRIBE_SHADER_LIB(gpu model)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
@ -21,4 +23,4 @@ if (WIN32)
target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}")
endif ()
endif()
endif (WIN32)
endif (WIN32)

View file

@ -1,5 +1,7 @@
set(TARGET_NAME script-engine)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
setup_hifi_library(Gui Network Script WebSockets Widgets)

View file

@ -21,13 +21,15 @@ class ScriptAudioInjector : public QObject {
Q_PROPERTY(bool isPlaying READ isPlaying)
Q_PROPERTY(float loudness READ getLoudness)
Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions)
public:
ScriptAudioInjector(AudioInjector* injector);
~ScriptAudioInjector();
public slots:
void restart() { _injector->restart(); }
void stop() { _injector->stop(); }
const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); }
void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); }
float getLoudness() const { return _injector->getLoudness(); }
@ -49,4 +51,4 @@ Q_DECLARE_METATYPE(ScriptAudioInjector*)
QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in);
void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& out);
#endif // hifi_ScriptAudioInjector_h
#endif // hifi_ScriptAudioInjector_h

View file

@ -1,5 +1,7 @@
set(TARGET_NAME shared)
setup_memory_debugger()
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
# TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp)
setup_hifi_library(Gui Network Script Widgets)

View file

@ -7,6 +7,7 @@ link_hifi_libraries(render-utils shared)
add_dependency_external_projects(glm)
find_package(GLM REQUIRED)
setup_memory_debugger()
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})

View file

@ -35,3 +35,5 @@ set_target_properties("all-tests" PROPERTIES FOLDER "hidden/test-targets")
set_target_properties("all-tests" PROPERTIES
EXCLUDE_FROM_DEFAULT_BUILD TRUE
EXCLUDE_FROM_ALL TRUE)
setup_memory_debugger()

View file

@ -6,4 +6,6 @@ macro (setup_testcase_dependencies)
copy_dlls_beside_windows_executable()
endmacro ()
setup_memory_debugger()
setup_hifi_testcase()

View file

@ -6,4 +6,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase()
setup_memory_debugger()
setup_hifi_testcase()

View file

@ -9,4 +9,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation environment)
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -7,4 +7,6 @@ macro (setup_testcase_dependencies)
copy_dlls_beside_windows_executable()
endmacro()
setup_hifi_testcase()
setup_memory_debugger()
setup_hifi_testcase()

View file

@ -7,4 +7,6 @@ macro (setup_testcase_dependencies)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase()
setup_memory_debugger()
setup_hifi_testcase()

View file

@ -0,0 +1,95 @@
//
// ResoruceTests.cpp
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QNetworkDiskCache>
#include "ResourceCache.h"
#include "NetworkAccessManager.h"
#include "DependencyManager.h"
#include "ResourceTests.h"
QTEST_MAIN(ResourceTests)
void ResourceTests::initTestCase() {
auto resourceCacheSharedItems = DependencyManager::set<ResourceCacheSharedItems>();
const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB
// set up the file cache
//QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QString cachePath = "./resourceTestCache";
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(cachePath);
cache->clear(); // clear the cache
networkAccessManager.setCache(cache);
}
static Resource* resource = nullptr;
static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) {
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(sender, signal, SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
return timer.isActive();
}
void ResourceTests::downloadFirst() {
// download the Mery fst file
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
resource = new Resource(meryUrl, false);
const int timeout = 1000;
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
QVERIFY(resource->isLoaded());
}
void ResourceTests::downloadAgain() {
// download the Mery fst file
QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst");
resource = new Resource(meryUrl, false);
const int timeout = 1000;
QEventLoop loop;
QTimer timer;
timer.setInterval(timeout);
timer.setSingleShot(true);
loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit()));
loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit()));
loop.connect(&timer, SIGNAL(timeout()), SLOT(quit()));
timer.start();
loop.exec();
QVERIFY(resource->isLoaded());
}

View file

@ -0,0 +1,23 @@
//
// ResourceTests.h
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ResourceTests_h
#define hifi_ResourceTests_h
#include <QtTest/QtTest>
class ResourceTests : public QObject {
Q_OBJECT
private slots:
void initTestCase();
void downloadFirst();
void downloadAgain();
};
#endif // hifi_ResourceTests_h

View file

@ -7,4 +7,6 @@ macro (setup_testcase_dependencies)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase(Script Network)
setup_memory_debugger()
setup_hifi_testcase(Script Network)

View file

@ -21,4 +21,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES)
copy_dlls_beside_windows_executable()
endmacro ()
setup_memory_debugger()
setup_hifi_testcase(Script)

View file

@ -9,4 +9,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
link_hifi_libraries(render-utils gpu shared)
message(${PROJECT_BINARY_DIR})
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -18,4 +18,7 @@ include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/")
include_directories("${PROJECT_BINARY_DIR}/../../libraries/model/")
message(${PROJECT_BINARY_DIR})
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -8,4 +8,6 @@ macro (setup_testcase_dependencies)
copy_dlls_beside_windows_executable()
endmacro ()
setup_hifi_testcase()
setup_memory_debugger()
setup_hifi_testcase()

View file

@ -13,4 +13,6 @@ endif()
# link in the shared libraries
link_hifi_libraries(ui render-utils gpu shared)
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -1,3 +1,5 @@
setup_memory_debugger()
# add the tool directories
add_subdirectory(mtc)
set_target_properties(mtc PROPERTIES FOLDER "Tools")
@ -7,4 +9,3 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools")
add_subdirectory(vhacd-util)
set_target_properties(vhacd-util PROPERTIES FOLDER "Tools")

View file

@ -1,4 +1,6 @@
set(TARGET_NAME mtc)
setup_hifi_project()
copy_dlls_beside_windows_executable()
setup_memory_debugger()
copy_dlls_beside_windows_executable()

View file

@ -1,2 +1,5 @@
set(TARGET_NAME scribe)
setup_hifi_project()
setup_memory_debugger()
setup_hifi_project()

View file

@ -8,6 +8,8 @@ find_package(VHACD REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES})
setup_memory_debugger()
if (UNIX AND NOT APPLE)
include(FindOpenMP)
if(OPENMP_FOUND)

View file

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