Merge branch 'master' into 20422

Conflicts:
	examples/users.js
	interface/src/Application.cpp
This commit is contained in:
David Rowe 2015-03-31 21:01:54 -07:00
commit e06f28b7c5
104 changed files with 1574 additions and 559 deletions

View file

@ -26,6 +26,7 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets")
project(hifi)
add_definitions(-DGLM_FORCE_RADIANS)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
if (WIN32)
add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)

View file

@ -45,7 +45,7 @@ Agent::Agent(const QByteArray& packet) :
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
DependencyManager::set<ResouceCacheSharedItems>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<SoundCache>();
}

View file

@ -180,7 +180,7 @@ void AssignmentClientMonitor::readPendingDatagrams() {
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
if (!packetUUID.isNull()) {
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode
(packetUUID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false);
(packetUUID, NodeType::Unassigned, senderSockAddr, senderSockAddr, false, false);
AssignmentClientChildData *childData = new AssignmentClientChildData("unknown");
matchingNode->setLinkedData(childData);
} else {

View file

@ -149,7 +149,8 @@ void AvatarMixer::broadcastAvatarData() {
// about a given otherNode to this node
// FIXME does this mean we should sort the othernodes by distance before iterating
// over them?
float outputBandwidth = node->getOutboundBandwidth();
// float outputBandwidth =
node->getOutboundBandwidth();
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
@ -169,7 +170,7 @@ void AvatarMixer::broadcastAvatarData() {
return true;
},
[&](const SharedNodePointer& otherNode) {
AvatarMixerClientData* otherNodeData = otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
MutexTryLocker lock(otherNodeData->getMutex());
if (!lock.isLocked()) {
return;

View file

@ -20,6 +20,7 @@
#include <AccountManager.h>
#include <HTTPConnection.h>
#include <LogHandler.h>
#include <NetworkingConstants.h>
#include <UUID.h>
#include "../AssignmentClient.h"
@ -252,7 +253,7 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
// make sure the AccountManager has an Auth URL for payment redemptions
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
}
OctreeServer::~OctreeServer() {

View file

@ -19,7 +19,7 @@ if (WIN32)
${EXTERNAL_NAME}
URL https://bullet.googlecode.com/files/bullet-2.82-r2704.zip
URL_MD5 f5e8914fc9064ad32e0d62d19d33d977
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_DEMOS=0 -DUSE_GLUT=0
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_DEMOS=0 -DUSE_GLUT=0 -DUSE_DX11=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1

View file

@ -13,16 +13,28 @@ if (ANDROID)
BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/AndroidTBBLibCopy.cmake
INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
elseif (APPLE)
find_program(MAKE_COMMAND NAMES make DOC "Path to the make command")
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz
URL_MD5 bf090eaa86cf89ea014b7b462786a440
BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
)
else ()
if (APPLE)
set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_osx.tgz)
set(DOWNLOAD_MD5 25a36ebff070ff801760ec658079f6aa)
elseif (WIN32)
if (WIN32)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip)
set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440)
else ()
@ -46,7 +58,7 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (APPLE)
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/libc++")
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib")
set(_LIB_PREFIX "lib")
set(_LIB_EXT "dylib")
@ -95,14 +107,8 @@ elseif (UNIX)
endif ()
if (DEFINED _TBB_LIB_DIR)
if (NOT APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location")
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location")
else ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "TBB debug library location")
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG "" CACHE FILEPATH "TBB malloc debug library location")
endif ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location")
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location")
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb.${_LIB_EXT} CACHE FILEPATH "TBB release library location")
set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc.${_LIB_EXT} CACHE FILEPATH "TBB malloc release library location")
endif ()

View file

@ -10,9 +10,11 @@
#
# first find the so files in the source dir
set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/libc++)
set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
file(GLOB_RECURSE _TBB_LIBRARIES "${_TBB_LIBRARY_DIR}/*.dylib")
message(${_TBB_LIBRARIES})
# raise an error if we found none
if (NOT _TBB_LIBRARIES)
message(FATAL_ERROR "Did not find any TBB libraries")
@ -28,20 +30,6 @@ find_program(LIPO_COMMAND NAMES lipo DOC "Path to the lipo command")
foreach(_TBB_LIBRARY ${_TBB_LIBRARIES})
get_filename_component(_TBB_LIBRARY_FILENAME ${_TBB_LIBRARY} NAME)
set(_LIPO_ARGS -remove i386 ${_TBB_LIBRARY_FILENAME} -output ${_TBB_LIBRARY_FILENAME})
message(STATUS "${LIPO_COMMAND} ${_LIPO_ARGS}")
# first we use lipo to remove i386 from each dylib
execute_process(
COMMAND ${LIPO_COMMAND} ${_LIPO_ARGS}
WORKING_DIRECTORY ${_TBB_LIBRARY_DIR}
ERROR_VARIABLE _LIPO_ERROR
)
if (_LIPO_ERROR)
message(FATAL_ERROR "There was an error removing i386 for ${_TBB_LIBRARY_FILENAME} - ${_LIPO_ERROR}")
endif ()
set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_TBB_LIBRARY} ${_TBB_LIBRARY_FILENAME})
message(STATUS "${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}")

View file

@ -1,5 +1,5 @@
#
# AndroidTBBLibCopy.cmake
# TBBLibCopy.cmake
# cmake/externals/tbb
#
# Copyright 2015 High Fidelity, Inc.
@ -10,7 +10,7 @@
#
# first find the so files in the source dir
file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.so")
file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.${TBB_LIBS_SUFFIX}")
# raise an error if we found none
if (NOT _TBB_LIBRARIES)

View file

@ -95,6 +95,13 @@
"can_set": true
}
]
},
{
"name": "editors_are_rezzers",
"type": "checkbox",
"label": "Only editors can create new entities",
"help": "When checked, only those who can edit the domain can create new entites.",
"default": false
}
]
},

View file

@ -27,6 +27,7 @@
#include <HifiConfigVariantMap.h>
#include <HTTPConnection.h>
#include <LogUtils.h>
#include <NetworkingConstants.h>
#include <PacketHeaders.h>
#include <SettingHandle.h>
#include <SharedUtil.h>
@ -46,6 +47,7 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
DomainServer::DomainServer(int argc, char* argv[]) :
@ -181,7 +183,7 @@ bool DomainServer::optionallySetupOAuth() {
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = DEFAULT_NODE_AUTH_URL;
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL;
}
AccountManager& accountManager = AccountManager::getInstance();
@ -645,9 +647,23 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
bool canAdjustLocks = allowedEditors.isEmpty() || allowedEditors.contains(username);
const QVariant* editorsAreRezzersVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
bool onlyEditorsAreRezzers = false;
if (editorsAreRezzersVariant) {
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
}
bool canRez = true;
if (onlyEditorsAreRezzers) {
canRez = canAdjustLocks;
}
SharedNodePointer newNode =
DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr, canAdjustLocks);
publicSockAddr, localSockAddr,
canAdjustLocks, canRez);
// when the newNode is created the linked data is also created
// if this was a static assignment set the UUID, set the sendingSockAddr
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
@ -902,6 +918,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
broadcastDataStream << node->getUUID();
broadcastDataStream << node->getCanAdjustLocks();
broadcastDataStream << node->getCanRez();
int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos();

View file

@ -43,8 +43,8 @@ var floor = Entities.addEntity(
var edge1 = Entities.addEntity(
{ type: "Box",
position: Vec3.sum(center, { x: FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }),
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE },
color: { red: 128, green: 128, blue: 128 },
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS },
color: { red: 100, green: 100, blue: 100 },
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
visible: true,
@ -54,8 +54,8 @@ var edge1 = Entities.addEntity(
var edge2 = Entities.addEntity(
{ type: "Box",
position: Vec3.sum(center, { x: -FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }),
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE },
color: { red: 128, green: 128, blue: 128 },
dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS },
color: { red: 100, green: 100, blue: 100 },
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
visible: true,
@ -65,8 +65,8 @@ var edge2 = Entities.addEntity(
var edge3 = Entities.addEntity(
{ type: "Box",
position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: -FLOOR_SIZE / 2.0 }),
dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS },
color: { red: 128, green: 128, blue: 128 },
dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS },
color: { red: 100, green: 100, blue: 100 },
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
visible: true,
@ -76,8 +76,8 @@ var edge3 = Entities.addEntity(
var edge4 = Entities.addEntity(
{ type: "Box",
position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: FLOOR_SIZE / 2.0 }),
dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS },
color: { red: 128, green: 128, blue: 128 },
dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS },
color: { red: 100, green: 100, blue: 100 },
gravity: { x: 0, y: 0, z: 0 },
ignoreCollisions: false,
visible: true,
@ -97,6 +97,7 @@ for (var i = 0; i < NUM_BLOCKS; i++) {
dimensions: { x: type.x * SCALE, y: type.y * SCALE, z: type.z * SCALE },
color: { red: type.red, green: type.green, blue: type.blue },
gravity: { x: 0, y: GRAVITY, z: 0 },
velocity: { x: 0, y: 0.05, z: 0 },
ignoreCollisions: false,
damping: DAMPING,
lifetime: LIFETIME,
@ -104,6 +105,11 @@ for (var i = 0; i < NUM_BLOCKS; i++) {
}
function scriptEnding() {
Entities.editEntity(edge1, { locked: false });
Entities.editEntity(edge2, { locked: false });
Entities.editEntity(edge3, { locked: false });
Entities.editEntity(edge4, { locked: false });
Entities.editEntity(floor, { locked: false });
Entities.deleteEntity(edge1);
Entities.deleteEntity(edge2);
Entities.deleteEntity(edge3);

View file

@ -23,6 +23,8 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var rollSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/dice/diceRoll.wav");
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to create new objects."
var screenSize = Controller.getViewportDimensions();
var offButton = Overlays.addOverlay("image", {
x: screenSize.x - 48,
@ -45,23 +47,30 @@ var diceButton = Overlays.addOverlay("image", {
var GRAVITY = -3.5;
var LIFETIME = 300;
// NOTE: angularVelocity is in radians/sec
var MAX_ANGULAR_SPEED = Math.PI;
function shootDice(position, velocity) {
for (var i = 0; i < NUMBER_OF_DICE; i++) {
dice.push(Entities.addEntity(
{ type: "Model",
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
position: position,
velocity: velocity,
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
// NOTE: angularVelocity is in radians/sec
var maxAngularSpeed = Math.PI;
angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed },
lifetime: LIFETIME,
gravity: { x: 0, y: GRAVITY, z: 0 },
shapeType: "box",
collisionsWillMove: true
}));
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
if (!Entities.canRez()) {
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
} else {
for (var i = 0; i < NUMBER_OF_DICE; i++) {
dice.push(Entities.addEntity(
{ type: "Model",
modelURL: HIFI_PUBLIC_BUCKET + "models/props/Dice/goldDie.fbx",
position: position,
velocity: velocity,
rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360),
angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED,
y: Math.random() * MAX_ANGULAR_SPEED,
z: Math.random() * MAX_ANGULAR_SPEED },
lifetime: LIFETIME,
gravity: { x: 0, y: GRAVITY, z: 0 },
shapeType: "box",
collisionsWillMove: true
}));
position = Vec3.sum(position, Vec3.multiply(DIE_SIZE, Vec3.normalize(Quat.getRight(Camera.getOrientation()))));
}
}
}

View file

@ -718,13 +718,8 @@ function mouseClickEvent(event) {
var result = findClickedEntity(event);
if (result) {
var properties = Entities.getEntityProperties(result.entityID);
var data = {};
try {
data = JSON.parse(properties.attribution);
} catch (e) {
}
if (data.marketplaceID) {
propertyMenu.marketplaceID = data.marketplaceID;
if (properties.marketplaceID) {
propertyMenu.marketplaceID = properties.marketplaceID;
propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace");
} else {
propertyMenu.marketplaceID = null;
@ -1360,7 +1355,7 @@ var propertyMenu = PopupMenu();
propertyMenu.onSelectMenuItem = function(name) {
if (propertyMenu.marketplaceID) {
var url = "https://metaverse.highfidelity.io/marketplace/items/" + propertyMenu.marketplaceID;
var url = MARKETPLACE_URL + "/items/" + propertyMenu.marketplaceID;
if (marketplaceWindow.url != url) {
marketplaceWindow.setURL(url);
}

View file

@ -132,7 +132,7 @@
this.updateLightIDInUserData();
} else {
var that = this;
Script.setTimeout(function() { that.maybeUpdateLightIDInUserData() }, 500);
Script.setTimeout(function() { that.maybeUpdateLightIDInUserData() }, 500);
}
}
@ -213,6 +213,10 @@
this.preload = function(entityID) {
this.preOperation(entityID);
};
this.unload = function(){
Entities.deleteEntity(this.lightID);
}
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
this.preOperation(entityID);

View file

@ -70,6 +70,7 @@
var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0);
var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation()));
var housePositions = []
for (var j = 0; j < measures.rows; j++) {
var posX1 = 0 - (xRange / 2);
@ -87,11 +88,8 @@
y: 0,
z: dd
};
print("House nr.:" + (houses.length + 1));
houses.push(
addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd)
);
housePositions.push(Vec3.sum(housePos, posShift));
posX1 += measures.parcelWidth;
}
}
@ -144,14 +142,21 @@
};
}
function cleanup() {
while (houses.length > 0) {
if (!houses[0].isKnownID) {
houses[0] = Entities.identifyEntity(houses[0]);
}
Entities.deleteEntity(houses.shift());
var addHouses = function() {
if (housePositions.length > 0) {
position = housePositions.pop();
print("House nr.:" + (houses.length + 1));
houses.push(
addHouseAt(position, (housePositions.length % 2 == 0) ? rotEven : rotOdd)
);
// max 20 per second
Script.setTimeout(addHouses, 50);
} else {
Script.stop();
}
}
Script.scriptEnding.connect(cleanup);
};
addHouses();
})();

View file

@ -140,7 +140,6 @@
var elLifetime = document.getElementById("property-lifetime");
var elScriptURL = document.getElementById("property-script-url");
var elUserData = document.getElementById("property-user-data");
var elAttribution = document.getElementById("property-attribution");
var elBoxSections = document.querySelectorAll(".box-section");
var elBoxColorRed = document.getElementById("property-box-red");
@ -211,6 +210,9 @@
disableChildren(document.getElementById("properties-list"), 'input');
} else {
var activeElement = document.activeElement;
var selected = activeElement.selectionStart == 0 && activeElement.selectionEnd == activeElement.value.length;
var properties = data.selections[0].properties;
elID.innerHTML = properties.id;
@ -264,7 +266,6 @@
elLifetime.value = properties.lifetime;
elScriptURL.value = properties.script;
elUserData.value = properties.userData;
elAttribution.value = properties.attribution;
if (properties.type != "Box") {
for (var i = 0; i < elBoxSections.length; i++) {
@ -337,6 +338,11 @@
elLightExponent.value = properties.exponent;
elLightCutoff.value = properties.cutoff;
}
if (selected) {
activeElement.focus();
activeElement.select();
}
}
}
});
@ -395,7 +401,6 @@
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData'));
elAttribution.addEventListener('change', createEmitTextPropertyUpdateFunction('attribution'));
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
@ -630,13 +635,6 @@
</div>
</div>
<div class="property">
<div class="label">Attribution</div>
<div class="value">
<textarea id="property-attribution"></textarea>
</div>
</div>
<div class="box-section property">
<div class="label">Color</div>

View file

@ -92,11 +92,13 @@ var NotificationType = {
SNAPSHOT: 2,
WINDOW_RESIZE: 3,
LOD_WARNING: 4,
CONNECTION_REFUSED: 5,
properties: [
{ text: "Mute Toggle" },
{ text: "Snapshot" },
{ text: "Window Resize" },
{ text: "Level of Detail" }
{ text: "Level of Detail" },
{ text: "Connection Refused" }
],
getTypeFromMenuItem: function(menuItemName) {
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
@ -501,6 +503,10 @@ function onMuteStateChanged() {
createNotification(muteString, NotificationType.MUTE_TOGGLE);
}
function onDomainConnectionRefused(reason) {
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED );
}
// handles mouse clicks on buttons
function mousePressEvent(event) {
var pickRay,
@ -608,5 +614,6 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.update.connect(update);
Script.scriptEnding.connect(scriptEnding);
Menu.menuItemEvent.connect(menuItemEvent);
Window.domainConnectionRefused.connect(onDomainConnectionRefused);
setup();

View file

@ -15,6 +15,7 @@ var usersWindow = (function () {
WINDOW_WIDTH_2D = 160,
WINDOW_MARGIN_2D = 12,
WINDOW_BASE_MARGIN_2D = 6, // A little less is needed in order look correct
WINDOW_FONT_2D = { size: 12 },
WINDOW_FOREGROUND_COLOR_2D = { red: 240, green: 240, blue: 240 },
WINDOW_FOREGROUND_ALPHA_2D = 0.9,
@ -24,6 +25,14 @@ var usersWindow = (function () {
WINDOW_BACKGROUND_ALPHA_2D = 0.7,
windowPane2D,
windowHeading2D,
MINIMIZE_BUTTON_SVG = HIFI_PUBLIC_BUCKET + "images/tools/min-max-toggle.svg",
MINIMIZE_BUTTON_SVG_WIDTH = 17.1,
MINIMIZE_BUTTON_SVG_HEIGHT = 32.5,
MINIMIZE_BUTTON_WIDTH_2D = 14,
MINIMIZE_BUTTON_HEIGHT_2D = MINIMIZE_BUTTON_WIDTH_2D,
MINIMIZE_BUTTON_COLOR_2D = { red: 255, green: 255, blue: 255 },
MINIMIZE_BUTTON_ALPHA_2D = 0.9,
minimizeButton2D,
SCROLLBAR_BACKGROUND_WIDTH_2D = 12,
SCROLLBAR_BACKGROUND_COLOR_2D = { red: 80, green: 80, blue: 80 },
SCROLLBAR_BACKGROUND_ALPHA_2D = 0.8,
@ -74,6 +83,7 @@ var usersWindow = (function () {
MENU_ITEM_AFTER = "Chat...",
isVisible = true,
isMinimized = false,
viewportHeight,
isMirrorDisplay = false,
@ -97,14 +107,19 @@ var usersWindow = (function () {
nonUsersHeight,
maxWindowHeight;
if (isMinimized) {
windowHeight = windowTextHeight + WINDOW_MARGIN_2D + WINDOW_BASE_MARGIN_2D;
return;
}
// Reserve 5 lines for window heading plus visibility heading and controls
// Subtract windowLineSpacing for both end of user list and end of controls
nonUsersHeight = 5 * windowLineHeight - 2 * windowLineSpacing
+ FRIENDS_BUTTON_SPACER_2D + FRIENDS_BUTTON_HEIGHT_2D
+ VISIBILITY_SPACER_2D + 2 * WINDOW_MARGIN_2D;
+ VISIBILITY_SPACER_2D + WINDOW_MARGIN_2D + WINDOW_BASE_MARGIN_2D;
// Limit window to height of viewport minus VU meter and mirror if displayed
windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight;
windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight; // DJRTODO: - windowLineSpacing or not?
maxWindowHeight = viewportHeight - AUDIO_METER_HEIGHT;
if (isMirrorDisplay && !isFullscreenMirror) {
maxWindowHeight -= MIRROR_HEIGHT;
@ -112,7 +127,7 @@ var usersWindow = (function () {
windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight);
// Corresponding number of users to actually display
numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0);
numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0); // DJRTODO: .floor or .round?
isUsingScrollbars = 0 < numUsersToDisplay && numUsersToDisplay < linesOfUsers.length;
if (isUsingScrollbars) {
firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay));
@ -133,6 +148,10 @@ var usersWindow = (function () {
y: viewportHeight - windowHeight + WINDOW_MARGIN_2D
});
Overlays.editOverlay(minimizeButton2D, {
y: viewportHeight - windowHeight + WINDOW_MARGIN_2D / 2
});
scrollbarBackgroundPosition.y = viewportHeight - windowHeight + WINDOW_MARGIN_2D + windowTextHeight;
Overlays.editOverlay(scrollbarBackground2D, {
y: scrollbarBackgroundPosition.y
@ -145,14 +164,14 @@ var usersWindow = (function () {
Overlays.editOverlay(friendsButton2D, {
y: viewportHeight - FRIENDS_BUTTON_HEIGHT_2D - VISIBILITY_SPACER_2D
- 4 * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D
- 4 * windowLineHeight + windowLineSpacing - WINDOW_BASE_MARGIN_2D
});
Overlays.editOverlay(visibilityHeading2D, {
y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D
y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_BASE_MARGIN_2D
});
for (i = 0; i < visibilityControls2D.length; i += 1) {
y = viewportHeight - (3 - i) * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D;
y = viewportHeight - (3 - i) * windowLineHeight + windowLineSpacing - WINDOW_BASE_MARGIN_2D;
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { y: y });
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { y: y });
}
@ -182,29 +201,43 @@ var usersWindow = (function () {
reducedTextWidth,
i;
maxTextWidth = WINDOW_WIDTH_2D - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH_2D : 0) - 2 * WINDOW_MARGIN_2D;
ellipsisWidth = Overlays.textSize(windowPane2D, "...").width;
reducedTextWidth = maxTextWidth - ellipsisWidth;
if (!isMinimized) {
maxTextWidth = WINDOW_WIDTH_2D - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH_2D : 0) - 2 * WINDOW_MARGIN_2D;
ellipsisWidth = Overlays.textSize(windowPane2D, "...").width;
reducedTextWidth = maxTextWidth - ellipsisWidth;
for (i = 0; i < numUsersToDisplay; i += 1) {
user = usersOnline[linesOfUsers[firstUserToDisplay + i]];
userText = user.text;
textWidth = user.textWidth;
for (i = 0; i < numUsersToDisplay; i += 1) {
user = usersOnline[linesOfUsers[firstUserToDisplay + i]];
userText = user.text;
textWidth = user.textWidth;
if (textWidth > maxTextWidth) {
// Trim and append "..." to fit window width
maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width;
while (textWidth > reducedTextWidth) {
userText = userText.slice(0, -1);
textWidth = Overlays.textSize(windowPane2D, userText).width;
if (textWidth > maxTextWidth) {
// Trim and append "..." to fit window width
maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width;
while (textWidth > reducedTextWidth) {
userText = userText.slice(0, -1);
textWidth = Overlays.textSize(windowPane2D, userText).width;
}
userText += "...";
}
userText += "...";
displayText += "\n" + userText;
}
displayText += "\n" + userText;
}
displayText = displayText.slice(1); // Remove leading "\n".
displayText = displayText.slice(1); // Remove leading "\n".
scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2;
Overlays.editOverlay(scrollbarBackground2D, {
height: scrollbarBackgroundHeight,
visible: isUsingScrollbars
});
scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight,
SCROLLBAR_BAR_MIN_HEIGHT);
Overlays.editOverlay(scrollbarBar2D, {
height: scrollbarBarHeight,
visible: isUsingScrollbars
});
}
Overlays.editOverlay(windowPane2D, {
height: windowHeight,
@ -214,20 +247,6 @@ var usersWindow = (function () {
Overlays.editOverlay(windowHeading2D, {
text: linesOfUsers.length > 0 ? "Users online" : "No users online"
});
scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2;
Overlays.editOverlay(scrollbarBackground2D, {
height: scrollbarBackgroundHeight,
visible: isUsingScrollbars
});
scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight,
SCROLLBAR_BAR_MIN_HEIGHT);
Overlays.editOverlay(scrollbarBar2D, {
height: scrollbarBarHeight,
visible: isUsingScrollbars
});
updateOverlayPositions();
}
function pollUsers() {
@ -280,6 +299,7 @@ var usersWindow = (function () {
calculateWindowHeight();
updateUsersDisplay();
updateOverlayPositions();
} else {
print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText);
@ -296,9 +316,23 @@ var usersWindow = (function () {
usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay.
};
function setVisible(visible) {
function updateOverlayVisibility() {
var i;
Overlays.editOverlay(windowPane2D, { visible: isVisible });
Overlays.editOverlay(windowHeading2D, { visible: isVisible });
Overlays.editOverlay(minimizeButton2D, { visible: isVisible });
Overlays.editOverlay(scrollbarBackground2D, { visible: isVisible && isUsingScrollbars && !isMinimized });
Overlays.editOverlay(scrollbarBar2D, { visible: isVisible && isUsingScrollbars && !isMinimized });
Overlays.editOverlay(friendsButton2D, { visible: isVisible && !isMinimized });
Overlays.editOverlay(visibilityHeading2D, { visible: isVisible && !isMinimized });
for (i = 0; i < visibilityControls2D.length; i += 1) {
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible && !isMinimized });
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { visible: isVisible && !isMinimized });
}
}
function setVisible(visible) {
isVisible = visible;
if (isVisible) {
@ -310,16 +344,16 @@ var usersWindow = (function () {
usersTimer = null;
}
Overlays.editOverlay(windowPane2D, { visible: isVisible });
Overlays.editOverlay(windowHeading2D, { visible: isVisible });
Overlays.editOverlay(scrollbarBackground2D, { visible: isVisible && isUsingScrollbars });
Overlays.editOverlay(scrollbarBar2D, { visible: isVisible && isUsingScrollbars });
Overlays.editOverlay(friendsButton2D, { visible: isVisible });
Overlays.editOverlay(visibilityHeading2D, { visible: isVisible });
for (i = 0; i < visibilityControls2D.length; i += 1) {
Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible });
Overlays.editOverlay(visibilityControls2D[i].textOverlay, { visible: isVisible });
}
updateOverlayVisibility();
}
function setMinimized(minimized) {
isMinimized = minimized;
Overlays.editOverlay(minimizeButton2D, {
subImage: { y: isMinimized ? MINIMIZE_BUTTON_SVG_HEIGHT / 2 : 0 }
});
updateOverlayVisibility();
}
function onMenuItemEvent(event) {
@ -377,21 +411,33 @@ var usersWindow = (function () {
//print("Go to " + usersOnline[linesOfUsers[userClicked]].username);
location.goToUser(usersOnline[linesOfUsers[userClicked]].username);
}
return;
}
for (i = 0; i < visibilityControls2D.length; i += 1) {
// Don't need to test radioOverlay if it us under textOverlay.
if (clickedOverlay === visibilityControls2D[i].textOverlay && event.x <= visibilityControls2D[i].optionWidth) {
GlobalServices.findableBy = VISIBILITY_VALUES[i];
return;
}
}
if (clickedOverlay === minimizeButton2D) {
setMinimized(!isMinimized);
calculateWindowHeight();
updateOverlayPositions();
updateUsersDisplay();
return;
}
if (clickedOverlay === scrollbarBar2D) {
scrollbarBarClickedAt = (event.y - scrollbarBarPosition.y) / scrollbarBarHeight;
Overlays.editOverlay(scrollbarBar2D, {
backgroundAlpha: SCROLLBAR_BAR_SELECTED_ALPHA_2D
});
isMovingScrollbar = true;
return;
}
if (clickedOverlay === scrollbarBackground2D) {
@ -406,6 +452,7 @@ var usersWindow = (function () {
firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay));
updateOverlayPositions();
updateUsersDisplay();
return;
}
if (clickedOverlay === friendsButton2D) {
@ -504,7 +551,19 @@ var usersWindow = (function () {
backgroundAlpha: 0.0,
text: "No users online",
font: WINDOW_FONT_2D,
visible: isVisible
visible: isVisible && !isMinimized
});
minimizeButton2D = Overlays.addOverlay("image", {
x: WINDOW_WIDTH_2D - WINDOW_MARGIN_2D / 2 - MINIMIZE_BUTTON_WIDTH_2D,
y: viewportHeight,
width: MINIMIZE_BUTTON_WIDTH_2D,
height: MINIMIZE_BUTTON_HEIGHT_2D,
imageURL: MINIMIZE_BUTTON_SVG,
subImage: { x: 0, y: 0, width: MINIMIZE_BUTTON_SVG_WIDTH, height: MINIMIZE_BUTTON_SVG_HEIGHT / 2 },
color: MINIMIZE_BUTTON_COLOR_2D,
alpha: MINIMIZE_BUTTON_ALPHA_2D,
visible: isVisible && !isMinimized
});
scrollbarBackgroundPosition = {
@ -519,7 +578,7 @@ var usersWindow = (function () {
backgroundColor: SCROLLBAR_BACKGROUND_COLOR_2D,
backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA_2D,
text: "",
visible: isVisible && isUsingScrollbars
visible: isVisible && isUsingScrollbars && !isMinimized
});
scrollbarBarPosition = {
@ -534,7 +593,7 @@ var usersWindow = (function () {
backgroundColor: SCROLLBAR_BAR_COLOR_2D,
backgroundAlpha: SCROLLBAR_BAR_ALPHA_2D,
text: "",
visible: isVisible && isUsingScrollbars
visible: isVisible && isUsingScrollbars && !isMinimized
});
friendsButton2D = Overlays.addOverlay("image", {
@ -560,7 +619,7 @@ var usersWindow = (function () {
backgroundAlpha: 0.0,
text: "I am visible to:",
font: WINDOW_FONT_2D,
visible: isVisible
visible: isVisible && !isMinimized
});
myVisibility = GlobalServices.findableBy;
@ -584,7 +643,8 @@ var usersWindow = (function () {
height: RADIO_BUTTON_SVG_DIAMETER
},
color: WINDOW_HEADING_COLOR_2D,
alpha: WINDOW_FOREGROUND_ALPHA_2D
alpha: WINDOW_FOREGROUND_ALPHA_2D,
visible: isVisible && !isMinimized
}),
textOverlay: Overlays.addOverlay("text", {
x: WINDOW_MARGIN_2D,
@ -598,7 +658,7 @@ var usersWindow = (function () {
backgroundAlpha: 0.0,
text: optionText,
font: WINDOW_FONT_2D,
visible: isVisible
visible: isVisible && !isMinimized
}),
selected: myVisibility === VISIBILITY_VALUES[0]
}];
@ -656,6 +716,7 @@ var usersWindow = (function () {
Script.clearTimeout(usersTimer);
Overlays.deleteOverlay(windowPane2D);
Overlays.deleteOverlay(windowHeading2D);
Overlays.deleteOverlay(minimizeButton2D);
Overlays.deleteOverlay(scrollbarBackground2D);
Overlays.deleteOverlay(scrollbarBar2D);
Overlays.deleteOverlay(friendsButton2D);

View file

@ -68,6 +68,7 @@
#include <MainWindow.h>
#include <ModelEntityItem.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <OctalCode.h>
#include <OctreeSceneStats.h>
#include <PacketHeaders.h>
@ -76,7 +77,7 @@
#include <PhysicsEngine.h>
#include <ProgramObject.h>
#include <ResourceCache.h>
//#include <ScriptCache.h>
#include <ScriptCache.h>
#include <SettingHandle.h>
#include <SoundCache.h>
#include <TextRenderer.h>
@ -137,6 +138,13 @@
#include "ui/StandAloneJSConsole.h"
#include "ui/Stats.h"
// ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
#if defined(Q_OS_WIN)
extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}
#endif
using namespace std;
// Starfield information
@ -224,7 +232,7 @@ bool setupEssentials(int& argc, char** argv) {
auto addressManager = DependencyManager::set<AddressManager>();
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
auto geometryCache = DependencyManager::set<GeometryCache>();
//auto scriptCache = DependencyManager::set<ScriptCache>();
auto scriptCache = DependencyManager::set<ScriptCache>();
auto soundCache = DependencyManager::set<SoundCache>();
auto glowEffect = DependencyManager::set<GlowEffect>();
auto faceshift = DependencyManager::set<Faceshift>();
@ -244,7 +252,7 @@ bool setupEssentials(int& argc, char** argv) {
auto jsConsole = DependencyManager::set<StandAloneJSConsole>();
auto dialogsManager = DependencyManager::set<DialogsManager>();
auto bandwidthRecorder = DependencyManager::set<BandwidthRecorder>();
auto resouceCacheSharedItems = DependencyManager::set<ResouceCacheSharedItems>();
auto resourceCacheSharedItems = DependencyManager::set<ResourceCacheSharedItems>();
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
auto windowScriptingInterface = DependencyManager::set<WindowScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -301,6 +309,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_isVSyncOn(true),
_aboutToQuit(false),
_notifiedPacketVersionMismatchThisDomain(false),
_domainConnectionRefusals(QList<QString>()),
_editFriendsDialog(nullptr)
{
#ifdef Q_OS_WIN
@ -348,6 +357,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
// geometry background downloads need to happen on the Datagram Processor Thread. The idle loop will
// emit checkBackgroundDownloads to cause the GeometryCache to check it's queue for requested background
// downloads.
QSharedPointer<GeometryCache> geometryCacheP = DependencyManager::get<GeometryCache>();
ResourceCache *geometryCache = geometryCacheP.data();
connect(this, &Application::checkBackgroundDownloads, geometryCache, &ResourceCache::checkAsynchronousGets);
// connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _datagramProcessor, &DatagramProcessor::processDatagrams);
@ -414,7 +430,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
// once the event loop has started, check and signal for an access token
@ -604,6 +620,7 @@ Application::~Application() {
Menu::getInstance()->deleteLater();
_physicsEngine.setCharacterController(NULL);
_myAvatar = NULL;
ModelEntityItem::cleanupLoadedAnimations();
@ -615,7 +632,7 @@ Application::~Application() {
DependencyManager::destroy<AnimationCache>();
DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<GeometryCache>();
//DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCache>();
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
@ -641,6 +658,11 @@ void Application::initializeGL() {
}
#endif
qDebug() << "GL Version: " << QString((const char*) glGetString(GL_VERSION));
qDebug() << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
qDebug() << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR));
qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER));
#ifdef WIN32
GLenum err = glewInit();
if (GLEW_OK != err) {
@ -1519,7 +1541,7 @@ void Application::idle() {
}
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
// details normally.
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
@ -1560,6 +1582,9 @@ void Application::idle() {
idleTimer->start(2);
}
}
// check for any requested background downloads.
emit checkBackgroundDownloads();
}
void Application::setFullscreen(bool fullscreen) {
@ -2532,7 +2557,7 @@ bool Application::isHMDMode() const {
QRect Application::getDesirableApplicationGeometry() {
QRect applicationGeometry = getWindow()->geometry();
// If our parent window is on the HMD, then don't use it's geometry, instead use
// If our parent window is on the HMD, then don't use its geometry, instead use
// the "main screen" geometry.
HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog();
if (hmdTools && hmdTools->hasHMDScreen()) {
@ -3129,7 +3154,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
bool eyeRelativeCamera = false;
// bool eyeRelativeCamera = false;
if (billboard) {
_mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees
_mirrorCamera.setPosition(_myAvatar->getPosition() +
@ -3282,6 +3307,14 @@ void Application::clearDomainOctreeDetails() {
void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle();
clearDomainOctreeDetails();
_domainConnectionRefusals.clear();
}
void Application::domainConnectionDenied(const QString& reason) {
if (!_domainConnectionRefusals.contains(reason)) {
_domainConnectionRefusals.append(reason);
emit domainConnectionRefused(reason);
}
}
void Application::connectedToDomain(const QString& hostname) {
@ -3360,7 +3393,7 @@ void Application::nodeKilled(SharedNodePointer node) {
void Application::trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
// Attempt to identify the sender from its address.
if (sendingNode) {
QUuid nodeUUID = sendingNode->getUUID();
@ -3429,7 +3462,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
}
// store jurisdiction details for later use
// This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it
// but OctreeSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the
// but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the
// details from the OctreeSceneStats to construct the JurisdictionMap
JurisdictionMap jurisdictionMap;
jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes());
@ -3649,17 +3682,56 @@ bool Application::askToSetAvatarUrl(const QString& url) {
msgBox.exec();
return false;
}
QString message = "Would you like to use this model for part of avatar:\n" + url;
// Download the FST file, to attempt to determine its model type
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest);
qDebug() << "Downloading avatar file at " << url;
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QByteArray fstContents = reply->readAll();
delete reply;
QVariantHash fstMapping = FSTReader::readMapping(fstContents);
FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping);
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Question);
msgBox.setWindowTitle("Set Avatar");
msgBox.setText(message);
QPushButton* headButton = NULL;
QPushButton* bodyButton = NULL;
QPushButton* bodyAndHeadButton = NULL;
QString message;
QString typeInfo;
switch (modelType) {
case FSTReader::HEAD_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar head?");
headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
QPushButton* headButton = msgBox.addButton(tr("Head"), QMessageBox::ActionRole);
QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole);
QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole);
case FSTReader::BODY_ONLY_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?");
bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
case FSTReader::HEAD_AND_BODY_MODEL:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar?");
bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole);
break;
default:
message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for some part of your avatar head?");
headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole);
bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole);
bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole);
break;
}
msgBox.setText(message);
msgBox.addButton(QMessageBox::Cancel);
msgBox.exec();
@ -3672,6 +3744,11 @@ bool Application::askToSetAvatarUrl(const QString& url) {
} else if (msgBox.clickedButton() == bodyButton) {
qDebug() << "Chose to use for body: " << url;
_myAvatar->setSkeletonModelURL(url);
// if the head is empty, reset it to the default head.
if (_myAvatar->getFaceModelURLString().isEmpty()) {
_myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL);
UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString());
}
UserActivityLogger::getInstance().changedModel("skeleton", url);
_myAvatar->sendIdentityPacket();
} else if (msgBox.clickedButton() == bodyAndHeadButton) {

View file

@ -333,6 +333,9 @@ signals:
void svoImportRequested(const QString& url);
void checkBackgroundDownloads();
void domainConnectionRefused(const QString& reason);
public slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
@ -385,6 +388,8 @@ public slots:
void setActiveFaceTracker();
void domainConnectionDenied(const QString& reason);
private slots:
void clearDomainOctreeDetails();
void checkFPS();
@ -610,6 +615,8 @@ private:
int _menuBarHeight;
QHash<QString, AcceptURLMethod> _acceptedExtensions;
QList<QString> _domainConnectionRefusals;
};
#endif // hifi_Application_h

View file

@ -127,6 +127,7 @@ void DatagramProcessor::processDatagrams() {
// and check and signal for an access token so that we can make sure they are logged in
qDebug() << "The domain-server denied a connection request: " << reason;
qDebug() << "You may need to re-log to generate a keypair so you can provide a username signature.";
application->domainConnectionDenied(reason);
AccountManager::getInstance().checkAndSignalForAccessToken();
break;
}

View file

@ -253,7 +253,7 @@ namespace MenuOption {
const QString ToolWindow = "Tool Window";
const QString TransmitterDrive = "Transmitter Drive";
const QString TurnWithHead = "Turn using Head";
const QString PackageModel = "Package Model";
const QString PackageModel = "Package Model...";
const QString Visage = "Visage";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";

View file

@ -85,7 +85,7 @@ bool ModelPackager::loadModel() {
return false;
}
qDebug() << "Reading FST file : " << _modelFile.filePath();
_mapping = readMapping(fst.readAll());
_mapping = FSTReader::readMapping(fst.readAll());
fst.close();
_fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString());
@ -119,21 +119,23 @@ bool ModelPackager::editProperties() {
return false;
}
_mapping = properties.getMapping();
// Make sure that a mapping for the root joint has been specified
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointRoot")) {
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
// Make sure that a mapping for the root joint has been specified
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointRoot")) {
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled.";
QMessageBox msgBox;
msgBox.setWindowTitle("Model Upload");
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
QMessageBox msgBox;
msgBox.setWindowTitle("Model Packager");
msgBox.setText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setIcon(QMessageBox::Warning);
msgBox.exec();
return false;
return false;
}
}
return true;
@ -183,7 +185,7 @@ bool ModelPackager::zipModel() {
// Copy FST
QFile fst(tempDir.path() + "/" + nameField + ".fst");
if (fst.open(QIODevice::WriteOnly)) {
fst.write(writeMapping(_mapping));
fst.write(FSTReader::writeMapping(_mapping));
fst.close();
} else {
qDebug() << "Couldn't write FST file" << fst.fileName();
@ -204,6 +206,18 @@ bool ModelPackager::zipModel() {
}
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) {
bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL;
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
geometry.blendshapeChannelNames.contains("MouthOpen") &&
geometry.blendshapeChannelNames.contains("Blink_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Right") &&
geometry.blendshapeChannelNames.contains("Squint_Right"));
if (!mapping.contains(NAME_FIELD)) {
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
}
@ -232,39 +246,40 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
if (!joints.contains("jointNeck")) {
joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
}
if (!joints.contains("jointRoot")) {
joints.insert("jointRoot", "Hips");
}
if (!joints.contains("jointLean")) {
joints.insert("jointLean", "Spine");
if (isBodyType) {
if (!joints.contains("jointRoot")) {
joints.insert("jointRoot", "Hips");
}
if (!joints.contains("jointLean")) {
joints.insert("jointLean", "Spine");
}
if (!joints.contains("jointLeftHand")) {
joints.insert("jointLeftHand", "LeftHand");
}
if (!joints.contains("jointRightHand")) {
joints.insert("jointRightHand", "RightHand");
}
}
if (!joints.contains("jointHead")) {
const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd";
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
}
if (!joints.contains("jointLeftHand")) {
joints.insert("jointLeftHand", "LeftHand");
}
if (!joints.contains("jointRightHand")) {
joints.insert("jointRightHand", "RightHand");
}
mapping.insert(JOINT_FIELD, joints);
if (!mapping.contains(FREE_JOINT_FIELD)) {
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
if (isBodyType) {
if (!mapping.contains(FREE_JOINT_FIELD)) {
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
}
}
// mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
geometry.blendshapeChannelNames.contains("MouthOpen") &&
geometry.blendshapeChannelNames.contains("Blink_Left") &&
geometry.blendshapeChannelNames.contains("Blink_Right") &&
geometry.blendshapeChannelNames.contains("Squint_Right"));
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
// then we can add the default mixamo to "faceshift" mappings
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
QVariantHash blendshapes;
blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);

View file

@ -35,7 +35,7 @@ private:
QFileInfo _modelFile;
QFileInfo _fbxInfo;
ModelType _modelType;
FSTReader::ModelType _modelType;
QString _texDir;
QVariantHash _mapping;

View file

@ -25,7 +25,7 @@
#include "ModelPropertiesDialog.h"
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry) :
_modelType(modelType),
_originalMapping(originalMapping),
@ -46,8 +46,8 @@ _geometry(geometry)
_scale->setMaximum(FLT_MAX);
_scale->setSingleStep(0.01);
if (_modelType != ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
QHBoxLayout* translation = new QHBoxLayout();
form->addRow("Translation:", translation);
translation->addWidget(_translationX = createTranslationBox());
@ -63,7 +63,7 @@ _geometry(geometry)
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
form->addRow("Neck Joint:", _neckJoint = createJointBox());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
form->addRow("Root Joint:", _rootJoint = createJointBox());
form->addRow("Lean Joint:", _leanJoint = createJointBox());
form->addRow("Head Joint:", _headJoint = createJointBox());
@ -89,8 +89,14 @@ _geometry(geometry)
reset();
}
QString ModelPropertiesDialog::getType() const {
return FSTReader::getNameFromType(_modelType);
}
QVariantHash ModelPropertiesDialog::getMapping() const {
QVariantHash mapping = _originalMapping;
mapping.insert(TYPE_FIELD, getType());
mapping.insert(NAME_FIELD, _name->text());
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
@ -102,9 +108,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
}
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
if (_modelType != ENTITY_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
glm::vec3 pivot;
if (_pivotAboutCenter->isChecked()) {
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
@ -121,7 +127,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
insertJointMapping(joints, "jointHead", _headJoint->currentText());
@ -151,8 +159,8 @@ void ModelPropertiesDialog::reset() {
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
if (_modelType != ENTITY_MODEL) {
if (_modelType == ATTACHMENT_MODEL) {
if (_modelType != FSTReader::ENTITY_MODEL) {
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
@ -164,7 +172,8 @@ void ModelPropertiesDialog::reset() {
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
}
if (_modelType == SKELETON_MODEL) {
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
setJointText(_leanJoint, jointHash.value("jointLean").toString());
setJointText(_headJoint, jointHash.value("jointHead").toString());

View file

@ -15,6 +15,7 @@
#include <QDialog>
#include <FBXReader.h>
#include <FSTReader.h>
#include "ui/ModelsBrowser.h"
@ -28,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
Q_OBJECT
public:
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
const QString& basePath, const FBXGeometry& geometry);
QVariantHash getMapping() const;
@ -43,8 +44,9 @@ private:
QComboBox* createJointBox(bool withNone = true) const;
QDoubleSpinBox* createTranslationBox() const;
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
QString getType() const;
ModelType _modelType;
FSTReader::ModelType _modelType;
QVariantHash _originalMapping;
QString _basePath;
FBXGeometry _geometry;

View file

@ -18,8 +18,9 @@
#include "ModelSelector.h"
static const QString AVATAR_HEAD_STRING = "Avatar Head";
static const QString AVATAR_BODY_STRING = "Avatar Body";
static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
static const QString AVATAR_BODY_STRING = "Avatar Body Only";
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
static const QString ENTITY_MODEL_STRING = "Entity Model";
@ -36,6 +37,7 @@ ModelSelector::ModelSelector() {
_modelType = new QComboBox(this);
_modelType->addItem(AVATAR_HEAD_STRING);
_modelType->addItem(AVATAR_BODY_STRING);
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
_modelType->addItem(ENTITY_MODEL_STRING);
form->addRow("Model Type:", _modelType);
@ -50,17 +52,19 @@ QFileInfo ModelSelector::getFileInfo() const {
return _modelFile;
}
ModelType ModelSelector::getModelType() const {
FSTReader::ModelType ModelSelector::getModelType() const {
QString text = _modelType->currentText();
if (text == AVATAR_HEAD_STRING) {
return HEAD_MODEL;
return FSTReader::HEAD_MODEL;
} else if (text == AVATAR_BODY_STRING) {
return SKELETON_MODEL;
return FSTReader::BODY_ONLY_MODEL;
} else if (text == AVATAR_HEAD_AND_BODY_STRING) {
return FSTReader::HEAD_AND_BODY_MODEL;
} else if (text == AVATAR_ATTACHEMENT_STRING) {
return ATTACHMENT_MODEL;
return FSTReader::ATTACHMENT_MODEL;
} else if (text == ENTITY_MODEL_STRING) {
return ENTITY_MODEL;
return FSTReader::ENTITY_MODEL;
} else {
Q_UNREACHABLE();
}

View file

@ -29,7 +29,7 @@ public:
ModelSelector();
QFileInfo getFileInfo() const;
ModelType getModelType() const;
FSTReader::ModelType getModelType() const;
public slots:
virtual void accept();

View file

@ -685,6 +685,10 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
* joint.preTransform * glm::mat4_cast(modifiedRotation) * joint.postTransform;
}
// Each joint contributes its point to the bounding box
glm::vec3 jointPosition = extractTranslation(transforms[i]);
totalExtents.addPoint(jointPosition);
Shape* shape = _shapes[i];
if (!shape) {
continue;
@ -694,8 +698,6 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
// that contains the sphere centered at the end of the joint with radius of the bone.
// TODO: skip hand and arm shapes for bounding box calculation
glm::vec3 jointPosition = extractTranslation(transforms[i]);
int type = shape->getType();
if (type == CAPSULE_SHAPE) {
// add the two furthest surface points of the capsule

View file

@ -353,7 +353,7 @@ void InputController::update() {
// TODO for now the InputController is only supporting a JointTracker from a MotionTracker
MotionTracker* motionTracker = dynamic_cast< MotionTracker*> (DeviceTracker::getDevice(_deviceTrackerId));
if (motionTracker) {
if (_subTrackerId < motionTracker->numJointTrackers()) {
if ((int)_subTrackerId < motionTracker->numJointTrackers()) {
const MotionTracker::JointTracker* joint = motionTracker->getJointTracker(_subTrackerId);
if (joint->isActive()) {

View file

@ -33,6 +33,7 @@ WindowScriptingInterface::WindowScriptingInterface() :
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged);
connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
connect(Application::getInstance(), &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
}
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) {
@ -637,7 +638,7 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS
/// \param const QString& nameFilter filter to filter filenames
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) {
ModelsBrowser browser(ENTITY_MODEL);
ModelsBrowser browser(FSTReader::ENTITY_MODEL);
if (nameFilter != "") {
browser.setNameFilter(nameFilter);
}

View file

@ -62,6 +62,7 @@ signals:
void inlineButtonClicked(const QString& name);
void nonBlockingFormClosed();
void svoImportRequested(const QString& url);
void domainConnectionRefused(const QString& reason);
private slots:
QScriptValue showAlert(const QString& message);

View file

@ -139,6 +139,7 @@ ApplicationOverlay::ApplicationOverlay() :
_magnifier(true),
_alpha(1.0f),
_oculusUIRadius(1.0f),
_trailingAudioLoudness(0.0f),
_crosshairTexture(0),
_previousBorderWidth(-1),
_previousBorderHeight(-1),

View file

@ -164,7 +164,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
}
void AttachmentPanel::chooseModelURL() {
ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this);
ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this);
connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&)));
modelBrowser.browse();
}

View file

@ -13,7 +13,7 @@
#include <qwebview.h>
#include <AccountManager.h>
#include <LimitedNodeList.h>
#include <NetworkingConstants.h>
#include "Application.h"
#include "DataWebPage.h"
@ -39,7 +39,7 @@ DataWebDialog* DataWebDialog::dialogForPath(const QString& path,
connect(dialogWebView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
dialogWebView, &DataWebDialog::addJavascriptObjectsToWindow);
QUrl dataWebUrl(DEFAULT_NODE_AUTH_URL);
QUrl dataWebUrl(NetworkingConstants::METAVERSE_SERVER_URL);
dataWebUrl.setPath(path);
qDebug() << "Opening a data web dialog for" << dataWebUrl.toString();

View file

@ -14,6 +14,7 @@
#include <QPushButton>
#include <QPixmap>
#include <NetworkingConstants.h>
#include <PathUtils.h>
#include "Application.h"
@ -23,7 +24,7 @@
#include "LoginDialog.h"
#include "UIUtil.h"
const QString FORGOT_PASSWORD_URL = "https://metaverse.highfidelity.com/users/password/new";
const QString FORGOT_PASSWORD_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/users/password/new";
LoginDialog::LoginDialog(QWidget* parent) :
FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP),

View file

@ -28,7 +28,7 @@
#include "ModelsBrowser.h"
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "attachments" };
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" };
static const QString S3_URL = "http://s3.amazonaws.com/hifi-public";
static const QString PUBLIC_URL = "http://public.highfidelity.io";
@ -71,7 +71,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = {
"Tags"
};
ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
QWidget(parent, Qt::WindowStaysOnTopHint),
_handler(new ModelHandler(modelsType))
{
@ -184,7 +184,7 @@ void ModelsBrowser::browse() {
}
ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) :
ModelHandler::ModelHandler(FSTReader::ModelType modelsType, QWidget* parent) :
QObject(parent),
_initiateExit(false),
_type(modelsType),

View file

@ -16,21 +16,16 @@
#include <QStandardItemModel>
#include <QTreeView>
class QNetworkReply;
#include <FSTReader.h>
enum ModelType {
ENTITY_MODEL,
HEAD_MODEL,
SKELETON_MODEL,
ATTACHMENT_MODEL
};
class QNetworkReply;
extern const char* MODEL_TYPE_NAMES[];
class ModelHandler : public QObject {
Q_OBJECT
public:
ModelHandler(ModelType modelsType, QWidget* parent = NULL);
ModelHandler(FSTReader::ModelType modelsType, QWidget* parent = NULL);
void lockModel() { _lock.lockForRead(); }
QStandardItemModel* getModel() { return &_model; }
@ -51,7 +46,7 @@ private slots:
private:
bool _initiateExit;
ModelType _type;
FSTReader::ModelType _type;
QReadWriteLock _lock;
QStandardItemModel _model;
QString _nameFilter;
@ -66,7 +61,7 @@ class ModelsBrowser : public QWidget {
Q_OBJECT
public:
ModelsBrowser(ModelType modelsType, QWidget* parent = NULL);
ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent = NULL);
QString getSelectedFile() { return _selectedFile; }
signals:

View file

@ -67,13 +67,13 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
}
void PreferencesDialog::openHeadModelBrowser() {
ModelsBrowser modelBrowser(HEAD_MODEL);
ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
modelBrowser.browse();
}
void PreferencesDialog::openBodyModelBrowser() {
ModelsBrowser modelBrowser(SKELETON_MODEL);
ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
modelBrowser.browse();
}

View file

@ -88,9 +88,9 @@ template< typename T >
void AudioFrameBuffer< T >::deallocateFrames() {
if (_frameBuffer) {
for (uint32_t i = 0; i < _channelCountMax; ++i) {
delete _frameBuffer[i];
delete[] _frameBuffer[i];
}
delete _frameBuffer;
delete[] _frameBuffer;
}
_frameBuffer = NULL;
}

View file

@ -88,7 +88,7 @@ public:
}
void loadProfile(int profileIndex) {
if (profileIndex >= 0 && profileIndex < _profileCount) {
if (profileIndex >= 0 && profileIndex < (int)_profileCount) {
for (uint32_t i = 0; i < _filterCount; ++i) {
FilterParameter p = _profiles[profileIndex][i];

View file

@ -101,7 +101,7 @@ void EntityTreeRenderer::init() {
_lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE);
connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity);
connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::checkAndCallPreload);
connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity);
connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::entitySciptChanging);
connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID);
}
@ -110,14 +110,30 @@ void EntityTreeRenderer::shutdown() {
_shuttingDown = true;
}
void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
if (_waitingOnPreload.contains(url)) {
QList<EntityItemID> entityIDs = _waitingOnPreload.values(url);
_waitingOnPreload.remove(url);
foreach(EntityItemID entityID, entityIDs) {
checkAndCallPreload(entityID);
}
}
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) {
if (_waitingOnPreload.contains(url)) {
_waitingOnPreload.remove(url);
}
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload) {
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
return loadEntityScript(entity);
return loadEntityScript(entity, isPreload);
}
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL) {
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut) {
isPending = false;
QUrl url(scriptMaybeURLorText);
// If the url is not valid, this must be script text...
@ -126,6 +142,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
return scriptMaybeURLorText;
}
isURL = true;
urlOut = url;
QString scriptContents; // assume empty
@ -148,20 +165,11 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
qDebug() << "ERROR Loading file:" << fileName;
}
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest);
qDebug() << "Downloading script at" << url;
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
scriptContents = reply->readAll();
} else {
qDebug() << "ERROR Loading file:" << url.toString();
auto scriptCache = DependencyManager::get<ScriptCache>();
if (!scriptCache->isInBadScriptList(url)) {
scriptContents = scriptCache->getScript(url, this, isPending);
}
delete reply;
}
}
@ -169,7 +177,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
}
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity, bool isPreload) {
if (_shuttingDown) {
return QScriptValue(); // since we're shutting down, we don't load any more scripts
}
@ -185,7 +193,7 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
// can accomplish all we need to here with just the script "text" and the ID.
EntityItemID entityID = entity->getEntityItemID();
QString entityScript = entity->getScript();
if (_entityScripts.contains(entityID)) {
EntityScriptDetails details = _entityScripts[entityID];
@ -203,7 +211,23 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
}
bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text.
QString scriptContents = loadScriptContents(entityScript, isURL);
bool isPending = false;
QUrl url;
QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url);
if (isPending && isPreload && isURL) {
_waitingOnPreload.insert(url, entityID);
}
auto scriptCache = DependencyManager::get<ScriptCache>();
if (isURL && scriptCache->isInBadScriptList(url)) {
return QScriptValue(); // no script contents...
}
if (scriptContents.isEmpty()) {
return QScriptValue(); // no script contents...
}
QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents);
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
@ -211,6 +235,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
qDebug() << " " << syntaxCheck.errorMessage() << ":"
<< syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber();
qDebug() << " SCRIPT:" << entityScript;
scriptCache->addScriptToBadScriptList(url);
return QScriptValue(); // invalid script
}
@ -223,6 +250,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " NOT CONSTRUCTOR";
qDebug() << " SCRIPT:" << entityScript;
scriptCache->addScriptToBadScriptList(url);
return QScriptValue(); // invalid script
} else {
entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
@ -910,6 +940,10 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
_entityScripts.remove(entityID);
}
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
checkAndCallPreload(entityID);
}
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) {
if (_tree && !_shuttingDown) {
checkAndCallUnload(entityID);
@ -920,7 +954,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) {
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) {
if (_tree && !_shuttingDown) {
// load the entity script if needed...
QScriptValue entityScript = loadEntityScript(entityID);
QScriptValue entityScript = loadEntityScript(entityID, true); // is preload!
if (entityScript.property("preload").isValid()) {
QScriptValueList entityArgs = createEntityArgs(entityID);
entityScript.property("preload").call(entityScript, entityArgs);

View file

@ -16,6 +16,7 @@
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
#include <MouseEvent.h>
#include <OctreeRenderer.h>
#include <ScriptCache.h>
class Model;
class ScriptEngine;
@ -31,7 +32,7 @@ public:
};
// Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser {
Q_OBJECT
public:
EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState,
@ -84,6 +85,9 @@ public:
/// hovering over, and entering entities
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
virtual void errorInLoadingScript(const QUrl& url);
signals:
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
@ -101,6 +105,7 @@ signals:
void leaveEntity(const EntityItemID& entityItemID);
public slots:
void addingEntity(const EntityItemID& entityID);
void deletingEntity(const EntityItemID& entityID);
void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID);
void entitySciptChanging(const EntityItemID& entityID);
@ -138,10 +143,10 @@ private:
ScriptEngine* _entitiesScriptEngine;
ScriptEngine* _sandboxScriptEngine;
QScriptValue loadEntityScript(EntityItem* entity);
QScriptValue loadEntityScript(const EntityItemID& entityItemID);
QScriptValue loadEntityScript(EntityItem* entity, bool isPreload = false);
QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false);
QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID);
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL);
QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url);
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent);
@ -157,6 +162,8 @@ private:
bool _dontDoPrecisionPicking;
bool _shuttingDown = false;
QMultiMap<QUrl, EntityItemID> _waitingOnPreload;
};

View file

@ -61,7 +61,7 @@ void RenderableModelEntityItem::remapTextures() {
}
if (!_model->isLoadedWithTextures()) {
return; // nothing to do if the model has not yet loaded it's default textures
return; // nothing to do if the model has not yet loaded its default textures
}
if (!_originalTexturesRead && _model->isLoadedWithTextures()) {
@ -220,7 +220,7 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
// if we have a URL, then we will want to end up returning a model...
if (!getModelURL().isEmpty()) {
// if we have a previously allocated model, but it's URL doesn't match
// if we have a previously allocated model, but its URL doesn't match
// then we need to let our renderer update our model for us.
if (_model && QUrl(getModelURL()) != _model->getURL()) {
result = _model = _myRenderer->updateModel(_model, getModelURL(), getCollisionModelURL());
@ -266,6 +266,26 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, extraInfo, precisionPicking);
}
void RenderableModelEntityItem::setCollisionModelURL(const QString& url) {
ModelEntityItem::setCollisionModelURL(url);
if (_model) {
_model->setCollisionModelURL(QUrl(url));
}
}
bool RenderableModelEntityItem::hasCollisionModel() const {
if (_model) {
return ! _model->getCollisionURL().isEmpty();
} else {
return !_collisionModelURL.isEmpty();
}
}
const QString& RenderableModelEntityItem::getCollisionModelURL() const {
// assert (!_model || _collisionModelURL == _model->getCollisionURL().toString());
return _collisionModelURL;
}
bool RenderableModelEntityItem::isReadyToComputeShape() {
if (!_model) {
@ -273,7 +293,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
}
if (_model->getCollisionURL().isEmpty()) {
// no model url, so we're ready to compute a shape.
// no collision-model url, so we're ready to compute a shape (of type None).
return true;
}
@ -292,15 +312,100 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
info.setParams(getShapeType(), 0.5f * getDimensions());
} else {
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry();
const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry();
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
const FBXGeometry& renderGeometry = renderNetworkGeometry->getFBXGeometry();
_points.clear();
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
_points << mesh.vertices;
unsigned int i = 0;
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
// to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case.
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
// each meshPart is a convex hull
foreach (const FBXMeshPart &meshPart, mesh.parts) {
QVector<glm::vec3> pointsInPart;
// run through all the triangles and (uniquely) add each point to the hull
unsigned int triangleCount = meshPart.triangleIndices.size() / 3;
for (unsigned int j = 0; j < triangleCount; j++) {
unsigned int p0Index = meshPart.triangleIndices[j*3];
unsigned int p1Index = meshPart.triangleIndices[j*3+1];
unsigned int p2Index = meshPart.triangleIndices[j*3+2];
glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
if (!pointsInPart.contains(p0)) {
pointsInPart << p0;
}
if (!pointsInPart.contains(p1)) {
pointsInPart << p1;
}
if (!pointsInPart.contains(p2)) {
pointsInPart << p2;
}
}
// run through all the quads and (uniquely) add each point to the hull
unsigned int quadCount = meshPart.quadIndices.size() / 4;
assert((unsigned int)meshPart.quadIndices.size() == quadCount*4);
for (unsigned int j = 0; j < quadCount; j++) {
unsigned int p0Index = meshPart.quadIndices[j*4];
unsigned int p1Index = meshPart.quadIndices[j*4+1];
unsigned int p2Index = meshPart.quadIndices[j*4+2];
unsigned int p3Index = meshPart.quadIndices[j*4+3];
glm::vec3 p0 = mesh.vertices[p0Index];
glm::vec3 p1 = mesh.vertices[p1Index];
glm::vec3 p2 = mesh.vertices[p2Index];
glm::vec3 p3 = mesh.vertices[p3Index];
if (!pointsInPart.contains(p0)) {
pointsInPart << p0;
}
if (!pointsInPart.contains(p1)) {
pointsInPart << p1;
}
if (!pointsInPart.contains(p2)) {
pointsInPart << p2;
}
if (!pointsInPart.contains(p3)) {
pointsInPart << p3;
}
}
if (pointsInPart.size() == 0) {
qDebug() << "Warning -- meshPart has no faces";
continue;
}
// add next convex hull
QVector<glm::vec3> newMeshPoints;
_points << newMeshPoints;
// add points to the new convex hull
_points[i++] << pointsInPart;
}
}
info.setParams(getShapeType(), 0.5f * getDimensions(), _collisionModelURL);
info.setConvexHull(_points);
// We expect that the collision model will have the same units and will be displaced
// from its origin in the same way the visual model is. The visual model has
// been centered and probably scaled. We take the scaling and offset which were applied
// to the visual model and apply them to the collision model (without regard for the
// collision model's extents).
glm::vec3 scale = _dimensions / renderGeometry.getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine
for (int i = 0; i < _points.size(); i++) {
for (int j = 0; j < _points[i].size(); j++) {
// compensate for registraion
_points[i][j] += _model->getOffset();
// scale so the collision points match the model points
_points[i][j] *= scale;
}
}
info.setParams(getShapeType(), _dimensions, _collisionModelURL);
info.setConvexHulls(_points);
}
}
@ -308,7 +413,9 @@ ShapeType RenderableModelEntityItem::getShapeType() const {
// XXX make hull an option in edit.js ?
if (!_model || _model->getCollisionURL().isEmpty()) {
return _shapeType;
} else {
} else if (_points.size() == 1) {
return SHAPE_TYPE_CONVEX_HULL;
} else {
return SHAPE_TYPE_COMPOUND;
}
}

View file

@ -52,6 +52,10 @@ public:
bool needsToCallUpdate() const;
virtual void setCollisionModelURL(const QString& url);
virtual bool hasCollisionModel() const;
virtual const QString& getCollisionModelURL() const;
bool isReadyToComputeShape();
void computeShapeInfo(ShapeInfo& info);
ShapeType getShapeType() const;
@ -66,7 +70,7 @@ private:
QString _currentTextures;
QStringList _originalTextures;
bool _originalTexturesRead;
QVector<glm::vec3> _points;
QVector<QVector<glm::vec3>> _points;
};
#endif // hifi_RenderableModelEntityItem_h

View file

@ -57,7 +57,7 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_collisionsWillMove = ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE;
_locked = ENTITY_ITEM_DEFAULT_LOCKED;
_userData = ENTITY_ITEM_DEFAULT_USER_DATA;
_attribution = ENTITY_ITEM_DEFAULT_ATTRIBUTION;
_marketplaceID = ENTITY_ITEM_DEFAULT_MARKETPLACE_ID;
}
EntityItem::EntityItem(const EntityItemID& entityItemID) {
@ -117,7 +117,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_COLLISIONS_WILL_MOVE;
requestedProperties += PROP_LOCKED;
requestedProperties += PROP_USER_DATA;
requestedProperties += PROP_ATTRIBUTION;
requestedProperties += PROP_MARKETPLACE_ID;
return requestedProperties;
}
@ -240,7 +240,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, appendValue, getCollisionsWillMove());
APPEND_ENTITY_PROPERTY(PROP_LOCKED, appendValue, getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, appendValue, getUserData());
APPEND_ENTITY_PROPERTY(PROP_ATTRIBUTION, appendValue, getAttribution());
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, getMarketplaceID());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
@ -555,8 +555,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked);
READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA, setUserData);
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_ATTRIBUTION) {
READ_ENTITY_PROPERTY_STRING(PROP_ATTRIBUTION, setAttribution);
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
}
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
@ -568,8 +568,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// by doing this parsing here... but it's not likely going to fully recover the content.
//
// TODO: Remove this conde once we've sufficiently migrated content past this damaged version
if (args.bitstreamVersion == VERSION_ENTITIES_HAS_ATTRIBUTION_DAMAGED) {
READ_ENTITY_PROPERTY_STRING(PROP_ATTRIBUTION, setAttribution);
if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) {
READ_ENTITY_PROPERTY_STRING(PROP_MARKETPLACE_ID, setMarketplaceID);
}
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
@ -838,7 +838,7 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(attribution, getAttribution);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID);
properties._defaultSettings = false;
@ -867,7 +867,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(attribution, setAttribution);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed

View file

@ -251,8 +251,8 @@ public:
const QString& getUserData() const { return _userData; }
void setUserData(const QString& value) { _userData = value; }
const QString& getAttribution() const { return _attribution; }
void setAttribution(const QString& value) { _attribution = value; }
const QString& getMarketplaceID() const { return _marketplaceID; }
void setMarketplaceID(const QString& value) { _marketplaceID = value; }
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -342,7 +342,7 @@ protected:
bool _collisionsWillMove;
bool _locked;
QString _userData;
QString _attribution;
QString _marketplaceID;
// NOTE: Damping is applied like this: v *= pow(1 - damping, dt)
//

View file

@ -70,7 +70,7 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH),
CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY),
CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS),
CONSTRUCT_PROPERTY(attribution, ENTITY_ITEM_DEFAULT_ATTRIBUTION),
CONSTRUCT_PROPERTY(marketplaceID, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID),
_id(UNKNOWN_ENTITY_ID),
_idSet(false),
@ -260,7 +260,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength);
CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity);
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius);
CHECK_PROPERTY_CHANGE(PROP_ATTRIBUTION, attribution);
CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID);
return changedProperties;
}
@ -323,7 +323,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength);
COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius);
COPY_PROPERTY_TO_QSCRIPTVALUE(attribution);
COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
@ -405,7 +405,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(emitStrength, setEmitStrength);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localGravity, setLocalGravity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(particleRadius, setParticleRadius);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(attribution, setAttribution);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(marketplaceID, setMarketplaceID);
_lastEdited = usecTimestampNow();
}
@ -591,7 +591,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, appendValue, properties.getParticleRadius());
}
APPEND_ENTITY_PROPERTY(PROP_ATTRIBUTION, appendValue, properties.getAttribution());
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, appendValue, properties.getMarketplaceID());
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
@ -822,7 +822,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius);
}
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ATTRIBUTION, setAttribution);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MARKETPLACE_ID, setMarketplaceID);
return valid;
}
@ -905,7 +905,7 @@ void EntityItemProperties::markAllChanged() {
_localGravityChanged = true;
_particleRadiusChanged = true;
_attributionChanged = true;
_marketplaceIDChanged = true;
}
/// The maximum bounding cube for the entity, independent of it's rotation.

View file

@ -94,11 +94,11 @@ enum EntityPropertyList {
PROP_PARTICLE_RADIUS,
PROP_COLLISION_MODEL_URL,
PROP_ATTRIBUTION,
PROP_MARKETPLACE_ID,
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties ABOVE this line and then modify PROP_LAST_ITEM below
PROP_LAST_ITEM = PROP_ATTRIBUTION,
PROP_LAST_ITEM = PROP_MARKETPLACE_ID,
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -205,7 +205,7 @@ public:
DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float);
DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float);
DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float);
DEFINE_PROPERTY_REF(PROP_ATTRIBUTION, Attribution, attribution, QString);
DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString);
public:
float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); }
@ -333,7 +333,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Attribution, attribution, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, "");
debug << " last edited:" << properties.getLastEdited() << "\n";
debug << " edited ago:" << properties.getEditedAgo() << "\n";

View file

@ -22,7 +22,7 @@ const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f);
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
const QString ENTITY_ITEM_DEFAULT_ATTRIBUTION = QString("");
const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f;
const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f;

View file

@ -23,6 +23,7 @@ EntityScriptingInterface::EntityScriptingInterface() :
{
auto nodeList = DependencyManager::get<NodeList>();
connect(nodeList.data(), &NodeList::canAdjustLocksChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);
connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);
}
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
@ -30,12 +31,15 @@ void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
}
bool EntityScriptingInterface::canAdjustLocks() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanAdjustLocks();
}
bool EntityScriptingInterface::canRez() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanRez();
}
void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
if (_entityTree) {

View file

@ -66,6 +66,9 @@ public slots:
// returns true if the DomainServer will allow this Node/Avatar to make changes
Q_INVOKABLE bool canAdjustLocks();
// returns true if the DomainServer will allow this Node/Avatar to rez new entities
Q_INVOKABLE bool canRez();
/// adds a model with the specific properties
Q_INVOKABLE EntityItemID addEntity(const EntityItemProperties& properties);
@ -117,6 +120,7 @@ signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
void canAdjustLocksChanged(bool canAdjustLocks);
void canRezChanged(bool canRez);
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);

View file

@ -148,6 +148,7 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
}
}
} else {
QString entityScriptBefore = entity->getScript();
uint32_t preFlags = entity->getDirtyFlags();
UpdateEntityOperator theOperator(this, containingElement, entity, properties);
recurseTreeWithOperator(&theOperator);
@ -166,6 +167,11 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
entity->clearDirtyFlags();
}
}
QString entityScriptAfter = entity->getScript();
if (entityScriptBefore != entityScriptAfter) {
emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed
}
}
// TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG).
@ -398,7 +404,8 @@ void EntityTree::handleAddEntityResponse(const QByteArray& packet) {
EntityItem* foundEntity = NULL;
EntityItemID creatorTokenVersion = searchEntityID.convertToCreatorTokenVersion();
EntityItemID knownIDVersion = searchEntityID.convertToKnownIDVersion();
_changedEntityIDs[creatorTokenVersion] = knownIDVersion;
// First look for and find the "viewed version" of this entity... it's possible we got
// the known ID version sent to us between us creating our local version, and getting this
@ -586,6 +593,9 @@ EntityItem* EntityTree::findEntityByEntityItemID(const EntityItemID& entityID) /
EntityTreeElement* containingElement = getContainingElement(entityID);
if (containingElement) {
foundEntity = containingElement->getEntityWithEntityItemID(entityID);
if (!foundEntity && _changedEntityIDs.contains(entityID)) {
foundEntity = containingElement->getEntityWithEntityItemID(_changedEntityIDs[entityID]);
}
}
return foundEntity;
}
@ -649,12 +659,14 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
qDebug() << "User attempted to edit an unknown entity. ID:" << entityItemID;
}
} else {
// this is a new entity... assign a new entityID
entityItemID = assignEntityID(entityItemID);
EntityItem* newEntity = addEntity(entityItemID, properties);
if (newEntity) {
newEntity->markAsChangedOnServer();
notifyNewlyCreatedEntity(*newEntity, senderNode);
if (senderNode->getCanRez()) {
// this is a new entity... assign a new entityID
entityItemID = assignEntityID(entityItemID);
EntityItem* newEntity = addEntity(entityItemID, properties);
if (newEntity) {
newEntity->markAsChangedOnServer();
notifyNewlyCreatedEntity(*newEntity, senderNode);
}
}
}
}
@ -952,6 +964,12 @@ EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityIt
creatorTokenOnly.isKnownID = false;
element = _entityToElementMap.value(creatorTokenOnly);
}
// If we still didn't find the entity, but the ID was in our changed entityIDs, search for the new ID version
if (!element && _changedEntityIDs.contains(entityItemID)) {
element = getContainingElement(_changedEntityIDs[entityItemID]);
}
return element;
}

View file

@ -95,7 +95,6 @@ public:
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false);
void removeEntityFromSimulation(EntityItem* entity);
/// \param position point of query in world-frame (meters)
/// \param targetRadius radius of query (meters)
@ -196,6 +195,7 @@ private:
EntityItemFBXService* _fbxService;
QHash<EntityItemID, EntityTreeElement*> _entityToElementMap;
QHash<EntityItemID, EntityItemID> _changedEntityIDs;
EntitySimulation* _simulation;

View file

@ -281,6 +281,13 @@ void ModelEntityItem::updateShapeType(ShapeType type) {
}
}
void ModelEntityItem::setCollisionModelURL(const QString& url) {
if (_collisionModelURL != url) {
_collisionModelURL = url;
_dirtyFlags |= EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS;
}
}
void ModelEntityItem::setAnimationURL(const QString& url) {
_dirtyFlags |= EntityItem::DIRTY_UPDATEABLE;
_animationURL = url;

View file

@ -57,13 +57,13 @@ public:
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
bool hasModel() const { return !_modelURL.isEmpty(); }
bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); }
virtual bool hasCollisionModel() const { return !_collisionModelURL.isEmpty(); }
static const QString DEFAULT_MODEL_URL;
const QString& getModelURL() const { return _modelURL; }
static const QString DEFAULT_COLLISION_MODEL_URL;
const QString& getCollisionModelURL() const { return _collisionModelURL; }
virtual const QString& getCollisionModelURL() const { return _collisionModelURL; }
bool hasAnimation() const { return !_animationURL.isEmpty(); }
static const QString DEFAULT_ANIMATION_URL;
@ -78,7 +78,7 @@ public:
// model related properties
void setModelURL(const QString& url) { _modelURL = url; }
void setCollisionModelURL(const QString& url) { _collisionModelURL = url; }
virtual void setCollisionModelURL(const QString& url);
void setAnimationURL(const QString& url);
static const float DEFAULT_ANIMATION_FRAME_INDEX;
void setAnimationFrameIndex(float value);

View file

@ -13,7 +13,7 @@
#include "FSTReader.h"
QVariantHash parseMapping(QIODevice* device) {
QVariantHash FSTReader::parseMapping(QIODevice* device) {
QVariantHash properties;
QByteArray line;
@ -48,13 +48,13 @@ QVariantHash parseMapping(QIODevice* device) {
return properties;
}
QVariantHash readMapping(const QByteArray& data) {
QVariantHash FSTReader::readMapping(const QByteArray& data) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);
return parseMapping(&buffer);
return FSTReader::parseMapping(&buffer);
}
void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
QByteArray key = it.key().toUtf8() + " = ";
QVariantHash hashValue = it.value().toHash();
if (hashValue.isEmpty()) {
@ -76,8 +76,8 @@ void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) {
}
}
QByteArray writeMapping(const QVariantHash& mapping) {
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD
QByteArray FSTReader::writeMapping(const QVariantHash& mapping) {
static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD
<< TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD
<< BLENDSHAPE_FIELD << JOINT_INDEX_FIELD;
QBuffer buffer;
@ -96,4 +96,76 @@ QByteArray writeMapping(const QVariantHash& mapping) {
}
}
return buffer.data();
}
}
QHash<FSTReader::ModelType, QString> FSTReader::_typesToNames;
QString FSTReader::getNameFromType(ModelType modelType) {
if (_typesToNames.size() == 0) {
_typesToNames[ENTITY_MODEL] = "entity";
_typesToNames[HEAD_MODEL] = "head";
_typesToNames[BODY_ONLY_MODEL] = "body";
_typesToNames[HEAD_AND_BODY_MODEL] = "body+head";
_typesToNames[ATTACHMENT_MODEL] = "attachment";
}
return _typesToNames[modelType];
}
QHash<QString, FSTReader::ModelType> FSTReader::_namesToTypes;
FSTReader::ModelType FSTReader::getTypeFromName(const QString& name) {
if (_namesToTypes.size() == 0) {
_namesToTypes["entity"] = ENTITY_MODEL;
_namesToTypes["head"] = HEAD_MODEL ;
_namesToTypes["body"] = BODY_ONLY_MODEL;
_namesToTypes["body+head"] = HEAD_AND_BODY_MODEL;
_namesToTypes["attachment"] = ATTACHMENT_MODEL;
}
return _namesToTypes[name];
}
FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) {
QVariantHash joints;
if (mapping.contains("joint") && mapping["joint"].type() == QVariant::Hash) {
joints = mapping["joint"].toHash();
}
// if the mapping includes the type hint... then we trust the mapping
if (mapping.contains(TYPE_FIELD)) {
return FSTReader::getTypeFromName(mapping[TYPE_FIELD].toString());
}
// check for blendshapes
bool hasBlendshapes = mapping.contains(BLENDSHAPE_FIELD);
// a Head needs to have these minimum fields...
//joint = jointEyeLeft = EyeL = 1
//joint = jointEyeRight = EyeR = 1
//joint = jointNeck = Head = 1
bool hasHeadMinimum = joints.contains("jointNeck") && joints.contains("jointEyeLeft") && joints.contains("jointEyeRight");
// a Body needs to have these minimum fields...
//joint = jointRoot = Hips
//joint = jointLean = Spine
//joint = jointNeck = Neck
//joint = jointHead = HeadTop_End
bool hasBodyMinimumJoints = joints.contains("jointRoot") && joints.contains("jointLean") && joints.contains("jointNeck")
&& joints.contains("jointHead");
bool isLikelyHead = hasBlendshapes || hasHeadMinimum;
if (isLikelyHead && hasBodyMinimumJoints) {
return HEAD_AND_BODY_MODEL;
}
if (isLikelyHead) {
return HEAD_MODEL;
}
if (hasBodyMinimumJoints) {
return BODY_ONLY_MODEL;
}
return ENTITY_MODEL;
}

View file

@ -12,9 +12,11 @@
#ifndef hifi_FSTReader_h
#define hifi_FSTReader_h
#include <QBuffer>
#include <QVariantHash>
static const QString NAME_FIELD = "name";
static const QString TYPE_FIELD = "type";
static const QString FILENAME_FIELD = "filename";
static const QString TEXDIR_FIELD = "texdir";
static const QString LOD_FIELD = "lod";
@ -27,10 +29,35 @@ static const QString JOINT_FIELD = "joint";
static const QString FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs";
/// Reads an FST mapping from the supplied data.
QVariantHash readMapping(const QByteArray& data);
class FSTReader {
public:
/// Writes an FST mapping to a byte array.
QByteArray writeMapping(const QVariantHash& mapping);
enum ModelType {
ENTITY_MODEL,
HEAD_MODEL,
BODY_ONLY_MODEL,
HEAD_AND_BODY_MODEL,
ATTACHMENT_MODEL
};
/// Reads an FST mapping from the supplied data.
static QVariantHash readMapping(const QByteArray& data);
/// Writes an FST mapping to a byte array.
static QByteArray writeMapping(const QVariantHash& mapping);
/// Predicts the type of model by examining the mapping
static ModelType predictModelType(const QVariantHash& mapping);
static QString getNameFromType(ModelType modelType);
static FSTReader::ModelType getTypeFromName(const QString& name);
private:
static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it);
static QVariantHash parseMapping(QIODevice* device);
static QHash<FSTReader::ModelType, QString> _typesToNames;
static QHash<QString, FSTReader::ModelType> _namesToTypes;
};
#endif // hifi_FSTReader_h

View file

@ -27,7 +27,8 @@ public:
enum SpecialToken {
NO_TOKEN = -1,
NO_PUSHBACKED_TOKEN = -1,
DATUM_TOKEN = 0x100
DATUM_TOKEN = 0x100,
COMMENT_TOKEN = 0x101
};
int nextToken();
const QByteArray& getDatum() const { return _datum; }
@ -35,11 +36,13 @@ public:
void skipLine() { _device->readLine(); }
void pushBackToken(int token) { _pushedBackToken = token; }
void ungetChar(char ch) { _device->ungetChar(ch); }
const QString getComment() const { return _comment; }
private:
QIODevice* _device;
QByteArray _datum;
int _pushedBackToken;
QString _comment;
};
@ -56,9 +59,11 @@ int OBJTokenizer::nextToken() {
continue; // skip whitespace
}
switch (ch) {
case '#':
_device->readLine(); // skip the comment
break;
case '#': {
_comment = _device->readLine(); // skip the comment
qDebug() << "COMMENT:" << _comment;
return COMMENT_TOKEN;
}
case '\"':
_datum = "";
@ -104,7 +109,8 @@ bool OBJTokenizer::isNextTokenFloat() {
}
bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
FBXGeometry &geometry, QVector<glm::vec3>& faceNormals, QVector<int>& faceNormalIndexes) {
FBXGeometry &geometry, QVector<glm::vec3>& faceNormals, QVector<int>& faceNormalIndexes,
float& scaleGuess) {
FBXMesh &mesh = geometry.meshes[0];
mesh.parts.append(FBXMeshPart());
FBXMeshPart &meshPart = mesh.parts.last();
@ -128,7 +134,17 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0));
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
int tokenType = tokenizer.nextToken();
if (tokenType == OBJTokenizer::COMMENT_TOKEN) {
if (tokenizer.getComment().contains("This file uses centimeters as units")) {
scaleGuess = 1.0f / 100.0f;
}
if (tokenizer.getComment().contains("This file uses millimeters as units")) {
scaleGuess = 1.0f / 1000.0f;
}
continue;
}
if (tokenType != OBJTokenizer::DATUM_TOKEN) {
result = false;
break;
}
@ -192,6 +208,7 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
while (true) {
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
if (indices.count() == 0) {
// nonsense, bail out.
goto done;
}
break;
@ -249,26 +266,39 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping,
} else if (indices.count() == 4) {
meshPart.quadIndices << indices;
} else {
qDebug() << "no support for more than 4 vertices on a face in OBJ files";
// some obj writers (maya) will write a face with lots of points.
for (int i = 1; i < indices.count() - 1; i++) {
// break the face into triangles
meshPart.triangleIndices.append(indices[0]);
meshPart.triangleIndices.append(indices[i]);
meshPart.triangleIndices.append(indices[i+1]);
}
if (indices.count() == normalIndices.count()) {
for (int i = 1; i < normalIndices.count() - 1; i++) {
faceNormalIndexes.append(normalIndices[0]);
faceNormalIndexes.append(normalIndices[i]);
faceNormalIndexes.append(normalIndices[i+1]);
}
}
}
} else {
// something we don't (yet) care about
qDebug() << "OBJ parser is skipping a line with" << token;
// qDebug() << "OBJ parser is skipping a line with" << token;
tokenizer.skipLine();
}
}
done:
if (meshPart.triangleIndices.size() == 0 && meshPart.quadIndices.size() == 0) {
// empty mesh?
mesh.parts.pop_back();
}
return result;
}
FBXGeometry extractOBJGeometry(const FBXNode& node, const QVariantHash& mapping) {
FBXGeometry geometry;
return geometry;
}
FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);
@ -281,24 +311,30 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) {
OBJTokenizer tokenizer(device);
QVector<int> faceNormalIndexes;
QVector<glm::vec3> faceNormals;
float scaleGuess = 1.0f;
faceNormalIndexes.clear();
geometry.meshExtents.reset();
geometry.meshes.append(FBXMesh());
try {
// call parseOBJGroup as long as it's returning true. Each successful call will
// add a new meshPart to the geometry's single mesh.
bool success = true;
while (success) {
success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes);
success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes, scaleGuess);
}
FBXMesh &mesh = geometry.meshes[0];
// if we got a hint about units, scale all the points
if (scaleGuess != 1.0f) {
for (int i = 0; i < mesh.vertices.size(); i++) {
mesh.vertices[i] *= scaleGuess;
}
}
mesh.meshExtents.reset();
foreach (const glm::vec3& vertex, mesh.vertices) {
mesh.meshExtents.addPoint(vertex);

View file

@ -33,8 +33,7 @@ AddressManager::AddressManager() :
_rootPlaceName(),
_rootPlaceID(),
_positionGetter(NULL),
_orientationGetter(NULL),
_localDSPortSharedMem(NULL)
_orientationGetter(NULL)
{
connect(qApp, &QCoreApplication::aboutToQuit, this, &AddressManager::storeCurrentAddress);
}
@ -331,13 +330,6 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
if (domainHostname == "localhost") {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
_localDSPortSharedMem,
domainPort);
}
if (!hostnameRegex.cap(2).isEmpty()) {
domainPort = (qint16) hostnameRegex.cap(2).toInt();
}

View file

@ -95,8 +95,6 @@ private:
QUuid _rootPlaceID;
PositionGetter _positionGetter;
OrientationGetter _orientationGetter;
QSharedMemory* _localDSPortSharedMem; // memory shared with domain server
};
#endif // hifi_AddressManager_h

View file

@ -47,6 +47,7 @@ public:
void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname);
unsigned short getPort() const { return _sockAddr.getPort(); }
void setPort(quint16 port) { _sockAddr.setPort(port); }
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }

View file

@ -36,8 +36,6 @@ const char SOLO_NODE_TYPES[2] = {
NodeType::AudioMixer
};
const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://metaverse.highfidelity.com");
LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) :
linkedDataCreateCallback(NULL),
_sessionUUID(),
@ -49,7 +47,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
_publicSockAddr(),
_stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT),
_packetStatTimer(),
_thisNodeCanAdjustLocks(false)
_thisNodeCanAdjustLocks(false),
_thisNodeCanRez(true)
{
static bool firstCall = true;
if (firstCall) {
@ -108,6 +107,13 @@ void LimitedNodeList::setThisNodeCanAdjustLocks(bool canAdjustLocks) {
}
}
void LimitedNodeList::setThisNodeCanRez(bool canRez) {
if (_thisNodeCanRez != canRez) {
_thisNodeCanRez = canRez;
emit canRezChanged(canRez);
}
}
QUdpSocket& LimitedNodeList::getDTLSSocket() {
if (!_dtlsSocket) {
// DTLS socket getter called but no DTLS socket exists, create it now
@ -392,8 +398,9 @@ void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
_nodeMutex.unlock();
QWriteLocker writeLocker(&_nodeMutex);
_nodeMutex.lockForWrite();
_nodeHash.unsafe_erase(it);
_nodeMutex.unlock();
handleNodeKill(matchingNode);
} else {
@ -416,7 +423,7 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool canAdjustLocks) {
bool canAdjustLocks, bool canRez) {
NodeHash::const_iterator it = _nodeHash.find(uuid);
if (it != _nodeHash.end()) {
@ -425,11 +432,12 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket);
matchingNode->setCanAdjustLocks(canAdjustLocks);
matchingNode->setCanRez(canRez);
return matchingNode;
} else {
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks);
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez);
SharedNodePointer newNodePointer(newNode);
_nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer));

View file

@ -42,8 +42,6 @@ const quint64 NODE_SILENCE_THRESHOLD_MSECS = 2 * 1000;
extern const char SOLO_NODE_TYPES[2];
extern const QUrl DEFAULT_NODE_AUTH_URL;
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io";
@ -86,6 +84,9 @@ public:
bool getThisNodeCanAdjustLocks() const { return _thisNodeCanAdjustLocks; }
void setThisNodeCanAdjustLocks(bool canAdjustLocks);
bool getThisNodeCanRez() const { return _thisNodeCanRez; }
void setThisNodeCanRez(bool canRez);
void rebindNodeSocket();
QUdpSocket& getNodeSocket() { return _nodeSocket; }
@ -116,7 +117,8 @@ public:
SharedNodePointer sendingNodeForPacket(const QByteArray& packet);
SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks);
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
bool canAdjustLocks, bool canRez);
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
@ -208,6 +210,7 @@ signals:
void publicSockAddrChanged(const HifiSockAddr& publicSockAddr);
void canAdjustLocksChanged(bool canAdjustLocks);
void canRezChanged(bool canRez);
void dataSent(const quint8 channel_type, const int bytes);
void dataReceived(const quint8 channel_type, const int bytes);
@ -243,6 +246,7 @@ protected:
QElapsedTimer _packetStatTimer;
bool _thisNodeCanAdjustLocks;
bool _thisNodeCanRez;
template<typename IteratorLambda>
void eachNodeHashIterator(IteratorLambda functor) {

View file

@ -0,0 +1,21 @@
//
// NetworkingConstants.h
// libraries/networking/src
//
// Created by Stephen Birarda on 2015-03-31.
// 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_NetworkingConstants_h
#define hifi_NetworkingConstants_h
#include <QtCore/QUrl>
namespace NetworkingConstants {
const QUrl METAVERSE_SERVER_URL = QUrl("https://metaverse.highfidelity.com");
}
#endif // hifi_NetworkingConstants_h

View file

@ -41,7 +41,7 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
}
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
const HifiSockAddr& localSocket, bool canAdjustLocks) :
const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez) :
NetworkPeer(uuid, publicSocket, localSocket),
_type(type),
_activeSocket(NULL),
@ -53,7 +53,8 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
_clockSkewUsec(0),
_mutex(),
_clockSkewMovingPercentile(30, 0.8f), // moving 80th percentile of 30 samples
_canAdjustLocks(canAdjustLocks)
_canAdjustLocks(canAdjustLocks),
_canRez(canRez)
{
}
@ -133,6 +134,7 @@ QDataStream& operator<<(QDataStream& out, const Node& node) {
out << node._publicSocket;
out << node._localSocket;
out << node._canAdjustLocks;
out << node._canRez;
return out;
}
@ -143,6 +145,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) {
in >> node._publicSocket;
in >> node._localSocket;
in >> node._canAdjustLocks;
in >> node._canRez;
return in;
}

View file

@ -45,7 +45,7 @@ class Node : public NetworkPeer {
Q_OBJECT
public:
Node(const QUuid& uuid, NodeType_t type,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks);
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez);
~Node();
bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; }
@ -79,6 +79,9 @@ public:
void setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; }
bool getCanAdjustLocks() { return _canAdjustLocks; }
void setCanRez(bool canRez) { _canRez = canRez; }
bool getCanRez() { return _canRez; }
void activatePublicSocket();
void activateLocalSocket();
@ -105,6 +108,7 @@ private:
QMutex _mutex;
MovingPercentile _clockSkewMovingPercentile;
bool _canAdjustLocks;
bool _canRez;
};
QDebug operator<<(QDebug debug, const Node &message);

View file

@ -277,6 +277,23 @@ void NodeList::sendDomainServerCheckIn() {
if (!_domainHandler.isConnected()) {
qDebug() << "Sending connect request to domain-server at" << _domainHandler.getHostname();
// is this our localhost domain-server?
// if so we need to make sure we have an up-to-date local port in case it restarted
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|| _domainHandler.getHostname() == "localhost") {
static QSharedMemory* localDSPortSharedMem = NULL;
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
localDSPortSharedMem,
domainPort);
qDebug() << "Local domain-server port read from shared memory (or default) is" << domainPort;
_domainHandler.setPort(domainPort);
}
}
// construct the DS check in packet
@ -386,6 +403,10 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
bool thisNodeCanAdjustLocks;
packetStream >> thisNodeCanAdjustLocks;
setThisNodeCanAdjustLocks(thisNodeCanAdjustLocks);
bool thisNodeCanRez;
packetStream >> thisNodeCanRez;
setThisNodeCanRez(thisNodeCanRez);
// pull each node in the packet
while(packetStream.device()->pos() < packet.size()) {
@ -394,8 +415,9 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
QUuid nodeUUID, connectionUUID;
HifiSockAddr nodePublicSocket, nodeLocalSocket;
bool canAdjustLocks;
bool canRez;
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks;
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks >> canRez;
// if the public socket address is 0 then it's reachable at the same IP
// as the domain server
@ -403,7 +425,8 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
nodePublicSocket.setAddress(_domainHandler.getIP());
}
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket, canAdjustLocks);
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
nodeLocalSocket, canAdjustLocks, canRez);
packetStream >> connectionUUID;
node->setConnectionSecret(connectionUUID);

View file

@ -15,6 +15,7 @@
#include "AccountManager.h"
#include "LimitedNodeList.h"
#include "NetworkingConstants.h"
#include "SharedUtil.h"
#include "OAuthNetworkAccessManager.h"
@ -33,7 +34,8 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O
QIODevice* outgoingData) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken() && req.url().host() == DEFAULT_NODE_AUTH_URL.host()) {
if (accountManager.hasValidAccessToken()
&& req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) {
QNetworkRequest authenticatedRequest(req);
authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,

View file

@ -64,7 +64,7 @@ PacketVersion versionForPacketType(PacketType type) {
return 2;
case PacketTypeDomainList:
case PacketTypeDomainListRequest:
return 4;
return 5;
case PacketTypeCreateAssignment:
case PacketTypeRequestAssignment:
return 2;
@ -74,7 +74,7 @@ PacketVersion versionForPacketType(PacketType type) {
return 1;
case PacketTypeEntityAddOrEdit:
case PacketTypeEntityData:
return VERSION_ENTITIES_HAS_ATTRIBUTION;
return VERSION_ENTITIES_HAS_MARKETPLACE_ID;
case PacketTypeEntityErase:
return 2;
case PacketTypeAudioStreamStats:

View file

@ -132,8 +132,8 @@ const PacketVersion VERSION_ENTITIES_LIGHT_HAS_INTENSITY_AND_COLOR_PROPERTIES =
const PacketVersion VERSION_ENTITIES_HAS_PARTICLES = 10;
const PacketVersion VERSION_ENTITIES_USE_METERS_AND_RADIANS = 11;
const PacketVersion VERSION_ENTITIES_HAS_COLLISION_MODEL = 12;
const PacketVersion VERSION_ENTITIES_HAS_ATTRIBUTION_DAMAGED = 13;
const PacketVersion VERSION_ENTITIES_HAS_ATTRIBUTION = 14;
const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED = 13;
const PacketVersion VERSION_ENTITIES_HAS_MARKETPLACE_ID = 14;
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
#endif // hifi_PacketHeaders_h

View file

@ -18,6 +18,7 @@
#include <QTimer>
#include <SharedUtil.h>
#include <assert.h>
#include "NetworkAccessManager.h"
#include "ResourceCache.h"
@ -48,42 +49,49 @@ void ResourceCache::refresh(const QUrl& url) {
}
}
void ResourceCache::getResourceAsynchronously(const QUrl& url) {
qDebug() << "ResourceCache::getResourceAsynchronously" << url.toString();
_resourcesToBeGottenLock.lockForWrite();
_resourcesToBeGotten.enqueue(QUrl(url));
_resourcesToBeGottenLock.unlock();
}
void ResourceCache::checkAsynchronousGets() {
assert(QThread::currentThread() == thread());
if (!_resourcesToBeGotten.isEmpty()) {
_resourcesToBeGottenLock.lockForWrite();
QUrl url = _resourcesToBeGotten.dequeue();
_resourcesToBeGottenLock.unlock();
getResource(url);
}
}
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback,
bool delayLoad, void* extra, bool block) {
bool delayLoad, void* extra) {
QSharedPointer<Resource> resource = _resources.value(url);
if (!resource.isNull()) {
removeUnusedResource(resource);
return resource;
}
if (QThread::currentThread() != thread()) {
// This will re-call this method in the main thread. If block is true and the main thread
// is waiting on a lock, we'll deadlock here.
if (block) {
QSharedPointer<Resource> result;
QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QSharedPointer<Resource>, result), Q_ARG(const QUrl&, url),
Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
return result;
} else {
// Queue the re-invocation of this method, but if the main thread is blocked, don't wait. The
// return value may be NULL -- it's expected that this will be called again later, in order
// to receive the actual Resource.
QMetaObject::invokeMethod(this, "getResource", Qt::QueuedConnection,
Q_ARG(const QUrl&, url),
Q_ARG(const QUrl&, fallback), Q_ARG(bool, delayLoad), Q_ARG(void*, extra));
return _resources.value(url);
}
assert(delayLoad);
getResourceAsynchronously(url);
return QSharedPointer<Resource>();
}
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
return getResource(fallback, QUrl(), delayLoad);
}
QSharedPointer<Resource> resource = _resources.value(url);
if (resource.isNull()) {
resource = createResource(url, fallback.isValid() ?
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), delayLoad, extra);
resource->setSelf(resource);
resource->setCache(this);
_resources.insert(url, resource);
} else {
removeUnusedResource(resource);
}
resource = createResource(url, fallback.isValid() ?
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), delayLoad, extra);
resource->setSelf(resource);
resource->setCache(this);
_resources.insert(url, resource);
removeUnusedResource(resource);
resource->ensureLoading();
return resource;
}
@ -125,7 +133,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
}
void ResourceCache::attemptRequest(Resource* resource) {
auto sharedItems = DependencyManager::get<ResouceCacheSharedItems>();
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
if (_requestLimit <= 0) {
// wait until a slot becomes available
sharedItems->_pendingRequests.append(resource);
@ -138,7 +146,7 @@ void ResourceCache::attemptRequest(Resource* resource) {
void ResourceCache::requestCompleted(Resource* resource) {
auto sharedItems = DependencyManager::get<ResouceCacheSharedItems>();
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->_loadingRequests.removeOne(resource);
_requestLimit++;

View file

@ -21,6 +21,8 @@
#include <QSharedPointer>
#include <QUrl>
#include <QWeakPointer>
#include <QReadWriteLock>
#include <QQueue>
#include <DependencyManager.h>
@ -46,14 +48,14 @@ static const qint64 MAX_UNUSED_MAX_SIZE = 10 * BYTES_PER_GIGABYTES;
// ResourceCache derived classes. Since we can't count on the ordering of
// static members destruction, we need to use this Dependency manager implemented
// object instead
class ResouceCacheSharedItems : public Dependency {
class ResourceCacheSharedItems : public Dependency {
SINGLETON_DEPENDENCY
public:
QList<QPointer<Resource> > _pendingRequests;
QList<Resource*> _loadingRequests;
private:
ResouceCacheSharedItems() { }
virtual ~ResouceCacheSharedItems() { }
ResourceCacheSharedItems() { }
virtual ~ResourceCacheSharedItems() { }
};
@ -69,16 +71,19 @@ public:
qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }
static const QList<Resource*>& getLoadingRequests()
{ return DependencyManager::get<ResouceCacheSharedItems>()->_loadingRequests; }
{ return DependencyManager::get<ResourceCacheSharedItems>()->_loadingRequests; }
static int getPendingRequestCount()
{ return DependencyManager::get<ResouceCacheSharedItems>()->_pendingRequests.size(); }
{ return DependencyManager::get<ResourceCacheSharedItems>()->_pendingRequests.size(); }
ResourceCache(QObject* parent = NULL);
virtual ~ResourceCache();
void refresh(const QUrl& url);
public slots:
void checkAsynchronousGets();
protected:
qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE;
qint64 _unusedResourcesSize = 0;
@ -89,7 +94,7 @@ protected:
/// \param delayLoad if true, don't load the resource immediately; wait until load is first requested
/// \param extra extra data to pass to the creator, if appropriate
Q_INVOKABLE QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
bool delayLoad = false, void* extra = NULL, bool block = true);
bool delayLoad = false, void* extra = NULL);
/// Creates a new resource.
virtual QSharedPointer<Resource> createResource(const QUrl& url,
@ -109,6 +114,11 @@ private:
int _lastLRUKey = 0;
static int _requestLimit;
void getResourceAsynchronously(const QUrl& url);
QReadWriteLock _resourcesToBeGottenLock;
QQueue<QUrl> _resourcesToBeGotten;
};
/// Base class for resources.

75
libraries/physics/src/CharacterController.cpp Normal file → Executable file
View file

@ -235,10 +235,18 @@ CharacterController::CharacterController(AvatarData* avatarData) {
_jumpToHoverStart = 0;
setMaxSlope(btRadians(45.0f));
_lastStepUp = 0.0f;
_pendingFlags = 0;
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
updateShapeIfNecessary();
}
CharacterController::~CharacterController() {
delete _ghostObject;
_ghostObject = NULL;
delete _convexShape;
_convexShape = NULL;
// make sure you remove this Character from its DynamicsWorld before reaching this spot
assert(_dynamicsWorld == NULL);
}
btPairCachingGhostObject* CharacterController::getGhostObject() {
@ -266,7 +274,6 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher());
_currentPosition = _ghostObject->getWorldTransform().getOrigin();
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
btVector3 currentPosition = _currentPosition;
@ -300,7 +307,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
btVector3 normal = pt.m_normalWorldOnB;
normal *= directionSign; // always points from object to character
btScalar normalDotUp = normal.dot(up);
btScalar normalDotUp = normal.dot(_currentUp);
if (normalDotUp < _maxSlopeCosine) {
// this contact has a non-vertical normal... might need to ignored
btVector3 collisionPoint;
@ -311,9 +318,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl
}
// we do math in frame where character base is origin
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up;
btVector3 characterBase = currentPosition - (_radius + _halfHeight) * _currentUp;
collisionPoint -= characterBase;
btScalar collisionHeight = collisionPoint.dot(up);
btScalar collisionHeight = collisionPoint.dot(_currentUp);
if (collisionHeight < _lastStepUp) {
// This contact is below the lastStepUp, so we ignore it for penetration resolution,
@ -349,11 +356,10 @@ void CharacterController::scanDown(btCollisionWorld* world) {
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
btVector3 start = _currentPosition;
const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover
const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover
btVector3 end = start - MAX_SCAN_HEIGHT * up;
btVector3 end = start - MAX_SCAN_HEIGHT * _currentUp;
world->rayTest(start, end, callback);
if (!callback.hasHit()) {
@ -369,15 +375,14 @@ void CharacterController::stepUp(btCollisionWorld* world) {
// compute start and end
btTransform start, end;
start.setIdentity();
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin));
start.setOrigin(_currentPosition + _currentUp * (_convexShape->getMargin() + _addedMargin));
_targetPosition = _currentPosition + up * _stepUpHeight;
_targetPosition = _currentPosition + _currentUp * _stepUpHeight;
end.setIdentity();
end.setOrigin(_targetPosition);
// sweep up
btVector3 sweepDirNegative = - up;
btVector3 sweepDirNegative = - _currentUp;
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
@ -389,7 +394,7 @@ void CharacterController::stepUp(btCollisionWorld* world) {
_verticalOffset = 0.0f;
// Only modify the position if the hit was a slope and not a wall or ceiling.
if (callback.m_hitNormalWorld.dot(up) > 0.0f) {
if (callback.m_hitNormalWorld.dot(_currentUp) > 0.0f) {
_lastStepUp = _stepUpHeight * callback.m_closestHitFraction;
_currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction);
} else {
@ -461,8 +466,8 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt
// sweep forward
btVector3 sweepDirNegative(_currentPosition - _targetPosition);
btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0));
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
_ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration);
if (callback.hasHit()) {
@ -494,17 +499,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
// reach of the character's feet.
// first sweep for ledge
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up;
btVector3 step = (_verticalVelocity * dt - _lastStepUp) * _currentUp;
StepDownConvexResultCallback callback(_ghostObject,
up,
_currentUp,
_currentPosition, step,
_walkDirection,
_maxSlopeCosine,
_radius, _halfHeight);
callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
btTransform start, end;
start.setIdentity();
@ -524,16 +528,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt
_isOnGround = true;
} else if (!_isJumping) {
// sweep again for floor within downStep threshold
step = -_stepDownHeight * up;
step = -_stepDownHeight * _currentUp;
StepDownConvexResultCallback callback2 (_ghostObject,
up,
_currentUp,
_currentPosition, step,
_walkDirection,
_maxSlopeCosine,
_radius, _halfHeight);
callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup;
callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask;
callback2.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup;
callback2.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask;
_currentPosition = _targetPosition;
_targetPosition = _currentPosition + step;
@ -609,10 +613,10 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
}
}
// the CharacterController algorithm can only change the position,
// so we don't bother to pull the rotation out of the transform
const btTransform& transform = _ghostObject->getWorldTransform();
_currentRotation = transform.getRotation();
_currentPosition = transform.getOrigin();
_targetPosition = _currentPosition;
}
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
@ -786,14 +790,17 @@ void CharacterController::setEnabled(bool enabled) {
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
if (_dynamicsWorld != world) {
if (_dynamicsWorld) {
_dynamicsWorld->removeCollisionObject(getGhostObject());
_dynamicsWorld->removeAction(this);
if (_dynamicsWorld) {
if (_ghostObject) {
_dynamicsWorld->removeCollisionObject(_ghostObject);
_dynamicsWorld->removeAction(this);
}
_dynamicsWorld = NULL;
}
_dynamicsWorld = world;
if (_dynamicsWorld) {
if (world && _ghostObject) {
_dynamicsWorld = world;
_pendingFlags &= ~ PENDING_FLAG_JUMP;
_dynamicsWorld->addCollisionObject(getGhostObject(),
_dynamicsWorld->addCollisionObject(_ghostObject,
btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter);
_dynamicsWorld->addAction(this);
@ -857,6 +864,7 @@ void CharacterController::updateShapeIfNecessary() {
void CharacterController::preSimulation(btScalar timeStep) {
if (_enabled && _dynamicsWorld) {
glm::quat rotation = _avatarData->getOrientation();
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset;
btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity());
@ -876,7 +884,7 @@ void CharacterController::preSimulation(btScalar timeStep) {
}
void CharacterController::postSimulation() {
if (_enabled) {
if (_enabled && _ghostObject) {
const btTransform& avatarTransform = _ghostObject->getWorldTransform();
glm::quat rotation = bulletToGLM(avatarTransform.getRotation());
glm::vec3 position = bulletToGLM(avatarTransform.getOrigin());
@ -884,8 +892,7 @@ void CharacterController::postSimulation() {
// cap the velocity of the step so that the character doesn't POP! so hard on steps
glm::vec3 finalStep = position - _lastPosition;
btVector3 finalVelocity = _walkDirection;
btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS);
finalVelocity += _verticalVelocity * up;
finalVelocity += _verticalVelocity * _currentUp;
const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec
btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt;
btScalar stepLength = glm::length(finalStep);

View file

@ -42,9 +42,22 @@ class btPairCachingGhostObject;
ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface
{
protected:
///this is the desired walk direction, set by the user
btVector3 _walkDirection;
btVector3 _normalizedDirection;
//some internal variables
btVector3 _currentPosition;
btVector3 _currentUp;
btVector3 _targetPosition;
glm::vec3 _lastPosition;
btVector3 _floorNormal; // points from object to character
glm::vec3 _shapeLocalOffset;
glm::vec3 _boxScale; // used to compute capsule shape
AvatarData* _avatarData = NULL;
btPairCachingGhostObject* _ghostObject;
btPairCachingGhostObject* _ghostObject = NULL;
btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast
btScalar _radius;
@ -64,22 +77,12 @@ protected:
btScalar _addedMargin;//@todo: remove this and fix the code
///this is the desired walk direction, set by the user
btVector3 _walkDirection;
btVector3 _normalizedDirection;
//some internal variables
btVector3 _currentPosition;
btQuaternion _currentRotation;
btVector3 _targetPosition;
glm::vec3 _lastPosition;
btScalar _lastStepUp;
///keep track of the contact manifolds
btManifoldArray _manifoldArray;
bool _touchingContact;
btVector3 _floorNormal; // points from object to character
bool _enabled;
bool _isOnGround;
@ -90,9 +93,6 @@ protected:
btScalar _stepDt;
uint32_t _pendingFlags;
glm::vec3 _shapeLocalOffset;
glm::vec3 _boxScale; // used to compute capsule shape
btDynamicsWorld* _dynamicsWorld = NULL;
btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal);

View file

@ -169,7 +169,9 @@ void EntityMotionState::updateObjectVelocities() {
}
void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) {
_entity->computeShapeInfo(shapeInfo);
if (_entity->isReadyToComputeShape()) {
_entity->computeShapeInfo(shapeInfo);
}
}
float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const {

View file

@ -28,7 +28,16 @@ PhysicsEngine::PhysicsEngine(const glm::vec3& offset)
}
PhysicsEngine::~PhysicsEngine() {
if (_characterController) {
_characterController->setDynamicsWorld(NULL);
}
// TODO: delete engine components... if we ever plan to create more than one instance
delete _collisionConfig;
delete _collisionDispatcher;
delete _broadphaseFilter;
delete _constraintSolver;
delete _dynamicsWorld;
delete _ghostPairCallback;
}
// begin EntitySimulation overrides
@ -608,8 +617,14 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
}
void PhysicsEngine::setCharacterController(CharacterController* character) {
if (!_characterController) {
if (_characterController != character) {
lock();
if (_characterController) {
// remove the character from the DynamicsWorld immediately
_characterController->setDynamicsWorld(NULL);
_characterController = NULL;
}
// the character will be added to the DynamicsWorld later
_characterController = character;
unlock();
}

View file

@ -29,6 +29,9 @@ int ShapeInfoUtil::toBulletShapeType(int shapeInfoType) {
case SHAPE_TYPE_CONVEX_HULL:
bulletShapeType = CONVEX_HULL_SHAPE_PROXYTYPE;
break;
case SHAPE_TYPE_COMPOUND:
bulletShapeType = COMPOUND_SHAPE_PROXYTYPE;
break;
}
return bulletShapeType;
}
@ -48,6 +51,9 @@ int ShapeInfoUtil::fromBulletShapeType(int bulletShapeType) {
case CONVEX_HULL_SHAPE_PROXYTYPE:
shapeInfoType = SHAPE_TYPE_CONVEX_HULL;
break;
case COMPOUND_SHAPE_PROXYTYPE:
shapeInfoType = SHAPE_TYPE_COMPOUND;
break;
}
return shapeInfoType;
}
@ -70,12 +76,34 @@ void ShapeInfoUtil::collectInfoFromShape(const btCollisionShape* shape, ShapeInf
const btConvexHullShape* convexHullShape = static_cast<const btConvexHullShape*>(shape);
const int numPoints = convexHullShape->getNumPoints();
const btVector3* btPoints = convexHullShape->getUnscaledPoints();
QVector<glm::vec3> points;
QVector<QVector<glm::vec3>> points;
QVector<glm::vec3> childPoints;
for (int i = 0; i < numPoints; i++) {
glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ());
points << point;
childPoints << point;
}
info.setConvexHull(points);
points << childPoints;
info.setConvexHulls(points);
}
break;
case SHAPE_TYPE_COMPOUND: {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
const int numChildShapes = compoundShape->getNumChildShapes();
QVector<QVector<glm::vec3>> points;
for (int i = 0; i < numChildShapes; i ++) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
const btConvexHullShape* convexHullShape = static_cast<const btConvexHullShape*>(childShape);
const int numPoints = convexHullShape->getNumPoints();
const btVector3* btPoints = convexHullShape->getUnscaledPoints();
QVector<glm::vec3> childPoints;
for (int j = 0; j < numPoints; j++) {
glm::vec3 point(btPoints->getX(), btPoints->getY(), btPoints->getZ());
childPoints << point;
}
points << childPoints;
}
info.setConvexHulls(points);
}
break;
default: {
@ -108,12 +136,32 @@ btCollisionShape* ShapeInfoUtil::createShapeFromInfo(const ShapeInfo& info) {
}
break;
case SHAPE_TYPE_CONVEX_HULL: {
shape = new btConvexHullShape();
const QVector<glm::vec3>& points = info.getPoints();
foreach (glm::vec3 point, points) {
auto hull = new btConvexHullShape();
const QVector<QVector<glm::vec3>>& points = info.getPoints();
foreach (glm::vec3 point, points[0]) {
btVector3 btPoint(point[0], point[1], point[2]);
static_cast<btConvexHullShape*>(shape)->addPoint(btPoint);
hull->addPoint(btPoint, false);
}
hull->recalcLocalAabb();
shape = hull;
}
break;
case SHAPE_TYPE_COMPOUND: {
auto compound = new btCompoundShape();
const QVector<QVector<glm::vec3>>& points = info.getPoints();
btTransform trans;
trans.setIdentity();
foreach (QVector<glm::vec3> hullPoints, points) {
auto hull = new btConvexHullShape();
foreach (glm::vec3 point, hullPoints) {
btVector3 btPoint(point[0], point[1], point[2]);
hull->addPoint(btPoint, false);
}
hull->recalcLocalAabb();
compound->addChildShape (trans, hull);
}
shape = compound;
}
break;
}

View file

@ -20,6 +20,8 @@
// translates between ShapeInfo and btShape
namespace ShapeInfoUtil {
// XXX is collectInfoFromShape no longer strictly needed?
void collectInfoFromShape(const btCollisionShape* shape, ShapeInfo& info);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QDebug>
#include <glm/gtx/norm.hpp>
#include "ShapeInfoUtil.h"
@ -35,6 +37,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e4f; // 100 m cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED || diagonal > MAX_SHAPE_DIAGONAL_SQUARED) {
// qDebug() << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL;
}
DoubleHashKey key = info.getHash();
@ -100,6 +103,18 @@ void ShapeManager::collectGarbage() {
DoubleHashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->refCount == 0) {
// if the shape we're about to delete is compound, delete the children first.
auto shapeType = ShapeInfoUtil::fromBulletShapeType(shapeRef->shape->getShapeType());
if (shapeType == SHAPE_TYPE_COMPOUND) {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shapeRef->shape);
const int numChildShapes = compoundShape->getNumChildShapes();
QVector<QVector<glm::vec3>> points;
for (int i = 0; i < numChildShapes; i ++) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
delete childShape;
}
}
delete shapeRef->shape;
_shapeMap.remove(key);
}

View file

@ -134,7 +134,7 @@ void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radi
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size();
unsigned int lightID = _pointLights.size() + _spotLights.size() + _globalLights.size();
if (lightID >= _allocatedLights.size()) {
_allocatedLights.push_back(model::LightPointer(new model::Light()));
}
@ -535,10 +535,13 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit
}
void DeferredLightingEffect::setAmbientLightMode(int preset) {
if ((preset >= -1) && (preset < model::SphericalHarmonics::NUM_PRESET)) {
if ((preset >= 0) && (preset < model::SphericalHarmonics::NUM_PRESET)) {
_ambientLightMode = preset;
auto light = _allocatedLights.front();
light->setAmbientSpherePreset(model::SphericalHarmonics::Preset(preset % model::SphericalHarmonics::NUM_PRESET));
} else {
// force to preset 0
setAmbientLightMode(0);
}
}

View file

@ -1771,14 +1771,14 @@ void GeometryCache::renderLine(const glm::vec2& p1, const glm::vec2& p2,
}
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad, bool block) {
return getResource(url, fallback, delayLoad, NULL, block).staticCast<NetworkGeometry>();
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<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);
&Resource::allReferencesCleared);
geometry->setLODParent(geometry);
return geometry.staticCast<Resource>();
}
@ -2132,7 +2132,7 @@ 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 = readMapping(reply->readAll());
_mapping = FSTReader::readMapping(reply->readAll());
reply->deleteLater();
QString filename = _mapping.value("filename").toString();
if (filename.isNull()) {

View file

@ -203,8 +203,7 @@ public:
/// Loads geometry from the specified URL.
/// \param fallback a fallback URL to load if the desired one is unavailable
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(),
bool delayLoad = false, bool block = true);
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
protected:

View file

@ -325,6 +325,8 @@ void Model::init() {
_skinTranslucentProgram = gpu::ShaderPointer(gpu::Shader::createProgram(skinModelVertex, modelTranslucentPixel));
makeResult = gpu::Shader::makeProgram(*_skinTranslucentProgram, slotBindings);
initSkinProgram(_skinTranslucentProgram, _skinTranslucentLocations);
(void) makeResult; // quiet compiler
}
}
@ -1032,12 +1034,22 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo
}
}
void Model::setCollisionModelURL(const QUrl& url, const QUrl& fallback, bool delayLoad) {
const QSharedPointer<NetworkGeometry> Model::getCollisionGeometry(bool delayLoad)
{
if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) {
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(_collisionUrl, QUrl(), delayLoad);
}
return _collisionGeometry;
}
void Model::setCollisionModelURL(const QUrl& url) {
if (_collisionUrl == url) {
return;
}
_collisionUrl = url;
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, fallback, delayLoad);
_collisionGeometry = DependencyManager::get<GeometryCache>()->getGeometry(url, QUrl(), true);
}
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
@ -2360,7 +2372,7 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool
int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,
bool forceRenderSomeMeshes) {
bool forceRenderMeshes) {
PROFILE_RANGE(__FUNCTION__);
int meshPartsRendered = 0;
@ -2383,7 +2395,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned,
args, locations, skinLocations);
meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold,
args, locations, skinLocations, forceRenderSomeMeshes);
args, locations, skinLocations, forceRenderMeshes);
GLBATCH(glUseProgram)(0);
return meshPartsRendered;
@ -2391,7 +2403,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl
int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args,
Locations* locations, SkinLocations* skinLocations, bool forceRenderSomeMeshes) {
Locations* locations, SkinLocations* skinLocations, bool forceRenderMeshes) {
PROFILE_RANGE(__FUNCTION__);
auto textureCache = DependencyManager::get<TextureCache>();
@ -2427,21 +2439,14 @@ int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMod
// if we got here, then check to see if this mesh is in view
if (args) {
bool shouldRender = true;
bool forceRender = false;
args->_meshesConsidered++;
if (args->_viewFrustum) {
// NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong
// for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have
// incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible.
if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) {
forceRender = true;
}
shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
shouldRender = forceRenderMeshes ||
args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
if (shouldRender && !forceRender) {
if (shouldRender && !forceRenderMeshes) {
float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter());
shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(),
distance);

View file

@ -109,7 +109,7 @@ public:
const QUrl& getURL() const { return _url; }
// Set the model to use for collisions
Q_INVOKABLE void setCollisionModelURL(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
const QUrl& getCollisionURL() const { return _collisionUrl; }
/// Sets the distance parameter used for LOD computations.
@ -134,7 +134,7 @@ public:
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
/// Returns a reference to the shared collision geometry.
const QSharedPointer<NetworkGeometry> getCollisionGeometry() {return _collisionGeometry; }
const QSharedPointer<NetworkGeometry> getCollisionGeometry(bool delayLoad = true);
/// Returns the number of joint states in the model.
int getJointStateCount() const { return _jointStates.size(); }
@ -460,14 +460,14 @@ private:
bool renderCore(float alpha, RenderMode mode, RenderArgs* args);
int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL,
bool forceRenderSomeMeshes = false);
bool forceRenderMeshes = false);
void setupBatchTransform(gpu::Batch& batch);
QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned);
int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
RenderArgs* args, Locations* locations, SkinLocations* skinLocations,
bool forceRenderSomeMeshes = false);
bool forceRenderMeshes = false);
static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold,
bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args,

View file

@ -359,8 +359,12 @@ void Font::setupGL() {
// FIXME there has to be a cleaner way of doing this
QStringList Font::tokenizeForWrapping(const QString & str) const {
QStringList result;
foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) {
foreach(const QString & token1, str.split(" ")) {
bool lineFeed = false;
if (token1.isEmpty()) {
result << token1;
continue;
}
foreach(const QString & token2, token1.split("\n")) {
if (lineFeed) {
result << "\n";

View file

@ -0,0 +1,76 @@
//
// ScriptCache.cpp
// libraries/script-engine/src
//
// Created by Brad Hefta-Gaub on 2015-03-30
// 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 <QCoreApplication>
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include "ScriptCache.h"
ScriptCache::ScriptCache(QObject* parent) {
// nothing to do here...
}
QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending) {
QString scriptContents;
if (_scriptCache.contains(url)) {
qDebug() << "Found script in cache:" << url.toString();
scriptContents = _scriptCache[url];
scriptUser->scriptContentsAvailable(url, scriptContents);
isPending = false;
} else {
isPending = true;
bool alreadyWaiting = _scriptUsers.contains(url);
_scriptUsers.insert(url, scriptUser);
if (alreadyWaiting) {
qDebug() << "Already downloading script at:" << url.toString();
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
qDebug() << "Downloading script at:" << url.toString();
QNetworkReply* reply = networkAccessManager.get(networkRequest);
connect(reply, &QNetworkReply::finished, this, &ScriptCache::scriptDownloaded);
}
}
return scriptContents;
}
void ScriptCache::scriptDownloaded() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QUrl url = reply->url();
QList<ScriptUser*> scriptUsers = _scriptUsers.values(url);
_scriptUsers.remove(url);
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
_scriptCache[url] = reply->readAll();
qDebug() << "Done downloading script at:" << url.toString();
foreach(ScriptUser* user, scriptUsers) {
user->scriptContentsAvailable(url, _scriptCache[url]);
}
} else {
qDebug() << "ERROR Loading file:" << reply->url().toString();
foreach(ScriptUser* user, scriptUsers) {
user->errorInLoadingScript(url);
}
}
reply->deleteLater();
}

View file

@ -0,0 +1,44 @@
//
// ScriptCache.h
// libraries/script-engine/src
//
// Created by Brad Hefta-Gaub on 2015-03-30
// 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_ScriptCache_h
#define hifi_ScriptCache_h
#include <ResourceCache.h>
class ScriptUser {
public:
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0;
virtual void errorInLoadingScript(const QUrl& url) = 0;
};
/// Interface for loading scripts
class ScriptCache : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending);
void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); }
bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); }
private slots:
void scriptDownloaded();
private:
ScriptCache(QObject* parent = NULL);
QHash<QUrl, QString> _scriptCache;
QMultiMap<QUrl, ScriptUser*> _scriptUsers;
QSet<QUrl> _badScripts;
};
#endif // hifi_ScriptCache_h

View file

@ -34,6 +34,7 @@
#include "EventTypes.h"
#include "MenuItemProperties.h"
#include "ScriptAudioInjector.h"
#include "ScriptCache.h"
#include "ScriptEngine.h"
#include "TypedArrays.h"
#include "XMLHttpRequestClass.h"
@ -275,31 +276,26 @@ void ScriptEngine::loadURL(const QUrl& scriptURL) {
_scriptContents = in.readAll();
emit scriptLoaded(_fileNameString);
} else {
qDebug() << "ERROR Loading file:" << _fileNameString;
qDebug() << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__;
emit errorLoadingScript(_fileNameString);
}
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest);
connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload);
bool isPending;
auto scriptCache = DependencyManager::get<ScriptCache>();
scriptCache->getScript(url, this, isPending);
}
}
}
void ScriptEngine::handleScriptDownload() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
_scriptContents = reply->readAll();
emit scriptLoaded(_fileNameString);
} else {
qDebug() << "ERROR Loading file:" << reply->url().toString();
emit errorLoadingScript(_fileNameString);
}
reply->deleteLater();
void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
_scriptContents = scriptContents;
emit scriptLoaded(_fileNameString);
}
void ScriptEngine::errorInLoadingScript(const QUrl& url) {
qDebug() << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;
emit errorLoadingScript(_fileNameString); // ??
}
void ScriptEngine::init() {
@ -765,7 +761,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
for (QUrl url : urls) {
QString contents = data[url];
if (contents.isNull()) {
qDebug() << "Error loading file: " << url;
qDebug() << "Error loading file: " << url << "line:" << __LINE__;
} else {
QScriptValue result = evaluate(contents, url.toString());
}

View file

@ -28,6 +28,7 @@
#include "ArrayBufferClass.h"
#include "AudioScriptingInterface.h"
#include "Quat.h"
#include "ScriptCache.h"
#include "ScriptUUID.h"
#include "Vec3.h"
@ -35,7 +36,7 @@ const QString NO_SCRIPT("");
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5);
class ScriptEngine : public QScriptEngine {
class ScriptEngine : public QScriptEngine, public ScriptUser {
Q_OBJECT
public:
ScriptEngine(const QString& scriptContents = NO_SCRIPT,
@ -94,6 +95,9 @@ public:
void waitTillDoneRunning();
virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents);
virtual void errorInLoadingScript(const QUrl& url);
public slots:
void loadURL(const QUrl& scriptURL);
void stop();
@ -160,8 +164,6 @@ private:
ArrayBufferClass* _arrayBufferClass;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
private slots:
void handleScriptDownload();
private:
static QSet<ScriptEngine*> _allKnownScriptEngines;

View file

@ -17,12 +17,13 @@
#include <qurlquery.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <AccountManager.h>
#include "XMLHttpRequestClass.h"
#include "ScriptEngine.h"
const QString METAVERSE_API_URL = "https://metaverse.highfidelity.com/api/";
const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/";
Q_DECLARE_METATYPE(QByteArray*)

View file

@ -14,6 +14,7 @@
#define hifi_Extents_h
#include <glm/glm.hpp>
#include <glm/gtx/extented_min_max.hpp>
#include <QDebug>
#include "StreamUtils.h"
@ -46,6 +47,9 @@ public:
/// rotate the extents around orign by rotation
void rotate(const glm::quat& rotation);
glm::vec3 size() const { return maximum - minimum; }
float largestDimension() const {glm::vec3 s = size(); return glm::max(s[0], s[1], s[2]); }
/// \return new Extents which is original rotated around orign by rotation
Extents getRotated(const glm::quat& rotation) const {
Extents temp = { minimum, maximum };
@ -68,4 +72,4 @@ inline QDebug operator<<(QDebug debug, const Extents& extents) {
}
#endif // hifi_Extents_h
#endif // hifi_Extents_h

View file

@ -1,6 +1,6 @@
//
// SettingInterface.cpp
//
// libraries/shared/src
//
// Created by Clement on 2/2/15.
// Copyright 2015 High Fidelity, Inc.
@ -9,7 +9,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
@ -23,8 +22,16 @@ namespace Setting {
// cleans up the settings private instance. Should only be run once at closing down.
void cleanupPrivateInstance() {
delete privateInstance;
privateInstance = nullptr;
// grab the thread before we nuke the instance
QThread* settingsManagerThread = privateInstance->thread();
// tell the private instance to clean itself up on its thread
privateInstance->deleteLater();
privateInstance = NULL;
// quit the settings manager thread and wait on it to make sure it's gone
settingsManagerThread->quit();
settingsManagerThread->wait();
}
// Sets up the settings private instance. Should only be run once at startup

View file

@ -23,6 +23,7 @@ void ShapeInfo::clear() {
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
_type = type;
_points.clear();
switch(type) {
case SHAPE_TYPE_NONE:
_halfExtents = glm::vec3(0.0f);
@ -37,6 +38,12 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
break;
}
case SHAPE_TYPE_CONVEX_HULL:
_url = QUrl(url);
// halfExtents aren't used by convex-hull or compound convex-hull except as part of
// the generation of the key for the ShapeManager.
_halfExtents = halfExtents;
break;
case SHAPE_TYPE_COMPOUND:
_url = QUrl(url);
_halfExtents = halfExtents;
break;
@ -47,31 +54,44 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
}
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
_url = "";
_type = SHAPE_TYPE_BOX;
_halfExtents = halfExtents;
_points.clear();
_doubleHashKey.clear();
}
void ShapeInfo::setSphere(float radius) {
_url = "";
_type = SHAPE_TYPE_SPHERE;
_halfExtents = glm::vec3(radius, radius, radius);
_points.clear();
_doubleHashKey.clear();
}
void ShapeInfo::setEllipsoid(const glm::vec3& halfExtents) {
_url = "";
_type = SHAPE_TYPE_ELLIPSOID;
_halfExtents = halfExtents;
_points.clear();
_doubleHashKey.clear();
}
void ShapeInfo::setConvexHull(const QVector<glm::vec3>& points) {
_type = SHAPE_TYPE_CONVEX_HULL;
void ShapeInfo::setConvexHulls(const QVector<QVector<glm::vec3>>& points) {
if (points.size() == 1) {
_type = SHAPE_TYPE_CONVEX_HULL;
} else {
_type = SHAPE_TYPE_COMPOUND;
}
_points = points;
_doubleHashKey.clear();
}
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
_url = "";
_type = SHAPE_TYPE_CAPSULE_Y;
_halfExtents = glm::vec3(radius, halfHeight, radius);
_points.clear();
_doubleHashKey.clear();
}

View file

@ -44,14 +44,14 @@ public:
void setBox(const glm::vec3& halfExtents);
void setSphere(float radius);
void setEllipsoid(const glm::vec3& halfExtents);
void setConvexHull(const QVector<glm::vec3>& points);
void setConvexHulls(const QVector<QVector<glm::vec3>>& points);
void setCapsuleY(float radius, float halfHeight);
const int getType() const { return _type; }
const glm::vec3& getHalfExtents() const { return _halfExtents; }
const QVector<glm::vec3>& getPoints() const { return _points; }
const QVector<QVector<glm::vec3>>& getPoints() const { return _points; }
void clearPoints () { _points.clear(); }
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
@ -64,8 +64,8 @@ protected:
ShapeType _type = SHAPE_TYPE_NONE;
glm::vec3 _halfExtents = glm::vec3(0.0f);
DoubleHashKey _doubleHashKey;
QVector<glm::vec3> _points; // points for convex collision hull
QUrl _url; // url for model of convex collision hull
QVector<QVector<glm::vec3>> _points; // points for convex collision hulls
QUrl _url; // url for model of convex collision hulls
};
#endif // hifi_ShapeInfo_h

View file

@ -14,6 +14,7 @@
SimpleMovingAverage::SimpleMovingAverage(int numSamplesToAverage) :
_numSamples(0),
_lastEventTimestamp(0),
_average(0.0f),
_eventDeltaAverage(0.0f),
WEIGHTING(1.0f / numSamplesToAverage),

View file

@ -66,7 +66,9 @@ QDataStream& operator>>(QDataStream& in, glm::quat& quaternion) {
}
// less common utils can be enabled with DEBUG
#ifdef DEBUG
// FIXME, remove the second defined clause once these compile, or remove the
// functions.
#if defined(DEBUG) && defined(FIXED_STREAMS)
std::ostream& operator<<(std::ostream& s, const CollisionInfo& c) {
s << "{penetration=" << c._penetration
@ -143,4 +145,12 @@ QDebug& operator<<(QDebug& dbg, const glm::mat4& m) {
return dbg << " ]}";
}
QDebug& operator<<(QDebug& dbg, const QVariantHash& v) {
dbg.nospace() << "[";
for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) {
dbg << it.key() << ":" << it.value();
}
return dbg << " ]";
}
#endif // QT_NO_DEBUG_STREAM

View file

@ -15,6 +15,7 @@
#include <iostream>
#include <QByteArray>
#include <QVariantHash>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
@ -54,6 +55,7 @@ QDebug& operator<<(QDebug& s, const glm::vec3& v);
QDebug& operator<<(QDebug& s, const glm::vec4& v);
QDebug& operator<<(QDebug& s, const glm::quat& q);
QDebug& operator<<(QDebug& s, const glm::mat4& m);
QDebug& operator<<(QDebug& dbg, const QVariantHash& v);
#endif // QT_NO_DEBUG_STREAM
#endif // hifi_StreamUtils_h

View file

@ -8,5 +8,5 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools")
find_package(VHACD)
if(VHACD_FOUND)
add_subdirectory(vhacd)
set_target_properties(vhacd PROPERTIES FOLDER "Tools")
# set_target_properties(vhacd PROPERTIES FOLDER "Tools")
endif()

Some files were not shown because too many files have changed in this diff Show more