Merge branch 'master' of github.com:highfidelity/hifi into oculus-tracking

This commit is contained in:
Dante Ruiz 2017-04-04 22:11:08 +01:00
commit 81ae3e44fd
39 changed files with 847 additions and 567 deletions

View file

@ -336,6 +336,10 @@ void Agent::executeScript() {
// call model URL setters with empty URLs so our avatar, if user, will have the default models
scriptedAvatar->setSkeletonModelURL(QUrl());
// force lazy initialization of the head data for the scripted avatar
// since it is referenced below by computeLoudness and getAudioLoudness
scriptedAvatar->getHeadOrientation();
// give this AvatarData object to the script engine
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());

View file

@ -2,6 +2,8 @@
"name": "Kinect to Standard",
"channels": [
{ "from": "Kinect.LeftHand", "to": "Standard.LeftHand" },
{ "from": "Kinect.RightHand", "to": "Standard.RightHand" }
{ "from": "Kinect.RightHand", "to": "Standard.RightHand" },
{ "from": "Kinect.LeftFoot", "to": "Standard.LeftFoot" },
{ "from": "Kinect.RightFoot", "to": "Standard.RightFoot" }
]
}

View file

@ -58,6 +58,9 @@
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
{ "from": "Standard.LeftHand", "to": "Actions.LeftHand" },
{ "from": "Standard.RightHand", "to": "Actions.RightHand" }
{ "from": "Standard.RightHand", "to": "Actions.RightHand" },
{ "from": "Standard.LeftFoot", "to": "Actions.LeftFoot" },
{ "from": "Standard.RightFoot", "to": "Actions.RightFoot" }
]
}

0
interface/resources/fonts/hifi-glyphs.ttf Executable file → Normal file
View file

View file

@ -110,7 +110,6 @@ Item {
Component.onCompleted: {
// Ensure the JS from the web-engine makes it to our logging
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("TabletBrowser");
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
@ -138,33 +137,22 @@ Item {
}
}
onActiveFocusOnPressChanged: {
console.log("on active focus changed");
setActiveFocusOnPress(true);
}
onNewViewRequested:{
// desktop is not defined for web-entities
if (stackRoot.isDesktop) {
var component = Qt.createComponent("./Browser.qml");
var newWindow = component.createObject(desktop);
request.openIn(newWindow.webView);
} else {
var component = Qt.createComponent("./TabletBrowser.qml");
if (component.status != Component.Ready) {
if (component.status == Component.Error) {
console.log("Error: " + component.errorString());
}
return;
onNewViewRequested: {
var component = Qt.createComponent("./TabletBrowser.qml");
if (component.status != Component.Ready) {
if (component.status == Component.Error) {
console.log("Error: " + component.errorString());
}
var newWindow = component.createObject();
newWindow.setProfile(webview.profile);
request.openIn(newWindow.webView);
newWindow.eventBridge = web.eventBridge;
stackRoot.push(newWindow);
newWindow.webView.forceActiveFocus();
return;
}
var newWindow = component.createObject();
newWindow.setProfile(webview.profile);
request.openIn(newWindow.webView);
newWindow.eventBridge = web.eventBridge;
stackRoot.push(newWindow);
newWindow.webView.forceActiveFocus();
}
}

View file

@ -19,12 +19,11 @@ Original.CheckBox {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
property bool isRedCheck: false
property int boxSize: 14
readonly property int boxRadius: 3
readonly property int checkSize: Math.max(boxSize - 8, 10)
readonly property int checkRadius: 2
activeFocusOnPress: true
style: CheckBoxStyle {
@ -37,6 +36,7 @@ Original.CheckBox {
border.color: pressed || hovered
? hifi.colors.checkboxCheckedBorder
: (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish)
gradient: Gradient {
GradientStop {
position: 0.2
@ -68,9 +68,9 @@ Original.CheckBox {
height: checkSize
radius: checkRadius
anchors.centerIn: parent
color: hifi.colors.checkboxChecked
color: isRedCheck ? hifi.colors.checkboxCheckedRed : hifi.colors.checkboxChecked
border.width: 2
border.color: hifi.colors.checkboxCheckedBorder
border.color: isRedCheck? hifi.colors.checkboxCheckedBorderRed : hifi.colors.checkboxCheckedBorder
visible: checked && !pressed || !checked && pressed
}

View file

@ -112,6 +112,7 @@ Item {
currentItem.goBack();
} else {
stackRoot.pop();
currentItem.webView.focus = true;
currentItem.webView.forceActiveFocus();
web.address = currentItem.webView.url;
}
@ -186,7 +187,6 @@ Item {
Component.onCompleted: {
// Ensure the JS from the web-engine makes it to our logging
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("WebView.qml");
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
@ -202,7 +202,6 @@ Item {
keyboardRaised = false;
punctuationMode = false;
keyboard.resetShiftMode(false);
console.log("[DR] -> printing user string " + root.profile.httpUserAgent);
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
@ -215,30 +214,21 @@ Item {
}
onNewViewRequested:{
// desktop is not defined for web-entities
if (web.isDesktop) {
var component = Qt.createComponent("../Browser.qml");
var newWindow = component.createObject(desktop);
newWindow.setProfile(root.profile);
request.openIn(newWindow.webView);
} else {
var component = Qt.createComponent("../TabletBrowser.qml");
if (component.status != Component.Ready) {
if (component.status == Component.Error) {
console.log("Error: " + component.errorString());
}
return;
var component = Qt.createComponent("../TabletBrowser.qml");
if (component.status != Component.Ready) {
if (component.status == Component.Error) {
console.log("Error: " + component.errorString());
}
var newWindow = component.createObject();
newWindow.setProfile(root.profile);
request.openIn(newWindow.webView);
newWindow.eventBridge = web.eventBridge;
stackRoot.push(newWindow);
return;
}
var newWindow = component.createObject();
newWindow.setProfile(root.profile);
request.openIn(newWindow.webView);
newWindow.eventBridge = web.eventBridge;
stackRoot.push(newWindow);
}
}
HiFiControls.Keyboard {
id: keyboard
raised: web.keyboardEnabled && web.keyboardRaised

View file

@ -14,11 +14,12 @@ import QtQuick.Controls 1.4
import "../styles-uit"
Item {
property var dialogTitleText;
property var optionTitleText;
property var optionBodyText;
property var optionValues;
property var selectedOptionIndex;
property var dialogTitleText : "";
property var optionTitleText: "";
property var optionBodyText: "";
property var optionValues: [];
property var selectedOptionIndex: 0;
property var callbackFunction;
property int dialogWidth;
property int dialogHeight;
property int comboOptionTextSize: 18;
@ -31,6 +32,14 @@ Item {
populateComboListViewModel();
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
combo.visible = false;
}
}
Rectangle {
id: dialogBackground;
anchors.fill: parent;
@ -42,12 +51,12 @@ Item {
id: dialogContainer;
color: "white";
anchors.centerIn: dialogBackground;
width: dialogWidth;
height: dialogHeight;
width: combo.dialogWidth;
height: combo.dialogHeight;
RalewayRegular {
id: dialogTitle;
text: dialogTitleText;
text: combo.dialogTitleText;
anchors.top: parent.top;
anchors.topMargin: 20;
anchors.left: parent.left;
@ -69,6 +78,7 @@ Item {
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
clip: true;
model: comboListViewModel;
delegate: comboListViewDelegate;
@ -77,7 +87,7 @@ Item {
Rectangle {
id: comboListViewItemContainer;
// Size
height: childrenRect.height + 10;
height: optionTitle.height + optionBody.height + 20;
width: dialogContainer.width;
color: selectedOptionIndex === index ? '#cee6ff' : 'white';
Rectangle {
@ -100,9 +110,11 @@ Item {
id: optionTitle;
text: titleText;
anchors.top: parent.top;
anchors.topMargin: 7;
anchors.left: comboOptionSelected.right;
anchors.leftMargin: 20;
anchors.leftMargin: 10;
anchors.right: parent.right;
anchors.rightMargin: 10;
height: 30;
size: comboOptionTextSize;
wrapMode: Text.WordWrap;
@ -112,10 +124,10 @@ Item {
id: optionBody;
text: bodyText;
anchors.top: optionTitle.bottom;
anchors.bottom: parent.bottom;
anchors.left: comboOptionSelected.right;
anchors.leftMargin: 25;
anchors.right: parent.right;
anchors.rightMargin: 10;
size: comboOptionTextSize;
wrapMode: Text.WordWrap;
}
@ -127,9 +139,8 @@ Item {
onEntered: comboListViewItemContainer.color = hifi.colors.blueHighlight
onExited: comboListViewItemContainer.color = selectedOptionIndex === index ? '#cee6ff' : 'white';
onClicked: {
GlobalServices.findableBy = optionValue;
UserActivityLogger.palAction("set_availability", optionValue);
print('Setting availability:', optionValue);
callbackFunction(optionValue);
combo.visible = false;
}
}
}
@ -137,18 +148,10 @@ Item {
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
combo.visible = false;
}
}
function populateComboListViewModel() {
comboListViewModel.clear();
optionTitleText.forEach(function(titleText, index) {
comboListViewModel.insert(index, {"titleText": titleText, "bodyText": optionBodyText[index], "optionValue": optionValues[index]});
});
}
}
}

View file

@ -56,7 +56,7 @@ Rectangle {
ComboDialog {
id: comboDialog;
z: 999; // Force the ComboDialog on top of everything else
dialogWidth: parent.width - 100;
dialogWidth: parent.width - 50;
dialogHeight: parent.height - 100;
}
function letterbox(headerGlyph, headerText, message) {
@ -66,7 +66,13 @@ Rectangle {
letterboxMessage.visible = true;
letterboxMessage.popupRadius = 0;
}
function popupComboDialogCallback(availability) {
GlobalServices.findableBy = availability;
UserActivityLogger.palAction("set_availability", availability);
print('Setting availability:', JSON.stringify(GlobalServices.findableBy));
}
function popupComboDialog(dialogTitleText, optionTitleText, optionBodyText, optionValues) {
comboDialog.callbackFunction = popupComboDialogCallback;
comboDialog.dialogTitleText = dialogTitleText;
comboDialog.optionTitleText = optionTitleText;
comboDialog.optionBodyText = optionBodyText;
@ -506,6 +512,7 @@ Rectangle {
// If this is an "Ignore" checkbox, disable the checkbox if user isn't present.
enabled: styleData.role === "ignore" ? (model ? model["isPresent"] : true) : true;
boxSize: 24;
isRedCheck: true
onClicked: {
var newValue = !model[styleData.role];
nearbyUserModel.setProperty(model.userIndex, styleData.role, newValue);
@ -605,7 +612,7 @@ Rectangle {
"Bold names in the list are <b>avatar display names</b>.<br>" +
"<font color='purple'>Purple borders around profile pictures are <b>connections</b></font>.<br>" +
"<font color='green'>Green borders around profile pictures are <b>friends</b>.</font><br>" +
"(TEMPORARY LANGUAGE) In some situations, you can also see others' usernames.<br>" +
"Others can find you and see your username according to your <b>availability</b> settings.<br>" +
"If you can see someone's username, you can GoTo them by selecting them in the PAL, then clicking their name.<br>" +
"<br>If someone's display name isn't set, a unique <b>session display name</b> is assigned to them.<br>" +
"<br>Administrators of this domain can also see the <b>username</b> or <b>machine ID</b> associated with each avatar present.");
@ -942,7 +949,7 @@ Rectangle {
}
Item {
id: upperRightInfoContainer;
width: 160;
width: 200;
height: parent.height;
anchors.top: parent.top;
anchors.right: parent.right;
@ -953,59 +960,53 @@ Rectangle {
// Text size
size: hifi.fontSizes.tabularData;
// Anchors
anchors.top: availabilityComboBox.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.top: myCard.top;
anchors.left: parent.left;
// Style
color: hifi.colors.baseGrayHighlight;
// Alignment
horizontalAlignment: Text.AlignHCenter;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignTop;
}
/*Rectangle {
Rectangle {
property var availabilityStrings: ["Everyone", "Friends and Connections", "Friends Only", "Appear Offline"];
id: availabilityComboBox;
color: hifi.colors.textFieldLightBackground
// Anchors
anchors.top: parent.top;
anchors.top: availabilityText.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
// Size
width: parent.width;
height: 40;
function determineAvailabilityIndex() {
return ['all', 'connections', 'friends', 'none'].indexOf(GlobalServices.findableBy);
}
function determineAvailabilityString() {
return availabilityStrings[determineAvailabilityIndex()];
}
RalewayRegular {
text: myData.userName === "Unknown user" ? "Login to Set" : availabilityComboBox.determineAvailabilityString();
anchors.fill: parent;
anchors.leftMargin: 10;
horizontalAlignment: Text.AlignLeft;
size: 16;
}
MouseArea {
anchors.fill: parent
anchors.fill: parent;
enabled: myData.userName !== "Unknown user";
hoverEnabled: true;
onClicked: {
popupComboDialog("Set your list visibility",
["Everyone", "Friends and Connections", "Friends Only", "Appear Offline"],
["You will be invisible in everyone's 'People' list.\nAnyone will be able to jump to your location if the domain allows.",
"You will be visible in the 'People' list only for those with whom you are connected or friends.\nThey will be able to jump to your location if the domain allows.",
"You will be visible in the 'People' list only for those with whom you are friends.\nThey will be able to jump to your location if the domain allows.",
"You will not be visible in the 'People' list of any other users."],
popupComboDialog("Set your availability:",
availabilityComboBox.availabilityStrings,
["Your username will be visible in everyone's 'Nearby' list.\nAnyone will be able to jump to your location from within the 'Nearby' list.",
"Your location will be visible in the 'Connections' list only for those with whom you are connected or friends.\nThey will be able to jump to your location if the domain allows.",
"Your location will be visible in the 'Connections' list only for those with whom you are friends.\nThey will be able to jump to your location if the domain allows.",
"Your location will not be visible in the 'Connections' list of any other users. Only domain admins will be able to see your username in the 'Nearby' list."],
["all", "connections", "friends", "none"]);
}
}
}*/
HifiControlsUit.ComboBox {
function determineAvailabilityIndex() {
return ['all', 'connections', 'friends', 'none'].indexOf(GlobalServices.findableBy)
}
id: availabilityComboBox;
// Anchors
anchors.top: parent.top;
anchors.horizontalCenter: parent.horizontalCenter;
// Size
width: parent.width;
height: 40;
currentIndex: determineAvailabilityIndex();
model: ListModel {
id: availabilityComboBoxListItems
ListElement { text: "Everyone"; value: "all"; }
ListElement { text: "All Connections"; value: "connections"; }
ListElement { text: "Friends Only"; value: "friends"; }
ListElement { text: "Appear Offline"; value: "none" }
}
onCurrentIndexChanged: {
GlobalServices.findableBy = availabilityComboBoxListItems.get(currentIndex).value;
UserActivityLogger.palAction("set_availability", availabilityComboBoxListItems.get(currentIndex).value);
print('Setting availability:', JSON.stringify(GlobalServices.findableBy));
onEntered: availabilityComboBox.color = hifi.colors.lightGrayText;
onExited: availabilityComboBox.color = hifi.colors.textFieldLightBackground;
}
}
}

View file

@ -38,7 +38,7 @@ StackView {
root.currentItem.focus = true;
root.currentItem.forceActiveFocus();
fillDestinations();
updateLocationText();
updateLocationText(false);
root.parentChanged.connect(center);
center();
}
@ -63,8 +63,8 @@ StackView {
root.push(card);
return;
}
addressLine.text = targetString;
toggleOrGo(true);
location.text = targetString;
toggleOrGo(true, targetString);
clearAddressLineTimer.start();
}
@ -539,7 +539,11 @@ StackView {
}
}
function toggleOrGo(fromSuggestions) {
function toggleOrGo(fromSuggestions, address) {
if (address !== undefined && address !== "") {
addressBarDialog.loadAddress(address, fromSuggestions)
}
if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text, fromSuggestions)
}

View file

@ -89,14 +89,17 @@ Preference {
if (categoryPreferences) {
console.log("Category " + root.name + " with " + categoryPreferences.length + " preferences");
for (var j = 0; j < categoryPreferences.length; ++j) {
buildPreference(categoryPreferences[j]);
//provide component position within column
//lowest numbers on top
buildPreference(categoryPreferences[j], j);
}
}
}
function buildPreference(preference) {
function buildPreference(preference, itemNum) {
console.log("\tPreference type " + preference.type + " name " + preference.name)
var builder;
var zpos;
switch (preference.type) {
case Preference.Editable:
checkBoxCount = 0;
@ -136,11 +139,14 @@ Preference {
case Preference.ComboBox:
checkBoxCount = 0;
builder = comboBoxBuilder;
//make sure that combo boxes sitting higher will have higher z coordinate
//to be not overlapped when drop down is active
zpos = root.z + 1000 - itemNum
break;
};
if (builder) {
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) }));
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos}));
}
}
}

View file

@ -70,6 +70,9 @@ Item {
readonly property color indigoAccent: "#9495FF"
readonly property color magentaHighlight: "#EF93D1"
readonly property color magentaAccent: "#A2277C"
readonly property color checkboxCheckedRed: "#FF0000"
readonly property color checkboxCheckedBorderRed: "#D00000"
// Semitransparent
readonly property color darkGray30: "#4d121212"
readonly property color darkGray0: "#00121212"

View file

@ -4383,6 +4383,10 @@ void Application::update(float deltaTime) {
auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix));
controller::Pose leftFootPose = userInputMapper->getPoseState(controller::Action::LEFT_FOOT);
controller::Pose rightFootPose = userInputMapper->getPoseState(controller::Action::RIGHT_FOOT);
myAvatar->setFootControllerPosesInSensorFrame(leftFootPose.transform(avatarToSensorMatrix), rightFootPose.transform(avatarToSensorMatrix));
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
updateDialogs(deltaTime); // update various stats dialogs if present
@ -5782,22 +5786,10 @@ void Application::toggleRunningScriptsWidget() const {
}
void Application::showScriptLogs() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QUrl defaultScriptsLoc = defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js");
if (tablet->getToolbarMode()) {
scriptEngines->loadScript(defaultScriptsLoc.toString());
} else {
QQuickItem* tabletRoot = tablet->getTabletRoot();
if (!tabletRoot && !isHMDMode()) {
scriptEngines->loadScript(defaultScriptsLoc.toString());
} else {
tablet->pushOntoStack("../../hifi/dialogs/TabletDebugWindow.qml");
}
}
scriptEngines->loadScript(defaultScriptsLoc.toString());
}
void Application::showAssetServerWidget(QString filePath) {

View file

@ -54,7 +54,7 @@ void DiscoverabilityManager::updateLocation() {
const QString CONNECTED_KEY_IN_LOCATION = "connected";
locationObject.insert(CONNECTED_KEY_IN_LOCATION, discoverable && domainHandler.isConnected());
if (discoverable) { // Don't consider changes to these as update-worthy if we're not discoverable.
if (discoverable || _lastLocationObject.isEmpty()) { // Don't consider changes to these as update-worthy if we're not discoverable.
const QString PATH_KEY_IN_LOCATION = "path";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);

View file

@ -71,6 +71,8 @@ void Head::reset() {
}
void Head::simulate(float deltaTime, bool isMine) {
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
// Update audio trailing average for rendering facial animations
const float AUDIO_AVERAGING_SECS = 0.05f;
const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f;
@ -94,7 +96,7 @@ void Head::simulate(float deltaTime, bool isMine) {
if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
calculateMouthShapes();
calculateMouthShapes(deltaTime);
const int JAW_OPEN_BLENDSHAPE = 21;
const int MMMM_BLENDSHAPE = 34;
@ -116,7 +118,7 @@ void Head::simulate(float deltaTime, bool isMine) {
_isEyeTrackerConnected = eyeTracker->isTracking();
}
}
if (!_isFaceTrackerConnected) {
if (!_isEyeTrackerConnected) {
@ -144,22 +146,23 @@ void Head::simulate(float deltaTime, bool isMine) {
_timeWithoutTalking += deltaTime;
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
_timeWithoutTalking = 0.0f;
} else if (_timeWithoutTalking < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
forceBlink = true;
}
// Update audio attack data for facial animation (eyebrows and mouth)
const float AUDIO_ATTACK_AVERAGING_RATE = 0.9f;
_audioAttack = AUDIO_ATTACK_AVERAGING_RATE * _audioAttack + (1.0f - AUDIO_ATTACK_AVERAGING_RATE) * fabs((_audioLoudness - _longTermAverageLoudness) - _lastLoudness);
float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz
_audioAttack = audioAttackAveragingRate * _audioAttack +
(1.0f - audioAttackAveragingRate) * fabs((_audioLoudness - _longTermAverageLoudness) - _lastLoudness);
_lastLoudness = (_audioLoudness - _longTermAverageLoudness);
const float BROW_LIFT_THRESHOLD = 100.0f;
if (_audioAttack > BROW_LIFT_THRESHOLD) {
_browAudioLift += sqrtf(_audioAttack) * 0.01f;
}
_browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
const float BLINK_SPEED = 10.0f;
const float BLINK_SPEED_VARIABILITY = 1.0f;
const float BLINK_START_VARIABILITY = 0.25f;
@ -182,23 +185,23 @@ void Head::simulate(float deltaTime, bool isMine) {
} else {
_leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
_rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
if (_leftEyeBlink == FULLY_CLOSED) {
_leftEyeBlinkVelocity = -BLINK_SPEED;
} else if (_leftEyeBlink == FULLY_OPEN) {
_leftEyeBlinkVelocity = 0.0f;
}
if (_rightEyeBlink == FULLY_CLOSED) {
_rightEyeBlinkVelocity = -BLINK_SPEED;
} else if (_rightEyeBlink == FULLY_OPEN) {
_rightEyeBlinkVelocity = 0.0f;
}
}
// use data to update fake Faceshift blendshape coefficients
calculateMouthShapes();
calculateMouthShapes(deltaTime);
DependencyManager::get<Faceshift>()->updateFakeCoefficients(_leftEyeBlink,
_rightEyeBlink,
_browAudioLift,
@ -216,7 +219,7 @@ void Head::simulate(float deltaTime, bool isMine) {
if (Menu::getInstance()->isOptionChecked(MenuOption::FixGaze)) { // if debug menu turns off, use no saccade
_saccade = glm::vec3();
}
_leftEyePosition = _rightEyePosition = getPosition();
_eyePosition = getPosition();
@ -230,7 +233,7 @@ void Head::simulate(float deltaTime, bool isMine) {
_eyePosition = calculateAverageEyePosition();
}
void Head::calculateMouthShapes() {
void Head::calculateMouthShapes(float deltaTime) {
const float JAW_OPEN_SCALE = 0.015f;
const float JAW_OPEN_RATE = 0.9f;
const float JAW_CLOSE_RATE = 0.90f;
@ -242,20 +245,24 @@ void Head::calculateMouthShapes() {
const float SMILE_SPEED = 1.0f;
const float FUNNEL_SPEED = 2.335f;
const float STOP_GAIN = 5.0f;
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
float deltaTimeRatio = deltaTime / (1.0f / NORMAL_HZ);
// From the change in loudness, decide how much to open or close the jaw
float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE;
if (audioDelta > _audioJawOpen) {
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE;
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE * deltaTimeRatio;
} else {
_audioJawOpen *= JAW_CLOSE_RATE;
_audioJawOpen *= powf(JAW_CLOSE_RATE, deltaTimeRatio);
}
_audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f);
_trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, 0.99f);
float trailingAudioJawOpenRatio = (100.0f - deltaTime * NORMAL_HZ) / 100.0f; // --> 0.99 at 60 Hz
_trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, trailingAudioJawOpenRatio);
// Advance time at a rate proportional to loudness, and move the mouth shapes through
// Advance time at a rate proportional to loudness, and move the mouth shapes through
// a cycle at differing speeds to create a continuous random blend of shapes.
_mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT;
_mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT * deltaTimeRatio;
_mouth2 = (sinf(_mouthTime * MMMM_SPEED) + 1.0f) * MMMM_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
_mouth3 = (sinf(_mouthTime * FUNNEL_SPEED) + 1.0f) * FUNNEL_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
_mouth4 = (sinf(_mouthTime * SMILE_SPEED) + 1.0f) * SMILE_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
@ -321,7 +328,7 @@ glm::quat Head::getFinalOrientationInLocalFrame() const {
//
// Everyone else's head also keeps track of a correctedLookAtPosition that may be different for the same head within
// different Interfaces. If that head is not looking at me, the correctedLookAtPosition is the same as the lookAtPosition.
// However, if that head is looking at me, then I will attempt to adjust the lookAtPosition by the difference between
// However, if that head is looking at me, then I will attempt to adjust the lookAtPosition by the difference between
// my (singular) eye position and my actual camera position. This adjustment is used on their eyeballs during rendering
// (and also on any lookAt vector display for that head, during rendering). Note that:
// 1. this adjustment can be made directly to the other head's eyeball joints, because we won't be send their joint information to others.

View file

@ -138,7 +138,7 @@ private:
int _rightEyeLookAtID;
// private methods
void calculateMouthShapes();
void calculateMouthShapes(float timeRatio);
void applyEyelidOffset(glm::quat headOrientation);
};

View file

@ -1345,6 +1345,45 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const {
return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) {
if (controller::InputDevice::getLowVelocityFilter()) {
auto oldLeftPose = getLeftFootControllerPoseInSensorFrame();
auto oldRightPose = getRightFootControllerPoseInSensorFrame();
_leftFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left));
_rightFootControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right));
} else {
_leftFootControllerPoseInSensorFrameCache.set(left);
_rightFootControllerPoseInSensorFrameCache.set(right);
}
}
controller::Pose MyAvatar::getLeftFootControllerPoseInSensorFrame() const {
return _leftFootControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getRightFootControllerPoseInSensorFrame() const {
return _rightFootControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getLeftFootControllerPoseInWorldFrame() const {
return _leftFootControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getRightFootControllerPoseInWorldFrame() const {
return _rightFootControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
}
controller::Pose MyAvatar::getLeftFootControllerPoseInAvatarFrame() const {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
return getLeftFootControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
controller::Pose MyAvatar::getRightFootControllerPoseInAvatarFrame() const {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
return getRightFootControllerPoseInWorldFrame().transform(invAvatarMatrix);
}
void MyAvatar::updateMotors() {
_characterController.clearMotors();
glm::quat motorRotation;

View file

@ -445,6 +445,14 @@ public:
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
void setFootControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
controller::Pose getLeftFootControllerPoseInSensorFrame() const;
controller::Pose getRightFootControllerPoseInSensorFrame() const;
controller::Pose getLeftFootControllerPoseInWorldFrame() const;
controller::Pose getRightFootControllerPoseInWorldFrame() const;
controller::Pose getLeftFootControllerPoseInAvatarFrame() const;
controller::Pose getRightFootControllerPoseInAvatarFrame() const;
bool hasDriveInput() const;
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
@ -684,6 +692,9 @@ private:
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _leftFootControllerPoseInSensorFrameCache{ controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightFootControllerPoseInSensorFrameCache{ controller::Pose() };
bool _hmdLeanRecenterEnabled = true;
AnimPose _prePhysicsRoomPose;

View file

@ -132,31 +132,49 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_rig->updateFromHeadParameters(headParams, deltaTime);
Rig::HandParameters handParams;
Rig::HandAndFeetParameters handAndFeetParams;
auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
if (leftPose.isValid()) {
handParams.isLeftEnabled = true;
handParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
handParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
handAndFeetParams.isLeftEnabled = true;
handAndFeetParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
handAndFeetParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
} else {
handParams.isLeftEnabled = false;
handAndFeetParams.isLeftEnabled = false;
}
auto rightPose = myAvatar->getRightHandControllerPoseInAvatarFrame();
if (rightPose.isValid()) {
handParams.isRightEnabled = true;
handParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
handParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
handAndFeetParams.isRightEnabled = true;
handAndFeetParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
handAndFeetParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
} else {
handParams.isRightEnabled = false;
handAndFeetParams.isRightEnabled = false;
}
handParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
handParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
handParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
auto leftFootPose = myAvatar->getLeftFootControllerPoseInAvatarFrame();
if (leftFootPose.isValid()) {
handAndFeetParams.isLeftFootEnabled = true;
handAndFeetParams.leftFootPosition = Quaternions::Y_180 * leftFootPose.getTranslation();
handAndFeetParams.leftFootOrientation = Quaternions::Y_180 * leftFootPose.getRotation();
} else {
handAndFeetParams.isLeftFootEnabled = false;
}
_rig->updateFromHandParameters(handParams, deltaTime);
auto rightFootPose = myAvatar->getRightFootControllerPoseInAvatarFrame();
if (rightFootPose.isValid()) {
handAndFeetParams.isRightFootEnabled = true;
handAndFeetParams.rightFootPosition = Quaternions::Y_180 * rightFootPose.getTranslation();
handAndFeetParams.rightFootOrientation = Quaternions::Y_180 * rightFootPose.getRotation();
} else {
handAndFeetParams.isRightFootEnabled = false;
}
handAndFeetParams.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius();
handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
_rig->updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());

View file

@ -1146,9 +1146,8 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
}
}
void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt) {
if (_animSkeleton && _animNode) {
const float HAND_RADIUS = 0.05f;
int hipsIndex = indexOfJoint("Hips");
glm::vec3 hipsTrans;
@ -1197,6 +1196,27 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
_animVars.unset("rightHandRotation");
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
if (params.isLeftFootEnabled) {
_animVars.set("leftFootPosition", params.leftFootPosition);
_animVars.set("leftFootRotation", params.leftFootOrientation);
_animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition);
} else {
_animVars.unset("leftFootPosition");
_animVars.unset("leftFootRotation");
_animVars.set("leftFootType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
if (params.isRightFootEnabled) {
_animVars.set("rightFootPosition", params.rightFootPosition);
_animVars.set("rightFootRotation", params.rightFootOrientation);
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
} else {
_animVars.unset("rightFootPosition");
_animVars.unset("rightFootRotation");
_animVars.set("rightFootType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
}
}

View file

@ -60,7 +60,7 @@ public:
int rightEyeJointIndex = -1;
};
struct HandParameters {
struct HandAndFeetParameters {
bool isLeftEnabled;
bool isRightEnabled;
float bodyCapsuleRadius;
@ -70,6 +70,13 @@ public:
glm::quat leftOrientation = glm::quat(); // rig space (z forward)
glm::vec3 rightPosition = glm::vec3(); // rig space
glm::quat rightOrientation = glm::quat(); // rig space (z forward)
bool isLeftFootEnabled;
bool isRightFootEnabled;
glm::vec3 leftFootPosition = glm::vec3(); // rig space
glm::quat leftFootOrientation = glm::quat(); // rig space (z forward)
glm::vec3 rightFootPosition = glm::vec3(); // rig space
glm::quat rightFootOrientation = glm::quat(); // rig space (z forward)
};
enum class CharacterControllerState {
@ -185,7 +192,7 @@ public:
void updateFromHeadParameters(const HeadParameters& params, float dt);
void updateFromEyeParameters(const EyeParameters& params);
void updateFromHandParameters(const HandParameters& params, float dt);
void updateFromHandAndFeetParameters(const HandAndFeetParameters& params, float dt);
void initAnimGraph(const QUrl& url);

View file

@ -51,6 +51,8 @@ namespace controller {
makePosePair(Action::LEFT_HAND, "LeftHand"),
makePosePair(Action::RIGHT_HAND, "RightHand"),
makePosePair(Action::LEFT_FOOT, "LeftFoot"),
makePosePair(Action::RIGHT_FOOT, "RightFoot"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"),

View file

@ -42,6 +42,8 @@ enum class Action {
LEFT_HAND = NUM_COMBINED_AXES,
RIGHT_HAND,
LEFT_FOOT,
RIGHT_FOOT,
LEFT_HAND_CLICK,
RIGHT_HAND_CLICK,

View file

@ -102,6 +102,8 @@ Input::NamedVector StandardController::getAvailableInputs() const {
// Poses
makePair(LEFT_HAND, "LeftHand"),
makePair(RIGHT_HAND, "RightHand"),
makePair(LEFT_FOOT, "LeftFoot"),
makePair(RIGHT_FOOT, "RightFoot"),
// Aliases, PlayStation style names
makePair(LB, "L1"),

View file

@ -571,10 +571,15 @@ void OpenGLDisplayPlugin::compositeLayers() {
compositeScene();
}
#ifdef HIFI_ENABLE_NSIGHT_DEBUG
if (false) // do not compositeoverlay if running nsight debug
#endif
{
PROFILE_RANGE_EX(render_detail, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount())
compositeOverlay();
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
if (compositorHelper->getReticleVisible()) {
PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount())

View file

@ -28,6 +28,9 @@
#include <GL/wglew.h>
// Uncomment this define and recompile to be able to avoid code path preventing to be able to run nsight graphics debug
//#define HIFI_ENABLE_NSIGHT_DEBUG 1
#endif
#endif // hifi_gpu_GPUConfig_h

View file

@ -278,6 +278,11 @@ void OffscreenQmlSurface::cleanup() {
}
void OffscreenQmlSurface::render() {
#ifdef HIFI_ENABLE_NSIGHT_DEBUG
return;
#endif
if (_paused) {
return;
}
@ -576,7 +581,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
return nullptr;
}
_qmlEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
newObject->setProperty("eventBridge", QVariant::fromValue(this));
newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
f(newContext, newObject);

View file

@ -95,7 +95,6 @@ void GL41Backend::updateTransform(const Batch& batch) {
} else {
if (!_transform._enabledDrawcallInfoBuffer) {
glEnableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); // Make sure attrib array is enabled
glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer);
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, (isStereo() ? 2 : 1));
#else
@ -103,6 +102,7 @@ void GL41Backend::updateTransform(const Batch& batch) {
#endif
_transform._enabledDrawcallInfoBuffer = true;
}
glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer);
glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0, _transform._drawCallInfoOffsets[batch._currentNamedCall]);
}

View file

@ -974,6 +974,8 @@ void ScriptEngine::run() {
return; // bail early - avoid setting state in init(), as evaluate() will bail too
}
scriptInfoMessage("Script Engine starting:" + getFilename());
if (!_isInitialized) {
init();
}

View file

@ -201,7 +201,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed()));
QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant)));
QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
// forward qml surface events to interface js
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
@ -271,12 +271,17 @@ bool TabletProxy::isMessageDialogOpen() {
return false;
}
void TabletProxy::emitWebEvent(QVariant msg) {
emit webEventReceived(msg);
}
void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
std::lock_guard<std::mutex> guard(_mutex);
_qmlOffscreenSurface = qmlOffscreenSurface;
_qmlTabletRoot = qmlTabletRoot;
if (_qmlTabletRoot && _qmlOffscreenSurface) {
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant)));
QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
// forward qml surface events to interface js
connect(dynamic_cast<OffscreenQmlSurface*>(_qmlOffscreenSurface), &OffscreenQmlSurface::fromQml, [this](QVariant message) {
@ -398,7 +403,7 @@ void TabletProxy::popFromStack() {
auto stack = _desktopWindow->asQuickItem()->findChild<QQuickItem*>("stack");
if (stack) {
QMetaObject::invokeMethod(stack, "popSource");
} else {
} else {
qCDebug(scriptengine) << "tablet cannot pop QML because _desktopWindow doesn't have child stack";
}
} else {
@ -708,4 +713,3 @@ void TabletButtonProxy::editProperties(QVariantMap properties) {
}
#include "TabletScriptingInterface.moc"

View file

@ -226,6 +226,7 @@ signals:
protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
void emitWebEvent(QVariant msg);
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);

View file

@ -113,16 +113,16 @@ static controller::StandardPoseChannel KinectJointIndexToPoseIndexMap[KinectJoin
controller::RIGHT_FORE_ARM,
controller::RIGHT_HAND,
controller::RIGHT_UP_LEG, // hip socket
controller::RIGHT_LEG, // knee?
controller::RIGHT_FOOT, // ankle?
UNKNOWN_JOINT, // ????
controller::LEFT_UP_LEG, // hip socket
controller::LEFT_LEG, // knee?
controller::LEFT_FOOT, // ankle?
UNKNOWN_JOINT, // ????
controller::RIGHT_UP_LEG, // hip socket
controller::RIGHT_LEG, // knee?
controller::RIGHT_FOOT, // ankle?
UNKNOWN_JOINT, // ????
UNKNOWN_JOINT, /* SpineShoulder */
controller::LEFT_HAND_INDEX4,
@ -130,7 +130,6 @@ static controller::StandardPoseChannel KinectJointIndexToPoseIndexMap[KinectJoin
controller::RIGHT_HAND_INDEX4,
controller::RIGHT_HAND_THUMB4,
};
// in rig frame

View file

@ -0,0 +1,81 @@
//
// proceduralFootPoseExample.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MAPPING_NAME = "com.highfidelity.examples.proceduralFootPose";
var mapping = Controller.newMapping(MAPPING_NAME);
var translation = { x: 0, y: 0.1, z: 0 };
var translationDx = 0.01;
var translationDy = 0.01;
var translationDz = -0.01;
var TRANSLATION_LIMIT = 0.5;
var pitch = 45;
var yaw = 0;
var roll = 45;
var pitchDelta = 1;
var yawDelta = -1;
var rollDelta = 1;
var ROTATION_MIN = -90;
var ROTATION_MAX = 90;
mapping.from(function() {
// adjust the hand translation in a periodic back and forth motion for each of the 3 axes
translation.x = translation.x + translationDx;
translation.y = translation.y + translationDy;
translation.z = translation.z + translationDz;
if ((translation.x > TRANSLATION_LIMIT) || (translation.x < (-1 * TRANSLATION_LIMIT))) {
translationDx = translationDx * -1;
}
if ((translation.y > TRANSLATION_LIMIT) || (translation.y < (-1 * TRANSLATION_LIMIT))) {
translationDy = translationDy * -1;
}
if ((translation.z > TRANSLATION_LIMIT) || (translation.z < (-1 * TRANSLATION_LIMIT))) {
translationDz = translationDz * -1;
}
// adjust the hand rotation in a periodic back and forth motion for each of pitch/yaw/roll
pitch = pitch + pitchDelta;
yaw = yaw + yawDelta;
roll = roll + rollDelta;
if ((pitch > ROTATION_MAX) || (pitch < ROTATION_MIN)) {
pitchDelta = pitchDelta * -1;
}
if ((yaw > ROTATION_MAX) || (yaw < ROTATION_MIN)) {
yawDelta = yawDelta * -1;
}
if ((roll > ROTATION_MAX) || (roll < ROTATION_MIN)) {
rollDelta = rollDelta * -1;
}
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll);
var pose = {
translation: translation,
rotation: rotation,
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 }
};
Vec3.print("foot translation:", translation);
return pose;
}).debug(true).to(Controller.Standard.LeftFoot);
//mapping.from(Controller.Standard.LeftFoot).debug(true).to(Controller.Actions.LeftFoot);
Controller.enableMapping(MAPPING_NAME);
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,35 @@
//
// proceduralFootPoseExample.js
// examples/controllers
//
// Created by Brad Hefta-Gaub on 2015/12/15
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MAPPING_NAME = "com.highfidelity.examples.proceduralFootPose";
var mapping = Controller.newMapping(MAPPING_NAME);
var puppetOffset = { x: 0, y: -1, z: 0 };
mapping.from(function() {
var leftHandPose = Controller.getPoseValue(Controller.Standard.LeftHand);
var pose = {
translation: Vec3.sum(leftHandPose.translation, puppetOffset),
rotation: { x: 0, y: 0, z: 0, w: 0 }, //leftHandPose.rotation,
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 }
};
return pose;
}).to(Controller.Standard.LeftFoot);
Controller.enableMapping(MAPPING_NAME);
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -16,66 +16,92 @@
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var tabletButton = tablet.addButton({
text: "SOUNDS"
text: "SOUNDS",
icon: "http://s3.amazonaws.com/hifi-public/tony/icons/trombone-i.png",
activeIcon: "http://s3.amazonaws.com/hifi-public/tony/icons/trombone-a.png"
});
var WEB_BRIDGE_TEST_HTML = "https://s3.amazonaws.com/hifi-public/tony/webBridgeTest.html?2";
var TROMBONE_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/sad-trombone.wav";
var tromboneSound = SoundCache.getSound(TROMBONE_URL);
var tromboneInjector;
var SCREAM_URL = "https://s3.amazonaws.com/hifi-public/tony/audio/wilhelm-scream.wav";
var screamSound = SoundCache.getSound(SCREAM_URL);
var screamInjector;
tabletButton.clicked.connect(function () {
tablet.gotoWebScreen(WEB_BRIDGE_TEST_HTML);
if (shown) {
tablet.gotoHomeScreen();
} else {
tablet.gotoWebScreen(WEB_BRIDGE_TEST_HTML);
}
});
// hook up to the event bridge
tablet.webEventReceived.connect(function (msg) {
var shown = false;
function onScreenChanged(type, url) {
if (type === "Web" && url === WEB_BRIDGE_TEST_HTML) {
tabletButton.editProperties({isActive: true});
if (!shown) {
// hook up to the event bridge
tablet.webEventReceived.connect(onWebEventReceived);
}
shown = true;
} else {
tabletButton.editProperties({isActive: false});
if (shown) {
// disconnect from the event bridge
tablet.webEventReceived.disconnect(onWebEventReceived);
}
shown = false;
}
}
tablet.screenChanged.connect(onScreenChanged);
// ctor
function SoundBuddy(url) {
this.sound = SoundCache.getSound(url);
this.injector = null;
}
SoundBuddy.prototype.play = function (options, doneCallback) {
if (this.sound.downloaded) {
if (this.injector) {
this.injector.setOptions(options);
this.injector.restart();
} else {
this.injector = Audio.playSound(this.sound, options);
this.injector.finished.connect(function () {
if (doneCallback) {
doneCallback();
}
});
}
}
};
var tromboneSound = new SoundBuddy(TROMBONE_URL);
var screamSound = new SoundBuddy(SCREAM_URL);
var soundOptions = { position: MyAvatar.position, volume: 1.0, loop: false, localOnly: true };
function onWebEventReceived(msg) {
Script.print("HIFI: recv web event = " + JSON.stringify(msg));
if (msg === "button-1-play") {
// play sad trombone
if (tromboneSound.downloaded) {
if (tromboneInjector) {
tromboneInjector.restart();
} else {
tromboneInjector = Audio.playSound(tromboneSound, { position: MyAvatar.position,
volume: 1.0,
loop: false });
}
}
// wait until sound is finished then send a done event
Script.setTimeout(function () {
soundOptions.position = MyAvatar.position;
tromboneSound.play(soundOptions, function () {
tablet.emitScriptEvent("button-1-done");
}, 3500);
}
if (msg === "button-2-play") {
// play scream
if (screamSound.downloaded) {
if (screamInjector) {
screamInjector.restart();
} else {
screamInjector = Audio.playSound(screamSound, { position: MyAvatar.position,
volume: 1.0,
loop: false });
}
}
// wait until sound is finished then send a done event
Script.setTimeout(function () {
});
} else if (msg === "button-2-play") {
soundOptions.position = MyAvatar.position;
screamSound.play(soundOptions, function () {
tablet.emitScriptEvent("button-2-done");
}, 1000);
});
}
});
}
Script.scriptEnding.connect(function () {
tablet.removeButton(tabletButton);
if (shown) {
tablet.webEventReceived.disconnect(onWebEventReceived);
}
});

View file

@ -2,166 +2,151 @@
<head>
<title>Photo Booth</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="../../../../../system/html/css/edit-style.css">
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
var EventBridge;
var openEventBridge = function (callback) {
var WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge;
callback();
});
};
var emit = function (eventType, data) {
data = data || {};
data.type = eventType;
EventBridge.emitWebEvent(JSON.stringify(data));
};
function loaded () {
openEventBridge(function () {
emit("onLoad", {value: "faye"});
var elModelURL = document.getElementById("model-url");
var elReloadModelButton = document.getElementById("reload-model-button");
var elCamera = document.getElementById("property-camera");
//var elLightingPreset = document.getElementById("property-lighting-preset");
var elPictureButton = document.getElementById("picture-button");
elReloadModelButton.addEventListener('click', function() {
emit("onClickReloadModelButton", {value: elModelURL.value});
});
elCamera.addEventListener('change', function() {
emit("onSelectCamera", {value: this.value});
});
// elLightingPreset.addEventListener('change', function() {
// emit("onSelectLightingPreset", {value: "faye"});
// });
elPictureButton.addEventListener('click', function() {
emit("onClickPictureButton", {value: "faye"});
});
});
// Drop downs
function setDropdownText(dropdown) {
var lis = dropdown.parentNode.getElementsByTagName("li");
var text = "";
for (var i = 0; i < lis.length; i++) {
if (lis[i].getAttribute("value") === dropdown.value) {
text = lis[i].textContent;
}
}
dropdown.firstChild.textContent = text;
}
function toggleDropdown(event) {
var element = event.target;
if (element.nodeName !== "DT") {
element = element.parentNode;
}
element = element.parentNode;
var isDropped = element.getAttribute("dropped");
element.setAttribute("dropped", isDropped !== "true" ? "true" : "false");
}
function setDropdownValue(event) {
var dt = event.target.parentNode.parentNode.previousSibling;
dt.value = event.target.getAttribute("value");
dt.firstChild.textContent = event.target.textContent;
dt.parentNode.setAttribute("dropped", "false");
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true);
dt.dispatchEvent(evt);
}
var elDropdowns = document.getElementsByTagName("select");
for (var i = 0; i < elDropdowns.length; i++) {
var options = elDropdowns[i].getElementsByTagName("option");
var selectedOption = 0;
for (var j = 0; j < options.length; j++) {
if (options[j].getAttribute("selected") === "selected") {
selectedOption = j;
}
}
var div = elDropdowns[i].parentNode;
var dl = document.createElement("dl");
div.appendChild(dl);
var dt = document.createElement("dt");
dt.name = elDropdowns[i].name;
dt.id = elDropdowns[i].id;
dt.addEventListener("click", toggleDropdown, true);
dl.appendChild(dt);
var span = document.createElement("span");
span.setAttribute("value", options[selectedOption].value);
span.textContent = options[selectedOption].firstChild.textContent;
dt.appendChild(span);
var span = document.createElement("span");
span.textContent = "5"; // caratDn
dt.appendChild(span);
var dd = document.createElement("dd");
dl.appendChild(dd);
var ul = document.createElement("ul");
dd.appendChild(ul);
for (var j = 0; j < options.length; j++) {
var li = document.createElement("li");
li.setAttribute("value", options[j].value);
li.textContent = options[j].firstChild.textContent;
li.addEventListener("click", setDropdownValue);
ul.appendChild(li);
}
}
elDropdowns = document.getElementsByTagName("select");
while (elDropdowns.length > 0) {
var el = elDropdowns[0];
el.parentNode.removeChild(el);
elDropdowns = document.getElementsByTagName("select");
}
}
</script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.2/css/bootstrap-slider.min.css">
<style>
</style>
body {
margin: 0;
padding: 0;
width: 100%;
color: white;
}
.top-bar {
height: 90px;
background: linear-gradient(#2b2b2b, #1e1e1e);
font-family: Raleway-Bold;
padding-left: 30px;
padding-right: 30px;
display: flex;
align-items: center;
position: fixed;
width: 480px;
top: 0;
z-index: 1;
font-size: 16px;
}
.content {
margin-top: 90px;
padding: 30px;
}
.slider {
margin-left: 70px;
}
#camera-toggle {
font-family: Raleway-Bold;
font-size: 13px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
padding: 0px 18px;
margin-right: 0px;
border-radius: 5px;
border: none;
color: #121212;
background-color: #afafaf;
background: linear-gradient(#fff 20%, #afafaf 100%);
cursor: pointer;
}
.dropdown li {
background-color: #ffffff;
}
</style>
</head>
<body onload="loaded()">
<div id="properties-list">
<div class="property url refresh">
<label>Model URL</label>
<input type="text" id="model-url"></input>
<input type="button" id="reload-model-button" class="glyph" value="F">
</div>
<!--
<div class="property dropdown">
<label>Lighting Preset</label>
<select id="property-lighting-preset">
<option>Default Lighting</option>
<option>Sam's Cool Light</option>
<option>Alan's Light Magic</option>
</select>
</div>
-->
<div class="property dropdown">
<label>Camera</label>
<select id="property-camera">
<option>First Person Camera</option>
<option>Main Camera</option>
<option>Left Camera</option>
<option>Right Camera</option>
</select>
</div>
<div class="property">
<input id="picture-button" type="button" class="blue" value="Take Picture">
<body>
<div class="top-bar">
<div>Photobooth</div>
</div>
<div class="content">
<div id="properties-list">
<div class="property url refresh">
<label>Model URL</label>
<input type="text" id="model-url"></input>
<input type="button" id="reload-model-button" class="glyph" value="F">
</div>
<div class="property">
<label>Rotate Model</label>
<input
id="rotate-slider"
type="text"
data-provide="slider"
data-slider-ticks="[-180, 0, 180]"
data-slider-ticks-labels='["clockwise", "centre", "anti-clockwise"]'
data-slider-min="-180"
data-slider-max="180"
data-slider-step="1"
data-slider-value="0"
data-slider-tooltip="show"
>
</div>
<div class="property">
<label>Camera</label>
<div class="dropdown">
<button id="camera-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
First Person Camera
<span class="glyphicon glyphicon-menu-down"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li>First Person Camera</li>
<li>Main Camera</li>
<li>Left Camera</li>
<li>Right Camera</li>
</ul>
</div>
</div>
<div class="property">
<input id="picture-button" type="button" class="blue" value="Take Picture">
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.2/bootstrap-slider.min.js"></script>
<script>
// Helper function to emit web events to photoboothApp.js
function emit(eventType, eventData) {
var eventObject = {
"app": "photobooth",
"type": eventType,
"data": eventData
};
EventBridge.emitWebEvent(JSON.stringify(eventObject));
}
$(document).ready(function() {
// Send a ready event to hifi
emit("ready", null);
// Send an event when camera selection changes
$(".dropdown-menu li").click(function() {
console.log("clicked " + this.textContent);
$("#camera-toggle").text(this.textContent + " ");
$("#camera-toggle").append("<span class='glyphicon glyphicon-menu-down'></span>");
emit("onSelectCamera", {value: this.textContent});
});
// Send an event to hifi to trigger snapshot
$("#picture-button").click(function() {
emit("onClickPictureButton", null);
});
// Send an event to hifi for loading the given model URL
$("#reload-model-button").click(function() {
emit("onClickReloadModelButton", {value: $("#model-url").val()});
});
$("#rotate-slider").slider().on("slide", function(e){
console.log("slided " + e.value);
emit("onRotateSlider", {value: e.value});
});
});
</script>
</body>
</html>

View file

@ -1,178 +0,0 @@
(function () {
var SNAPSHOT_DELAY = 500; // 500ms
var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html");
var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json");
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165};
var PhotoBooth = {};
PhotoBooth.init = function () {
var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL);
var forwardFactor = 10;
var forwardUnitVector = Vec3.normalize(Quat.getForward(MyAvatar.orientation));
var forwardOffset = Vec3.multiply(forwardUnitVector,forwardFactor);
var rightFactor = 3;
// TODO: rightUnitVec is unused and spawnLocation declaration is incorrect
var rightUnitVec = Vec3.normalize(Quat.getRight(MyAvatar.orientation));
var spawnLocation = Vec3.sum(Vec3.sum(MyAvatar.position,forwardOffset),rightFactor);
if (success) {
this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation);
this.processPastedEntities();
}
};
PhotoBooth.processPastedEntities = function () {
var cameraResults = {};
var modelResult;
var modelPos;
this.pastedEntityIDs.forEach(function(id) {
var props = Entities.getEntityProperties(id);
var parts = props["name"].split(":");
if (parts[0] === "Photo Booth Camera") {
cameraResults[parts[1]] = id;
}
if (parts[0] === "Photo Booth Model") {
modelResult = id;
modelPos = props.position;
}
});
print(JSON.stringify(cameraResults));
print(JSON.stringify(modelResult));
this.cameraEntities = cameraResults;
this.modelEntityID = modelResult;
this.centrePos = modelPos;
};
// replace the model in scene with new model
PhotoBooth.changeModel = function (newModelURL) {
// deletes old model
Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth
var newProps = {
name: "Photo Booth Model",
type: "Model",
modelURL: newModelURL,
position: this.centrePos
};
var newModelEntityID = Entities.addEntity(newProps);
// scale model dimensions to fit in bounding box
var scaleModel = function () {
newProps = Entities.getEntityProperties(newModelEntityID);
var myDimensions = newProps.dimensions;
print("myDimensions: " + JSON.stringify(myDimensions));
var k;
if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z;
myDimensions = Vec3.multiply(k, myDimensions);
}
// position the new model on the table
var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2;
var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0});
Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions});
};
// add a delay before scaling to make sure the entity server have gotten the right model dimensions
Script.setTimeout(function () {
scaleModel();
}, 400);
this.modelEntityID = newModelEntityID;
};
PhotoBooth.destroy = function () {
this.pastedEntityIDs.forEach(function(id) {
Entities.deleteEntity(id);
});
Entities.deleteEntity(this.modelEntityID);
};
var main = function () {
PhotoBooth.init();
var photoboothWindowListener = {};
photoboothWindowListener.onLoad = function (event) {
print("loaded" + event.value);
if (!event.hasOwnProperty("value")){
return;
}
};
photoboothWindowListener.onSelectCamera = function (event) {
print("selected camera " + event.value);
if (!event.hasOwnProperty("value")){
return;
}
if (event.value === "First Person Camera") {
Camera.mode = "first person";
} else {
Camera.mode = "entity";
var cameraID = PhotoBooth.cameraEntities[event.value];
Camera.setCameraEntity(cameraID);
}
};
photoboothWindowListener.onSelectLightingPreset = function (event) {
print("selected lighting preset" + event.value);
if (!event.hasOwnProperty("value")){
return;
}
};
photoboothWindowListener.onClickPictureButton = function (event) {
print("clicked picture button");
// hide HUD tool bar
toolbar.writeProperty("visible", false);
// hide Overlays (such as Running Scripts or other Dialog UI)
Menu.setIsOptionChecked("Overlays", false);
// hide mouse cursor
Reticle.visible = false;
// giving a delay here before snapshotting so that there is time to hide toolbar and other UIs
// void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio)
Script.setTimeout(function () {
Window.takeSnapshot(false, false, 1.91);
// show hidden items after snapshot is taken
toolbar.writeProperty("visible", true);
Menu.setIsOptionChecked("Overlays", true);
// unknown issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot
// Reticle.visible = true;
}, SNAPSHOT_DELAY);
};
photoboothWindowListener.onClickReloadModelButton = function (event) {
print("clicked reload model button " + event.value);
PhotoBooth.changeModel(event.value);
};
var photoboothWindow = new OverlayWebWindow({
title: 'Photo Booth',
source: PHOTOBOOTH_WINDOW_HTML_URL,
width: 450,
height: 450,
visible: true
});
photoboothWindow.webEventReceived.connect(function (data) {
var event = JSON.parse(data);
if (photoboothWindowListener[event.type]) {
photoboothWindowListener[event.type](event);
}
});
};
main();
function cleanup() {
Camera.mode = "first person";
PhotoBooth.destroy();
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -0,0 +1,199 @@
//
// photobooth.js
// scripts/developer/utilities/render/photobooth
//
// Created by Faye Li on 2 Nov 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */
(function () {
var SNAPSHOT_DELAY = 500; // 500ms
var PHOTOBOOTH_WINDOW_HTML_URL = Script.resolvePath("./html/photobooth.html");
var PHOTOBOOTH_SETUP_JSON_URL = Script.resolvePath("./photoboothSetup.json");
var MODEL_BOUNDING_BOX_DIMENSIONS = {x: 1.0174,y: 1.1925,z: 1.0165};
var PhotoBooth = {};
var photoboothCreated = false;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/snap-i.svg",
text: "PHOTOBOOTH"
});
function onClicked() {
if (photoboothCreated) {
tablet.gotoHomeScreen();
PhotoBooth.destroy();
} else {
tablet.gotoWebScreen(PHOTOBOOTH_WINDOW_HTML_URL);
PhotoBooth.init();
}
}
function onScreenChanged() {
if (photoboothCreated) {
tablet.gotoHomeScreen();
PhotoBooth.destroy();
button.editProperties({isActive: false});
} else {
button.editProperties({isActive: true});
}
}
tablet.screenChanged.connect(onScreenChanged);
button.clicked.connect(onClicked);
tablet.webEventReceived.connect(onWebEventReceived);
function onWebEventReceived(event) {
print("photobooth.js received a web event:" + event);
// Converts the event to a JavasScript Object
if (typeof event === "string") {
event = JSON.parse(event);
}
if (event.app === "photobooth") {
if (event.type === "onClickPictureButton") {
print("clicked picture button");
// // hide HUD tool bar
// toolbar.writeProperty("visible", false);
// hide Overlays (such as Running Scripts or other Dialog UI)
Menu.setIsOptionChecked("Overlays", false);
// hide mouse cursor
Reticle.visible = false;
// hide tablet
HMD.closeTablet();
// // giving a delay here before snapshotting so that there is time to hide other UIs
// void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio)
Script.setTimeout(function () {
Window.takeSnapshot(false, false, 1.91);
// show hidden items after snapshot is taken
// issue: currently there's no way to show tablet via a script command. user will have to manually open tablet again
// issue: somehow we don't need to reset cursor to visible in script and the mouse still returns after snapshot
// Reticle.visible = true;
// toolbar.writeProperty("visible", true);
Menu.setIsOptionChecked("Overlays", true);
}, SNAPSHOT_DELAY);
} else if (event.type === "onClickReloadModelButton") {
print("clicked reload model button " + event.data.value);
PhotoBooth.changeModel(event.data.value);
} else if (event.type === "onSelectCamera") {
print("selected camera " + event.data.value);
if (!event.data.hasOwnProperty("value")){
return;
}
if (event.data.value === "First Person Camera") {
Camera.mode = "first person";
} else {
Camera.mode = "entity";
var cameraID = PhotoBooth.cameraEntities[event.data.value];
Camera.setCameraEntity(cameraID);
}
} else if (event.type === "onRotateSlider") {
var props = {};
props.rotation = Quat.fromPitchYawRollDegrees(0, event.data.value, 0);
Entities.editEntity(PhotoBooth.modelEntityID, props);
}
}
}
PhotoBooth.init = function () {
photoboothCreated = true;
var success = Clipboard.importEntities(PHOTOBOOTH_SETUP_JSON_URL);
var frontFactor = 10;
// getForward is preffered as getFront function is deprecated
var frontUnitVec = Vec3.normalize(Quat.getFront(MyAvatar.orientation));
var frontOffset = Vec3.multiply(frontUnitVec,frontFactor);
var upFactor = 3;
var upUnitVec = Vec3.normalize(Quat.getUp(MyAvatar.orientation));
var upOffset = Vec3.multiply(upUnitVec, upFactor);
var spawnLocation = Vec3.sum(MyAvatar.position,frontOffset);
spawnLocation = Vec3.sum(spawnLocation, upOffset);
if (success) {
this.pastedEntityIDs = Clipboard.pasteEntities(spawnLocation);
this.processPastedEntities();
}
};
PhotoBooth.processPastedEntities = function () {
var cameraResults = {};
var modelResult;
var modelPos;
this.pastedEntityIDs.forEach(function(id) {
var props = Entities.getEntityProperties(id);
var parts = props["name"].split(":");
if (parts[0] === "Photo Booth Camera") {
cameraResults[parts[1]] = id;
}
if (parts[0] === "Photo Booth Model") {
modelResult = id;
modelPos = props.position;
}
});
print(JSON.stringify(cameraResults));
print(JSON.stringify(modelResult));
this.cameraEntities = cameraResults;
this.modelEntityID = modelResult;
this.centrePos = modelPos;
};
// replace the model in scene with new model
PhotoBooth.changeModel = function (newModelURL) {
// deletes old model
Entities.deleteEntity(this.modelEntityID);
// create new model at centre of the photobooth
var newProps = {
name: "Photo Booth Model",
type: "Model",
modelURL: newModelURL,
position: this.centrePos
};
var newModelEntityID = Entities.addEntity(newProps);
// scale model dimensions to fit in bounding box
var scaleModel = function () {
newProps = Entities.getEntityProperties(newModelEntityID);
var myDimensions = newProps.dimensions;
print("myDimensions: " + JSON.stringify(myDimensions));
var k;
if (myDimensions.x > MODEL_BOUNDING_BOX_DIMENSIONS.x) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.x / myDimensions.x;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.y > MODEL_BOUNDING_BOX_DIMENSIONS.y) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.y / myDimensions.y;
myDimensions = Vec3.multiply(k, myDimensions);
}
if (myDimensions.z > MODEL_BOUNDING_BOX_DIMENSIONS.z) {
k = MODEL_BOUNDING_BOX_DIMENSIONS.z / myDimensions.z;
myDimensions = Vec3.multiply(k, myDimensions);
}
// position the new model on the table
var y_offset = (MODEL_BOUNDING_BOX_DIMENSIONS.y - myDimensions.y) / 2;
var myPosition = Vec3.sum(newProps.position, {x:0, y:-y_offset, z:0});
Entities.editEntity(newModelEntityID,{position: myPosition, dimensions: myDimensions});
};
// add a delay before scaling to make sure the entity server have gotten the right model dimensions
Script.setTimeout(function () {
scaleModel();
}, 400);
this.modelEntityID = newModelEntityID;
};
PhotoBooth.destroy = function () {
this.pastedEntityIDs.forEach(function(id) {
Entities.deleteEntity(id);
});
Entities.deleteEntity(this.modelEntityID);
photoboothCreated = false;
Camera.mode = "first person";
};
function cleanup() {
tablet.removeButton(button);
PhotoBooth.destroy();
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -266,6 +266,13 @@
}
Script.scriptEnding.connect(function () {
// if we reload scripts in tablet mode make sure we close the currently open window, by calling gotoHomeScreen
var tabletProxy = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (tabletProxy && tabletProxy.toolbarMode) {
tabletProxy.gotoHomeScreen();
}
var tabletID = HMD.tabletID;
Entities.deleteEntity(tabletID);
Overlays.deleteOverlay(tabletID);