mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-12 08:42:31 +02:00
Merge branch 'master' of https://github.com/worklist/hifi into 20638
This commit is contained in:
commit
fd82f05307
83 changed files with 1341 additions and 975 deletions
|
@ -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 ()
|
||||
|
|
|
@ -18,4 +18,7 @@ if (UNIX)
|
|||
endif (UNIX)
|
||||
|
||||
include_application_version()
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
21
cmake/macros/MemoryDebugger.cmake
Normal file
21
cmake/macros/MemoryDebugger.cmake
Normal 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)
|
|
@ -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 ()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
82
examples/toys/basketball.js
Normal file
82
examples/toys/basketball.js
Normal 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
201
examples/toys/grenade.js
Normal 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);
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -88,4 +88,6 @@ if (ANDROID)
|
|||
|
||||
endif (ANDROID)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
setup_memory_debugger()
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -205,4 +205,6 @@ else (APPLE)
|
|||
endif()
|
||||
endif (APPLE)
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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 ()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
set(TARGET_NAME auto-updater)
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_library(Network)
|
||||
link_hifi_libraries(shared networking)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -6,4 +6,6 @@ macro (setup_testcase_dependencies)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro ()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase()
|
||||
|
|
|
@ -6,4 +6,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro ()
|
||||
|
||||
setup_hifi_testcase()
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -7,4 +7,6 @@ macro (setup_testcase_dependencies)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro()
|
||||
|
||||
setup_hifi_testcase()
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase()
|
||||
|
|
|
@ -7,4 +7,6 @@ macro (setup_testcase_dependencies)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro ()
|
||||
|
||||
setup_hifi_testcase()
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase()
|
||||
|
|
95
tests/networking/src/ResourceTests.cpp
Normal file
95
tests/networking/src/ResourceTests.cpp
Normal 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());
|
||||
|
||||
}
|
23
tests/networking/src/ResourceTests.h
Normal file
23
tests/networking/src/ResourceTests.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -21,4 +21,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro ()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase(Script)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -8,4 +8,6 @@ macro (setup_testcase_dependencies)
|
|||
copy_dlls_beside_windows_executable()
|
||||
endmacro ()
|
||||
|
||||
setup_hifi_testcase()
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_testcase()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
set(TARGET_NAME mtc)
|
||||
setup_hifi_project()
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
setup_memory_debugger()
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
set(TARGET_NAME scribe)
|
||||
setup_hifi_project()
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
setup_hifi_project()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue