Merge branch 'master' of github.com:highfidelity/hifi into inspect-tool

This commit is contained in:
Ryan Huffman 2014-11-04 08:32:58 -08:00
commit a3fa1e4ce8
50 changed files with 2563 additions and 629 deletions

View file

@ -96,17 +96,14 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
assignmentServerPort =
argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
}
HifiSockAddr assignmentServerSocket(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, assignmentServerPort);
// check for an overriden assignment server hostname
if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) {
_assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString();
// change the hostname for our assignment server
assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerSocket.getPort());
_assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString();
}
HifiSockAddr assignmentServerSocket(_assignmentServerHostname, assignmentServerPort, true);
nodeList->setAssignmentServerSocket(assignmentServerSocket);
qDebug() << "Assignment server socket is" << assignmentServerSocket;

View file

@ -50,6 +50,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_hostname(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
_settingsManager()
{
LogUtils::init();
@ -327,17 +328,17 @@ void DomainServer::setupAutomaticNetworking() {
return;
}
QString automaticNetworkValue =
_automaticNetworkingSetting =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE) {
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->getSessionUUID();
if (!domainID.isNull()) {
qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID"
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
@ -347,7 +348,7 @@ void DomainServer::setupAutomaticNetworking() {
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
// send public socket changes to the data server so nodes can find us at our new IP
@ -361,11 +362,11 @@ void DomainServer::setupAutomaticNetworking() {
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
// call our sendHeartbeaToIceServer immediately anytime a local or public socket changes
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer);
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer);
connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer);
// tell the data server which type of automatic networking we are using
updateNetworkingInfoWithDataServer(automaticNetworkValue);
// send our heartbeat to data server so it knows what our network settings are
sendHeartbeatToDataServer();
}
// attempt to update our sockets now
@ -378,8 +379,17 @@ void DomainServer::setupAutomaticNetworking() {
return;
}
} else {
updateNetworkingInfoWithDataServer(automaticNetworkValue);
sendHeartbeatToDataServer();
}
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
// no matter the auto networking settings we should heartbeat to the data-server every 15s
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
QTimer* dataHeartbeatTimer = new QTimer(this);
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer()));
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
}
void DomainServer::loginFailed() {
@ -627,7 +637,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
}
if (allowedUsers.count() > 0) {
if (allowedUsers.contains(username)) {
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
// it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username);
@ -647,7 +657,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
rsaPublicKey, RSA_PKCS1_PADDING);
if (decryptResult != -1) {
if (username == decryptedArray) {
if (username.toLower() == decryptedArray) {
qDebug() << "Username signature matches for" << username << "- allowing connection.";
// free up the public key before we return
@ -1081,10 +1091,10 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
updateNetworkingInfoWithDataServer(IP_ONLY_AUTOMATIC_NETWORKING_VALUE, newPublicSockAddr.getAddress().toString());
sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString());
}
void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress) {
void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID();
@ -1097,9 +1107,22 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting,
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
}
qDebug() << "Updating automatic networking setting in domain-server to" << newSetting;
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
domainObject[AUTOMATIC_NETWORKING_KEY] = newSetting;
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;
foreach(const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
if (node->getLinkedData() && !static_cast<DomainServerNodeData*>(node->getLinkedData())->getUsername().isEmpty()) {
++numConnectedAuthedUsers;
}
}
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
QJsonObject heartbeatObject;
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers;
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
@ -1112,11 +1135,11 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting,
// todo: have data-web respond with ice-server hostname to use
void DomainServer::performICEUpdates() {
sendHearbeatToIceServer();
sendHeartbeatToIceServer();
sendICEPingPackets();
}
void DomainServer::sendHearbeatToIceServer() {
void DomainServer::sendHeartbeatToIceServer() {
const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT);
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR);
}

View file

@ -66,7 +66,8 @@ private slots:
void requestCurrentPublicSocketViaSTUN();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void performICEUpdates();
void sendHearbeatToIceServer();
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
void sendHeartbeatToIceServer();
void sendICEPingPackets();
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
@ -76,7 +77,7 @@ private:
bool optionallySetupAssignmentPayment();
void setupAutomaticNetworking();
void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString());
void sendHeartbeatToDataServer(const QString& networkAddress);
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
void processICEHeartbeatResponse(const QByteArray& packet);
@ -150,6 +151,8 @@ private:
QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
QString _automaticNetworkingSetting;
DomainServerSettingsManager _settingsManager;
};

View file

@ -2787,13 +2787,13 @@ function setupModelMenus() {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S",
afterItem: "Allow Select Large Models", isCheckable: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
@ -2809,6 +2809,7 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Delete");
}
Menu.removeMenuItem("Edit", "Model List...");
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
@ -2883,16 +2884,21 @@ function handeMenuEvent(menuItem) {
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Model List") {
} else if (menuItem == "Model List...") {
var models = new Array();
models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE);
for (var i = 0; i < models.length; i++) {
models[i].properties = Entities.getEntityProperties(models[i]);
models[i].toString = function() {
var modelname = decodeURIComponent(
this.properties.modelURL.indexOf("/") != -1 ?
this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
this.properties.modelURL);
var modelname;
if (this.properties.type == "Model") {
modelname = decodeURIComponent(
this.properties.modelURL.indexOf("/") != -1 ?
this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
this.properties.modelURL);
} else {
modelname = this.properties.id;
}
return "[" + this.properties.type + "] " + modelname;
};
}

View file

@ -0,0 +1,38 @@
//
// changeColorOnHover.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will
// change the color of the entity when you hover over it. This script uses the JavaScript prototype/class functionality
// to construct an object that has methods for hoverEnterEntity and hoverLeaveEntity;
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
this.oldColor = {};
this.oldColorKnown = false;
this.storeOldColor = function(entityID) {
var oldProperties = Entities.getEntityProperties(entityID);
this.oldColor = oldProperties.color;
this.oldColorKnown = true;
print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
};
this.hoverEnterEntity = function(entityID, mouseEvent) {
if (!this.oldColorKnown) {
this.storeOldColor(entityID);
}
Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} });
};
this.hoverLeaveEntity = function(entityID, mouseEvent) {
if (this.oldColorKnown) {
print("leave restoring old color... this.oldColor="
+ this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
Entities.editEntity(entityID, { color: this.oldColor });
}
};
})

View file

@ -0,0 +1,57 @@
//
// crazylegsOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will make your avatar do the
// crazyLegs dance if you click on it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var cumulativeTime = 0.0;
var FREQUENCY = 5.0;
var AMPLITUDE = 45.0;
var jointList = MyAvatar.getJointNames();
var jointMappings = "\n# Joint list start";
for (var i = 0; i < jointList.length; i++) {
jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i;
}
print(jointMappings + "\n# Joint list end");
this.crazyLegsUpdate = function(deltaTime) {
print("crazyLegsUpdate... deltaTime:" + deltaTime);
cumulativeTime += deltaTime;
print("crazyLegsUpdate... cumulativeTime:" + cumulativeTime);
MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0));
MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(-AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0));
MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees(
AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0));
MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees(
AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0));
};
this.stopCrazyLegs = function() {
MyAvatar.clearJointData("RightUpLeg");
MyAvatar.clearJointData("LeftUpLeg");
MyAvatar.clearJointData("RightLeg");
MyAvatar.clearJointData("LeftLeg");
};
this.clickDownOnEntity = function(entityID, mouseEvent) {
print("clickDownOnEntity()...");
cumulativeTime = 0.0;
Script.update.connect(this.crazyLegsUpdate);
};
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
print("clickReleaseOnEntity()...");
this.stopCrazyLegs();
Script.update.disconnect(this.crazyLegsUpdate);
};
})

View file

@ -0,0 +1,24 @@
//
// playSoundOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when
// you click on it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
this.clickDownOnEntity = function(entityID, mouseEvent) {
print("clickDownOnEntity()...");
var options = new AudioInjectionOptions();
var position = MyAvatar.position;
options.position = position;
options.volume = 0.5;
Audio.playSound(bird, options);
};
})

View file

@ -0,0 +1,32 @@
//
// playSoundOnEnterOrLeave.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when
// your avatar enters or leaves the bounds of the entity.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
function playSound(entityID) {
var options = new AudioInjectionOptions();
var position = MyAvatar.position;
options.position = position;
options.volume = 0.5;
Audio.playSound(bird, options);
};
this.enterEntity = function(entityID) {
playSound();
};
this.leaveEntity = function(entityID) {
playSound();
};
})

View file

@ -0,0 +1,18 @@
//
// teleportOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will teleport your avatar if you
// click on it the entity.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
this.clickDownOnEntity = function(entityID, mouseEvent) {
MyAvatar.position = Entities.getEntityProperties(entityID).position;
};
})

View file

@ -0,0 +1,986 @@
//
// follow-through-and-overlapping-action.js
//
// Simple demonstration showing the visual effect of adding the Disney
// follow through and overlapping action animation technique to avatar movement.
//
// Designed and created by David Wooldridge and Ozan Serim, August 2014
//
// Version 1.001
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// location of overlay images
var pathToOverlays = "https://s3.amazonaws.com/hifi-public/ArmSwingScript/ArmSwingOverlays/";
// animation
var LEFT = 1;
var RIGHT = 2;
var DIRECTION = 0;
// min max
var weightMin = 1;
var weightMax = 20;
var jointEffectMax = 5;
// animation effect variables
var handEffect = 3.4; // 0 to jointEffectMax
var forearmEffect = 2.5; // 0 to jointEffectMax
var limbWeight = 8; // will only use nearest integer, as defines an array length
var effectStrength = 1; // 0 to 1 - overall effect strength
// Overshoot: false uses upper arm as driver for forearm and hand
// true uses upper arm for forearm and lower arm as driver for hand.
var overShoot = false;
// animate self (tap the 'r' key)
var animateSelf = false;
var selfAnimateFrequency = 7.5;
// overlay values
var handleValue = 0;
var controlPosX = Window.innerWidth / 2 - 500;
var controlPosY = 0;
var minSliderX = controlPosX + 18;
var sliderRangeX = 190;
var minHandleX = controlPosX - 50;
var handleRangeX = 350 / 2;
var handlePosition = 0;
// background overlay
var controllerBackground = Overlays.addOverlay("image", {
bounds: {x: controlPosX, y: controlPosY, width: 250, height: 380},
imageURL: pathToOverlays + "flourish-augmentation-control-overlay.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
var controllerRadioSelectedBackground = Overlays.addOverlay("image", {
bounds: {x: controlPosX, y: controlPosY, width: 250, height: 380},
imageURL: pathToOverlays + "flourish-augmentation-control-radio-selected-overlay.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1,
visible: false
});
// handle overlay
var applyMotionHandle = Overlays.addOverlay("image", {
bounds: {x: minHandleX+handleRangeX-39, y: controlPosY+232,
width: 79, height: 100},
imageURL: pathToOverlays + "flourish-augmentation-handle-overlay.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
// slider overlays
var handEffectSlider = Overlays.addOverlay("image", {
bounds: {x: minSliderX + (handEffect / jointEffectMax * sliderRangeX),
y: controlPosY + 46, width: 25, height: 25},
imageURL: pathToOverlays + "ddao-slider-handle.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
var forearmEffectSlider = Overlays.addOverlay("image", {
bounds: {x: minSliderX + (forearmEffect / jointEffectMax * sliderRangeX), y: controlPosY + 86,
width: 25, height: 25},
imageURL: pathToOverlays + "ddao-slider-handle.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
var limbWeightSlider = Overlays.addOverlay("image", {
bounds: {x: minSliderX + (limbWeight / weightMax * sliderRangeX), y: controlPosY+126,
width: 25, height: 25},
imageURL: pathToOverlays + "ddao-slider-handle.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
var effectStrengthSlider = Overlays.addOverlay("image", {
bounds: {x: minSliderX + (effectStrength * sliderRangeX), y: controlPosY+206,
width: 25, height: 25},
imageURL: pathToOverlays + "ddao-slider-handle.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1
});
// main loop - using averaging filters to add limb element follow-through
var upperArmPDampingFilter = [];
upperArmPDampingFilter.length = parseInt(limbWeight); // sets amount of damping for upper arm pitch
var forearmPDampingFilter = [];
forearmPDampingFilter.length = parseInt(limbWeight) + 2; // sets amount of damping for lower arm pitch
var cumulativeTime = 0;
Script.update.connect(function(deltaTime) {
// used for self animating (press r to invoke)
cumulativeTime += deltaTime;
// blend three keyframes using handle position to determine progress between keyframes
var animationProgress = handleValue;
if(animateSelf) {
animationProgress = Math.sin(cumulativeTime * selfAnimateFrequency);
animationProgress++;
animationProgress /= 2;
}
var keyframeOneWeight = 0;
var keyframeTwoWeight = 0;
var keyframeThreeWeight = 0;
if(movingHandle || animateSelf) {
keyframeOneWeight = 0;
keyframeTwoWeight = animationProgress;
keyframeThreeWeight = 1 - animationProgress;
}
else if(!movingHandle) {
// idle
keyframeOneWeight = 1;
keyframeTwoWeight = 0;
keyframeThreeWeight = 0;
}
var shoulderPitch =
keyframeOneWeight * keyFrameOne.joints[8].pitchOffset +
keyframeTwoWeight * keyFrameTwo.joints[8].pitchOffset +
keyframeThreeWeight * keyFrameThree.joints[8].pitchOffset;
var upperArmPitch =
keyframeOneWeight * keyFrameOne.joints[9].pitchOffset +
keyframeTwoWeight * keyFrameTwo.joints[9].pitchOffset +
keyframeThreeWeight * keyFrameThree.joints[9].pitchOffset;
// get the change in upper arm pitch and use to add weight effect to forearm (always) and hand (only for overShoot)
var deltaUpperArmPitch = effectStrength *
(upperArmPDampingFilter[upperArmPDampingFilter.length - 1] -
upperArmPDampingFilter[0]);
var forearmPitch =
keyframeOneWeight * keyFrameOne.joints[10].pitchOffset +
keyframeTwoWeight * keyFrameTwo.joints[10].pitchOffset +
keyframeThreeWeight * keyFrameThree.joints[10].pitchOffset -
(deltaUpperArmPitch/(jointEffectMax - forearmEffect));
// there are two methods for calculating the hand follow through
var handPitch = 0;
if(overShoot) {
// get the change in forearm pitch and use to add weight effect to hand
var deltaForearmPitch = effectStrength *
(forearmPDampingFilter[forearmPDampingFilter.length - 1] -
forearmPDampingFilter[0]);
handPitch =
keyframeOneWeight * keyFrameOne.joints[11].pitchOffset +
keyframeTwoWeight * keyFrameTwo.joints[11].pitchOffset +
keyframeThreeWeight * keyFrameThree.joints[11].pitchOffset +
(deltaForearmPitch /(jointEffectMax - handEffect)); // hand driven by forearm
} else {
handPitch =
keyframeOneWeight * keyFrameOne.joints[11].pitchOffset +
keyframeTwoWeight * keyFrameTwo.joints[11].pitchOffset +
keyframeThreeWeight * keyFrameThree.joints[11].pitchOffset -
(deltaUpperArmPitch /(jointEffectMax - handEffect)); // hand driven by upper arm
}
var shoulderYaw =
keyframeOneWeight * keyFrameOne.joints[8].yawOffset +
keyframeTwoWeight * keyFrameTwo.joints[8].yawOffset +
keyframeThreeWeight * keyFrameThree.joints[8].yawOffset;
var upperArmYaw =
keyframeOneWeight * keyFrameOne.joints[9].yawOffset +
keyframeTwoWeight * keyFrameTwo.joints[9].yawOffset +
keyframeThreeWeight * keyFrameThree.joints[9].yawOffset;
var lowerArmYaw =
keyframeOneWeight * keyFrameOne.joints[10].yawOffset +
keyframeTwoWeight * keyFrameTwo.joints[10].yawOffset +
keyframeThreeWeight * keyFrameThree.joints[10].yawOffset;
var handYaw =
keyframeOneWeight * keyFrameOne.joints[11].yawOffset +
keyframeTwoWeight * keyFrameTwo.joints[11].yawOffset +
keyframeThreeWeight * keyFrameThree.joints[11].yawOffset;
var shoulderRoll =
keyframeOneWeight * keyFrameOne.joints[8].rollOffset +
keyframeTwoWeight * keyFrameTwo.joints[8].rollOffset +
keyframeThreeWeight * keyFrameThree.joints[8].rollOffset;
var upperArmRoll =
keyframeOneWeight * keyFrameOne.joints[9].rollOffset +
keyframeTwoWeight * keyFrameTwo.joints[9].rollOffset +
keyframeThreeWeight * keyFrameThree.joints[9].rollOffset;
var lowerArmRoll =
keyframeOneWeight * keyFrameOne.joints[10].rollOffset +
keyframeTwoWeight * keyFrameTwo.joints[10].rollOffset +
keyframeThreeWeight * keyFrameThree.joints[10].rollOffset;
var handRoll =
keyframeOneWeight * keyFrameOne.joints[11].rollOffset +
keyframeTwoWeight * keyFrameTwo.joints[11].rollOffset +
keyframeThreeWeight * keyFrameThree.joints[11].rollOffset;
// filter upper arm pitch
upperArmPDampingFilter.push(upperArmPitch);
upperArmPDampingFilter.shift();
var upperArmPitchFiltered = 0;
for(ea in upperArmPDampingFilter) upperArmPitchFiltered += upperArmPDampingFilter[ea];
upperArmPitchFiltered /= upperArmPDampingFilter.length;
upperArmPitch = (effectStrength * upperArmPitchFiltered) + ((1 - effectStrength) * upperArmPitch);
// filter forearm pitch only if using for hand follow-though
if(overShoot) {
forearmPDampingFilter.push(forearmPitch);
forearmPDampingFilter.shift();
var forearmPitchFiltered = 0;
for(ea in forearmPDampingFilter) forearmPitchFiltered += forearmPDampingFilter[ea];
forearmPitchFiltered /= forearmPDampingFilter.length;
forearmPitch = (effectStrength*forearmPitchFiltered) + ((1-effectStrength) * forearmPitch);
}
// apply the new rotation data to the joints
MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(shoulderPitch, shoulderYaw, shoulderRoll));
MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees(upperArmPitch, -upperArmYaw, upperArmRoll));
MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees(forearmPitch, lowerArmYaw, lowerArmRoll));
MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollDegrees(handPitch, handYaw, handRoll));
});
// mouse handling
var movingHandEffectSlider = false;
var movingForearmEffectSlider = false;
var movingLimbWeightSlider = false;
var movingDampingSlider = false;
var movingEffectStrengthSlider = false;
var movingHandle = false;
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
if(clickedOverlay === applyMotionHandle) movingHandle = true;
else if(clickedOverlay === handEffectSlider) movingHandEffectSlider = true;
else if(clickedOverlay === forearmEffectSlider) movingForearmEffectSlider = true;
else if(clickedOverlay === limbWeightSlider) movingLimbWeightSlider = true;
else if(clickedOverlay === effectStrengthSlider) movingEffectStrengthSlider = true;
else if(clickedOverlay === controllerRadioSelectedBackground &&
event.x > 477 && event.x < 497 && event.y > 338 && event.y < 360) {
overShoot = false;
Overlays.editOverlay(controllerBackground, {visible: true});
Overlays.editOverlay(controllerRadioSelectedBackground, {visible: false});
}
else if(clickedOverlay === controllerBackground &&
event.x > 477 && event.x < 497 && event.y > 338 && event.y < 360){
overShoot = true;
Overlays.editOverlay(controllerBackground, {visible: false});
Overlays.editOverlay(controllerRadioSelectedBackground, {visible: true});
}
}
function mouseMoveEvent(event) {
if(movingHandle) {
var thumbClickOffsetX = event.x - minHandleX;
var thumbPositionNormalised = (thumbClickOffsetX - handleRangeX) / handleRangeX;
if(thumbPositionNormalised <= -1) thumbPositionNormalised = -1;
else if(thumbPositionNormalised > 1) thumbPositionNormalised = 1;
if(thumbPositionNormalised < 0) DIRECTION = LEFT;
else DIRECTION = RIGHT;
handleValue = (thumbPositionNormalised + 1) / 2;
var handleX = (thumbPositionNormalised * handleRangeX) + handleRangeX - 39;
Overlays.editOverlay(applyMotionHandle, {x: handleX + minHandleX});
return;
}
else if(movingHandEffectSlider) {
var thumbClickOffsetX = event.x - minSliderX;
var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX;
if(thumbPositionNormalised < 0) thumbPositionNormalised = 0;
if(thumbPositionNormalised > 1) thumbPositionNormalised = 1;
handEffect = (thumbPositionNormalised - 0.08) * jointEffectMax;
var sliderX = thumbPositionNormalised * sliderRangeX ;
Overlays.editOverlay(handEffectSlider, {x: sliderX + minSliderX});
}
else if(movingForearmEffectSlider) {
var thumbClickOffsetX = event.x - minSliderX;
var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX;
if(thumbPositionNormalised < 0) thumbPositionNormalised = 0;
if(thumbPositionNormalised > 1) thumbPositionNormalised = 1;
forearmEffect = (thumbPositionNormalised - 0.1) * jointEffectMax;
var sliderX = thumbPositionNormalised * sliderRangeX ;
Overlays.editOverlay(forearmEffectSlider, {x: sliderX + minSliderX});
}
else if(movingLimbWeightSlider) {
var thumbClickOffsetX = event.x - minSliderX;
var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX;
if(thumbPositionNormalised<0) thumbPositionNormalised = 0;
if(thumbPositionNormalised>1) thumbPositionNormalised = 1;
limbWeight = thumbPositionNormalised * weightMax;
if(limbWeight < weightMin) limbWeight = weightMin;
upperArmPDampingFilter.length = parseInt(limbWeight);
var sliderX = thumbPositionNormalised * sliderRangeX ;
Overlays.editOverlay(limbWeightSlider, {x: sliderX + minSliderX});
}
else if(movingEffectStrengthSlider) {
var thumbClickOffsetX = event.x - minSliderX;
var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX;
if(thumbPositionNormalised < 0) thumbPositionNormalised = 0;
if(thumbPositionNormalised > 1) thumbPositionNormalised = 1;
effectStrength = thumbPositionNormalised;
var sliderX = thumbPositionNormalised * sliderRangeX ;
Overlays.editOverlay(effectStrengthSlider, {x: sliderX + minSliderX});
return;
}
}
function mouseReleaseEvent(event) {
if(movingHandle) {
movingHandle = false;
handleValue = 0;
Overlays.editOverlay(applyMotionHandle, {x: minHandleX+handleRangeX - 39});
}
else if(movingHandEffectSlider) movingHandEffectSlider = false;
else if(movingForearmEffectSlider) movingForearmEffectSlider = false;
else if(movingLimbWeightSlider) movingLimbWeightSlider = false;
else if(movingEffectStrengthSlider) movingEffectStrengthSlider = false;
else if(movingDampingSlider) movingDampingSlider = false;
}
// set up mouse and keyboard callbacks
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);
// keyboard command
function keyPressEvent(event) {
if (event.text == "q") {
print('hand effect = ' + handEffect + '\n');
print('forearmEffect = ' + forearmEffect + '\n');
print('limbWeight = ' + limbWeight + '\n');
print('effectStrength = ' + effectStrength + '\n');
}
else if (event.text == "r") {
animateSelf = !animateSelf;
}
else if (event.text == "[") {
selfAnimateFrequency += 0.5;
print('selfAnimateFrequency = '+selfAnimateFrequency);
}
else if (event.text == "]") {
selfAnimateFrequency -= 0.5;
print('selfAnimateFrequency = '+selfAnimateFrequency);
}
}
// zero out all joints
function resetJoints() {
var avatarJointNames = MyAvatar.getJointNames();
for (var i = 0; i < avatarJointNames.length; i++)
MyAvatar.clearJointData(avatarJointNames[i]);
}
// Script ending
Script.scriptEnding.connect(function() {
// delete the overlays
Overlays.deleteOverlay(controllerBackground);
Overlays.deleteOverlay(controllerRadioSelectedBackground);
Overlays.deleteOverlay(handEffectSlider);
Overlays.deleteOverlay(forearmEffectSlider);
Overlays.deleteOverlay(limbWeightSlider);
Overlays.deleteOverlay(effectStrengthSlider);
Overlays.deleteOverlay(applyMotionHandle);
// leave the avi in zeroed out stance
resetJoints();
});
// animation data. animation keyframes produced using walk.js
MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(80,0,0));
var keyFrameOne =
{
"name":"FemaleStandingOne",
"settings":{
"baseFrequency":70,
"flyingHipsPitch":60,
"takeFlightVelocity":40,
"maxBankingAngle":40
},
"adjusters":{
"legsSeparation":{
"strength":-0.03679245283018867,
"separationAngle":50
},
"stride":{
"strength":0,
"upperLegsPitch":30,
"lowerLegsPitch":15,
"upperLegsPitchOffset":0.2,
"lowerLegsPitchOffset":1.5
}
},
"joints":[
{
"name":"hips",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0,
"thrust":0,
"bob":0,
"sway":0,
"thrustPhase":180,
"bobPhase":0,
"swayPhase":-90,
"thrustOffset":0,
"bobOffset":0,
"swayOffset":0
},
{
"name":"upperLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"lowerLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"feet",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"toes",
"pitch":2.0377358490566038,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":4.415094339622641,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine",
"pitch":1.660377358490566,
"yaw":0,
"roll":0,
"pitchPhase":-180,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine1",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine2",
"pitch":2.1132075471698113,
"yaw":0,
"roll":0,
"pitchPhase":-0.6792452830188722,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"shoulders",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0.6792452830188678,
"yawOffset":-5.20754716981132,
"rollOffset":-2.9433962264150937
},
{
"name":"upperArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":77.77358490566039,
"yawOffset":9.169811320754715,
"rollOffset":0
},
{
"name":"lowerArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"hands",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":1.6981132075471694,
"yawOffset":-1.0188679245283017,
"rollOffset":1.0188679245283017
},
{
"name":"head",
"pitch":0,
"yaw":1.7358490566037734,
"roll":1.5094339622641508,
"pitchPhase":-90.33962264150944,
"yawPhase":94.41509433962267,
"rollPhase":0,
"pitchOffset":1.6981132075471694,
"yawOffset":0,
"rollOffset":0
}
]
};
var keyFrameTwo =
{
"name":"FemaleStandingOne",
"settings":{
"baseFrequency":70,
"flyingHipsPitch":60,
"takeFlightVelocity":40,
"maxBankingAngle":40
},
"adjusters":{
"legsSeparation":{
"strength":-0.03679245283018867,
"separationAngle":50
},
"stride":{
"strength":0,
"upperLegsPitch":30,
"lowerLegsPitch":15,
"upperLegsPitchOffset":0.2,
"lowerLegsPitchOffset":1.5
}
},
"joints":[
{
"name":"hips",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0,
"thrust":0,
"bob":0,
"sway":0,
"thrustPhase":180,
"bobPhase":0,
"swayPhase":-90,
"thrustOffset":0,
"bobOffset":0,
"swayOffset":0
},
{
"name":"upperLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"lowerLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"feet",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"toes",
"pitch":2.0377358490566038,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":4.415094339622641,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine",
"pitch":1.660377358490566,
"yaw":0,
"roll":0,
"pitchPhase":-180,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine1",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine2",
"pitch":2.1132075471698113,
"yaw":0,
"roll":0,
"pitchPhase":-0.6792452830188722,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"shoulders",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0.6792452830188678,
"yawOffset":-5.20754716981132,
"rollOffset":-2.9433962264150937
},
{
"name":"upperArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":49.584905660377345,
"yawOffset":9.169811320754715,
"rollOffset":0
},
{
"name":"lowerArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"hands",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":-13,
"yawOffset":-1.0188679245283017,
"rollOffset":1.0188679245283017
},
{
"name":"head",
"pitch":0,
"yaw":1.7358490566037734,
"roll":1.5094339622641508,
"pitchPhase":-90.33962264150944,
"yawPhase":94.41509433962267,
"rollPhase":0,
"pitchOffset":1.6981132075471694,
"yawOffset":0,
"rollOffset":0
}
]
};
var keyFrameThree =
{
"name":"FemaleStandingOne",
"settings":{
"baseFrequency":70,
"flyingHipsPitch":60,
"takeFlightVelocity":40,
"maxBankingAngle":40
},
"adjusters":{
"legsSeparation":{
"strength":-0.03679245283018867,
"separationAngle":50
},
"stride":{
"strength":0,
"upperLegsPitch":30,
"lowerLegsPitch":15,
"upperLegsPitchOffset":0.2,
"lowerLegsPitchOffset":1.5
}
},
"joints":[
{
"name":"hips",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0,
"thrust":0,
"bob":0,
"sway":0,
"thrustPhase":180,
"bobPhase":0,
"swayPhase":-90,
"thrustOffset":0,
"bobOffset":0,
"swayOffset":0
},
{
"name":"upperLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"lowerLegs",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"feet",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"toes",
"pitch":2.0377358490566038,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":4.415094339622641,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine",
"pitch":1.660377358490566,
"yaw":0,
"roll":0,
"pitchPhase":-180,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine1",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"spine2",
"pitch":2.1132075471698113,
"yaw":0,
"roll":0,
"pitchPhase":-0.6792452830188722,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"shoulders",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":-21.0566037735849,
"yawOffset":-5.20754716981132,
"rollOffset":-2.9433962264150937
},
{
"name":"upperArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":-33.28301886792452,
"yawOffset":9.169811320754715,
"rollOffset":0
},
{
"name":"lowerArms",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":0,
"yawOffset":0,
"rollOffset":0
},
{
"name":"hands",
"pitch":0,
"yaw":0,
"roll":0,
"pitchPhase":0,
"yawPhase":0,
"rollPhase":0,
"pitchOffset":-13,
"yawOffset":-1.0188679245283017,
"rollOffset":1.0188679245283017
},
{
"name":"head",
"pitch":0,
"yaw":1.7358490566037734,
"roll":1.5094339622641508,
"pitchPhase":-90.33962264150944,
"yawPhase":94.41509433962267,
"rollPhase":0,
"pitchOffset":1.6981132075471694,
"yawOffset":0,
"rollOffset":0
}
]
};

View file

@ -17,13 +17,17 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var PI = 3.14 // No need for something more precise
var PI = Math.PI;
var RAD_TO_DEG = 180.0 / PI;
var AZIMUTH_RATE = 90.0;
var ALTITUDE_RATE = 200.0;
var RADIUS_RATE = 1.0 / 100.0;
var PAN_RATE = 50.0;
var Y_AXIS = { x: 0, y: 1, z: 0 };
var X_AXIS = { x: 1, y: 0, z: 0 };
var alt = false;
var shift = false;
var control = false;
@ -53,6 +57,18 @@ var avatarPosition;
var avatarOrientation;
function orientationOf(vector) {
var direction,
yaw,
pitch;
direction = Vec3.normalize(vector);
yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
return Quat.multiply(yaw, pitch);
}
function handleRadialMode(dx, dy) {
azimuth += dx / AZIMUTH_RATE;
radius += radius * dy * RADIUS_RATE;
@ -65,6 +81,7 @@ function handleRadialMode(dx, dy) {
z: (Math.cos(altitude) * Math.sin(azimuth)) * radius };
position = Vec3.sum(center, vector);
Camera.setPosition(position);
Camera.setOrientation(orientationOf(vector));
}
function handleOrbitMode(dx, dy) {
@ -82,6 +99,7 @@ function handleOrbitMode(dx, dy) {
z:(Math.cos(altitude) * Math.sin(azimuth)) * radius };
position = Vec3.sum(center, vector);
Camera.setPosition(position);
Camera.setOrientation(orientationOf(vector));
}
@ -96,7 +114,7 @@ function handlePanMode(dx, dy) {
position = Vec3.sum(position, dv);
Camera.setPosition(position);
Camera.keepLookingAt(center);
Camera.setOrientation(orientationOf(vector));
}
function saveCameraState() {
@ -107,7 +125,6 @@ function saveCameraState() {
}
function restoreCameraState() {
Camera.stopLooking();
Camera.setMode(oldMode);
}
@ -245,7 +262,6 @@ function mousePressEvent(event) {
azimuth = Math.atan2(vector.z, vector.x);
altitude = Math.asin(vector.y / Vec3.length(vector));
Camera.keepLookingAt(center);
print(string);
isActive = true;
}

View file

@ -16,7 +16,7 @@ var leapHands = (function () {
var isOnHMD,
LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD",
LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip
HMD_OFFSET = 0.100, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief
HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief
hands,
wrists,
NUM_HANDS = 2, // 0 = left; 1 = right

View file

@ -140,6 +140,9 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Visible:", value: properties.visible });
index++;
array.push({ label: "Script:", value: properties.script });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
@ -282,6 +285,7 @@ EntityPropertyDialogBox = (function () {
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
properties.script = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header

View file

@ -174,7 +174,7 @@ function maybeCleanupLobby() {
function toggleEnvironmentRendering(shouldRender) {
Menu.setIsOptionChecked("Voxels", shouldRender);
Menu.setIsOptionChecked("Models", shouldRender);
Menu.setIsOptionChecked("Entities", shouldRender);
Menu.setIsOptionChecked("Metavoxels", shouldRender);
Menu.setIsOptionChecked("Avatars", shouldRender);
}

View file

@ -578,6 +578,7 @@ function setupModelMenus() {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true });
@ -603,6 +604,7 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Delete");
}
Menu.removeMenuItem("Edit", "Model List...");
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
@ -660,6 +662,38 @@ function handeMenuEvent(menuItem) {
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Model List...") {
var models = new Array();
models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE);
for (var i = 0; i < models.length; i++) {
models[i].properties = Entities.getEntityProperties(models[i]);
models[i].toString = function() {
var modelname;
if (this.properties.type == "Model") {
modelname = decodeURIComponent(
this.properties.modelURL.indexOf("/") != -1 ?
this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
this.properties.modelURL);
} else {
modelname = this.properties.id;
}
return "[" + this.properties.type + "] " + modelname;
};
}
var form = [{label: "Model: ", options: models}];
form.push({label: "Action: ", options: ["Properties", "Delete", "Teleport"]});
form.push({ button: "Cancel" });
if (Window.form("Model List", form)) {
var selectedModel = form[0].value;
if (form[1].value == "Properties") {
editModelID = selectedModel;
showPropertiesForm(editModelID);
} else if (form[1].value == "Delete") {
Entities.deleteEntity(selectedModel);
} else if (form[1].value == "Teleport") {
MyAvatar.position = selectedModel.properties.position;
}
}
} else if (menuItem == "Edit Properties...") {
// good place to put the properties dialog

View file

@ -107,8 +107,8 @@ static unsigned STARFIELD_SEED = 1;
static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored
const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff
// in the idle loop? (60 FPS is default)
const unsigned MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
static QTimer* idleTimer = NULL;
const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml";
@ -148,7 +148,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_entityClipboardRenderer(),
_entities(true),
_entityCollisionSystem(),
_entityClipboardRenderer(false),
_entityClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
@ -182,9 +184,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_trayIcon(new QSystemTrayIcon(_window)),
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_renderTargetFramerate(0),
_isVSyncOn(true),
_renderResolutionScale(1.0f)
_isVSyncOn(true)
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
@ -347,9 +347,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
networkAccessManager.setCache(cache);
@ -601,7 +601,7 @@ void Application::paintGL() {
if (OculusManager::isConnected()) {
_textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize());
} else {
QSize fbSize = _glWidget->getDeviceSize() * _renderResolutionScale;
QSize fbSize = _glWidget->getDeviceSize() * getRenderResolutionScale();
_textureCache.setFrameBufferSize(fbSize);
}
@ -1248,6 +1248,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
if (_lastMouseMoveWasSimulated) {
showMouse = false;
}
_entities.mouseMoveEvent(event, deviceID);
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
@ -1269,6 +1271,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
}
void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
_entities.mousePressEvent(event, deviceID);
_controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -1306,6 +1311,9 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
}
void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
_entities.mouseReleaseEvent(event, deviceID);
_controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -1471,12 +1479,11 @@ void Application::idle() {
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
// Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran
// Only run simulation code if more than the targetFramePeriod have passed since last time we ran
double targetFramePeriod = 0.0;
if (_renderTargetFramerate > 0) {
targetFramePeriod = 1000.0 / _renderTargetFramerate;
} else if (_renderTargetFramerate < 0) {
targetFramePeriod = IDLE_SIMULATE_MSECS;
unsigned int targetFramerate = getRenderTargetFramerate();
if (targetFramerate > 0) {
targetFramePeriod = 1000.0 / targetFramerate;
}
double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0;
if (timeSinceLastUpdate > targetFramePeriod) {
@ -1558,12 +1565,9 @@ void Application::setEnableVRMode(bool enableVRMode) {
OculusManager::disconnect();
OculusManager::connect();
}
int oculusMaxFPS = Menu::getInstance()->getOculusUIMaxFPS();
setRenderTargetFramerate(oculusMaxFPS);
OculusManager::recalibrate();
} else {
OculusManager::abandonCalibration();
setRenderTargetFramerate(0);
}
resizeGL(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight());
@ -1943,6 +1947,10 @@ void Application::init() {
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
// of events related clicking, hovering over, and entering entities
_entities.connectSignalsToSlots(ScriptEngine::getEntityScriptingInterface());
_entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
@ -2338,7 +2346,7 @@ void Application::update(float deltaTime) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
queryOctree(NodeType::EntityServer, PacketTypeEntityQuery, _entityServerJurisdictions);
}
_lastQueriedViewFrustum = _viewFrustum;
@ -2985,7 +2993,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
}
// render models...
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
PerformanceTimer perfTimer("entities");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::displaySide() ... entities...");
@ -3179,7 +3187,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
} else {
// if not rendering the billboard, the region is in device independent coordinates; must convert to device
QSize size = getTextureCache()->getFrameBufferSize();
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * _renderResolutionScale;
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale();
int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio;
glViewport(x, size.height() - y - height, width, height);
glScissor(x, size.height() - y - height, width, height);
@ -3822,35 +3830,7 @@ void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) {
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application.
scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
@ -3932,6 +3912,38 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
// Starts an event loop, and emits workerThread->started()
workerThread->start();
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
registerScriptEngineWithApplicationServices(scriptEngine);
// restore the main window's active state
if (activateMainWindow && !loadScriptFromEditor) {
@ -4237,37 +4249,56 @@ void Application::takeSnapshot() {
_snapshotShareDialog->show();
}
void Application::setRenderTargetFramerate(unsigned int framerate, bool vsyncOn) {
if (vsyncOn != _isVSyncOn) {
void Application::setVSyncEnabled(bool vsyncOn) {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
glxSwapIntervalEXT(vsyncOn);
int swapInterval = xglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
*/
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
}
_renderTargetFramerate = framerate;
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
glxSwapIntervalEXT(vsyncOn);
int swapInterval = xglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
*/
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
}
bool Application::isVSyncEditable() {
bool Application::isVSyncOn() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
return (swapInterval > 0);
} else {
return true;
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
int swapInterval = xglGetSwapIntervalEXT();
return (swapInterval > 0);
} else {
return true;
}
*/
#else
return true;
#endif
}
bool Application::isVSyncEditable() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
return true;
@ -4284,6 +4315,33 @@ bool Application::isVSyncEditable() {
return false;
}
void Application::setRenderResolutionScale(float scale) {
_renderResolutionScale = scale;
unsigned int Application::getRenderTargetFramerate() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) {
return 0;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) {
return 60;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) {
return 50;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) {
return 40;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) {
return 30;
}
return 0;
}
float Application::getRenderResolutionScale() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) {
return 1.f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) {
return 0.666f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) {
return 0.5f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) {
return 0.333f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) {
return 0.25f;
} else {
return 1.f;
}
}

View file

@ -303,7 +303,14 @@ public:
bool isLookingAtMyAvatar(Avatar* avatar);
float getRenderResolutionScale() const { return _renderResolutionScale; }
float getRenderResolutionScale() const;
unsigned int getRenderTargetFramerate() const;
bool isVSyncOn() const;
bool isVSyncEditable() const;
void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
signals:
@ -367,12 +374,7 @@ public slots:
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
void setRenderTargetFramerate(unsigned int framerate, bool vsyncOn = true);
bool isVSyncOn() { return _isVSyncOn; }
bool isVSyncEditable();
unsigned int getRenderTargetFramerate() const { return _renderTargetFramerate; }
void setRenderResolutionScale(float scale);
void setVSyncEnabled(bool vsyncOn);
void resetSensors();
@ -624,9 +626,7 @@ private:
quint64 _lastNackTime;
quint64 _lastSendDownstreamAudioStats;
int _renderTargetFramerate;
bool _isVSyncOn;
float _renderResolutionScale;
};
#endif // hifi_Application_h

View file

@ -41,7 +41,7 @@ void FileLogger::addMessage(QString message) {
QFile file(_fileName);
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
QTextStream out(&file);
out << message;
out << message << "\n";
}
}

View file

@ -105,7 +105,6 @@ Menu::Menu() :
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE),
_oculusUIMaxFPS(DEFAULT_OCULUS_UI_MAX_FPS),
_sixenseReticleMoveSpeed(DEFAULT_SIXENSE_RETICLE_MOVE_SPEED),
_invertSixenseButtons(DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS),
_automaticAvatarLOD(true),
@ -367,7 +366,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Avatars, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true);
QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows");
QActionGroup* shadowGroup = new QActionGroup(shadowMenu);
@ -378,13 +377,12 @@ Menu::Menu() :
{
QMenu* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate);
QActionGroup* framerateGroup = new QActionGroup(framerateMenu);
framerateGroup->setExclusive(true);
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate60, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate50, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate40, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate30, 0, false));
connect(framerateMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderTargetFramerate(QAction*)));
#if defined(Q_OS_MAC)
#else
@ -395,12 +393,12 @@ Menu::Menu() :
QMenu* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution);
QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu);
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false));
resolutionGroup->setExclusive(true);
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, true));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false));
connect(resolutionMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderResolution(QAction*)));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu,
@ -434,14 +432,16 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
QMenu* modelDebugMenu = developerMenu->addMenu("Models");
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
QMenu* modelCullingMenu = modelDebugMenu->addMenu("Culling");
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false);
QMenu* entitiesDebugMenu = developerMenu->addMenu("Entities");
addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);
addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisableLightEntities, 0, false);
QMenu* entityCullingMenu = entitiesDebugMenu->addMenu("Culling");
addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false);
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels");
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
@ -784,8 +784,6 @@ void Menu::loadSettings(QSettings* settings) {
settings->endGroup();
_walletPrivateKey = settings->value("privateKey").toByteArray();
_oculusUIMaxFPS = loadSetting(settings, "oculusUIMaxFPS", 0.0f);
scanMenuBar(&loadAction, settings);
Application::getInstance()->getAvatar()->loadData(settings);
@ -847,9 +845,6 @@ void Menu::saveSettings(QSettings* settings) {
settings->setValue("viewFrustumOffsetUp", _viewFrustumOffset.up);
settings->endGroup();
settings->setValue("privateKey", _walletPrivateKey);
// Oculus Rift settings
settings->setValue("oculusUIMaxFPS", _oculusUIMaxFPS);
scanMenuBar(&saveAction, settings);
Application::getInstance()->getAvatar()->saveData(settings);
@ -1265,46 +1260,7 @@ void Menu::muteEnvironment() {
}
void Menu::changeVSync() {
Application::getInstance()->setRenderTargetFramerate(
Application::getInstance()->getRenderTargetFramerate(),
isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn));
}
void Menu::changeRenderTargetFramerate(QAction* action) {
bool vsynOn = Application::getInstance()->isVSyncOn();
QString text = action->text();
if (text == MenuOption::RenderTargetFramerateUnlimited) {
Application::getInstance()->setRenderTargetFramerate(0, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate60) {
Application::getInstance()->setRenderTargetFramerate(60, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate50) {
Application::getInstance()->setRenderTargetFramerate(50, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate40) {
Application::getInstance()->setRenderTargetFramerate(40, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate30) {
Application::getInstance()->setRenderTargetFramerate(30, vsynOn);
}
}
void Menu::changeRenderResolution(QAction* action) {
QString text = action->text();
if (text == MenuOption::RenderResolutionOne) {
Application::getInstance()->setRenderResolutionScale(1.f);
} else if (text == MenuOption::RenderResolutionTwoThird) {
Application::getInstance()->setRenderResolutionScale(0.666f);
} else if (text == MenuOption::RenderResolutionHalf) {
Application::getInstance()->setRenderResolutionScale(0.5f);
} else if (text == MenuOption::RenderResolutionThird) {
Application::getInstance()->setRenderResolutionScale(0.333f);
} else if (text == MenuOption::RenderResolutionQuarter) {
Application::getInstance()->setRenderResolutionScale(0.25f);
} else {
Application::getInstance()->setRenderResolutionScale(1.f);
}
Application::getInstance()->setVSyncEnabled(isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn));
}
void Menu::displayNameLocationResponse(const QString& errorString) {

View file

@ -100,8 +100,6 @@ public:
void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; bumpSettings(); }
float getOculusUIAngularSize() const { return _oculusUIAngularSize; }
void setOculusUIAngularSize(float oculusUIAngularSize) { _oculusUIAngularSize = oculusUIAngularSize; bumpSettings(); }
int getOculusUIMaxFPS() const { return _oculusUIMaxFPS; }
void setOculusUIMaxFPS(int oculusUIMaxFPS) { _oculusUIMaxFPS = oculusUIMaxFPS; bumpSettings(); }
float getSixenseReticleMoveSpeed() const { return _sixenseReticleMoveSpeed; }
void setSixenseReticleMoveSpeed(float sixenseReticleMoveSpeed) { _sixenseReticleMoveSpeed = sixenseReticleMoveSpeed; bumpSettings(); }
bool getInvertSixenseButtons() const { return _invertSixenseButtons; }
@ -232,9 +230,7 @@ private slots:
void displayAddressOfflineMessage();
void displayAddressNotFoundMessage();
void muteEnvironment();
void changeRenderTargetFramerate(QAction* action);
void changeVSync();
void changeRenderResolution(QAction* action);
private:
static Menu* _instance;
@ -295,7 +291,6 @@ private:
int _maxVoxels;
float _voxelSizeScale;
float _oculusUIAngularSize;
int _oculusUIMaxFPS;
float _sixenseReticleMoveSpeed;
bool _invertSixenseButtons;
bool _automaticAvatarLOD;
@ -387,6 +382,7 @@ namespace MenuOption {
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableActivityLogger = "Disable Activity Logger";
const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD";
const QString DisableLightEntities = "Disable Light Entities";
const QString DisableNackPackets = "Disable NACK Packets";
const QString DisplayFrustum = "Display Frustum";
const QString DisplayHands = "Show Hand Info";
@ -403,6 +399,7 @@ namespace MenuOption {
const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
const QString EnableVRMode = "Enable VR Mode";
const QString Entities = "Entities";
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
@ -433,8 +430,6 @@ namespace MenuOption {
const QString MetavoxelEditor = "Metavoxel Editor...";
const QString Metavoxels = "Metavoxels";
const QString Mirror = "Mirror";
const QString ModelOptions = "Model Options";
const QString Models = "Models";
const QString MoveWithLean = "Move with Lean";
const QString MuteAudio = "Mute Microphone";
const QString MuteEnvironment = "Mute Environment";

View file

@ -961,6 +961,7 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale,
_scale(scale),
_heightBounds(translation, translation + glm::vec3(scale, scale, scale)),
_colorBounds(_heightBounds),
_materialBounds(_heightBounds),
_height(height),
_color(color),
_material(material),
@ -968,10 +969,12 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale,
_heightTextureID(0),
_colorTextureID(0),
_materialTextureID(0),
_heightSize(glm::sqrt(float(height.size()))),
_heightSize(glm::sqrt((float)height.size())),
_heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)),
_colorSize(glm::sqrt(float(color.size() / DataBlock::COLOR_BYTES))),
_colorIncrement(scale / (_colorSize - SHARED_EDGE)) {
_colorSize(glm::sqrt((float)color.size() / DataBlock::COLOR_BYTES)),
_colorIncrement(scale / (_colorSize - SHARED_EDGE)),
_materialSize(glm::sqrt((float)material.size())),
_materialIncrement(scale / (_materialSize - SHARED_EDGE)) {
_heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER;
_heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER;
@ -980,6 +983,9 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale,
_colorBounds.maximum.x += _colorIncrement * SHARED_EDGE;
_colorBounds.maximum.z += _colorIncrement * SHARED_EDGE;
_materialBounds.maximum.x += _materialIncrement * SHARED_EDGE;
_materialBounds.maximum.z += _materialIncrement * SHARED_EDGE;
}
HeightfieldBuffer::~HeightfieldBuffer() {
@ -1006,16 +1012,14 @@ QByteArray HeightfieldBuffer::getUnextendedHeight() const {
return unextended;
}
QByteArray HeightfieldBuffer::getUnextendedColor() const {
int srcSize = glm::sqrt(float(_color.size() / DataBlock::COLOR_BYTES));
int destSize = srcSize - 1;
QByteArray unextended(destSize * destSize * DataBlock::COLOR_BYTES, 0);
const char* src = _color.constData();
int srcStride = srcSize * DataBlock::COLOR_BYTES;
QByteArray HeightfieldBuffer::getUnextendedColor(int x, int y) const {
int unextendedSize = _heightSize - HEIGHT_EXTENSION;
QByteArray unextended(unextendedSize * unextendedSize * DataBlock::COLOR_BYTES, 0);
char* dest = unextended.data();
int destStride = destSize * DataBlock::COLOR_BYTES;
for (int z = 0; z < destSize; z++, src += srcStride, dest += destStride) {
memcpy(dest, src, destStride);
const char* src = _color.constData() + (y * _colorSize + x) * unextendedSize * DataBlock::COLOR_BYTES;
for (int z = 0; z < unextendedSize; z++, dest += unextendedSize * DataBlock::COLOR_BYTES,
src += _colorSize * DataBlock::COLOR_BYTES) {
memcpy(dest, src, unextendedSize * DataBlock::COLOR_BYTES);
}
return unextended;
}
@ -1702,134 +1706,246 @@ public:
HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector<Box>& intersections);
void init(HeightfieldBuffer* buffer) { _buffer = buffer; }
void init(HeightfieldBuffer* buffer);
virtual int visit(MetavoxelInfo& info);
virtual bool postVisit(MetavoxelInfo& info);
private:
const QVector<Box>& _intersections;
HeightfieldBuffer* _buffer;
QVector<int> _depthFlags;
};
enum DepthFlags { HEIGHT_FLAG = 0x01, COLOR_FLAG = 0x02, MATERIAL_FLAG = 0x04 };
HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector<Box>& intersections) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getHeightfieldAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector<AttributePointer>(), lod),
AttributeRegistry::getInstance()->getHeightfieldColorAttribute() <<
AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), QVector<AttributePointer>(), lod),
_intersections(intersections) {
}
void HeightfieldFetchVisitor::init(HeightfieldBuffer* buffer) {
_buffer = buffer;
}
int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) {
Box bounds = info.getBounds();
const Box& heightBounds = _buffer->getHeightBounds();
if (!bounds.intersects(heightBounds)) {
if (!info.getBounds().intersects(_buffer->getHeightBounds())) {
return STOP_RECURSION;
}
if (!info.isLeaf && info.size > _buffer->getScale()) {
if (_depthFlags.size() > _depth) {
_depthFlags[_depth] = 0;
} else {
_depthFlags.append(0);
}
if (!info.isLeaf) {
return DEFAULT_ORDER;
}
postVisit(info);
return STOP_RECURSION;
}
bool HeightfieldFetchVisitor::postVisit(MetavoxelInfo& info) {
HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
if (!height) {
return STOP_RECURSION;
int flags = _depthFlags.at(_depth);
if (height) {
// to handle borders correctly, make sure we only sample nodes with resolution <= ours
int heightSize = glm::sqrt((float)height->getContents().size());
float heightIncrement = info.size / heightSize;
if (heightIncrement < _buffer->getHeightIncrement() || (flags & HEIGHT_FLAG)) {
height.reset();
} else {
flags |= HEIGHT_FLAG;
}
}
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
if (color) {
int colorSize = glm::sqrt((float)color->getContents().size() / DataBlock::COLOR_BYTES);
float colorIncrement = info.size / colorSize;
if (colorIncrement < _buffer->getColorIncrement() || (flags & COLOR_FLAG)) {
color.reset();
} else {
flags |= COLOR_FLAG;
}
}
HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue<HeightfieldMaterialDataPointer>();
if (material) {
int materialSize = glm::sqrt((float)material->getContents().size());
float materialIncrement = info.size / materialSize;
if (materialIncrement < _buffer->getMaterialIncrement() || (flags & MATERIAL_FLAG)) {
material.reset();
} else {
flags |= MATERIAL_FLAG;
}
}
if (_depth > 0) {
_depthFlags[_depth - 1] |= flags;
}
if (!(height || color || material)) {
return false;
}
Box bounds = info.getBounds();
foreach (const Box& intersection, _intersections) {
Box overlap = intersection.getIntersection(bounds);
if (overlap.isEmpty()) {
continue;
}
float heightIncrement = _buffer->getHeightIncrement();
int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement;
int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement;
int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement);
int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement);
int heightSize = _buffer->getHeightSize();
char* dest = _buffer->getHeight().data() + destY * heightSize + destX;
const QByteArray& srcHeight = height->getContents();
int srcSize = glm::sqrt(float(srcHeight.size()));
float srcIncrement = info.size / srcSize;
if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) {
// easy case: same resolution
int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
if (height) {
float heightIncrement = _buffer->getHeightIncrement();
const Box& heightBounds = _buffer->getHeightBounds();
int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement;
int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement;
int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement);
int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement);
int heightSize = _buffer->getHeightSize();
char* dest = _buffer->getHeight().data() + destY * heightSize + destX;
const char* src = srcHeight.constData() + srcY * srcSize + srcX;
for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) {
memcpy(dest, src, destWidth);
}
} else {
// more difficult: different resolutions
float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
float srcAdvance = heightIncrement / srcIncrement;
int shift = 0;
float size = _buffer->getScale();
while (size < info.size) {
shift++;
size *= 2.0f;
}
int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale();
for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) {
const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize;
float lineSrcX = srcX;
for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) {
*lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM);
const QByteArray& srcHeight = height->getContents();
int srcSize = glm::sqrt((float)srcHeight.size());
float srcIncrement = info.size / srcSize;
if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) {
// easy case: same resolution
int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
const char* src = srcHeight.constData() + srcY * srcSize + srcX;
for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) {
memcpy(dest, src, destWidth);
}
} else {
// more difficult: different resolutions
float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
float srcAdvance = heightIncrement / srcIncrement;
int shift = 0;
float size = _buffer->getScale();
while (size < info.size) {
shift++;
size *= 2.0f;
}
int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale();
for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) {
const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize;
float lineSrcX = srcX;
for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) {
*lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM);
}
}
}
}
int colorSize = _buffer->getColorSize();
if (colorSize == 0) {
continue;
}
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
if (!color) {
continue;
}
const Box& colorBounds = _buffer->getColorBounds();
overlap = colorBounds.getIntersection(overlap);
float colorIncrement = _buffer->getColorIncrement();
destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement;
destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement;
destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement);
destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement);
dest = _buffer->getColor().data() + (destY * colorSize + destX) * DataBlock::COLOR_BYTES;
int destStride = colorSize * DataBlock::COLOR_BYTES;
int destBytes = destWidth * DataBlock::COLOR_BYTES;
const QByteArray& srcColor = color->getContents();
srcSize = glm::sqrt(float(srcColor.size() / DataBlock::COLOR_BYTES));
int srcStride = srcSize * DataBlock::COLOR_BYTES;
srcIncrement = info.size / srcSize;
if (srcIncrement == colorIncrement) {
// easy case: same resolution
int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
if (color) {
const Box& colorBounds = _buffer->getColorBounds();
overlap = colorBounds.getIntersection(overlap);
float colorIncrement = _buffer->getColorIncrement();
int destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement;
int destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement;
int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement);
int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement);
int colorSize = _buffer->getColorSize();
char* dest = _buffer->getColor().data() + (destY * colorSize + destX) * DataBlock::COLOR_BYTES;
int destStride = colorSize * DataBlock::COLOR_BYTES;
int destBytes = destWidth * DataBlock::COLOR_BYTES;
const char* src = srcColor.constData() + (srcY * srcSize + srcX) * DataBlock::COLOR_BYTES;
for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) {
memcpy(dest, src, destBytes);
}
} else {
// more difficult: different resolutions
float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
float srcAdvance = colorIncrement / srcIncrement;
for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) {
const char* src = srcColor.constData() + (int)srcY * srcStride;
float lineSrcX = srcX;
for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += DataBlock::COLOR_BYTES,
lineSrcX += srcAdvance) {
const char* lineSrc = src + (int)lineSrcX * DataBlock::COLOR_BYTES;
lineDest[0] = lineSrc[0];
lineDest[1] = lineSrc[1];
lineDest[2] = lineSrc[2];
const QByteArray& srcColor = color->getContents();
int srcSize = glm::sqrt(float(srcColor.size() / DataBlock::COLOR_BYTES));
int srcStride = srcSize * DataBlock::COLOR_BYTES;
float srcIncrement = info.size / srcSize;
if (srcIncrement == colorIncrement) {
// easy case: same resolution
int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
const char* src = srcColor.constData() + (srcY * srcSize + srcX) * DataBlock::COLOR_BYTES;
for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) {
memcpy(dest, src, destBytes);
}
} else {
// more difficult: different resolutions
float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
float srcAdvance = colorIncrement / srcIncrement;
for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) {
const char* src = srcColor.constData() + (int)srcY * srcStride;
float lineSrcX = srcX;
for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += DataBlock::COLOR_BYTES,
lineSrcX += srcAdvance) {
const char* lineSrc = src + (int)lineSrcX * DataBlock::COLOR_BYTES;
lineDest[0] = lineSrc[0];
lineDest[1] = lineSrc[1];
lineDest[2] = lineSrc[2];
}
}
}
}
if (material) {
const Box& materialBounds = _buffer->getMaterialBounds();
overlap = materialBounds.getIntersection(overlap);
float materialIncrement = _buffer->getMaterialIncrement();
int destX = (overlap.minimum.x - materialBounds.minimum.x) / materialIncrement;
int destY = (overlap.minimum.z - materialBounds.minimum.z) / materialIncrement;
int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / materialIncrement);
int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / materialIncrement);
int materialSize = _buffer->getMaterialSize();
char* dest = _buffer->getMaterial().data() + destY * materialSize + destX;
const QByteArray& srcMaterial = material->getContents();
const QVector<SharedObjectPointer> srcMaterials = material->getMaterials();
int srcSize = glm::sqrt((float)srcMaterial.size());
float srcIncrement = info.size / srcSize;
QHash<int, int> materialMappings;
if (srcIncrement == materialIncrement) {
// easy case: same resolution
int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
const uchar* src = (const uchar*)srcMaterial.constData() + srcY * srcSize + srcX;
for (int y = 0; y < destHeight; y++, src += srcSize, dest += materialSize) {
const uchar* lineSrc = src;
for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrc++) {
int value = *lineSrc;
if (value != 0) {
int& mapping = materialMappings[value];
if (mapping == 0) {
mapping = getMaterialIndex(material->getMaterials().at(value - 1),
_buffer->getMaterials(), _buffer->getMaterial());
}
value = mapping;
}
*lineDest = value;
}
}
} else {
// more difficult: different resolutions
float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement;
float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement;
float srcAdvance = materialIncrement / srcIncrement;
for (int y = 0; y < destHeight; y++, dest += materialSize, srcY += srcAdvance) {
const uchar* src = (const uchar*)srcMaterial.constData() + (int)srcY * srcSize;
float lineSrcX = srcX;
for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) {
int value = src[(int)lineSrcX];
if (value != 0) {
int& mapping = materialMappings[value];
if (mapping == 0) {
mapping = getMaterialIndex(material->getMaterials().at(value - 1),
_buffer->getMaterials(), _buffer->getMaterial());
}
value = mapping;
}
*lineDest = value;
}
}
}
clearUnusedMaterials(_buffer->getMaterials(), _buffer->getMaterial());
}
}
return STOP_RECURSION;
return false;
}
class HeightfieldRegionVisitor : public MetavoxelVisitor {
@ -1841,11 +1957,22 @@ public:
HeightfieldRegionVisitor(const MetavoxelLOD& lod);
virtual int visit(MetavoxelInfo& info);
virtual bool postVisit(MetavoxelInfo& info);
private:
void addRegion(const Box& unextended, const Box& extended);
class DepthInfo {
public:
float minimumColorIncrement;
float minimumMaterialIncrement;
DepthInfo() : minimumColorIncrement(FLT_MAX), minimumMaterialIncrement(FLT_MAX) { }
};
QVector<DepthInfo> _depthInfo;
QVector<Box> _intersections;
HeightfieldFetchVisitor _fetchVisitor;
};
@ -1861,70 +1988,99 @@ HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) :
}
int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) {
DepthInfo depthInfo;
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
if (color) {
int colorSize = glm::sqrt((float)color->getContents().size() / DataBlock::COLOR_BYTES);
depthInfo.minimumColorIncrement = info.size / colorSize;
}
HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue<HeightfieldMaterialDataPointer>();
if (material) {
int materialSize = glm::sqrt((float)material->getContents().size());
depthInfo.minimumMaterialIncrement = info.size / materialSize;
}
if (_depth < _depthInfo.size()) {
_depthInfo[_depth] = depthInfo;
} else {
_depthInfo.append(depthInfo);
}
if (!info.isLeaf) {
return DEFAULT_ORDER;
return _visitations.at(_depth).isInputLeaf(0) ? (DEFAULT_ORDER | ALL_NODES_REST) : DEFAULT_ORDER;
}
HeightfieldBuffer* buffer = NULL;
HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
if (height) {
const QByteArray& heightContents = height->getContents();
int size = glm::sqrt(float(heightContents.size()));
int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION;
int heightContentsSize = extendedSize * extendedSize;
HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue<HeightfieldColorDataPointer>();
int colorContentsSize = 0;
if (color) {
const QByteArray& colorContents = color->getContents();
int colorSize = glm::sqrt(float(colorContents.size() / DataBlock::COLOR_BYTES));
int extendedColorSize = colorSize + HeightfieldBuffer::SHARED_EDGE;
colorContentsSize = extendedColorSize * extendedColorSize * DataBlock::COLOR_BYTES;
}
HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue<HeightfieldMaterialDataPointer>();
QByteArray materialContents;
QVector<SharedObjectPointer> materials;
if (material) {
materialContents = material->getContents();
materials = material->getMaterials();
}
const HeightfieldBuffer* existingBuffer = static_cast<const HeightfieldBuffer*>(
info.inputValues.at(3).getInlineValue<BufferDataPointer>().data());
Box bounds = info.getBounds();
if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize &&
existingBuffer->getColor().size() == colorContentsSize) {
// we already have a buffer of the correct resolution
addRegion(bounds, existingBuffer->getHeightBounds());
buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(),
existingBuffer->getColor(), materialContents, materials);
} else {
// we must create a new buffer and update its borders
buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0),
QByteArray(colorContentsSize, 0), materialContents, materials);
const Box& heightBounds = buffer->getHeightBounds();
addRegion(bounds, heightBounds);
_intersections.clear();
_intersections.append(Box(heightBounds.minimum,
glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z)));
_intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z),
glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z)));
_intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z),
heightBounds.maximum));
_intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z),
glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z)));
_fetchVisitor.init(buffer);
_data->guide(_fetchVisitor);
}
}
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
postVisit(info);
return STOP_RECURSION;
}
bool HeightfieldRegionVisitor::postVisit(MetavoxelInfo& info) {
const DepthInfo& depthInfo = _depthInfo.at(_depth);
if (_depth > 0) {
DepthInfo& parentDepthInfo = _depthInfo[_depth - 1];
parentDepthInfo.minimumColorIncrement = qMin(parentDepthInfo.minimumColorIncrement, depthInfo.minimumColorIncrement);
parentDepthInfo.minimumMaterialIncrement = qMin(parentDepthInfo.minimumMaterialIncrement,
depthInfo.minimumMaterialIncrement);
}
if (_visitations.at(_depth).isInputLeaf(0)) {
HeightfieldBuffer* buffer = NULL;
HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue<HeightfieldHeightDataPointer>();
if (height) {
int heightSize = glm::sqrt((float)height->getContents().size());
int extendedHeightSize = heightSize + HeightfieldBuffer::HEIGHT_EXTENSION;
int heightContentsSize = extendedHeightSize * extendedHeightSize;
float minimumColorIncrement = depthInfo.minimumColorIncrement;
float minimumMaterialIncrement = depthInfo.minimumMaterialIncrement;
for (int i = _depth - 1; i >= 0 && qMax(minimumColorIncrement, minimumMaterialIncrement) == FLT_MAX; i--) {
const DepthInfo& ancestorDepthInfo = _depthInfo.at(i);
minimumColorIncrement = qMin(minimumColorIncrement, ancestorDepthInfo.minimumColorIncrement);
minimumMaterialIncrement = qMin(minimumMaterialIncrement, ancestorDepthInfo.minimumMaterialIncrement);
}
int colorContentsSize = 0;
if (minimumColorIncrement != FLT_MAX) {
int colorSize = (int)glm::round(info.size / minimumColorIncrement) + HeightfieldBuffer::SHARED_EDGE;
colorContentsSize = colorSize * colorSize * DataBlock::COLOR_BYTES;
}
int materialContentsSize = 0;
if (minimumMaterialIncrement != FLT_MAX) {
int materialSize = (int)glm::round(info.size / minimumMaterialIncrement) + HeightfieldBuffer::SHARED_EDGE;
materialContentsSize = materialSize * materialSize;
}
const HeightfieldBuffer* existingBuffer = static_cast<const HeightfieldBuffer*>(
info.inputValues.at(3).getInlineValue<BufferDataPointer>().data());
Box bounds = info.getBounds();
if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize &&
existingBuffer->getColor().size() == colorContentsSize &&
existingBuffer->getMaterial().size() == materialContentsSize) {
// we already have a buffer of the correct resolution
addRegion(bounds, existingBuffer->getHeightBounds());
buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(),
existingBuffer->getColor(), existingBuffer->getMaterial(), existingBuffer->getMaterials());
} else {
// we must create a new buffer and update its borders
buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0),
QByteArray(colorContentsSize, 0), QByteArray(materialContentsSize, 0));
const Box& heightBounds = buffer->getHeightBounds();
addRegion(bounds, heightBounds);
_intersections.clear();
_intersections.append(Box(heightBounds.minimum,
glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z)));
_intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z),
glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z)));
_intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z),
heightBounds.maximum));
_intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z),
glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z)));
_fetchVisitor.init(buffer);
_data->guide(_fetchVisitor);
}
}
BufferDataPointer pointer(buffer);
info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer));
}
return true;
}
void HeightfieldRegionVisitor::addRegion(const Box& unextended, const Box& extended) {
regions.append(unextended);
regionBounds.add(extended);

View file

@ -236,6 +236,7 @@ public:
const Box& getHeightBounds() const { return _heightBounds; }
const Box& getColorBounds() const { return _colorBounds; }
const Box& getMaterialBounds() const { return _materialBounds; }
QByteArray& getHeight() { return _height; }
const QByteArray& getHeight() const { return _height; }
@ -246,10 +247,11 @@ public:
QByteArray& getMaterial() { return _material; }
const QByteArray& getMaterial() const { return _material; }
QVector<SharedObjectPointer>& getMaterials() { return _materials; }
const QVector<SharedObjectPointer>& getMaterials() const { return _materials; }
QByteArray getUnextendedHeight() const;
QByteArray getUnextendedColor() const;
QByteArray getUnextendedColor(int x = 0, int y = 0) const;
int getHeightSize() const { return _heightSize; }
float getHeightIncrement() const { return _heightIncrement; }
@ -257,6 +259,9 @@ public:
int getColorSize() const { return _colorSize; }
float getColorIncrement() const { return _colorIncrement; }
int getMaterialSize() const { return _materialSize; }
float getMaterialIncrement() const { return _materialIncrement; }
virtual void render(bool cursor = false);
private:
@ -265,6 +270,7 @@ private:
float _scale;
Box _heightBounds;
Box _colorBounds;
Box _materialBounds;
QByteArray _height;
QByteArray _color;
QByteArray _material;
@ -277,6 +283,8 @@ private:
float _heightIncrement;
int _colorSize;
float _colorIncrement;
int _materialSize;
float _materialIncrement;
typedef QPair<QOpenGLBuffer, QOpenGLBuffer> BufferPair;
static QHash<int, BufferPair> _bufferPairs;

View file

@ -1269,6 +1269,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
_isPushing = false;
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
glm::vec3 newLocalVelocity = localVelocity;
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
@ -1285,31 +1286,47 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
if (directionLength > EPSILON) {
direction /= directionLength;
// Compute the target keyboard velocity (which ramps up slowly, and damps very quickly)
// the max magnitude of which depends on what we're doing:
float motorSpeed = glm::length(_keyboardMotorVelocity);
float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = _scale * MAX_BOOST_SPEED;
if (motorSpeed < maxBoostSpeed) {
// an active keyboard motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
motorSpeed += MIN_AVATAR_SPEED * boostCoefficient;
motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient;
} else if (motorSpeed > finalMaxMotorSpeed) {
motorSpeed = finalMaxMotorSpeed;
if (hasFloor) {
// we're walking --> simple exponential decay toward target walk speed
const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e
_keyboardMotorVelocity = MAX_WALKING_SPEED * direction;
motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f);
} else {
// we're flying --> more complex curve
float motorSpeed = glm::length(_keyboardMotorVelocity);
float finalMaxMotorSpeed = _scale * MAX_KEYBOARD_MOTOR_SPEED;
float speedGrowthTimescale = 2.0f;
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = _scale * MAX_BOOST_SPEED;
if (motorSpeed < maxBoostSpeed) {
// an active keyboard motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
motorSpeed += MIN_AVATAR_SPEED * boostCoefficient;
motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient;
} else if (motorSpeed > finalMaxMotorSpeed) {
motorSpeed = finalMaxMotorSpeed;
}
_keyboardMotorVelocity = motorSpeed * direction;
}
_keyboardMotorVelocity = motorSpeed * direction;
_isPushing = true;
}
newLocalVelocity = localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity);
} else {
_keyboardMotorVelocity = glm::vec3(0.0f);
newLocalVelocity = (1.0f - motorEfficiency) * localVelocity;
if (hasFloor && !_wasPushing) {
float speed = glm::length(newLocalVelocity);
if (speed > MIN_AVATAR_SPEED) {
// add small constant friction to help avatar drift to a stop sooner at low speeds
const float CONSTANT_FRICTION_DECELERATION = MIN_AVATAR_SPEED / 0.20f;
newLocalVelocity *= (speed - timescale * CONSTANT_FRICTION_DECELERATION) / speed;
}
}
}
// apply keyboard motor
return localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity);
return newLocalVelocity;
}
glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) {

View file

@ -21,7 +21,6 @@
#include "ui/overlays/Text3DOverlay.h"
const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f;
const int DEFAULT_OCULUS_UI_MAX_FPS = 75;
class Camera;
class PalmData;

View file

@ -11,19 +11,25 @@
#include <glm/gtx/quaternion.hpp>
#include <QScriptSyntaxCheckResult>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <BoxEntityItem.h>
#include <ModelEntityItem.h>
#include <MouseEvent.h>
#include <PerfStat.h>
#include <RenderArgs.h>
#include "Menu.h"
#include "NetworkAccessManager.h"
#include "EntityTreeRenderer.h"
#include "devices/OculusManager.h"
#include "RenderableBoxEntityItem.h"
#include "RenderableLightEntityItem.h"
#include "RenderableModelEntityItem.h"
@ -36,15 +42,21 @@ QThread* EntityTreeRenderer::getMainThread() {
EntityTreeRenderer::EntityTreeRenderer() :
OctreeRenderer() {
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) :
OctreeRenderer(),
_wantScripts(wantScripts),
_entitiesScriptEngine(NULL) {
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
}
EntityTreeRenderer::~EntityTreeRenderer() {
// do we need to delete the _entitiesScriptEngine?? or is it deleted by default
}
void EntityTreeRenderer::clear() {
@ -54,6 +66,117 @@ void EntityTreeRenderer::clear() {
void EntityTreeRenderer::init() {
OctreeRenderer::init();
static_cast<EntityTree*>(_tree)->setFBXService(this);
if (_wantScripts) {
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
Application::getInstance()->getControllerScriptingInterface());
Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
}
// make sure our "last avatar position" is something other than our current position, so that on our
// first chance, we'll check for enter/leave entity events.
glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition();
_lastAvatarPosition = avatarPosition + glm::vec3(1.f, 1.f, 1.f);
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
return loadEntityScript(entity);
}
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText) {
QUrl url(scriptMaybeURLorText);
// If the url is not valid, this must be script text...
if (!url.isValid()) {
return scriptMaybeURLorText;
}
QString scriptContents; // assume empty
// if the scheme length is one or lower, maybe they typed in a file, let's try
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
url = QUrl::fromLocalFile(scriptMaybeURLorText);
}
// ok, let's see if it's valid... and if so, load it
if (url.isValid()) {
if (url.scheme() == "file") {
QString fileName = url.toLocalFile();
QFile scriptFile(fileName);
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
qDebug() << "Loading file:" << fileName;
QTextStream in(&scriptFile);
scriptContents = in.readAll();
} else {
qDebug() << "ERROR Loading file:" << fileName;
}
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
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();
}
}
}
return scriptContents;
}
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
if (!entity) {
return QScriptValue(); // no entity...
}
EntityItemID entityID = entity->getEntityItemID();
if (_entityScripts.contains(entityID)) {
EntityScriptDetails details = _entityScripts[entityID];
// check to make sure our script text hasn't changed on us since we last loaded it
if (details.scriptText == entity->getScript()) {
return details.scriptObject; // previously loaded
}
// if we got here, then we previously loaded a script, but the entity's script value
// has changed and so we need to reload it.
_entityScripts.remove(entityID);
}
if (entity->getScript().isEmpty()) {
return QScriptValue(); // no script
}
QString scriptContents = loadScriptContents(entity->getScript());
if (QScriptEngine::checkSyntax(scriptContents).state() != QScriptSyntaxCheckResult::Valid) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " INVALID SYNTAX";
qDebug() << " SCRIPT:" << scriptContents;
return QScriptValue(); // invalid script
}
QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
if (!entityScriptConstructor.isFunction()) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " NOT CONSTRUCTOR";
qDebug() << " SCRIPT:" << scriptContents;
return QScriptValue(); // invalid script
}
QScriptValue entityScriptObject = entityScriptConstructor.construct();
EntityScriptDetails newDetails = { entity->getScript(), entityScriptObject };
_entityScripts[entityID] = newDetails;
return entityScriptObject; // newly constructed
}
void EntityTreeRenderer::setTree(Octree* newTree) {
@ -65,6 +188,58 @@ void EntityTreeRenderer::update() {
if (_tree) {
EntityTree* tree = static_cast<EntityTree*>(_tree);
tree->update();
// check to see if the avatar has moved and if we need to handle enter/leave entity logic
checkEnterLeaveEntities();
}
}
void EntityTreeRenderer::checkEnterLeaveEntities() {
if (_tree) {
glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float) TREE_SCALE;
if (avatarPosition != _lastAvatarPosition) {
float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius
QVector<const EntityItem*> foundEntities;
QVector<EntityItemID> entitiesContainingAvatar;
// find the entities near us
static_cast<EntityTree*>(_tree)->findEntities(avatarPosition, radius, foundEntities);
// create a list of entities that actually contain the avatar's position
foreach(const EntityItem* entity, foundEntities) {
if (entity->contains(avatarPosition)) {
entitiesContainingAvatar << entity->getEntityItemID();
}
}
// for all of our previous containing entities, if they are no longer containing then send them a leave event
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
if (!entitiesContainingAvatar.contains(entityID)) {
emit leaveEntity(entityID);
QScriptValueList entityArgs = createEntityArgs(entityID);
QScriptValue entityScript = loadEntityScript(entityID);
if (entityScript.property("leaveEntity").isValid()) {
entityScript.property("leaveEntity").call(entityScript, entityArgs);
}
}
}
// for all of our new containing entities, if they weren't previously containing then send them an enter event
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
if (!_currentEntitiesInside.contains(entityID)) {
emit enterEntity(entityID);
QScriptValueList entityArgs = createEntityArgs(entityID);
QScriptValue entityScript = loadEntityScript(entityID);
if (entityScript.property("enterEntity").isValid()) {
entityScript.property("enterEntity").call(entityScript, entityArgs);
}
}
}
_currentEntitiesInside = entitiesContainingAvatar;
_lastAvatarPosition = avatarPosition;
}
}
}
@ -101,8 +276,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI
}
void renderElementProxy(EntityTreeElement* entityTreeElement) {
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE;
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
@ -175,9 +350,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
AACube minCube = entity->getMinimumAACube();
AABox entityBox = entity->getAABox();
maxCube.scale((float)TREE_SCALE);
minCube.scale((float)TREE_SCALE);
entityBox.scale((float)TREE_SCALE);
maxCube.scale((float) TREE_SCALE);
minCube.scale((float) TREE_SCALE);
entityBox.scale((float) TREE_SCALE);
glm::vec3 maxCenter = maxCube.calcCenter();
glm::vec3 minCenter = minCube.calcCenter();
@ -207,9 +382,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
glPopMatrix();
glm::vec3 position = entity->getPosition() * (float)TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float)TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE;
glm::vec3 position = entity->getPosition() * (float) TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float) TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE;
glm::quat rotation = entity->getRotation();
glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
@ -382,4 +557,207 @@ void EntityTreeRenderer::deleteReleasedModels() {
}
}
PickRay EntityTreeRenderer::computePickRay(float x, float y) {
float screenWidth = Application::getInstance()->getGLWidget()->width();
float screenHeight = Application::getInstance()->getGLWidget()->height();
PickRay result;
if (OculusManager::isConnected()) {
Camera* camera = Application::getInstance()->getCamera();
result.origin = camera->getPosition();
Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction);
} else {
ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum();
viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction);
}
return result;
}
RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType) {
RayToEntityIntersectionResult result;
if (_tree) {
EntityTree* entityTree = static_cast<EntityTree*>(_tree);
OctreeElement* element;
EntityItem* intersectedEntity = NULL;
result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedEntity, lockType, &result.accurate);
if (result.intersects && intersectedEntity) {
result.entityID = intersectedEntity->getEntityItemID();
result.properties = intersectedEntity->getProperties();
result.intersection = ray.origin + (ray.direction * result.distance);
result.entity = intersectedEntity;
}
}
return result;
}
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity);
connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity);
connect(this, &EntityTreeRenderer::clickReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickReleaseOnEntity);
connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity);
connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity);
connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity);
}
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) {
QScriptValueList args;
args << entityID.toScriptValue(_entitiesScriptEngine);
args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine);
return args;
}
QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) {
QScriptValueList args;
args << entityID.toScriptValue(_entitiesScriptEngine);
return args;
}
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock);
if (rayPickResult.intersects) {
//qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID;
emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mousePressOnEntity").isValid()) {
entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs);
}
_currentClickingOnEntityID = rayPickResult.entityID;
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
if (entityScript.property("clickDownOnEntity").isValid()) {
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
}
}
}
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock);
if (rayPickResult.intersects) {
//qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseReleaseOnEntity").isValid()) {
entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs);
}
}
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
// we're releasing the button, then this is considered a clickOn event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
}
}
// makes it the unknown ID, we just released so we can't be clicking on anything
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID();
}
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock);
if (rayPickResult.intersects) {
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
// load the entity script if needed...
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseMoveEvent").isValid()) {
entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs);
}
//qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID;
emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("mouseMoveOnEntity").isValid()) {
entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs);
}
// handle the hover logic...
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
// then we need to send the hover leave.
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
}
}
// If the new hover entity does not match the previous hover entity then we are entering the new one
// this is true if the _currentHoverOverEntityID is known or unknown
if (rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverEnterEntity").isValid()) {
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
}
}
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
// we should send our hover over event
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverOverEntity").isValid()) {
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
}
// remember what we're hovering over
_currentHoverOverEntityID = rayPickResult.entityID;
} else {
// handle the hover logic...
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
// send the hover leave for our previous entity
if (!_currentHoverOverEntityID.isInvalidID()) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
}
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
}
}
// Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have
// not yet released the hold then this is still considered a holdingClickOnEntity event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
}
}
}

View file

@ -16,6 +16,7 @@
#include <stdint.h>
#include <EntityTree.h>
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
@ -26,11 +27,17 @@
#include "renderer/Model.h"
class EntityScriptDetails {
public:
QString scriptText;
QScriptValue scriptObject;
};
// Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
Q_OBJECT
public:
EntityTreeRenderer();
EntityTreeRenderer(bool wantScripts);
virtual ~EntityTreeRenderer();
virtual Octree* createTree() { return new EntityTree(true); }
@ -75,9 +82,55 @@ public:
void releaseModel(Model* model);
void deleteReleasedModels();
// event handles which may generate entity related events
void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID);
void mousePressEvent(QMouseEvent* event, unsigned int deviceID);
void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID);
/// connect our signals to anEntityScriptingInterface for firing of events related clicking,
/// hovering over, and entering entities
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
signals:
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
private:
QList<Model*> _releasedModels;
void renderProxies(const EntityItem* entity, RenderArgs* args);
PickRay computePickRay(float x, float y);
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType);
EntityItemID _currentHoverOverEntityID;
EntityItemID _currentClickingOnEntityID;
QScriptValueList createEntityArgs(const EntityItemID& entityID);
void checkEnterLeaveEntities();
glm::vec3 _lastAvatarPosition;
QVector<EntityItemID> _currentEntitiesInside;
bool _wantScripts;
ScriptEngine* _entitiesScriptEngine;
QScriptValue loadEntityScript(EntityItem* entity);
QScriptValue loadEntityScript(const EntityItemID& entityItemID);
QString loadScriptContents(const QString& scriptMaybeURLorText);
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
};
#endif // hifi_EntityTreeRenderer_h

View file

@ -61,15 +61,18 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
float exponent = getExponent();
float cutoff = glm::radians(getCutoff());
if (_isSpotlight) {
Application::getInstance()->getDeferredLightingEffect()->addSpotLight(position, largestDiameter / 2.0f,
ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation,
direction, exponent, cutoff);
} else {
Application::getInstance()->getDeferredLightingEffect()->addPointLight(position, largestDiameter / 2.0f,
ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation);
}
bool disableLights = Menu::getInstance()->isOptionChecked(MenuOption::DisableLightEntities);
if (!disableLights) {
if (_isSpotlight) {
Application::getInstance()->getDeferredLightingEffect()->addSpotLight(position, largestDiameter / 2.0f,
ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation,
direction, exponent, cutoff);
} else {
Application::getInstance()->getDeferredLightingEffect()->addPointLight(position, largestDiameter / 2.0f,
ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation);
}
}
bool wantDebug = false;
if (wantDebug) {
glColor4f(diffuseR, diffuseG, diffuseB, 1.0f);

View file

@ -38,12 +38,9 @@ RenderableModelEntityItem::~RenderableModelEntityItem() {
bool RenderableModelEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
QString oldModelURL = getModelURL();
QString oldTextures = getTextures();
bool somethingChanged = ModelEntityItem::setProperties(properties, forceCopy);
if (somethingChanged) {
if ((oldModelURL != getModelURL()) || (oldTextures != getTextures())) {
_needsModelReload = true;
}
if (somethingChanged && oldModelURL != getModelURL()) {
_needsModelReload = true;
}
return somethingChanged;
}
@ -60,6 +57,47 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
return bytesRead;
}
void RenderableModelEntityItem::remapTextures() {
if (!_model) {
return; // nothing to do if we don't have a model
}
if (_currentTextures == _textures) {
return; // nothing to do if our recently mapped textures match our desired textures
}
qDebug() << "void RenderableModelEntityItem::remapTextures()....";
// since we're changing here, we need to run through our current texture map
// and any textures in the recently mapped texture, that is not in our desired
// textures, we need to "unset"
QJsonDocument currentTexturesAsJson = QJsonDocument::fromJson(_currentTextures.toUtf8());
QJsonObject currentTexturesAsJsonObject = currentTexturesAsJson.object();
QVariantMap currentTextureMap = currentTexturesAsJsonObject.toVariantMap();
QJsonDocument texturesAsJson = QJsonDocument::fromJson(_textures.toUtf8());
QJsonObject texturesAsJsonObject = texturesAsJson.object();
QVariantMap textureMap = texturesAsJsonObject.toVariantMap();
foreach(const QString& key, currentTextureMap.keys()) {
// if the desired texture map (what we're setting the textures to) doesn't
// contain this texture, then remove it by setting the URL to null
if (!textureMap.contains(key)) {
QUrl noURL;
qDebug() << "Removing texture named" << key << "by replacing it with no URL";
_model->setTextureWithNameToURL(key, noURL);
}
}
// here's where we remap any textures if needed...
foreach(const QString& key, textureMap.keys()) {
QUrl newTextureURL = textureMap[key].toUrl();
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
_model->setTextureWithNameToURL(key, newTextureURL);
}
_currentTextures = _textures;
}
void RenderableModelEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RMEIrender");
@ -72,6 +110,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
if (drawAsModel) {
remapTextures();
glPushMatrix();
{
float alpha = getLocalRenderAlpha();
@ -159,6 +198,10 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
}
assert(_myRenderer == renderer); // you should only ever render on one renderer
if (QThread::currentThread() != _myRenderer->thread()) {
return _model;
}
_needsModelReload = false; // this is the reload
// if we have a URL, then we will want to end up returning a model...
@ -183,18 +226,6 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
}
}
// here's where we remap any textures if needed...
if (!_textures.isEmpty() && _model) {
QJsonDocument texturesAsJson = QJsonDocument::fromJson(_textures.toUtf8());
QJsonObject texturesAsJsonObject = texturesAsJson.object();
QVariantMap textureMap = texturesAsJsonObject.toVariantMap();
foreach(const QString& key, textureMap.keys()) {
QUrl newTextureURL = textureMap[key].toUrl();
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
_model->setTextureWithNameToURL(key, newTextureURL);
}
}
return result;
}

View file

@ -51,12 +51,14 @@ public:
virtual void render(RenderArgs* args);
Model* getModel(EntityTreeRenderer* renderer);
private:
void remapTextures();
bool needsSimulation() const;
Model* _model;
bool _needsInitialSimulation;
bool _needsModelReload;
EntityTreeRenderer* _myRenderer;
QString _currentTextures;
};
#endif // hifi_RenderableModelEntityItem_h

View file

@ -1038,26 +1038,42 @@ void ImportHeightfieldTool::apply() {
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue(
AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer))));
QByteArray color;
if (buffer->getColor().isEmpty()) {
const unsigned char WHITE_VALUE = 0xFF;
color = QByteArray(height.size() * DataBlock::COLOR_BYTES, WHITE_VALUE);
} else {
color = buffer->getUnextendedColor();
}
HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color));
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue(
AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer))));
int size = glm::sqrt(float(height.size()));
QByteArray material(size * size, 0);
HeightfieldMaterialDataPointer materialPointer(new HeightfieldMaterialData(material));
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), new MetavoxelNode(AttributeValue(
AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), encodeInline(materialPointer))));
MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit(
_translation->getValue() + buffer->getTranslation() * scale, data)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
int colorUnits = buffer->getColor().isEmpty() ? 1 : (buffer->getColorSize() - HeightfieldBuffer::SHARED_EDGE) /
(buffer->getHeightSize() - HeightfieldBuffer::HEIGHT_EXTENSION);
float colorScale = scale / colorUnits;
for (int y = 0; y < colorUnits; y++) {
for (int x = 0; x < colorUnits; x++) {
MetavoxelData data;
data.setSize(colorScale);
QByteArray color;
if (buffer->getColor().isEmpty()) {
const unsigned char WHITE_VALUE = 0xFF;
color = QByteArray(height.size() * DataBlock::COLOR_BYTES, WHITE_VALUE);
} else {
color = buffer->getUnextendedColor(x, y);
}
HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color));
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(
AttributeValue(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(),
encodeInline(colorPointer))));
QByteArray material(height.size(), 0);
HeightfieldMaterialDataPointer materialPointer(new HeightfieldMaterialData(material));
data.setRoot(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), new MetavoxelNode(
AttributeValue(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(),
encodeInline(materialPointer))));
MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit(
_translation->getValue() + buffer->getTranslation() * scale + glm::vec3(x, 0.0f, y) * colorScale, data)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
}
}
}
@ -1143,7 +1159,9 @@ void ImportHeightfieldTool::updatePreview() {
float z = 0.0f;
int blockSize = pow(2.0, _blockSize->value());
int heightSize = blockSize + HeightfieldBuffer::HEIGHT_EXTENSION;
int colorSize = blockSize + HeightfieldBuffer::SHARED_EDGE;
int colorScale = glm::round(glm::log(_colorImage.height() / (float)_heightImage.height()) / glm::log(2.0f));
int colorBlockSize = blockSize * pow(2.0, qMax(colorScale, 0));
int colorSize = colorBlockSize + HeightfieldBuffer::SHARED_EDGE;
for (int i = 0; i < _heightImage.height(); i += blockSize, z++) {
float x = 0.0f;
for (int j = 0; j < _heightImage.width(); j += blockSize, x++) {
@ -1164,12 +1182,14 @@ void ImportHeightfieldTool::updatePreview() {
}
QByteArray color;
if (!_colorImage.isNull()) {
int colorI = (i / blockSize) * colorBlockSize;
int colorJ = (j / blockSize) * colorBlockSize;
color = QByteArray(colorSize * colorSize * DataBlock::COLOR_BYTES, 0);
rows = qMax(0, qMin(colorSize, _colorImage.height() - i));
columns = qMax(0, qMin(colorSize, _colorImage.width() - j));
rows = qMax(0, qMin(colorSize, _colorImage.height() - colorI));
columns = qMax(0, qMin(colorSize, _colorImage.width() - colorJ));
for (int y = 0; y < rows; y++) {
memcpy(color.data() + y * colorSize * DataBlock::COLOR_BYTES,
_colorImage.scanLine(i + y) + j * DataBlock::COLOR_BYTES,
_colorImage.scanLine(colorI + y) + colorJ * DataBlock::COLOR_BYTES,
columns * DataBlock::COLOR_BYTES);
}
}

View file

@ -147,8 +147,6 @@ void PreferencesDialog::loadPreferences() {
ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond());
ui.oculusUIAngularSizeSpin->setValue(menuInstance->getOculusUIAngularSize());
ui.oculusUIMaxFPSSpin->setValue(menuInstance->getOculusUIMaxFPS());
ui.sixenseReticleMoveSpeedSpin->setValue(menuInstance->getSixenseReticleMoveSpeed());
@ -231,8 +229,6 @@ void PreferencesDialog::savePreferences() {
Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value());
Menu::getInstance()->setOculusUIAngularSize(ui.oculusUIAngularSizeSpin->value());
Menu::getInstance()->setOculusUIMaxFPS(ui.oculusUIMaxFPSSpin->value());
Menu::getInstance()->setSixenseReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value());

View file

@ -86,13 +86,13 @@ void OctreePacketProcessor::processPacket(const SharedNodePointer& sendingNode,
switch(voxelPacketType) {
case PacketTypeEntityErase: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
app->_entities.processEraseMessage(mutablePacket, sendingNode);
}
} break;
case PacketTypeEntityData: {
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) {
app->_entities.processDatagram(mutablePacket, sendingNode);
}
} break;

View file

@ -61,7 +61,7 @@
<x>0</x>
<y>0</y>
<width>500</width>
<height>1459</height>
<height>1386</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
@ -1772,24 +1772,24 @@
</item>
<item>
<widget class="QLineEdit" name="faceshiftHostnameEdit">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="placeholderText">
<string>localhost</string>
</property>
</widget>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="placeholderText">
<string>localhost</string>
</property>
</widget>
</item>
</layout>
</item>
</item>
<item>
<widget class="QLabel" name="voxelsTitleLabel">
<property name="sizePolicy">
@ -2084,85 +2084,6 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Oculus Rift FPS</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="buddy">
<cstring>maxVoxelsSpin</cstring>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_18">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="oculusUIMaxFPSSpin">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="minimum">
<number>30</number>
</property>
<property name="maximum">
<number>95</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>75</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="sixenseControllersTitleLabel">
<property name="sizePolicy">

View file

@ -253,6 +253,7 @@ public:
void applyHardCollision(const CollisionInfo& collisionInfo);
virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; }
virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); }
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init

View file

@ -124,6 +124,10 @@ void EntityItemID::handleAddEntityResponse(const QByteArray& packet) {
_tokenIDsToIDs[creatorTokenID] = entityID;
}
QScriptValue EntityItemID::toScriptValue(QScriptEngine* engine) const {
return EntityItemIDtoScriptValue(engine, *this);
}
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) {
QScriptValue obj = engine->newObject();
obj.setProperty("id", id.id.toString());

View file

@ -49,11 +49,16 @@ public:
EntityItemID convertToKnownIDVersion() const;
EntityItemID convertToCreatorTokenVersion() const;
bool isInvalidID() const { return id == UNKNOWN_ENTITY_ID && creatorTokenID == UNKNOWN_ENTITY_TOKEN && isKnownID == false; }
// these methods allow you to create models, and later edit them.
static EntityItemID createInvalidEntityID() { return EntityItemID(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); }
static EntityItemID getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddEntityResponse(const QByteArray& packet);
static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead);
QScriptValue toScriptValue(QScriptEngine* engine) const;
private:
friend class EntityTree;
@ -68,12 +73,22 @@ inline bool operator<(const EntityItemID& a, const EntityItemID& b) {
}
inline bool operator==(const EntityItemID& a, const EntityItemID& b) {
if (a.isInvalidID() && b.isInvalidID()) {
return true;
}
if (a.isInvalidID() != b.isInvalidID()) {
return false;
}
if (a.id == UNKNOWN_ENTITY_ID || b.id == UNKNOWN_ENTITY_ID) {
return a.creatorTokenID == b.creatorTokenID;
}
return a.id == b.id;
}
inline bool operator!=(const EntityItemID& a, const EntityItemID& b) {
return !(a == b);
}
inline uint qHash(const EntityItemID& a, uint seed) {
QUuid temp;
if (a.id == UNKNOWN_ENTITY_ID) {
@ -94,5 +109,4 @@ Q_DECLARE_METATYPE(QVector<EntityItemID>);
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties);
void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties);
#endif // hifi_EntityItemID_h

View file

@ -217,7 +217,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
entityID(),
properties(),
distance(0),
face()
face(),
entity(NULL)
{
}

View file

@ -25,6 +25,7 @@
class EntityTree;
class MouseEvent;
class RayToEntityIntersectionResult {
@ -37,6 +38,7 @@ public:
float distance;
BoxFace face;
glm::vec3 intersection;
EntityItem* entity;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
@ -101,6 +103,21 @@ signals:
void entityCollisionWithVoxel(const EntityItemID& entityID, const VoxelDetail& voxel, const CollisionInfo& collision);
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const CollisionInfo& collision);
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
private:
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -53,6 +53,9 @@ public:
virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; }
// TODO: implement proper contains for 3D ellipsoid
//virtual bool contains(const glm::vec3& point) const;
protected:
virtual void recalculateCollisionShape();

View file

@ -1198,6 +1198,10 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead)
return false;
}
AttributeValue HeightfieldAttribute::inherit(const AttributeValue& parentValue) const {
return AttributeValue(parentValue.getAttribute());
}
HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) :
InlineAttribute<HeightfieldColorDataPointer>(name) {
}
@ -1337,6 +1341,10 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post
return false;
}
AttributeValue HeightfieldColorAttribute::inherit(const AttributeValue& parentValue) const {
return AttributeValue(parentValue.getAttribute());
}
HeightfieldMaterialAttribute::HeightfieldMaterialAttribute(const QString& name) :
InlineAttribute<HeightfieldMaterialDataPointer>(name) {
}
@ -1404,6 +1412,71 @@ bool HeightfieldMaterialAttribute::merge(void*& parent, void* children[], bool p
return maxSize == 0;
}
AttributeValue HeightfieldMaterialAttribute::inherit(const AttributeValue& parentValue) const {
return AttributeValue(parentValue.getAttribute());
}
static QHash<uchar, int> countIndices(const QByteArray& contents) {
QHash<uchar, int> counts;
for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) {
if (*src != 0) {
counts[*src]++;
}
}
return counts;
}
uchar getMaterialIndex(const SharedObjectPointer& material, QVector<SharedObjectPointer>& materials, QByteArray& contents) {
if (!(material && static_cast<MaterialObject*>(material.data())->getDiffuse().isValid())) {
return 0;
}
// first look for a matching existing material, noting the first reusable slot
int firstEmptyIndex = -1;
for (int i = 0; i < materials.size(); i++) {
const SharedObjectPointer& existingMaterial = materials.at(i);
if (existingMaterial) {
if (existingMaterial->equals(material.data())) {
return i + 1;
}
} else if (firstEmptyIndex == -1) {
firstEmptyIndex = i;
}
}
// if nothing found, use the first empty slot or append
if (firstEmptyIndex != -1) {
materials[firstEmptyIndex] = material;
return firstEmptyIndex + 1;
}
if (materials.size() < EIGHT_BIT_MAXIMUM) {
materials.append(material);
return materials.size();
}
// last resort: find the least-used material and remove it
QHash<uchar, int> counts = countIndices(contents);
uchar materialIndex = 0;
int lowestCount = INT_MAX;
for (QHash<uchar, int>::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) {
if (it.value() < lowestCount) {
materialIndex = it.key();
lowestCount = it.value();
}
}
contents.replace((char)materialIndex, (char)0);
return materialIndex;
}
void clearUnusedMaterials(QVector<SharedObjectPointer>& materials, const QByteArray& contents) {
QHash<uchar, int> counts = countIndices(contents);
for (int i = 0; i < materials.size(); i++) {
if (counts.value(i + 1) == 0) {
materials[i] = SharedObjectPointer();
}
}
while (!(materials.isEmpty() || materials.last())) {
materials.removeLast();
}
}
const int VOXEL_COLOR_HEADER_SIZE = sizeof(qint32) * 6;
static QByteArray encodeVoxelColor(int offsetX, int offsetY, int offsetZ,

View file

@ -536,6 +536,8 @@ public:
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
};
typedef QExplicitlySharedDataPointer<HeightfieldColorData> HeightfieldColorDataPointer;
@ -580,6 +582,8 @@ public:
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
};
typedef QExplicitlySharedDataPointer<HeightfieldMaterialData> HeightfieldMaterialDataPointer;
@ -646,8 +650,17 @@ public:
virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const;
virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
virtual AttributeValue inherit(const AttributeValue& parentValue) const;
};
/// Utility method for editing: given a material pointer and a list of materials, returns the corresponding material index,
/// creating a new entry in the list if necessary.
uchar getMaterialIndex(const SharedObjectPointer& material, QVector<SharedObjectPointer>& materials, QByteArray& contents);
/// Utility method for editing: removes any unused materials from the supplied list.
void clearUnusedMaterials(QVector<SharedObjectPointer>& materials, const QByteArray& contents);
typedef QExplicitlySharedDataPointer<VoxelColorData> VoxelColorDataPointer;
/// Contains a block of voxel color data.

View file

@ -1509,7 +1509,7 @@ bool MetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
DefaultMetavoxelGuide::DefaultMetavoxelGuide() {
}
static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float lodBase, int encodedOrder) {
static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, int encodedOrder) {
MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation();
nextVisitation.info.size = visitation.info.size * 0.5f;
for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
@ -1519,14 +1519,14 @@ static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float
for (int j = 0; j < visitation.inputNodes.size(); j++) {
MetavoxelNode* node = visitation.inputNodes.at(j);
const AttributeValue& parentValue = visitation.info.inputValues.at(j);
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase *
parentValue.getAttribute()->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ?
child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue);
}
for (int j = 0; j < visitation.outputNodes.size(); j++) {
MetavoxelNode* node = visitation.outputNodes.at(j);
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase *
visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
nextVisitation.outputNodes[j] = child;
}
@ -1604,9 +1604,10 @@ static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float
bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
visitation.visitor->getLOD().threshold;
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier());
visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase *
visitation.visitor->getMinimumLODThresholdMultiplier());
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
int encodedOrder = visitation.visitor->visit(visitation.info);
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
@ -1628,14 +1629,15 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) {
return true;
}
return (encodedOrder == MetavoxelVisitor::STOP_RECURSION || defaultGuideToChildren(visitation, lodBase, encodedOrder));
return (encodedOrder == MetavoxelVisitor::STOP_RECURSION || defaultGuideToChildren(visitation, encodedOrder));
}
bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
// save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute
float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) *
visitation.visitor->getLOD().threshold;
visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier());
visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase *
visitation.visitor->getMinimumLODThresholdMultiplier());
visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves();
int encodedOrder = visitation.visitor->visit(visitation.info);
if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) {
@ -1658,7 +1660,7 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
return true;
}
if (encodedOrder & MetavoxelVisitor::ALL_NODES_REST) {
return defaultGuideToChildren(visitation, lodBase, encodedOrder);
return defaultGuideToChildren(visitation, encodedOrder);
}
bool onlyVisitDifferent = !(encodedOrder & MetavoxelVisitor::ALL_NODES);
MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation();
@ -1672,7 +1674,8 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
for (int j = 0; j < visitation.inputNodes.size(); j++) {
MetavoxelNode* node = visitation.inputNodes.at(j);
const AttributeValue& parentValue = visitation.info.inputValues.at(j);
bool expand = (visitation.info.size >= lodBase * parentValue.getAttribute()->getLODThresholdMultiplier());
bool expand = (visitation.info.size >= visitation.info.lodBase *
parentValue.getAttribute()->getLODThresholdMultiplier());
MetavoxelNode* child = (node && expand) ? node->getChild(index) : NULL;
nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ?
child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue);
@ -1686,7 +1689,7 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) {
}
for (int j = 0; j < visitation.outputNodes.size(); j++) {
MetavoxelNode* node = visitation.outputNodes.at(j);
MetavoxelNode* child = (node && (visitation.info.size >= lodBase *
MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase *
visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL;
nextVisitation.outputNodes[j] = child;
}
@ -1911,6 +1914,12 @@ MetavoxelVisitation::MetavoxelVisitation(MetavoxelVisitation* previous,
MetavoxelVisitation::MetavoxelVisitation() {
}
bool MetavoxelVisitation::isInputLeaf(int index) const {
MetavoxelNode* node = inputNodes.at(index);
return !node || node->isLeaf() || info.size < info.lodBase *
info.inputValues.at(index).getAttribute()->getLODThresholdMultiplier();
}
bool MetavoxelVisitation::allInputNodesLeaves() const {
foreach (MetavoxelNode* node, inputNodes) {
if (node && !node->isLeaf()) {

View file

@ -278,6 +278,7 @@ public:
float size; ///< the size of the voxel in all dimensions
QVector<AttributeValue> inputValues;
QVector<OwnedAttributeValue> outputValues;
float lodBase;
bool isLODLeaf;
bool isLeaf;
@ -537,6 +538,7 @@ public:
MetavoxelVisitation(MetavoxelVisitation* previous, MetavoxelVisitor* visitor, int inputNodesSize, int outputNodesSize);
MetavoxelVisitation();
bool isInputLeaf(int index) const;
bool allInputNodesLeaves() const;
AttributeValue getInheritedOutputValue(int index) const;
};

View file

@ -445,71 +445,11 @@ PaintHeightfieldMaterialEditVisitor::PaintHeightfieldMaterialEditVisitor(const g
_material(material),
_color(color) {
glm::vec3 extents(_radius, _radius, _radius);
const float LARGE_EXTENT = 100000.0f;
glm::vec3 extents(_radius, LARGE_EXTENT, _radius);
_bounds = Box(_position - extents, _position + extents);
}
static QHash<uchar, int> countIndices(const QByteArray& contents) {
QHash<uchar, int> counts;
for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) {
if (*src != 0) {
counts[*src]++;
}
}
return counts;
}
uchar getMaterialIndex(const SharedObjectPointer& material, QVector<SharedObjectPointer>& materials, QByteArray& contents) {
if (!(material && static_cast<MaterialObject*>(material.data())->getDiffuse().isValid())) {
return 0;
}
// first look for a matching existing material, noting the first reusable slot
int firstEmptyIndex = -1;
for (int i = 0; i < materials.size(); i++) {
const SharedObjectPointer& existingMaterial = materials.at(i);
if (existingMaterial) {
if (existingMaterial->equals(material.data())) {
return i + 1;
}
} else if (firstEmptyIndex == -1) {
firstEmptyIndex = i;
}
}
// if nothing found, use the first empty slot or append
if (firstEmptyIndex != -1) {
materials[firstEmptyIndex] = material;
return firstEmptyIndex + 1;
}
if (materials.size() < EIGHT_BIT_MAXIMUM) {
materials.append(material);
return materials.size();
}
// last resort: find the least-used material and remove it
QHash<uchar, int> counts = countIndices(contents);
uchar materialIndex = 0;
int lowestCount = INT_MAX;
for (QHash<uchar, int>::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) {
if (it.value() < lowestCount) {
materialIndex = it.key();
lowestCount = it.value();
}
}
contents.replace((char)materialIndex, (char)0);
return materialIndex;
}
void clearUnusedMaterials(QVector<SharedObjectPointer>& materials, QByteArray& contents) {
QHash<uchar, int> counts = countIndices(contents);
for (int i = 0; i < materials.size(); i++) {
if (counts.value(i + 1) == 0) {
materials[i] = SharedObjectPointer();
}
}
while (!(materials.isEmpty() || materials.last())) {
materials.removeLast();
}
}
int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) {
if (!info.getBounds().intersects(_bounds)) {
return STOP_RECURSION;
@ -581,7 +521,6 @@ int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) {
uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX;
float squaredRadius = scaledRadius * scaledRadius;
bool changed = false;
QHash<uchar, int> counts;
for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) {
uchar* dest = lineDest;
for (float x = startX; x <= endX; x += 1.0f, dest++) {

View file

@ -130,11 +130,11 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() {
reinterpret_cast<const unsigned char**>(&privateKeyData),
_privateKey.size());
if (rsaPrivateKey) {
QByteArray usernameByteArray = _username.toUtf8();
QByteArray lowercaseUsername = _username.toLower().toUtf8();
_usernameSignature.resize(RSA_size(rsaPrivateKey));
int encryptReturn = RSA_private_encrypt(usernameByteArray.size(),
reinterpret_cast<const unsigned char*>(usernameByteArray.constData()),
int encryptReturn = RSA_private_encrypt(lowercaseUsername.size(),
reinterpret_cast<const unsigned char*>(lowercaseUsername.constData()),
reinterpret_cast<unsigned char*>(_usernameSignature.data()),
rsaPrivateKey, RSA_PKCS1_PADDING);

View file

@ -9,9 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDataStream>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
#include <qdatastream.h>
#include <qhostinfo.h>
#include <qnetworkinterface.h>
#include "HifiSockAddr.h"
@ -36,13 +36,20 @@ HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) {
_port = otherSockAddr._port;
}
HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) {
// lookup the IP by the hostname
QHostInfo hostInfo = QHostInfo::fromName(hostname);
foreach(const QHostAddress& address, hostInfo.addresses()) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
_address = address;
_port = hostOrderPort;
HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) :
_address(hostname),
_port(hostOrderPort)
{
// if we parsed an IPv4 address out of the hostname, don't look it up
if (_address.protocol() != QAbstractSocket::IPv4Protocol) {
// lookup the IP by the hostname
if (shouldBlockForLookup) {
qDebug() << "Asynchronously looking up IP address for hostname" << hostname;
QHostInfo result = QHostInfo::fromName(hostname);
handleLookupResult(result);
} else {
int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo)));
qDebug() << "Synchronously looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID;
}
}
}
@ -75,6 +82,22 @@ bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const {
return _address == rhsSockAddr._address && _port == rhsSockAddr._port;
}
void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) {
if (hostInfo.error() != QHostInfo::NoError) {
qDebug() << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString();
}
foreach(const QHostAddress& address, hostInfo.addresses()) {
// just take the first IPv4 address
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
_address = address;
qDebug() << "QHostInfo lookup result for"
<< hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString();
break;
}
}
}
QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr) {
debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port;
return debug.space();

View file

@ -19,14 +19,15 @@
#include <netinet/in.h>
#endif
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QHostInfo>
class HifiSockAddr {
class HifiSockAddr : public QObject {
Q_OBJECT
public:
HifiSockAddr();
HifiSockAddr(const QHostAddress& address, quint16 port);
HifiSockAddr(const HifiSockAddr& otherSockAddr);
HifiSockAddr(const QString& hostname, quint16 hostOrderPort);
HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
HifiSockAddr(const sockaddr* sockaddr);
bool isNull() const { return _address.isNull() && _port == 0; }
@ -51,6 +52,8 @@ public:
friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr);
friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr);
friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr);
private slots:
void handleLookupResult(const QHostInfo& hostInfo);
private:
QHostAddress _address;
quint16 _port;

View file

@ -73,6 +73,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
_dtlsSocket(NULL),
_localSockAddr(),
_publicSockAddr(),
_stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT),
_numCollectedPackets(0),
_numCollectedBytes(0),
_packetStatTimer()
@ -583,11 +584,8 @@ void LimitedNodeList::sendSTUNRequest() {
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
// lookup the IP for the STUN server
HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
_nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket),
stunSockAddr.getAddress(), stunSockAddr.getPort());
_stunSockAddr.getAddress(), _stunSockAddr.getPort());
}
void LimitedNodeList::rebindNodeSocket() {

View file

@ -163,6 +163,7 @@ protected:
QUdpSocket* _dtlsSocket;
HifiSockAddr _localSockAddr;
HifiSockAddr _publicSockAddr;
HifiSockAddr _stunSockAddr;
int _numCollectedPackets;
int _numCollectedBytes;
QElapsedTimer _packetStatTimer;

View file

@ -21,6 +21,8 @@ public:
static QScriptValue toScriptValue(QScriptEngine* engine, const MouseEvent& event);
static void fromScriptValue(const QScriptValue& object, MouseEvent& event);
QScriptValue toScriptValue(QScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); }
int x;
int y;