mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-17 02:05:37 +02:00
commit
b3d704fa61
138 changed files with 5894 additions and 592 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -99,12 +99,9 @@ tools/jsdoc/package-lock.json
|
|||
# Python compile artifacts
|
||||
**/__pycache__
|
||||
|
||||
# ignore unneeded unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter/Library
|
||||
tools/unity-avatar-exporter/Logs
|
||||
tools/unity-avatar-exporter/Packages
|
||||
tools/unity-avatar-exporter/ProjectSettings
|
||||
tools/unity-avatar-exporter/Temp
|
||||
# ignore local unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter
|
||||
|
||||
server-console/package-lock.json
|
||||
vcpkg/
|
||||
/tools/nitpick/compiledResources
|
||||
|
|
|
@ -36,11 +36,6 @@ apply plugin: 'com.android.application'
|
|||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
//buildToolsVersion '27.0.3'
|
||||
|
||||
def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
|
||||
def appVersionName = RELEASE_NUMBER ?: "1.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.highfidelity.hifiinterface"
|
||||
minSdkVersion 24
|
||||
|
|
|
@ -18,12 +18,11 @@ task renameHifiACTaskRelease(type: Copy) {
|
|||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.highfidelity.questInterface"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionCode appVersionCode
|
||||
versionName appVersionName
|
||||
ndk { abiFilters 'arm64-v8a' }
|
||||
externalNativeBuild {
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <TryLocker.h>
|
||||
#include "../AssignmentDynamicFactory.h"
|
||||
#include "../entities/AssignmentParentFinder.h"
|
||||
#include <model-networking/ModelCache.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
|
||||
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||
|
||||
|
@ -60,7 +62,10 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
{
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
DependencyManager::set<AssignmentDynamicFactory>();
|
||||
|
||||
DependencyManager::set<ModelFormatRegistry>();
|
||||
DependencyManager::set<ModelCache>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<ResourceManager>();
|
||||
// make sure we hear about node kills so we can tell the other nodes
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||
|
||||
|
@ -1060,6 +1065,10 @@ void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, Sh
|
|||
}
|
||||
|
||||
void AvatarMixer::aboutToFinish() {
|
||||
DependencyManager::destroy<ResourceManager>();
|
||||
DependencyManager::destroy<ResourceCacheSharedItems>();
|
||||
DependencyManager::destroy<ModelCache>();
|
||||
DependencyManager::destroy<ModelFormatRegistry>();
|
||||
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||
DependencyManager::destroy<AssignmentParentFinder>();
|
||||
|
||||
|
|
27
interface/resources/icons/standalone-optimized.svg
Normal file
27
interface/resources/icons/standalone-optimized.svg
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 66.7 66" style="enable-background:new 0 0 66.7 66;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#00B4EF;}
|
||||
</style>
|
||||
<path class="st0" d="M66.7,29.5l-7-5.9l1.3-9l-8.9-2l-3.1-8.5l-8.9,2.3l-6.7-6.1l-6.7,6.1l-8.9-2.3l-3.1,8.5l-8.9,2l1.3,9l-7,5.9
|
||||
l5.3,7.4l-3.4,8.4l8.2,4.1l0.9,9l9.2-0.2l5.1,7.5l8-4.4l8,4.4l5.1-7.5l9.2,0.2l0.9-9l8.2-4.1l-3.4-8.4L66.7,29.5z M33.3,58.4
|
||||
C19.3,58.4,8,47,8,33S19.3,7.6,33.3,7.6c14,0,25.4,11.4,25.4,25.4S47.3,58.4,33.3,58.4z M45.5,22.9H21.2c-3,0-5.5,2.3-5.8,5.2h-1.5
|
||||
c-1.1,0-2.1,0.9-2.1,2c0,0,0,0,0,0v5.7c0,1.1,0.9,2.1,2,2.1c0,0,0,0,0,0h1.5c0.1,0.5,0.2,0.9,0.3,1.3l0.1,0.2
|
||||
c0.6,1.5,1.8,2.7,3.3,3.3h0.1c0.3,0.1,0.6,0.2,1,0.2c0.1,0,0.2,0.1,0.3,0.1H27c1.1,0,2.2-0.4,3-1.2c0.9-0.9,2.1-1.4,3.3-1.3
|
||||
c1.2,0,2.4,0.4,3.3,1.3c0.8,0.8,1.9,1.2,3,1.2h5.9c2.9,0,5.4-2.2,5.8-5.1h1.5c1.1,0,2.1-0.9,2.1-2c0,0,0,0,0,0v-5.7
|
||||
c-0.1-1.1-1-1.9-2.1-1.9h-1.4C51,25.2,48.5,22.9,45.5,22.9z M15.3,36.7h-1.4c-0.4,0-0.7-0.3-0.7-0.7c0,0,0,0,0,0v-5.7
|
||||
c0-0.4,0.3-0.7,0.7-0.7h1.4V36.7z M33.3,38.1c-1,0-1.8-0.8-1.8-1.8s0.8-1.8,1.8-1.8s1.8,0.8,1.8,1.8S34.3,38.1,33.3,38.1z
|
||||
M37.2,34.8C37,34.9,36.9,35,36.7,35c-0.3,0-0.6-0.1-0.7-0.4c-0.6-0.9-1.6-1.5-2.7-1.5c-1.1,0-2.1,0.5-2.7,1.5
|
||||
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c0.9-1.4,2.5-2.2,4.1-2.2c1.7,0,3.2,0.8,4.1,2.3C37.7,34,37.6,34.6,37.2,34.8z
|
||||
M39.8,33.1c-0.1,0.1-0.3,0.1-0.5,0.1c-0.3,0-0.6-0.1-0.7-0.4C37.4,31,35.5,30,33.3,30c-2.1,0-4.1,1.1-5.3,2.9
|
||||
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c1.5-2.3,4-3.7,6.8-3.7c2.7,0,5.3,1.4,6.8,3.7C40.3,32.3,40.2,32.8,39.8,33.1z
|
||||
M42.5,31.5c-0.1,0.1-0.3,0.1-0.5,0.1c-0.3,0-0.6-0.1-0.7-0.4c-1.8-2.8-4.7-4.4-8-4.4c-3.2,0-6.2,1.6-8,4.4
|
||||
c-0.3,0.4-0.8,0.5-1.2,0.3c-0.4-0.3-0.5-0.8-0.3-1.2c2.1-3.3,5.6-5.2,9.5-5.2c3.9,0,7.4,2,9.5,5.2C43,30.7,42.9,31.3,42.5,31.5z
|
||||
M51.3,29.6h1.4c0.4,0,0.7,0.3,0.7,0.7v5.7c0,0.4-0.3,0.7-0.7,0.7h-1.4V29.6z M30.9,16.9l2.4-1.8l2.4,1.8l-0.9-2.8l2.4-1.8h-3
|
||||
l-0.9-2.8l-0.9,2.8h-3l2.4,1.8L30.9,16.9z M35.8,49.1l-2.4,1.8l-2.4-1.8l0.9,2.8l-2.4,1.8h3l0.9,2.8l0.9-2.8h3l-2.4-1.8L35.8,49.1z
|
||||
M42,17.4l1.3,2.7l0.6-2.9l3-0.4l-2.6-1.5l0.6-2.9l-2.2,2L40,13l1.3,2.7l-2.2,2L42,17.4z M24.7,48.6l-1.3-2.7l-0.6,2.9l-3,0.4
|
||||
l2.6,1.5l-0.6,2.9l2.2-2l2.6,1.5l-1.3-2.7l2.2-2L24.7,48.6z M43.8,48.8l-0.6-2.9L42,48.6l-3-0.4l2.2,2L40,53l2.6-1.5l2.2,2l-0.6-2.9
|
||||
l2.6-1.5L43.8,48.8z M22.8,17.2l0.6,2.9l1.3-2.7l3,0.4l-2.2-2l1.3-2.7l-2.6,1.5l-2.2-2l0.6,2.9l-2.6,1.5L22.8,17.2z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -273,11 +273,7 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = null;
|
||||
root.canceled();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,11 +295,7 @@ ModalWindow {
|
|||
}
|
||||
root.result = JSON.stringify(result);
|
||||
root.selected(root.result);
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -815,7 +815,7 @@ ModalWindow {
|
|||
Action {
|
||||
id: cancelAction
|
||||
text: "Cancel"
|
||||
onTriggered: { canceled(); root.shown = false; }
|
||||
onTriggered: { canceled(); root.destroy(); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -168,11 +168,7 @@ ModalWindow {
|
|||
shortcut: "Esc"
|
||||
onTriggered: {
|
||||
root.canceled();
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,11 +179,7 @@ ModalWindow {
|
|||
onTriggered: {
|
||||
root.result = items ? comboBox.currentText : textResult.text
|
||||
root.selected(root.result);
|
||||
// FIXME we are leaking memory to avoid a crash
|
||||
// root.destroy();
|
||||
|
||||
root.disableFade = true
|
||||
visible = false;
|
||||
root.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Item {
|
|||
property string imageUrl: "";
|
||||
property var goFunction: null;
|
||||
property string storyId: "";
|
||||
property bool standaloneOptimized: false;
|
||||
|
||||
property bool drillDownToPlace: false;
|
||||
property bool showPlace: isConcurrency;
|
||||
|
@ -40,6 +41,7 @@ Item {
|
|||
property bool isAnnouncement: action === 'announcement';
|
||||
property bool isStacked: !isConcurrency && drillDownToPlace;
|
||||
|
||||
|
||||
property int textPadding: 10;
|
||||
property int smallMargin: 4;
|
||||
property int messageHeight: 40;
|
||||
|
@ -267,9 +269,33 @@ Item {
|
|||
hoverEnabled: false
|
||||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
goFunction("hifi://" + hifiUrl);
|
||||
goFunction("hifi://" + hifiUrl, standaloneOptimized);
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: standaloneOptomizedBadge
|
||||
|
||||
anchors {
|
||||
right: actionIcon.left
|
||||
top: actionIcon.top
|
||||
topMargin: 2
|
||||
rightMargin: 3
|
||||
}
|
||||
height: root.standaloneOptimized ? 25 : 0
|
||||
width: 25
|
||||
|
||||
visible: root.standaloneOptimized && isConcurrency
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../icons/standalone-optimized.svg"
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: standaloneOptomizedBadge
|
||||
source: standaloneOptomizedBadge
|
||||
color: hifi.colors.blueHighlight
|
||||
visible: root.standaloneOptimized && isConcurrency
|
||||
}
|
||||
|
||||
StateImage {
|
||||
id: actionIcon;
|
||||
visible: !isAnnouncement;
|
||||
|
@ -281,7 +307,8 @@ Item {
|
|||
right: parent.right;
|
||||
margins: smallMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function go() {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId));
|
||||
|
|
|
@ -82,6 +82,7 @@ Column {
|
|||
action: data.action || "",
|
||||
thumbnail_url: resolveUrl(thumbnail_url),
|
||||
image_url: resolveUrl(data.details && data.details.image_url),
|
||||
standalone_optimized: data.standalone_optimized,
|
||||
|
||||
metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity.
|
||||
|
||||
|
@ -127,6 +128,7 @@ Column {
|
|||
hifiUrl: model.place_name + model.path;
|
||||
thumbnail: model.thumbnail_url;
|
||||
imageUrl: model.image_url;
|
||||
standaloneOptimized: model.standalone_optimized;
|
||||
action: model.action;
|
||||
timestamp: model.created_at;
|
||||
onlineUsers: model.online_users;
|
||||
|
@ -187,4 +189,14 @@ Column {
|
|||
}
|
||||
}
|
||||
}
|
||||
function isStandalone(address) {
|
||||
var lowerAddress = address.toLowerCase();
|
||||
|
||||
for (var i=0; i < suggestions.count; i++) {
|
||||
if (suggestions.get(i).place_name.toLowerCase() === lowerAddress) {
|
||||
return suggestions.get(i).standalone_optimized;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,6 +224,67 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator {}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: muteMic.spacing;
|
||||
AudioControls.CheckBox {
|
||||
spacing: muteMic.spacing
|
||||
text: qsTr("Push To Talk (T)");
|
||||
checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop;
|
||||
onClicked: {
|
||||
if (isVR) {
|
||||
AudioScriptingInterface.pushToTalkHMD = checked;
|
||||
} else {
|
||||
AudioScriptingInterface.pushToTalkDesktop = checked;
|
||||
}
|
||||
checked = Qt.binding(function() {
|
||||
if (isVR) {
|
||||
return AudioScriptingInterface.pushToTalkHMD;
|
||||
} else {
|
||||
return AudioScriptingInterface.pushToTalkDesktop;
|
||||
}
|
||||
}); // restore binding
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: pttTextContainer
|
||||
x: margins.paddings;
|
||||
width: rightMostInputLevelPos
|
||||
height: pttTextMetrics.height
|
||||
visible: true
|
||||
TextMetrics {
|
||||
id: pttTextMetrics
|
||||
text: pttText.text
|
||||
font: pttText.font
|
||||
}
|
||||
RalewayRegular {
|
||||
id: pttText
|
||||
wrapMode: Text.WordWrap
|
||||
color: hifi.colors.white;
|
||||
width: parent.width;
|
||||
font.italic: true
|
||||
size: 16;
|
||||
text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") :
|
||||
qsTr("Press and hold the button \"T\" to unmute.");
|
||||
onTextChanged: {
|
||||
if (pttTextMetrics.width > rightMostInputLevelPos) {
|
||||
pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height;
|
||||
} else {
|
||||
pttTextContainer.height = pttTextMetrics.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if (pttTextMetrics.width > rightMostInputLevelPos) {
|
||||
pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height;
|
||||
} else {
|
||||
pttTextContainer.height = pttTextMetrics.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator {}
|
||||
|
|
|
@ -19,13 +19,13 @@ Rectangle {
|
|||
HifiConstants { id: hifi; }
|
||||
|
||||
readonly property var level: AudioScriptingInterface.inputLevel;
|
||||
|
||||
|
||||
property bool gated: false;
|
||||
Component.onCompleted: {
|
||||
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
|
||||
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
|
||||
}
|
||||
|
||||
|
||||
property bool standalone: false;
|
||||
property var dragTarget: null;
|
||||
|
||||
|
@ -235,12 +235,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: gatedIndicator;
|
||||
visible: gated && !AudioScriptingInterface.clipping
|
||||
|
||||
radius: 4;
|
||||
|
||||
radius: 4;
|
||||
width: 2 * radius;
|
||||
height: 2 * radius;
|
||||
color: "#0080FF";
|
||||
|
@ -249,12 +249,12 @@ Rectangle {
|
|||
verticalCenter: parent.verticalCenter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: clippingIndicator;
|
||||
visible: AudioScriptingInterface.clipping
|
||||
|
||||
radius: 4;
|
||||
|
||||
radius: 4;
|
||||
width: 2 * radius;
|
||||
height: 2 * radius;
|
||||
color: colors.red;
|
||||
|
|
|
@ -30,6 +30,8 @@ Rectangle {
|
|||
property string dateAcquired: "--";
|
||||
property string itemCost: "--";
|
||||
property string marketplace_item_id: "";
|
||||
property bool standaloneOptimized: false;
|
||||
property bool standaloneIncompatible: false;
|
||||
property string certTitleTextColor: hifi.colors.darkGray;
|
||||
property string certTextColor: hifi.colors.white;
|
||||
property string infoTextColor: hifi.colors.blueAccent;
|
||||
|
@ -71,6 +73,8 @@ Rectangle {
|
|||
} else {
|
||||
root.marketplace_item_id = result.data.marketplace_item_id;
|
||||
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
||||
root.standaloneOptimized = result.data.standalone_optimized;
|
||||
root.standaloneIncompatible = result.data.standalone_incompatible;
|
||||
|
||||
if (root.certInfoReplaceMode > 3) {
|
||||
root.itemName = result.data.marketplace_item_name;
|
||||
|
@ -421,6 +425,24 @@ Rectangle {
|
|||
anchors.rightMargin: 24;
|
||||
height: root.useGoldCert ? 220 : 372;
|
||||
|
||||
HiFiGlyphs {
|
||||
id: standaloneOptomizedBadge
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: ownedByHeader.top
|
||||
rightMargin: 15
|
||||
topMargin: 28
|
||||
}
|
||||
|
||||
visible: root.standaloneOptimized
|
||||
|
||||
text: hifi.glyphs.hmd
|
||||
size: 34
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: hifi.colors.blueHighlight
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorText;
|
||||
visible: !root.useGoldCert;
|
||||
|
@ -467,6 +489,7 @@ Rectangle {
|
|||
color: root.infoTextColor;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
|
||||
AnonymousProRegular {
|
||||
id: isMyCertText;
|
||||
visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== "";
|
||||
|
@ -485,14 +508,46 @@ Rectangle {
|
|||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: standaloneHeader;
|
||||
text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED" : "STAND-ALONE INCOMPATIBLE";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.topMargin: 15;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
visible: root.standaloneOptimized || root.standaloneIncompatible;
|
||||
height: visible ? paintedHeight : 0;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: standaloneText;
|
||||
text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices";
|
||||
// Text size
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: standaloneHeader.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: standaloneHeader.left;
|
||||
visible: root.standaloneOptimized || root.standaloneIncompatible;
|
||||
height: visible ? paintedHeight : 0;
|
||||
// Style
|
||||
color: root.infoTextColor;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: dateAcquiredHeader;
|
||||
text: "ACQUISITION DATE";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.top: standaloneText.bottom;
|
||||
anchors.topMargin: 20;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.horizontalCenter;
|
||||
anchors.rightMargin: 8;
|
||||
|
@ -521,8 +576,8 @@ Rectangle {
|
|||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.top: standaloneText.bottom;
|
||||
anchors.topMargin: 20;
|
||||
anchors.left: parent.horizontalCenter;
|
||||
anchors.right: parent.right;
|
||||
height: paintedHeight;
|
||||
|
|
|
@ -41,6 +41,7 @@ Rectangle {
|
|||
property string searchScopeString: "Featured"
|
||||
property bool isLoggedIn: false
|
||||
property bool supports3DHTML: true
|
||||
property bool pendingGetMarketplaceItemCall: false
|
||||
|
||||
anchors.fill: (typeof parent === undefined) ? undefined : parent
|
||||
|
||||
|
@ -90,6 +91,14 @@ Rectangle {
|
|||
id: -1,
|
||||
name: "Everything"
|
||||
});
|
||||
categoriesModel.append({
|
||||
id: -1,
|
||||
name: "Stand-alone Optimized"
|
||||
});
|
||||
categoriesModel.append({
|
||||
id: -1,
|
||||
name: "Stand-alone Compatible"
|
||||
});
|
||||
result.data.items.forEach(function(category) {
|
||||
categoriesModel.append({
|
||||
id: category.id,
|
||||
|
@ -100,7 +109,9 @@ Rectangle {
|
|||
getMarketplaceItems();
|
||||
}
|
||||
onGetMarketplaceItemsResult: {
|
||||
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
|
||||
if (!pendingGetMarketplaceItemCall) {
|
||||
marketBrowseModel.handlePage(result.status !== "success" && result.message, result);
|
||||
}
|
||||
}
|
||||
|
||||
onGetMarketplaceItemResult: {
|
||||
|
@ -124,9 +135,12 @@ Rectangle {
|
|||
marketplaceItem.availability = result.data.availability;
|
||||
marketplaceItem.updated_item_id = result.data.updated_item_id || "";
|
||||
marketplaceItem.created_at = result.data.created_at;
|
||||
marketplaceItem.standaloneOptimized = result.data.standalone_optimized;
|
||||
marketplaceItem.standaloneVisible = result.data.standalone_optimized || result.data.standalone_incompatible;
|
||||
marketplaceItemScrollView.contentHeight = marketplaceItemContent.height;
|
||||
itemsList.visible = false;
|
||||
marketplaceItemView.visible = true;
|
||||
pendingGetMarketplaceItemCall = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,16 +201,16 @@ Rectangle {
|
|||
visible: true
|
||||
|
||||
Image {
|
||||
id: marketplaceHeaderImage;
|
||||
source: "../common/images/marketplaceHeaderImage.png";
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 2;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 0;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 8;
|
||||
width: 140;
|
||||
fillMode: Image.PreserveAspectFit;
|
||||
id: marketplaceHeaderImage
|
||||
source: "../common/images/marketplaceHeaderImage.png"
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
width: 140
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
|
@ -542,7 +556,8 @@ Rectangle {
|
|||
price: model.cost
|
||||
availability: model.availability
|
||||
isLoggedIn: root.isLoggedIn;
|
||||
|
||||
standaloneOptimized: model.standalone_optimized
|
||||
|
||||
onShowItem: {
|
||||
MarketplaceScriptingInterface.getMarketplaceItem(item_id);
|
||||
}
|
||||
|
@ -1224,6 +1239,7 @@ Rectangle {
|
|||
console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!");
|
||||
return;
|
||||
}
|
||||
pendingGetMarketplaceItemCall = true;
|
||||
marketplaceItem.edition = message.params.edition ? message.params.edition : -1;
|
||||
MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId);
|
||||
break;
|
||||
|
|
|
@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import stylesUit 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
import "../../../controls" as HifiControls
|
||||
import "../common" as HifiCommerceCommon
|
||||
|
@ -40,9 +41,11 @@ Rectangle {
|
|||
property string license: ""
|
||||
property string posted: ""
|
||||
property string created_at: ""
|
||||
property bool isLoggedIn: false;
|
||||
property int edition: -1;
|
||||
property bool supports3DHTML: false;
|
||||
property bool isLoggedIn: false
|
||||
property int edition: -1
|
||||
property bool supports3DHTML: false
|
||||
property bool standaloneVisible: false
|
||||
property bool standaloneOptimized: false
|
||||
|
||||
onCategoriesChanged: {
|
||||
categoriesListModel.clear();
|
||||
|
@ -52,13 +55,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
onDescriptionChanged: {
|
||||
|
||||
if(root.supports3DHTML) {
|
||||
descriptionTextModel.clear();
|
||||
descriptionTextModel.append({text: description});
|
||||
} else {
|
||||
descriptionText.text = description;
|
||||
}
|
||||
descriptionText.text = description;
|
||||
}
|
||||
|
||||
onAttributionsChanged: {
|
||||
|
@ -246,11 +243,43 @@ Rectangle {
|
|||
right: parent.right;
|
||||
top: itemImage.bottom;
|
||||
}
|
||||
height: categoriesList.y - buyButton.y + categoriesList.height
|
||||
height: categoriesList.y - badges.y + categoriesList.height
|
||||
|
||||
function evalHeight() {
|
||||
height = categoriesList.y - buyButton.y + categoriesList.height;
|
||||
console.log("HEIGHT: " + height);
|
||||
height = categoriesList.y - badges.y + categoriesList.height;
|
||||
}
|
||||
|
||||
Item {
|
||||
id: badges
|
||||
|
||||
anchors {
|
||||
right: buyButton.left
|
||||
top: parent.top
|
||||
rightMargin: 10
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
Image {
|
||||
id: standaloneOptomizedBadge
|
||||
|
||||
anchors {
|
||||
topMargin: 15
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
height: root.standaloneOptimized ? 50 : 0
|
||||
width: 50
|
||||
|
||||
visible: root.standaloneOptimized
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../../../icons/standalone-optimized.svg"
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: standaloneOptomizedBadge
|
||||
source: standaloneOptomizedBadge
|
||||
color: hifi.colors.blueHighlight
|
||||
visible: root.standaloneOptimized
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
|
@ -259,10 +288,10 @@ Rectangle {
|
|||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
topMargin: 15
|
||||
}
|
||||
height: 50
|
||||
height: 50
|
||||
width: 180
|
||||
|
||||
property bool isUpdate: root.edition >= 0 // Special case of updating from a specific older item
|
||||
property bool isStocking: (creator === Account.username) && (availability === "not for sale") && !updated_item_id // Note: server will say "sold out" or "invalidated" before it says NFS
|
||||
|
@ -272,6 +301,9 @@ Rectangle {
|
|||
text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
|
||||
color: hifi.buttons.blue
|
||||
|
||||
buttonGlyphSize: 24
|
||||
fontSize: 24
|
||||
|
||||
onClicked: root.buy();
|
||||
}
|
||||
|
||||
|
@ -279,11 +311,11 @@ Rectangle {
|
|||
id: creatorItem
|
||||
|
||||
anchors {
|
||||
top: buyButton.bottom
|
||||
top: parent.top
|
||||
leftMargin: 15
|
||||
topMargin: 15
|
||||
}
|
||||
width: parent.width
|
||||
width: paintedWidth
|
||||
height: childrenRect.height
|
||||
|
||||
RalewaySemiBold {
|
||||
|
@ -532,13 +564,55 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: standaloneItem
|
||||
|
||||
anchors {
|
||||
top: licenseItem.bottom
|
||||
topMargin: 15
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: root.standaloneVisible ? childrenRect.height : 0
|
||||
|
||||
visible: root.standaloneVisible
|
||||
|
||||
RalewaySemiBold {
|
||||
id: standaloneLabel
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: paintedWidth
|
||||
height: 20
|
||||
|
||||
text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED:" : "STAND-ALONE INCOMPATIBLE:"
|
||||
size: 14
|
||||
color: hifi.colors.lightGrayText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
id: standaloneOptimizedText
|
||||
|
||||
anchors.top: standaloneLabel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 5
|
||||
width: paintedWidth
|
||||
|
||||
text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices"
|
||||
size: 14
|
||||
color: hifi.colors.lightGray
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: descriptionItem
|
||||
property string text: ""
|
||||
|
||||
anchors {
|
||||
top: licenseItem.bottom
|
||||
top: standaloneItem.bottom
|
||||
topMargin: 15
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
|
|
|
@ -35,8 +35,9 @@ Rectangle {
|
|||
property string category: ""
|
||||
property int price: 0
|
||||
property string availability: "unknown"
|
||||
property bool isLoggedIn: false;
|
||||
|
||||
property bool isLoggedIn: false
|
||||
property bool standaloneOptimized: false
|
||||
|
||||
signal buy()
|
||||
signal showItem()
|
||||
signal categoryClicked(string category)
|
||||
|
@ -226,6 +227,7 @@ Rectangle {
|
|||
top: parent.top
|
||||
left: parent.left
|
||||
leftMargin: 15
|
||||
topMargin: 10
|
||||
}
|
||||
width: paintedWidth
|
||||
|
||||
|
@ -239,16 +241,18 @@ Rectangle {
|
|||
id: creatorText
|
||||
|
||||
anchors {
|
||||
top: creatorLabel.top;
|
||||
left: creatorLabel.right;
|
||||
leftMargin: 15;
|
||||
top: creatorLabel.top
|
||||
left: creatorLabel.right
|
||||
leftMargin: 15
|
||||
right: badges.left
|
||||
}
|
||||
width: paintedWidth;
|
||||
width: paintedWidth
|
||||
|
||||
text: root.creator;
|
||||
size: 14;
|
||||
color: hifi.colors.lightGray;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
text: root.creator
|
||||
size: 14
|
||||
elide: Text.ElideRight
|
||||
color: hifi.colors.lightGray
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
|
@ -259,12 +263,12 @@ Rectangle {
|
|||
left: parent.left
|
||||
leftMargin: 15
|
||||
}
|
||||
width: paintedWidth;
|
||||
width: paintedWidth
|
||||
|
||||
text: "IN:";
|
||||
size: 14;
|
||||
color: hifi.colors.lightGrayText;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
text: "IN:"
|
||||
size: 14
|
||||
color: hifi.colors.lightGrayText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
|
@ -274,22 +278,56 @@ Rectangle {
|
|||
top: categoryLabel.top
|
||||
left: categoryLabel.right
|
||||
leftMargin: 15
|
||||
right: badges.left
|
||||
}
|
||||
width: paintedWidth
|
||||
|
||||
text: root.category
|
||||
size: 14
|
||||
color: hifi.colors.blueHighlight;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
|
||||
color: hifi.colors.blueHighlight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: root.categoryClicked(root.category);
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: badges
|
||||
|
||||
anchors {
|
||||
right: buyButton.left
|
||||
top: parent.top
|
||||
topMargin: 10
|
||||
rightMargin: 10
|
||||
}
|
||||
height: 50
|
||||
|
||||
Image {
|
||||
id: standaloneOptomizedBadge
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
height: root.standaloneOptimized ? 40 : 0
|
||||
width: 40
|
||||
|
||||
visible: root.standaloneOptimized
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../../../icons/standalone-optimized.svg"
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: standaloneOptomizedBadge
|
||||
source: standaloneOptomizedBadge
|
||||
color: hifi.colors.blueHighlight
|
||||
visible: root.standaloneOptimized
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
id: buyButton
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
|
@ -298,19 +336,21 @@ Rectangle {
|
|||
topMargin:10
|
||||
bottomMargin: 10
|
||||
}
|
||||
width: 180
|
||||
|
||||
property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS
|
||||
property bool isMine: creator === Account.username
|
||||
property bool isUpgrade: root.edition >= 0
|
||||
property int costToMe: ((isMine && isNFS) || isUpgrade) ? 0 : price
|
||||
property bool isAvailable: costToMe >= 0
|
||||
property bool isAvailable: availability === "available"
|
||||
|
||||
text: isUpgrade ? "UPGRADE FOR FREE" : (isAvailable ? (costToMe || "FREE") : availability)
|
||||
enabled: isAvailable
|
||||
buttonGlyph: isAvailable ? (costToMe ? hifi.glyphs.hfc : "") : ""
|
||||
|
||||
color: hifi.buttons.blue;
|
||||
|
||||
buttonGlyphSize: 24
|
||||
fontSize: 24
|
||||
onClicked: root.buy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import stylesUit 1.0
|
||||
|
@ -50,6 +51,8 @@ Item {
|
|||
property string upgradeTitle;
|
||||
property bool updateAvailable: root.updateItemId && root.updateItemId !== "";
|
||||
property bool valid;
|
||||
property bool standaloneOptimized;
|
||||
property bool standaloneIncompatible;
|
||||
|
||||
property string originalStatusText;
|
||||
property string originalStatusColor;
|
||||
|
@ -403,7 +406,9 @@ Item {
|
|||
id: permissionExplanationText;
|
||||
anchors.fill: parent;
|
||||
text: {
|
||||
if (root.itemType === "contentSet") {
|
||||
if (root.standaloneIncompatible) {
|
||||
"This item is incompatible with stand-alone devices. <a href='#standaloneIncompatible'>Learn more</a>";
|
||||
} else if (root.itemType === "contentSet") {
|
||||
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
|
||||
} else if (root.itemType === "entity") {
|
||||
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
|
||||
|
@ -417,7 +422,11 @@ Item {
|
|||
verticalAlignment: Text.AlignVCenter;
|
||||
|
||||
onLinkActivated: {
|
||||
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
|
||||
if (link === "#standaloneIncompatible") {
|
||||
sendToPurchases({method: 'showStandaloneIncompatibleExplanation'});
|
||||
} else {
|
||||
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -699,7 +708,8 @@ Item {
|
|||
anchors.bottomMargin: 8;
|
||||
width: 160;
|
||||
height: 40;
|
||||
enabled: root.hasPermissionToRezThis &&
|
||||
enabled: !root.standaloneIncompatible &&
|
||||
root.hasPermissionToRezThis &&
|
||||
MyAvatar.skeletonModelURL !== root.itemHref &&
|
||||
!root.wornEntityID &&
|
||||
root.valid;
|
||||
|
@ -838,6 +848,28 @@ Item {
|
|||
root.sendToPurchases({ method: 'flipCard' });
|
||||
}
|
||||
}
|
||||
}
|
||||
Image {
|
||||
id: standaloneOptomizedBadge
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
rightMargin: 15
|
||||
bottomMargin:12
|
||||
}
|
||||
height: root.standaloneOptimized ? 36 : 0
|
||||
width: 36
|
||||
|
||||
visible: root.standaloneOptimized
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../../../icons/standalone-optimized.svg"
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: standaloneOptomizedBadge
|
||||
source: standaloneOptomizedBadge
|
||||
color: hifi.colors.blueHighlight
|
||||
visible: root.standaloneOptimized
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.5
|
||||
import QtQuick 2.9
|
||||
import stylesUit 1.0
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
import "../../../controls" as HifiControls
|
||||
|
@ -33,6 +33,7 @@ Rectangle {
|
|||
property bool purchasesReceived: false;
|
||||
property bool punctuationMode: false;
|
||||
property bool isDebuggingFirstUseTutorial: false;
|
||||
property bool isStandalone: false;
|
||||
property string installedApps;
|
||||
property bool keyboardRaised: false;
|
||||
property int numUpdatesAvailable: 0;
|
||||
|
@ -44,6 +45,7 @@ Rectangle {
|
|||
purchasesModel.getFirstPage();
|
||||
Commerce.getAvailableUpdates();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
||||
|
@ -110,6 +112,10 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
isStandalone = PlatformInfo.isStandalone();
|
||||
}
|
||||
|
||||
HifiCommerceCommon.CommerceLightbox {
|
||||
id: lightboxPopup;
|
||||
z: 999;
|
||||
|
@ -527,6 +533,7 @@ Rectangle {
|
|||
ListView {
|
||||
id: purchasesContentsList;
|
||||
visible: purchasesModel.count !== 0;
|
||||
interactive: !lightboxPopup.visible;
|
||||
clip: true;
|
||||
model: purchasesModel;
|
||||
snapMode: ListView.NoSnap;
|
||||
|
@ -553,6 +560,8 @@ Rectangle {
|
|||
upgradeTitle: model.upgrade_title;
|
||||
itemType: model.item_type;
|
||||
valid: model.valid;
|
||||
standaloneOptimized: model.standalone_optimized
|
||||
standaloneIncompatible: root.isStandalone && model.standalone_incompatible
|
||||
anchors.topMargin: 10;
|
||||
anchors.bottomMargin: 10;
|
||||
|
||||
|
@ -673,6 +682,14 @@ Rectangle {
|
|||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showStandaloneIncompatibleExplanation") {
|
||||
lightboxPopup.titleText = "Stand-alone Incompatible";
|
||||
lightboxPopup.bodyText = "The item is incompatible with stand-alone devices.";
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "setFilterText") {
|
||||
filterBar.text = msg.filterText;
|
||||
} else if (msg.method === "flipCard") {
|
||||
|
|
|
@ -97,10 +97,11 @@ Rectangle {
|
|||
textFormat: Text.StyledText
|
||||
linkColor: "#00B4EF"
|
||||
color: "white"
|
||||
text: "Blockchain technology from <a href=\"https://elementsproject.org/elements/\">Elements</a>."
|
||||
property string link: "https://eos.io/"
|
||||
text: "Blockchain technology from <a href=\"" + link + "\">EOS</a>."
|
||||
size: 14
|
||||
onLinkActivated: {
|
||||
HiFiAbout.openUrl("https://elementsproject.org/elements/");
|
||||
HiFiAbout.openUrl(link);
|
||||
}
|
||||
}
|
||||
RalewayRegular {
|
||||
|
|
59
interface/resources/qml/hifi/tablet/OculusConfiguration.qml
Normal file
59
interface/resources/qml/hifi/tablet/OculusConfiguration.qml
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Created by Dante Ruiz on 3/4/19.
|
||||
// Copyright 2019 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
|
||||
//
|
||||
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import stylesUit 1.0
|
||||
import controlsUit 1.0 as HifiControls
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
property string pluginName: ""
|
||||
property var displayInformation: null
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
color: hifi.colors.baseGray
|
||||
|
||||
HifiControls.CheckBox {
|
||||
id: box
|
||||
width: 15
|
||||
height: 15
|
||||
|
||||
anchors {
|
||||
left: root.left
|
||||
leftMargin: 75
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
sendConfigurationSettings( { trackControllersInOculusHome: checked });
|
||||
}
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
id: head
|
||||
|
||||
text: "Track hand controllers in Oculus Home"
|
||||
size: 12
|
||||
|
||||
color: "white"
|
||||
anchors.left: box.right
|
||||
anchors.leftMargin: 5
|
||||
}
|
||||
|
||||
function displayConfiguration() {
|
||||
var configurationSettings = InputConfiguration.configurationSettings(root.pluginName);
|
||||
box.checked = configurationSettings.trackControllersInOculusHome;
|
||||
}
|
||||
|
||||
function sendConfigurationSettings(settings) {
|
||||
InputConfiguration.setConfigurationSettings(settings, root.pluginName);
|
||||
}
|
||||
}
|
144
interface/resources/qml/hifi/tablet/TADLightbox.qml
Normal file
144
interface/resources/qml/hifi/tablet/TADLightbox.qml
Normal file
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// TADLightbox.qml
|
||||
// qml/hifi/tablet
|
||||
//
|
||||
// TADLightbox
|
||||
//
|
||||
// Created by Roxanne Skelly on 2019-03-07
|
||||
// Copyright 2019 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
|
||||
//
|
||||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
import stylesUit 1.0
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
import "qrc:////qml//controls" as HifiControls
|
||||
|
||||
// references XXX from root context
|
||||
|
||||
Rectangle {
|
||||
property string titleText;
|
||||
property string bodyImageSource;
|
||||
property string bodyText;
|
||||
property string button1color: hifi.buttons.noneBorderlessGray;
|
||||
property string button1text;
|
||||
property var button1method;
|
||||
property string button2color: hifi.buttons.blue;
|
||||
property string button2text;
|
||||
property var button2method;
|
||||
property string buttonLayout: "leftright";
|
||||
|
||||
id: root;
|
||||
visible: false;
|
||||
anchors.fill: parent;
|
||||
color: Qt.rgba(0, 0, 0, 0.5);
|
||||
z: 999;
|
||||
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent;
|
||||
width: 376;
|
||||
height: childrenRect.height + 30;
|
||||
color: "white";
|
||||
|
||||
RalewaySemiBold {
|
||||
id: titleText;
|
||||
text: root.titleText;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 30;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 30;
|
||||
height: paintedHeight;
|
||||
color: hifi.colors.black;
|
||||
size: 24;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
wrapMode: Text.WordWrap;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: bodyText;
|
||||
text: root.bodyText;
|
||||
anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top);
|
||||
anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30);
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 30;
|
||||
height: paintedHeight;
|
||||
color: hifi.colors.black;
|
||||
size: 20;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
wrapMode: Text.WordWrap;
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttons;
|
||||
anchors.top: bodyText.bottom;
|
||||
anchors.topMargin: 30;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: root.buttonLayout === "leftright" ? 70 : 150;
|
||||
|
||||
// Button 1
|
||||
HifiControlsUit.Button {
|
||||
id: button1;
|
||||
color: root.button1color;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10;
|
||||
anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right;
|
||||
anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10;
|
||||
width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) :
|
||||
(undefined);
|
||||
height: 40;
|
||||
text: root.button1text;
|
||||
onClicked: {
|
||||
button1method();
|
||||
}
|
||||
visible: (root.button1text !== "");
|
||||
}
|
||||
|
||||
// Button 2
|
||||
HifiControlsUit.Button {
|
||||
id: button2;
|
||||
visible: root.button2text;
|
||||
color: root.button2color;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom;
|
||||
anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20;
|
||||
anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left;
|
||||
anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10;
|
||||
width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined;
|
||||
height: 40;
|
||||
text: root.button2text;
|
||||
onClicked: {
|
||||
button2method();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
//
|
||||
}
|
|
@ -73,7 +73,7 @@ StackView {
|
|||
function resetAfterTeleport() {
|
||||
//storyCardFrame.shown = root.shown = false;
|
||||
}
|
||||
function goCard(targetString) {
|
||||
function goCard(targetString, standaloneOptimized) {
|
||||
if (0 !== targetString.indexOf('hifi://')) {
|
||||
var card = tabletWebView.createObject();
|
||||
card.url = addressBarDialog.metaverseServerUrl + targetString;
|
||||
|
@ -82,7 +82,7 @@ StackView {
|
|||
return;
|
||||
}
|
||||
location.text = targetString;
|
||||
toggleOrGo(targetString, true);
|
||||
toggleOrGo(targetString, true, standaloneOptimized);
|
||||
clearAddressLineTimer.start();
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,7 @@ StackView {
|
|||
updateLocationText(text.length > 0);
|
||||
}
|
||||
onAccepted: {
|
||||
toggleOrGo();
|
||||
toggleOrGo(addressLine.text, false, places.isStandalone(addressLine.text));
|
||||
}
|
||||
|
||||
// unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style
|
||||
|
@ -392,7 +392,18 @@ StackView {
|
|||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TADLightbox {
|
||||
id: unoptimizedDomain
|
||||
titleText: "Unoptimized Domain"
|
||||
bodyText: "You're trying to access a place that hasn't been optimized for your device. Are you sure you want to continue."
|
||||
button1text: "CANCEL"
|
||||
button2text: "YES CONTINUE"
|
||||
visible: false
|
||||
button1method: function() {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocationText(enteringAddress) {
|
||||
|
@ -407,14 +418,30 @@ StackView {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleOrGo(address, fromSuggestions) {
|
||||
if (address !== undefined && address !== "") {
|
||||
addressBarDialog.loadAddress(address, fromSuggestions);
|
||||
clearAddressLineTimer.start();
|
||||
} else if (addressLine.text !== "") {
|
||||
addressBarDialog.loadAddress(addressLine.text, fromSuggestions);
|
||||
clearAddressLineTimer.start();
|
||||
function toggleOrGo(address, fromSuggestions, standaloneOptimized) {
|
||||
|
||||
var goTarget = function () {
|
||||
if (address !== undefined && address !== "") {
|
||||
addressBarDialog.loadAddress(address, fromSuggestions);
|
||||
clearAddressLineTimer.start();
|
||||
} else if (addressLine.text !== "") {
|
||||
addressBarDialog.loadAddress(addressLine.text, fromSuggestions);
|
||||
clearAddressLineTimer.start();
|
||||
}
|
||||
DialogsManager.hideAddressBar();
|
||||
}
|
||||
|
||||
unoptimizedDomain.button2method = function() {
|
||||
Settings.setValue("ShowUnoptimizedDomainWarning", false);
|
||||
goTarget();
|
||||
}
|
||||
|
||||
var showPopup = PlatformInfo.isStandalone() && !standaloneOptimized && Settings.getValue("ShowUnoptimizedDomainWarning", true);
|
||||
|
||||
if(!showPopup) {
|
||||
goTarget();
|
||||
} else {
|
||||
unoptimizedDomain.visible = true;
|
||||
}
|
||||
DialogsManager.hideAddressBar();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,6 +246,8 @@
|
|||
|
||||
#include "AboutUtil.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
|
@ -289,13 +291,6 @@ static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" };
|
|||
static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static bool DISABLE_DEFERRED = true;
|
||||
#else
|
||||
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
||||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
|
||||
#else
|
||||
|
@ -755,6 +750,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
bool isStore = cmdOptionExists(argc, const_cast<const char**>(argv), OCULUS_STORE_ARG);
|
||||
qApp->setProperty(hifi::properties::OCULUS_STORE, isStore);
|
||||
|
||||
// emulate standalone device
|
||||
static const auto STANDALONE_ARG = "--standalone";
|
||||
bool isStandalone = cmdOptionExists(argc, const_cast<const char**>(argv), STANDALONE_ARG);
|
||||
qApp->setProperty(hifi::properties::STANDALONE, isStandalone);
|
||||
|
||||
// Ignore any previous crashes if running from command line with a test script.
|
||||
bool inTestMode { false };
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
|
@ -2358,6 +2358,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
} else {
|
||||
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
|
||||
}
|
||||
auto surfaceContext = webSurface->getSurfaceContext();
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
} else {
|
||||
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
|
||||
webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) {
|
||||
|
@ -3040,6 +3042,10 @@ void Application::initializeUi() {
|
|||
};
|
||||
OffscreenQmlSurface::addWhitelistContextHandler({
|
||||
QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
|
||||
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
|
||||
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
|
||||
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
|
||||
QUrl{ "hifi/tablet/TabletAddressDialog.qml" },
|
||||
}, platformInfoCallback);
|
||||
|
||||
QmlContextCallback ttsCallback = [](QQmlContext* context) {
|
||||
|
@ -3294,6 +3300,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
|
||||
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
|
||||
if (setAdditionalContextProperties) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
|
@ -3304,7 +3311,6 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
|
|||
|
||||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
|
||||
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
|
|
|
@ -12,10 +12,52 @@
|
|||
#include "AvatarDoctor.h"
|
||||
#include <model-networking/ModelCache.h>
|
||||
#include <AvatarConstants.h>
|
||||
#include <Rig.h>
|
||||
#include <ResourceManager.h>
|
||||
#include <QDir>
|
||||
#include <FSTReader.h>
|
||||
|
||||
const int NETWORKED_JOINTS_LIMIT = 256;
|
||||
const float HIPS_GROUND_MIN_Y = 0.01f;
|
||||
const float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
|
||||
const QString LEFT_JOINT_PREFIX = "Left";
|
||||
const QString RIGHT_JOINT_PREFIX = "Right";
|
||||
|
||||
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
||||
const QStringList LEG_MAPPING_SUFFIXES = {
|
||||
"UpLeg"
|
||||
"Leg",
|
||||
"Foot",
|
||||
"ToeBase",
|
||||
};
|
||||
|
||||
static QStringList ARM_MAPPING_SUFFIXES = {
|
||||
"Shoulder",
|
||||
"Arm",
|
||||
"ForeArm",
|
||||
"Hand",
|
||||
};
|
||||
|
||||
static QStringList HAND_MAPPING_SUFFIXES = {
|
||||
"HandIndex3",
|
||||
"HandIndex2",
|
||||
"HandIndex1",
|
||||
"HandPinky3",
|
||||
"HandPinky2",
|
||||
"HandPinky1",
|
||||
"HandMiddle3",
|
||||
"HandMiddle2",
|
||||
"HandMiddle1",
|
||||
"HandRing3",
|
||||
"HandRing2",
|
||||
"HandRing1",
|
||||
"HandThumb3",
|
||||
"HandThumb2",
|
||||
"HandThumb1",
|
||||
};
|
||||
|
||||
const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
|
||||
|
||||
AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) :
|
||||
_avatarFSTFileUrl(avatarFSTFileUrl) {
|
||||
|
||||
connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) {
|
||||
|
@ -39,136 +81,215 @@ void AvatarDoctor::startDiagnosing() {
|
|||
|
||||
const auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(_avatarFSTFileUrl);
|
||||
resource->refresh();
|
||||
const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar");
|
||||
const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) {
|
||||
|
||||
const auto resourceLoaded = [this, resource](bool success) {
|
||||
// MODEL
|
||||
if (!success) {
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
|
||||
_errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL });
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
}
|
||||
_model = resource;
|
||||
const auto model = resource.data();
|
||||
const auto avatarModel = resource.data()->getHFMModel();
|
||||
if (!avatarModel.originalURL.endsWith(".fbx")) {
|
||||
_errors.push_back({ "Unsupported avatar model format", DEFAULT_URL });
|
||||
_errors.push_back({ "Unsupported avatar model format.", DEFAULT_DOCS_URL });
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
}
|
||||
|
||||
// RIG
|
||||
if (avatarModel.joints.isEmpty()) {
|
||||
_errors.push_back({ "Avatar has no rig", DEFAULT_URL });
|
||||
_errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL });
|
||||
} else {
|
||||
if (avatarModel.joints.length() > 256) {
|
||||
_errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL });
|
||||
auto jointNames = avatarModel.getJointNames();
|
||||
|
||||
if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) {
|
||||
_errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_DOCS_URL });
|
||||
}
|
||||
// Avatar does not have Hips bone mapped
|
||||
if (!avatarModel.getJointNames().contains("Hips")) {
|
||||
_errors.push_back({ "Hips are not mapped", DEFAULT_URL });
|
||||
if (!jointNames.contains("Hips")) {
|
||||
_errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Spine")) {
|
||||
_errors.push_back({ "Spine is not mapped", DEFAULT_URL });
|
||||
if (!jointNames.contains("Spine")) {
|
||||
_errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Head")) {
|
||||
_errors.push_back({ "Head is not mapped", DEFAULT_URL });
|
||||
if (!jointNames.contains("Spine1")) {
|
||||
_errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (!jointNames.contains("Neck")) {
|
||||
_errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (!jointNames.contains("Head")) {
|
||||
_errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
if (!jointNames.contains("LeftEye")) {
|
||||
if (jointNames.contains("RightEye")) {
|
||||
_errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL });
|
||||
} else {
|
||||
_errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
} else if (!jointNames.contains("RightEye")) {
|
||||
_errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) {
|
||||
for (const QString& jointSuffix : jointMappingSuffixes) {
|
||||
if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) !=
|
||||
jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto isDescendantOfJointWhenJointsExist = [avatarModel, jointNames] (const QString& jointName, const QString& ancestorName) {
|
||||
if (!jointNames.contains(jointName) || !jointNames.contains(ancestorName)) {
|
||||
return true;
|
||||
}
|
||||
auto currentJoint = avatarModel.joints[avatarModel.jointIndices[jointName] - 1];
|
||||
while (currentJoint.parentIndex != -1) {
|
||||
currentJoint = avatarModel.joints[currentJoint.parentIndex];
|
||||
if (currentJoint.name == ancestorName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) {
|
||||
_errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) {
|
||||
_errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) {
|
||||
_errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
// Multiple skeleton root joints checkup
|
||||
int skeletonRootJoints = 0;
|
||||
for (const HFMJoint& joint: avatarModel.joints) {
|
||||
if (joint.parentIndex == -1 && joint.isSkeletonJoint) {
|
||||
skeletonRootJoints++;
|
||||
}
|
||||
}
|
||||
|
||||
if (skeletonRootJoints > 1) {
|
||||
_errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
Rig rig;
|
||||
rig.reset(avatarModel);
|
||||
const float eyeHeight = rig.getUnscaledEyeHeight();
|
||||
const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT;
|
||||
const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
|
||||
// SCALE
|
||||
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
|
||||
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
|
||||
|
||||
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too short.", DEFAULT_DOCS_URL });
|
||||
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
// HipsNotOnGround
|
||||
auto hipsIndex = rig.indexOfJoint("Hips");
|
||||
if (hipsIndex >= 0) {
|
||||
glm::vec3 hipsPosition;
|
||||
if (rig.getJointPosition(hipsIndex, hipsPosition)) {
|
||||
const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips"));
|
||||
|
||||
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
|
||||
_errors.push_back({ "Hips are on ground.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HipsSpineChestNotCoincident
|
||||
auto spineIndex = rig.indexOfJoint("Spine");
|
||||
auto chestIndex = rig.indexOfJoint("Spine1");
|
||||
if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) {
|
||||
glm::vec3 hipsPosition;
|
||||
glm::vec3 spinePosition;
|
||||
glm::vec3 chestPosition;
|
||||
if (rig.getJointPosition(hipsIndex, hipsPosition) &&
|
||||
rig.getJointPosition(spineIndex, spinePosition) &&
|
||||
rig.getJointPosition(chestIndex, chestPosition)) {
|
||||
|
||||
const auto hipsToSpine = glm::length(hipsPosition - spinePosition);
|
||||
const auto spineToChest = glm::length(spinePosition - chestPosition);
|
||||
if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) {
|
||||
_errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto mapping = resource->getMapping();
|
||||
|
||||
if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
|
||||
const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash();
|
||||
QStringList jointValues;
|
||||
for (const auto& jointVariant: jointNameMappings.values()) {
|
||||
jointValues << jointVariant.toString();
|
||||
}
|
||||
|
||||
const auto& uniqueJointValues = jointValues.toSet();
|
||||
for (const auto& jointName: uniqueJointValues) {
|
||||
if (jointValues.count(jointName) > 1) {
|
||||
_errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_DOCS_URL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) {
|
||||
_errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) {
|
||||
_errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) {
|
||||
_errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL });
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE
|
||||
const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f;
|
||||
const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f;
|
||||
|
||||
const float avatarHeight = avatarModel.bindExtents.largestDimension();
|
||||
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL });
|
||||
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL });
|
||||
}
|
||||
|
||||
// TEXTURES
|
||||
QStringList externalTextures{};
|
||||
QSet<QString> textureNames{};
|
||||
auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable {
|
||||
if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) {
|
||||
externalTextures << texture.name;
|
||||
auto materialMappingHandled = [this]() mutable {
|
||||
_materialMappingLoadedCount++;
|
||||
// Continue diagnosing the textures as soon as the material mappings have tried to load.
|
||||
if (_materialMappingLoadedCount == _materialMappingCount) {
|
||||
// TEXTURES
|
||||
diagnoseTextures();
|
||||
}
|
||||
};
|
||||
|
||||
foreach(const HFMMaterial material, avatarModel.materials) {
|
||||
addTextureToList(material.normalTexture);
|
||||
addTextureToList(material.albedoTexture);
|
||||
addTextureToList(material.opacityTexture);
|
||||
addTextureToList(material.glossTexture);
|
||||
addTextureToList(material.roughnessTexture);
|
||||
addTextureToList(material.specularTexture);
|
||||
addTextureToList(material.metallicTexture);
|
||||
addTextureToList(material.emissiveTexture);
|
||||
addTextureToList(material.occlusionTexture);
|
||||
addTextureToList(material.scatteringTexture);
|
||||
addTextureToList(material.lightmapTexture);
|
||||
}
|
||||
|
||||
if (!externalTextures.empty()) {
|
||||
// Check External Textures:
|
||||
auto modelTexturesURLs = model->getTextures();
|
||||
_externalTextureCount = externalTextures.length();
|
||||
foreach(const QString textureKey, externalTextures) {
|
||||
if (!modelTexturesURLs.contains(textureKey)) {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const QUrl textureURL = modelTexturesURLs[textureKey].toUrl();
|
||||
|
||||
auto textureResource = DependencyManager::get<TextureCache>()->getTexture(textureURL);
|
||||
auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable {
|
||||
qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount;
|
||||
|
||||
if (_checkedTextureCount == _externalTextureCount) {
|
||||
if (_missingTextureCount > 0) {
|
||||
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL });
|
||||
}
|
||||
if (_unsupportedTextureCount > 0) {
|
||||
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL });
|
||||
}
|
||||
emit complete(getErrors());
|
||||
}
|
||||
};
|
||||
|
||||
auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable {
|
||||
if (!success) {
|
||||
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(textureResource->getURL());
|
||||
if (normalizedURL.isLocalFile()) {
|
||||
QFile textureFile(normalizedURL.toLocalFile());
|
||||
if (textureFile.exists()) {
|
||||
_unsupportedTextureCount++;
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
}
|
||||
_checkedTextureCount++;
|
||||
checkTextureLoadingComplete();
|
||||
};
|
||||
|
||||
if (textureResource) {
|
||||
textureResource->refresh();
|
||||
if (textureResource->isLoaded()) {
|
||||
textureLoaded(!textureResource->isFailed());
|
||||
} else {
|
||||
connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
|
||||
}
|
||||
_materialMappingCount = (int)model->getMaterialMapping().size();
|
||||
_materialMappingLoadedCount = 0;
|
||||
for (const auto& materialMapping : model->getMaterialMapping()) {
|
||||
// refresh the texture mappings
|
||||
auto materialMappingResource = materialMapping.second;
|
||||
if (materialMappingResource) {
|
||||
materialMappingResource->refresh();
|
||||
if (materialMappingResource->isLoaded()) {
|
||||
materialMappingHandled();
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
checkTextureLoadingComplete();
|
||||
connect(materialMappingResource.data(), &NetworkTexture::finished, this,
|
||||
[materialMappingHandled](bool success) mutable {
|
||||
|
||||
materialMappingHandled();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
materialMappingHandled();
|
||||
}
|
||||
} else {
|
||||
emit complete(getErrors());
|
||||
}
|
||||
if (_materialMappingCount == 0) {
|
||||
// TEXTURES
|
||||
diagnoseTextures();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -179,11 +300,117 @@ void AvatarDoctor::startDiagnosing() {
|
|||
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
|
||||
}
|
||||
} else {
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL });
|
||||
emit complete(getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarDoctor::diagnoseTextures() {
|
||||
const auto model = _model.data();
|
||||
const auto avatarModel = _model.data()->getHFMModel();
|
||||
QVector<QString> externalTextures{};
|
||||
QVector<QString> textureNames{};
|
||||
int texturesFound = 0;
|
||||
auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable {
|
||||
if (texture.filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (texture.content.isEmpty() && !externalTextures.contains(texture.name)) {
|
||||
externalTextures << texture.name;
|
||||
}
|
||||
texturesFound++;
|
||||
};
|
||||
|
||||
for (const auto& material : avatarModel.materials) {
|
||||
addTextureToList(material.normalTexture);
|
||||
addTextureToList(material.albedoTexture);
|
||||
addTextureToList(material.opacityTexture);
|
||||
addTextureToList(material.glossTexture);
|
||||
addTextureToList(material.roughnessTexture);
|
||||
addTextureToList(material.specularTexture);
|
||||
addTextureToList(material.metallicTexture);
|
||||
addTextureToList(material.emissiveTexture);
|
||||
addTextureToList(material.occlusionTexture);
|
||||
addTextureToList(material.scatteringTexture);
|
||||
addTextureToList(material.lightmapTexture);
|
||||
}
|
||||
|
||||
for (const auto& materialMapping : model->getMaterialMapping()) {
|
||||
for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) {
|
||||
texturesFound += (int)networkMaterial.second->getTextureMaps().size();
|
||||
}
|
||||
}
|
||||
|
||||
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(
|
||||
QUrl(avatarModel.originalURL)).resolved(QUrl("textures"));
|
||||
|
||||
if (texturesFound == 0) {
|
||||
_errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
if (!externalTextures.empty()) {
|
||||
// Check External Textures:
|
||||
auto modelTexturesURLs = model->getTextures();
|
||||
_externalTextureCount = externalTextures.length();
|
||||
|
||||
auto checkTextureLoadingComplete = [this]() mutable {
|
||||
if (_checkedTextureCount == _externalTextureCount) {
|
||||
if (_missingTextureCount > 0) {
|
||||
_errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL });
|
||||
}
|
||||
if (_unsupportedTextureCount > 0) {
|
||||
_errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount),
|
||||
DEFAULT_DOCS_URL });
|
||||
}
|
||||
|
||||
emit complete(getErrors());
|
||||
}
|
||||
};
|
||||
|
||||
for (const QString& textureKey : externalTextures) {
|
||||
if (!modelTexturesURLs.contains(textureKey)) {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
continue;
|
||||
}
|
||||
const QUrl textureURL = modelTexturesURLs[textureKey].toUrl();
|
||||
auto textureResource = DependencyManager::get<TextureCache>()->getTexture(textureURL);
|
||||
auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable {
|
||||
if (!success) {
|
||||
auto normalizedURL = DependencyManager::get<ResourceManager>()->normalizeURL(textureResource->getURL());
|
||||
if (normalizedURL.isLocalFile()) {
|
||||
QFile textureFile(normalizedURL.toLocalFile());
|
||||
if (textureFile.exists()) {
|
||||
_unsupportedTextureCount++;
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
}
|
||||
}
|
||||
_checkedTextureCount++;
|
||||
checkTextureLoadingComplete();
|
||||
};
|
||||
|
||||
if (textureResource) {
|
||||
textureResource->refresh();
|
||||
if (textureResource->isLoaded()) {
|
||||
textureLoaded(!textureResource->isFailed());
|
||||
} else {
|
||||
connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
|
||||
}
|
||||
} else {
|
||||
_missingTextureCount++;
|
||||
_checkedTextureCount++;
|
||||
}
|
||||
}
|
||||
checkTextureLoadingComplete();
|
||||
} else {
|
||||
emit complete(getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList AvatarDoctor::getErrors() const {
|
||||
QVariantList result;
|
||||
for (const auto& error : _errors) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include <QVariantMap>
|
||||
#include "GeometryCache.h"
|
||||
|
||||
struct AvatarDiagnosticResult {
|
||||
QString message;
|
||||
|
@ -27,7 +28,7 @@ Q_DECLARE_METATYPE(QVector<AvatarDiagnosticResult>)
|
|||
class AvatarDoctor : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarDoctor(QUrl avatarFSTFileUrl);
|
||||
AvatarDoctor(const QUrl& avatarFSTFileUrl);
|
||||
|
||||
Q_INVOKABLE void startDiagnosing();
|
||||
|
||||
|
@ -37,6 +38,8 @@ signals:
|
|||
void complete(QVariantList errors);
|
||||
|
||||
private:
|
||||
void diagnoseTextures();
|
||||
|
||||
QUrl _avatarFSTFileUrl;
|
||||
QVector<AvatarDiagnosticResult> _errors;
|
||||
|
||||
|
@ -45,6 +48,11 @@ private:
|
|||
int _missingTextureCount = 0;
|
||||
int _unsupportedTextureCount = 0;
|
||||
|
||||
int _materialMappingCount = 0;
|
||||
int _materialMappingLoadedCount = 0;
|
||||
|
||||
GeometryResource::Pointer _model;
|
||||
|
||||
bool _isDiagnosing = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -2325,23 +2325,25 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
||||
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
||||
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
|
||||
if (skeletonModelChangeCount == _skeletonModelChangeCount) {
|
||||
|
||||
if (_fullAvatarModelName.isEmpty()) {
|
||||
// Store the FST file name into preferences
|
||||
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
|
||||
if (mapping.value("name").isValid()) {
|
||||
_fullAvatarModelName = mapping.value("name").toString();
|
||||
}
|
||||
}
|
||||
if (_fullAvatarModelName.isEmpty()) {
|
||||
// Store the FST file name into preferences
|
||||
const auto& mapping = _skeletonModel->getGeometry()->getMapping();
|
||||
if (mapping.value("name").isValid()) {
|
||||
_fullAvatarModelName = mapping.value("name").toString();
|
||||
}
|
||||
}
|
||||
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
_skeletonModelLoaded = true;
|
||||
}
|
||||
QObject::disconnect(*skeletonConnection);
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
initFlowFromFST();
|
||||
|
||||
_skeletonModelLoaded = true;
|
||||
}
|
||||
QObject::disconnect(*skeletonConnection);
|
||||
});
|
||||
|
||||
saveAvatarUrl();
|
||||
|
@ -5385,6 +5387,15 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::initFlowFromFST() {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
auto &flowData = _skeletonModel->getHFMModel().flowData;
|
||||
if (flowData.shouldInitFlow()) {
|
||||
useFlow(true, flowData.shouldInitCollisions(), flowData._physicsConfig, flowData._collisionsConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
|
|
|
@ -1751,6 +1751,7 @@ private:
|
|||
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
|
||||
void initHeadBones();
|
||||
void initAnimGraph();
|
||||
void initFlowFromFST();
|
||||
|
||||
// Avatar Preferences
|
||||
QUrl _fullAvatarURLFromPreferences;
|
||||
|
|
|
@ -89,6 +89,8 @@ glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const gl
|
|||
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
|
||||
}
|
||||
|
||||
|
||||
|
||||
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
|
@ -102,6 +104,19 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3
|
|||
return pos2D;
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
|
||||
glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint;
|
||||
|
||||
glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.z));
|
||||
if (unNormalized) {
|
||||
pos2D *= glm::vec2(dimensions.x, dimensions.z);
|
||||
}
|
||||
return pos2D;
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
EntityPropertyFlags desiredProperties;
|
||||
desiredProperties += PROP_POSITION;
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
|
||||
|
||||
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
|
||||
static glm::vec2 projectOntoXZPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNoemalized);
|
||||
|
||||
private:
|
||||
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);
|
||||
|
|
|
@ -155,15 +155,33 @@ PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
|
|||
|
||||
const auto entityRotation = entity->getWorldOrientation();
|
||||
const auto entityPosition = entity->getWorldPosition();
|
||||
const auto entityType = entity->getType();
|
||||
glm::vec3 normal;
|
||||
|
||||
glm::vec3 normal = entityRotation * Vectors::UNIT_Z;
|
||||
// TODO: Use the xz projection method for Sphere and Quad.
|
||||
if (entityType == EntityTypes::Gizmo) {
|
||||
normal = entityRotation * Vectors::UNIT_Y;
|
||||
} else {
|
||||
normal = entityRotation * Vectors::UNIT_Z;
|
||||
}
|
||||
float distance = glm::dot(pick.position - entityPosition, normal);
|
||||
if (distance < nearestTarget.distance) {
|
||||
const auto entityDimensions = entity->getScaledDimensions();
|
||||
const auto entityRegistrationPoint = entity->getRegistrationPoint();
|
||||
glm::vec3 intersection = pick.position - (normal * distance);
|
||||
glm::vec2 pos2D = RayPick::projectOntoXYPlane(intersection, entityPosition, entityRotation,
|
||||
entityDimensions, entityRegistrationPoint, false);
|
||||
glm::vec2 pos2D;
|
||||
|
||||
|
||||
auto entityType = entity->getType();
|
||||
|
||||
if (entityType == EntityTypes::Gizmo) {
|
||||
pos2D = RayPick::projectOntoXZPlane(intersection, entityPosition, entityRotation,
|
||||
entityDimensions, entityRegistrationPoint, false);
|
||||
} else {
|
||||
pos2D = RayPick::projectOntoXYPlane(intersection, entityPosition, entityRotation,
|
||||
entityDimensions, entityRegistrationPoint, false);
|
||||
}
|
||||
|
||||
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
|
||||
IntersectionType type = IntersectionType::ENTITY;
|
||||
if (getFilter().doesPickLocalEntities()) {
|
||||
|
|
|
@ -231,6 +231,46 @@ void Audio::loadData() {
|
|||
_pttHMD = _pttHMDSetting.get();
|
||||
}
|
||||
|
||||
bool Audio::getPTTHMD() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _pttHMD;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::saveData() {
|
||||
_desktopMutedSetting.set(getMutedDesktop());
|
||||
_hmdMutedSetting.set(getMutedHMD());
|
||||
_pttDesktopSetting.set(getPTTDesktop());
|
||||
_pttHMDSetting.set(getPTTHMD());
|
||||
}
|
||||
|
||||
void Audio::loadData() {
|
||||
_desktopMuted = _desktopMutedSetting.get();
|
||||
_hmdMuted = _hmdMutedSetting.get();
|
||||
_pttDesktop = _pttDesktopSetting.get();
|
||||
_pttHMD = _pttHMDSetting.get();
|
||||
}
|
||||
|
||||
bool Audio::getPTTHMD() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _pttHMD;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::saveData() {
|
||||
_desktopMutedSetting.set(getMutedDesktop());
|
||||
_hmdMutedSetting.set(getMutedHMD());
|
||||
_pttDesktopSetting.set(getPTTDesktop());
|
||||
_pttHMDSetting.set(getPTTHMD());
|
||||
}
|
||||
|
||||
void Audio::loadData() {
|
||||
_desktopMuted = _desktopMutedSetting.get();
|
||||
_hmdMuted = _hmdMutedSetting.get();
|
||||
_pttDesktop = _pttDesktopSetting.get();
|
||||
_pttHMD = _pttHMDSetting.get();
|
||||
}
|
||||
|
||||
bool Audio::noiseReductionEnabled() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _enableNoiseReduction;
|
||||
|
|
|
@ -41,16 +41,16 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
|||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {boolean} muted - <code>true</code> if the audio input is muted, otherwise <code>false</code>.
|
||||
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
|
||||
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
|
||||
* @property {boolean} noiseReduction - <code>true</code> if noise reduction is enabled, otherwise <code>false</code>. When
|
||||
* enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
|
||||
* above the noise floor.
|
||||
* @property {number} inputLevel - The loudness of the audio input, range <code>0.0</code> (no sound) –
|
||||
* @property {number} inputLevel - The loudness of the audio input, range <code>0.0</code> (no sound) –
|
||||
* <code>1.0</code> (the onset of clipping). <em>Read-only.</em>
|
||||
* @property {boolean} clipping - <code>true</code> if the audio input is clipping, otherwise <code>false</code>.
|
||||
* @property {number} inputVolume - Adjusts the volume of the input audio; range <code>0.0</code> – <code>1.0</code>.
|
||||
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
|
||||
* @property {number} inputVolume - Adjusts the volume of the input audio; range <code>0.0</code> – <code>1.0</code>.
|
||||
* If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
|
||||
* devices, and others might only support values of <code>0.0</code> and <code>1.0</code>.
|
||||
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
|
||||
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
|
||||
* <code>false</code>. Some devices do not support stereo, in which case the value is always <code>false</code>.
|
||||
* @property {string} context - The current context of the audio: either <code>"Desktop"</code> or <code>"HMD"</code>.
|
||||
* <em>Read-only.</em>
|
||||
|
@ -115,7 +115,7 @@ public:
|
|||
/**jsdoc
|
||||
* @function Audio.setInputDevice
|
||||
* @param {object} device
|
||||
* @param {boolean} isHMD
|
||||
* @param {boolean} isHMD
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
|
@ -129,8 +129,8 @@ public:
|
|||
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
|
||||
|
||||
/**jsdoc
|
||||
* Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
|
||||
* come from either the domain's audio zone if used — configured on the server — or as scripted by
|
||||
* Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
|
||||
* come from either the domain's audio zone if used — configured on the server — or as scripted by
|
||||
* {@link Audio.setReverbOptions|setReverbOptions}.
|
||||
* @function Audio.setReverb
|
||||
* @param {boolean} enable - <code>true</code> to enable reverberation, <code>false</code> to disable.
|
||||
|
@ -140,13 +140,13 @@ public:
|
|||
* var injectorOptions = {
|
||||
* position: MyAvatar.position
|
||||
* };
|
||||
*
|
||||
*
|
||||
* Script.setTimeout(function () {
|
||||
* print("Reverb OFF");
|
||||
* Audio.setReverb(false);
|
||||
* injector = Audio.playSound(sound, injectorOptions);
|
||||
* }, 1000);
|
||||
*
|
||||
*
|
||||
* Script.setTimeout(function () {
|
||||
* var reverbOptions = new AudioEffectOptions();
|
||||
* reverbOptions.roomSize = 100;
|
||||
|
@ -154,26 +154,26 @@ public:
|
|||
* print("Reverb ON");
|
||||
* Audio.setReverb(true);
|
||||
* }, 4000);
|
||||
*
|
||||
*
|
||||
* Script.setTimeout(function () {
|
||||
* print("Reverb OFF");
|
||||
* Audio.setReverb(false);
|
||||
* }, 8000); */
|
||||
Q_INVOKABLE void setReverb(bool enable);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation.
|
||||
* @function Audio.setReverbOptions
|
||||
* @param {AudioEffectOptions} options - The reverberation options.
|
||||
*/
|
||||
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
|
||||
* @function Audio.startRecording
|
||||
* @param {string} filename - The path and name of the file to make the recording in. Should have a <code>.wav</code>
|
||||
* @param {string} filename - The path and name of the file to make the recording in. Should have a <code>.wav</code>
|
||||
* extension. The file is overwritten if it already exists.
|
||||
* @returns {boolean} <code>true</code> if the specified file could be opened and audio recording has started, otherwise
|
||||
* @returns {boolean} <code>true</code> if the specified file could be opened and audio recording has started, otherwise
|
||||
* <code>false</code>.
|
||||
* @example <caption>Make a 10 second audio recording.</caption>
|
||||
* var filename = File.getTempDir() + "/audio.wav";
|
||||
|
@ -182,13 +182,13 @@ public:
|
|||
* Audio.stopRecording();
|
||||
* print("Audio recording made in: " + filename);
|
||||
* }, 10000);
|
||||
*
|
||||
*
|
||||
* } else {
|
||||
* print("Could not make an audio recording in: " + filename);
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE bool startRecording(const QString& filename);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Finish making an audio recording started with {@link Audio.startRecording|startRecording}.
|
||||
* @function Audio.stopRecording
|
||||
|
@ -222,7 +222,47 @@ signals:
|
|||
* });
|
||||
*/
|
||||
void mutedChanged(bool isMuted);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when desktop audio input is muted or unmuted.
|
||||
* @function Audio.desktopMutedChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for desktop mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void desktopMutedChanged(bool isMuted);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when HMD audio input is muted or unmuted.
|
||||
* @function Audio.hmdMutedChanged
|
||||
* @param {boolean} isMuted - <code>true</code> if the audio input is muted for HMD mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void hmdMutedChanged(bool isMuted);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled.
|
||||
* @function Audio.pushToTalkChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushToTalkChanged(bool enabled);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled for desktop mode.
|
||||
* @function Audio.pushToTalkDesktopChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is emabled for Desktop mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushToTalkDesktopChanged(bool enabled);
|
||||
|
||||
/**
|
||||
* Triggered when Push-to-Talk has been enabled or disabled for HMD mode.
|
||||
* @function Audio.pushToTalkHMDChanged
|
||||
* @param {boolean} enabled - <code>true</code> if Push-to-Talk is emabled for HMD mode, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pushToTalkHMDChanged(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when desktop audio input is muted or unmuted.
|
||||
* @function Audio.desktopMutedChanged
|
||||
|
@ -274,9 +314,9 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when the input audio volume changes.
|
||||
* @function Audio.inputVolumeChanged
|
||||
* @param {number} volume - The requested volume to be applied to the audio input, range <code>0.0</code> –
|
||||
* <code>1.0</code>. The resulting value of <code>Audio.inputVolume</code> depends on the capabilities of the device:
|
||||
* for example, the volume can't be changed on some devices, and others might only support values of <code>0.0</code>
|
||||
* @param {number} volume - The requested volume to be applied to the audio input, range <code>0.0</code> –
|
||||
* <code>1.0</code>. The resulting value of <code>Audio.inputVolume</code> depends on the capabilities of the device:
|
||||
* for example, the volume can't be changed on some devices, and others might only support values of <code>0.0</code>
|
||||
* and <code>1.0</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
@ -285,7 +325,7 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when the input audio level changes.
|
||||
* @function Audio.inputLevelChanged
|
||||
* @param {number} level - The loudness of the input audio, range <code>0.0</code> (no sound) – <code>1.0</code> (the
|
||||
* @param {number} level - The loudness of the input audio, range <code>0.0</code> (no sound) – <code>1.0</code> (the
|
||||
* onset of clipping).
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
#include "PlatformInfoScriptingInterface.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <thread>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -138,6 +138,14 @@ bool PlatformInfoScriptingInterface::has3DHTML() {
|
|||
#if defined(Q_OS_ANDROID)
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
return !qApp->property(hifi::properties::STANDALONE).toBool();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PlatformInfoScriptingInterface::isStandalone() {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return false;
|
||||
#else
|
||||
return qApp->property(hifi::properties::STANDALONE).toBool();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -68,9 +68,15 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* Returns true if device supports 3d HTML
|
||||
* @function Window.hasRift
|
||||
* @function Window.has3DHTML
|
||||
* @returns {boolean} <code>true</code> if device supports 3d HTML, otherwise <code>false</code>.*/
|
||||
bool has3DHTML();
|
||||
|
||||
/**jsdoc
|
||||
* Returns true if device is standalone
|
||||
* @function Window.hasRift
|
||||
* @returns {boolean} <code>true</code> if device is a standalone device, otherwise <code>false</code>.*/
|
||||
bool isStandalone();
|
||||
};
|
||||
|
||||
#endif // hifi_PlatformInfoScriptingInterface_h
|
||||
|
|
|
@ -65,7 +65,7 @@ static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f};
|
|||
static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f};
|
||||
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f};
|
||||
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
|
||||
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.3f, 0.0f, -0.7f};
|
||||
static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f};
|
||||
|
||||
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3";
|
||||
|
@ -373,6 +373,12 @@ void Keyboard::raiseKeyboardAnchor(bool raise) const {
|
|||
void Keyboard::scaleKeyboard(float sensorToWorldScale) {
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
{
|
||||
EntityItemProperties properties;
|
||||
properties.setDimensions(_anchor.originalDimensions * sensorToWorldScale);
|
||||
entityScriptingInterface->editEntity(_anchor.entityID, properties);
|
||||
}
|
||||
|
||||
{
|
||||
EntityItemProperties properties;
|
||||
properties.setLocalPosition(_backPlate.localPosition * sensorToWorldScale);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <OffscreenUi.h>
|
||||
#include <Preferences.h>
|
||||
#include <RenderShadowTask.h>
|
||||
#include <plugins/PluginUtils.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "AnimPose.h"
|
||||
#include <GLMHelpers.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include "AnimUtil.h"
|
||||
|
||||
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
||||
|
@ -19,16 +18,29 @@ const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
|
|||
glm::vec3(0.0f));
|
||||
|
||||
AnimPose::AnimPose(const glm::mat4& mat) {
|
||||
static const float EPSILON = 0.0001f;
|
||||
_scale = extractScale(mat);
|
||||
// quat_cast doesn't work so well with scaled matrices, so cancel it out.
|
||||
glm::mat4 tmp = glm::scale(mat, 1.0f / _scale);
|
||||
glm::mat3 m(mat);
|
||||
_scale = glm::vec3(glm::length(m[0]), glm::length(m[1]), glm::length(m[2]));
|
||||
float det = glm::determinant(m);
|
||||
|
||||
glm::mat3 tmp;
|
||||
if (det < 0.0f) {
|
||||
_scale *= -1.0f;
|
||||
}
|
||||
|
||||
// quat_cast doesn't work so well with scaled matrices, so cancel out scale.
|
||||
// also, as a side effect, multiply mirrored matrices by -1 to get the right rotation out.
|
||||
tmp[0] = m[0] * (1.0f / _scale[0]);
|
||||
tmp[1] = m[1] * (1.0f / _scale[1]);
|
||||
tmp[2] = m[2] * (1.0f / _scale[2]);
|
||||
_rot = glm::quat_cast(tmp);
|
||||
|
||||
// normalize quat if necessary
|
||||
float lengthSquared = glm::length2(_rot);
|
||||
if (glm::abs(lengthSquared - 1.0f) > EPSILON) {
|
||||
float oneOverLength = 1.0f / sqrtf(lengthSquared);
|
||||
_rot = glm::quat(_rot.w * oneOverLength, _rot.x * oneOverLength, _rot.y * oneOverLength, _rot.z * oneOverLength);
|
||||
}
|
||||
|
||||
_trans = extractTranslation(mat);
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
glm::quat relMidRot = glm::angleAxis(midAngle, _midHingeAxis);
|
||||
|
||||
// insert new relative pose into the chain and rebuild it.
|
||||
ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(relMidRot, underPoses[_midJointIndex].trans()));
|
||||
ikChain.setRelativePoseAtJointIndex(_midJointIndex, AnimPose(underPoses[_midJointIndex].scale(), relMidRot, underPoses[_midJointIndex].trans()));
|
||||
ikChain.buildDirtyAbsolutePoses();
|
||||
|
||||
// recompute tip pose after mid joint has been rotated
|
||||
|
@ -180,7 +180,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
|
||||
// transform result back into parent relative frame.
|
||||
glm::quat relBaseRot = glm::inverse(baseParentPose.rot()) * absRot;
|
||||
ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(relBaseRot, underPoses[_baseJointIndex].trans()));
|
||||
ikChain.setRelativePoseAtJointIndex(_baseJointIndex, AnimPose(underPoses[_baseJointIndex].scale(), relBaseRot, underPoses[_baseJointIndex].trans()));
|
||||
}
|
||||
|
||||
// recompute midJoint pose after base has been rotated.
|
||||
|
@ -189,7 +189,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
|
||||
// transform target rotation in to parent relative frame.
|
||||
glm::quat relTipRot = glm::inverse(midJointPose.rot()) * targetPose.rot();
|
||||
ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(relTipRot, underPoses[_tipJointIndex].trans()));
|
||||
ikChain.setRelativePoseAtJointIndex(_tipJointIndex, AnimPose(underPoses[_tipJointIndex].scale(), relTipRot, underPoses[_tipJointIndex].trans()));
|
||||
|
||||
// blend with the underChain
|
||||
ikChain.blend(underChain, alpha);
|
||||
|
|
|
@ -2179,3 +2179,52 @@ void Rig::initFlow(bool isActive) {
|
|||
_networkFlow.cleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
float Rig::getUnscaledEyeHeight() const {
|
||||
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
|
||||
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
|
||||
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
|
||||
|
||||
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
|
||||
// Typically it will be the unit conversion from cm to m.
|
||||
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
|
||||
|
||||
int headTopJoint = indexOfJoint("HeadTop_End");
|
||||
int headJoint = indexOfJoint("Head");
|
||||
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
|
||||
int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase");
|
||||
|
||||
// Makes assumption that the y = 0 plane in geometry is the ground plane.
|
||||
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
|
||||
const float GROUND_Y = 0.0f;
|
||||
|
||||
// Values from the skeleton are in the geometry coordinate frame.
|
||||
auto skeleton = getAnimSkeleton();
|
||||
if (eyeJoint >= 0 && toeJoint >= 0) {
|
||||
// Measure from eyes to toes.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (eyeJoint >= 0) {
|
||||
// Measure Eye joint to y = 0 plane.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
||||
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * (height - height * ratio);
|
||||
} else if (headTopJoint >= 0) {
|
||||
// Measure from HeadTop_End joint to the ground, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (headHeight - headHeight * ratio);
|
||||
} else if (headJoint >= 0) {
|
||||
// Measure Head joint to the ground, then add in distance from neck to eye.
|
||||
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
|
||||
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (neckHeight + neckHeight * ratio);
|
||||
} else {
|
||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "SimpleMovingAverage.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "Flow.h"
|
||||
#include "AvatarConstants.h"
|
||||
|
||||
class Rig;
|
||||
class AnimInverseKinematics;
|
||||
|
@ -237,6 +238,8 @@ public:
|
|||
void initFlow(bool isActive);
|
||||
Flow& getFlow() { return _internalFlow; }
|
||||
|
||||
float getUnscaledEyeHeight() const;
|
||||
|
||||
|
||||
signals:
|
||||
void onLoadComplete();
|
||||
|
|
|
@ -2040,54 +2040,7 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
|
|||
// TODO: if performance becomes a concern we can cache this value rather then computing it everytime.
|
||||
|
||||
if (_skeletonModel) {
|
||||
auto& rig = _skeletonModel->getRig();
|
||||
|
||||
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
|
||||
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans());
|
||||
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose();
|
||||
|
||||
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
|
||||
// Typically it will be the unit conversion from cm to m.
|
||||
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
|
||||
|
||||
int headTopJoint = rig.indexOfJoint("HeadTop_End");
|
||||
int headJoint = rig.indexOfJoint("Head");
|
||||
int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye");
|
||||
int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase");
|
||||
|
||||
// Makes assumption that the y = 0 plane in geometry is the ground plane.
|
||||
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
|
||||
const float GROUND_Y = 0.0f;
|
||||
|
||||
// Values from the skeleton are in the geometry coordinate frame.
|
||||
auto skeleton = rig.getAnimSkeleton();
|
||||
if (eyeJoint >= 0 && toeJoint >= 0) {
|
||||
// Measure from eyes to toes.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (eyeJoint >= 0) {
|
||||
// Measure Eye joint to y = 0 plane.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
||||
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * (height - height * ratio);
|
||||
} else if (headTopJoint >= 0) {
|
||||
// Measure from HeadTop_End joint to the ground, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (headHeight - headHeight * ratio);
|
||||
} else if (headJoint >= 0) {
|
||||
// Measure Head joint to the ground, then add in distance from neck to eye.
|
||||
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
|
||||
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (neckHeight + neckHeight * ratio);
|
||||
} else {
|
||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
return _skeletonModel->getRig().getUnscaledEyeHeight();
|
||||
} else {
|
||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include <PerfStat.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
#include "paintStroke_Shared.slh"
|
||||
|
||||
using namespace render;
|
||||
|
@ -29,13 +31,6 @@ gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr;
|
|||
|
||||
static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png");
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static bool DISABLE_DEFERRED = true;
|
||||
#else
|
||||
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
||||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {
|
||||
_texture = DependencyManager::get<TextureCache>()->getTexture(DEFAULT_POLYLINE_TEXTURE);
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "RenderPipelines.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
//#define SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#ifdef SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#include <FadeEffect.h>
|
||||
|
@ -30,13 +32,6 @@ using namespace render::entities;
|
|||
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_ENTITY_SCALE = 0.5f;
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static bool DISABLE_DEFERRED = true;
|
||||
#else
|
||||
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
||||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
static_assert(shader::render_utils::program::simple != 0, "Validate simple program exists");
|
||||
static_assert(shader::render_utils::program::simple_transparent != 0, "Validate simple transparent program exists");
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
||||
|
@ -160,6 +162,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
glm::vec4 backgroundColor;
|
||||
Transform modelTransform;
|
||||
glm::vec3 dimensions;
|
||||
bool forwardRendered;
|
||||
withReadLock([&] {
|
||||
modelTransform = _renderTransform;
|
||||
dimensions = _dimensions;
|
||||
|
@ -169,6 +172,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created);
|
||||
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
|
||||
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
|
||||
forwardRendered = _renderLayer != RenderLayer::WORLD || DISABLE_DEFERRED;
|
||||
});
|
||||
|
||||
// Render background
|
||||
|
@ -188,7 +192,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (backgroundColor.a > 0.0f) {
|
||||
batch.setModelTransform(transformToTopLeft);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false);
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forwardRendered);
|
||||
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
|
||||
}
|
||||
|
||||
|
@ -199,7 +203,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
batch.setModelTransform(transformToTopLeft);
|
||||
|
||||
glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin));
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale);
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forwardRendered);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_EDITION_NUMBER;
|
||||
requestedProperties += PROP_ENTITY_INSTANCE_NUMBER;
|
||||
requestedProperties += PROP_CERTIFICATE_ID;
|
||||
requestedProperties += PROP_CERTIFICATE_TYPE;
|
||||
requestedProperties += PROP_STATIC_CERTIFICATE_VERSION;
|
||||
|
||||
return requestedProperties;
|
||||
|
@ -337,6 +338,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, getCertificateType());
|
||||
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion());
|
||||
|
||||
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
|
||||
|
@ -942,6 +944,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber);
|
||||
READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber);
|
||||
READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID);
|
||||
READ_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, QString, setCertificateType);
|
||||
READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
|
||||
|
||||
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
|
||||
|
@ -1381,6 +1384,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateType, getCertificateType);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion);
|
||||
|
||||
// Script local data
|
||||
|
@ -1529,6 +1533,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateType, setCertificateType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion);
|
||||
|
||||
if (updateQueryAACube()) {
|
||||
|
@ -3150,6 +3155,7 @@ DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID)
|
|||
DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber)
|
||||
DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber)
|
||||
DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID)
|
||||
DEFINE_PROPERTY_ACCESSOR(QString, CertificateType, certificateType)
|
||||
DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVersion)
|
||||
|
||||
uint32_t EntityItem::getDirtyFlags() const {
|
||||
|
|
|
@ -369,6 +369,8 @@ public:
|
|||
void setEntityInstanceNumber(const quint32&);
|
||||
QString getCertificateID() const;
|
||||
void setCertificateID(const QString& value);
|
||||
QString getCertificateType() const;
|
||||
void setCertificateType(const QString& value);
|
||||
quint32 getStaticCertificateVersion() const;
|
||||
void setStaticCertificateVersion(const quint32&);
|
||||
|
||||
|
@ -653,6 +655,7 @@ protected:
|
|||
QString _itemLicense { ENTITY_ITEM_DEFAULT_ITEM_LICENSE };
|
||||
quint32 _limitedRun { ENTITY_ITEM_DEFAULT_LIMITED_RUN };
|
||||
QString _certificateID { ENTITY_ITEM_DEFAULT_CERTIFICATE_ID };
|
||||
QString _certificateType { ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE };
|
||||
quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER };
|
||||
quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER };
|
||||
QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID };
|
||||
|
|
|
@ -545,6 +545,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber);
|
||||
CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_TYPE, certificateType);
|
||||
CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion);
|
||||
|
||||
// Location data for scripts
|
||||
|
@ -1644,6 +1645,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_TYPE, certificateType);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion);
|
||||
|
||||
// Local props for scripts
|
||||
|
@ -2054,6 +2056,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateType, QString, setCertificateType);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion);
|
||||
|
||||
// Script location data
|
||||
|
@ -2335,6 +2338,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
|||
COPY_PROPERTY_IF_CHANGED(editionNumber);
|
||||
COPY_PROPERTY_IF_CHANGED(entityInstanceNumber);
|
||||
COPY_PROPERTY_IF_CHANGED(certificateID);
|
||||
COPY_PROPERTY_IF_CHANGED(certificateType);
|
||||
COPY_PROPERTY_IF_CHANGED(staticCertificateVersion);
|
||||
|
||||
// Local props for scripts
|
||||
|
@ -2649,6 +2653,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
|
|||
ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32);
|
||||
|
||||
// Local script props
|
||||
|
@ -3094,6 +3099,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_TYPE, properties.getCertificateType());
|
||||
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion());
|
||||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
|
@ -3573,6 +3579,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_TYPE, QString, setCertificateType);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
|
||||
|
||||
if (properties.getType() == EntityTypes::ParticleEffect) {
|
||||
|
@ -3982,6 +3989,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_editionNumberChanged = true;
|
||||
_entityInstanceNumberChanged = true;
|
||||
_certificateIDChanged = true;
|
||||
_certificateTypeChanged = true;
|
||||
_staticCertificateVersionChanged = true;
|
||||
|
||||
// Common
|
||||
|
@ -4443,6 +4451,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (certificateIDChanged()) {
|
||||
out += "certificateID";
|
||||
}
|
||||
if (certificateTypeChanged()) {
|
||||
out += "certificateType";
|
||||
}
|
||||
if (staticCertificateVersionChanged()) {
|
||||
out += "staticCertificateVersion";
|
||||
}
|
||||
|
@ -4879,6 +4890,9 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const {
|
|||
if (!getAnimation().getURL().isEmpty()) {
|
||||
json["animationURL"] = getAnimation().getURL();
|
||||
}
|
||||
if (staticCertificateVersion >= 3) {
|
||||
ADD_STRING_PROPERTY(certificateType, CertificateType);
|
||||
}
|
||||
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
||||
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
||||
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
||||
|
|
|
@ -226,6 +226,7 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER);
|
||||
DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER);
|
||||
DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID);
|
||||
DEFINE_PROPERTY_REF(PROP_CERTIFICATE_TYPE, CertificateType, certificateType, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE);
|
||||
DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION);
|
||||
|
||||
// these are used when bouncing location data into and out of scripts
|
||||
|
@ -630,6 +631,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateType, certificateType, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalPosition, localPosition, "");
|
||||
|
|
|
@ -41,6 +41,7 @@ const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
|
|||
const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0;
|
||||
const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0;
|
||||
const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString("");
|
||||
const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_TYPE = QString("");
|
||||
const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0;
|
||||
|
||||
const glm::u8vec3 ENTITY_ITEM_DEFAULT_COLOR = { 255, 255, 255 };
|
||||
|
|
|
@ -98,6 +98,7 @@ enum EntityPropertyList {
|
|||
PROP_EDITION_NUMBER,
|
||||
PROP_ENTITY_INSTANCE_NUMBER,
|
||||
PROP_CERTIFICATE_ID,
|
||||
PROP_CERTIFICATE_TYPE,
|
||||
PROP_STATIC_CERTIFICATE_VERSION,
|
||||
|
||||
// Used to convert values to and from scripts
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
|
||||
const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
|
||||
static const QString DOMAIN_UNLIMITED = "domainUnlimited";
|
||||
|
||||
EntityTree::EntityTree(bool shouldReaverage) :
|
||||
Octree(shouldReaverage)
|
||||
|
@ -300,7 +301,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
|
|||
|
||||
// Delete an already-existing entity from the tree if it has the same
|
||||
// CertificateID as the entity we're trying to add.
|
||||
if (!existingEntityItemID.isNull()) {
|
||||
if (!existingEntityItemID.isNull() && !entity->getCertificateType().contains(DOMAIN_UNLIMITED)) {
|
||||
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
|
||||
<< existingEntityItemID << ". Deleting existing entity.";
|
||||
deleteEntity(existingEntityItemID, true);
|
||||
|
@ -1870,7 +1871,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
failedAdd = true;
|
||||
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add a certified entity with ID:" << entityItemID;
|
||||
} else if (isClone && isCertified) {
|
||||
} else if (isClone && isCertified && !properties.getCertificateType().contains(DOMAIN_UNLIMITED)) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone;
|
||||
} else if (isClone && !isCloneable) {
|
||||
|
|
|
@ -85,7 +85,7 @@ public:
|
|||
void setUnscaledDimensions(const glm::vec3& value) override;
|
||||
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
|
||||
bool supportsDetailedIntersection() const override;
|
||||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
|
|
|
@ -257,7 +257,7 @@ template <typename T> struct GpuVec3ToGlm : GpuToGlmAdapter { static T get(con
|
|||
case gpu::FLOAT: view.edit<glm::fvec3>(index) = value; return true;
|
||||
case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit<glm::uint32>(index) = glm::packUnorm4x8(glm::fvec4(value,0.0f)); return true;
|
||||
case gpu::UINT8: view.edit<glm::u8vec3>(index) = value; return true;
|
||||
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm::packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true;
|
||||
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm_packSnorm3x10_1x2(glm::fvec4(value,0.0f)); return true;
|
||||
default: break;
|
||||
} error("GpuVec3ToGlm::set", view, index, hint); return false;
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ template <typename T> struct GpuVec4ToGlm : GpuToGlmAdapter { static T get(const
|
|||
case gpu::FLOAT: view.edit<glm::fvec4>(index) = value; return true;
|
||||
case gpu::HALF: CHECK_SIZE(glm::uint64); view.edit<glm::uint64_t>(index) = glm::packHalf4x16(value); return true;
|
||||
case gpu::UINT8: view.edit<glm::u8vec4>(index) = value; return true;
|
||||
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm::packSnorm3x10_1x2(value); return true;
|
||||
case gpu::NINT2_10_10_10: view.edit<glm::uint32>(index) = glm_packSnorm3x10_1x2(value); return true;
|
||||
case gpu::NUINT16: CHECK_SIZE(glm::uint64); view.edit<glm::uint64>(index) = glm::packUnorm4x16(value); return true;
|
||||
case gpu::NUINT8: CHECK_SIZE(glm::uint32); view.edit<glm::uint32>(index) = glm::packUnorm4x8(value); return true;
|
||||
default: break;
|
||||
|
|
|
@ -46,30 +46,6 @@ namespace buffer_helpers {
|
|||
gpu::BufferView clone(const gpu::BufferView& input);
|
||||
gpu::BufferView resized(const gpu::BufferView& input, glm::uint32 numElements);
|
||||
|
||||
inline void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) {
|
||||
auto absNormal = glm::abs(normal);
|
||||
auto absTangent = glm::abs(tangent);
|
||||
normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z));
|
||||
tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z));
|
||||
normal = glm::clamp(normal, -1.0f, 1.0f);
|
||||
tangent = glm::clamp(tangent, -1.0f, 1.0f);
|
||||
normal *= 511.0f;
|
||||
tangent *= 511.0f;
|
||||
|
||||
glm::detail::i10i10i10i2 normalStruct;
|
||||
glm::detail::i10i10i10i2 tangentStruct;
|
||||
normalStruct.data.x = fastLrintf(normal.x);
|
||||
normalStruct.data.y = fastLrintf(normal.y);
|
||||
normalStruct.data.z = fastLrintf(normal.z);
|
||||
normalStruct.data.w = 0;
|
||||
tangentStruct.data.x = fastLrintf(tangent.x);
|
||||
tangentStruct.data.y = fastLrintf(tangent.y);
|
||||
tangentStruct.data.z = fastLrintf(tangent.z);
|
||||
tangentStruct.data.w = 0;
|
||||
packedNormal = normalStruct.pack;
|
||||
packedTangent = tangentStruct.pack;
|
||||
}
|
||||
|
||||
namespace mesh {
|
||||
glm::uint32 forEachVertex(const graphics::MeshPointer& mesh, std::function<bool(glm::uint32 index, const QVariantMap& attributes)> func);
|
||||
bool setVertexAttributes(const graphics::MeshPointer& mesh, glm::uint32 index, const QVariantMap& attributes);
|
||||
|
|
|
@ -273,6 +273,15 @@ public:
|
|||
{}
|
||||
};
|
||||
|
||||
class FlowData {
|
||||
public:
|
||||
FlowData() {};
|
||||
QVariantMap _physicsConfig;
|
||||
QVariantMap _collisionsConfig;
|
||||
bool shouldInitFlow() const { return _physicsConfig.size() > 0; }
|
||||
bool shouldInitCollisions() const { return _collisionsConfig.size() > 0; }
|
||||
};
|
||||
|
||||
/// The runtime model format.
|
||||
class Model {
|
||||
public:
|
||||
|
@ -319,6 +328,7 @@ public:
|
|||
QList<QString> blendshapeChannelNames;
|
||||
|
||||
QMap<int, glm::quat> jointRotationOffsets;
|
||||
FlowData flowData;
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -343,6 +353,7 @@ typedef hfm::Mesh HFMMesh;
|
|||
typedef hfm::AnimationFrame HFMAnimationFrame;
|
||||
typedef hfm::Light HFMLight;
|
||||
typedef hfm::Model HFMModel;
|
||||
typedef hfm::FlowData FlowData;
|
||||
|
||||
Q_DECLARE_METATYPE(HFMAnimationFrame)
|
||||
Q_DECLARE_METATYPE(QVector<HFMAnimationFrame>)
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <StatTracker.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
#include "TGAReader.h"
|
||||
|
||||
#include "ImageLogging.h"
|
||||
|
||||
using namespace gpu;
|
||||
|
@ -203,6 +205,16 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) {
|
|||
// Help the QImage loader by extracting the image file format from the url filename ext.
|
||||
// Some tga are not created properly without it.
|
||||
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
|
||||
content.open(QIODevice::ReadOnly);
|
||||
|
||||
if (filenameExtension == "tga") {
|
||||
QImage image = image::readTGA(content);
|
||||
if (!image.isNull()) {
|
||||
return image;
|
||||
}
|
||||
content.reset();
|
||||
}
|
||||
|
||||
QImageReader imageReader(&content, filenameExtension.c_str());
|
||||
|
||||
if (imageReader.canRead()) {
|
||||
|
|
202
libraries/image/src/image/TGAReader.cpp
Normal file
202
libraries/image/src/image/TGAReader.cpp
Normal file
|
@ -0,0 +1,202 @@
|
|||
//
|
||||
// TGAReader.cpp
|
||||
// image/src/image
|
||||
//
|
||||
// Created by Ryan Huffman
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "TGAReader.h"
|
||||
|
||||
#include "ImageLogging.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QDebug>
|
||||
|
||||
QImage image::readTGA(QIODevice& content) {
|
||||
enum class TGAImageType : uint8_t {
|
||||
NoImageData = 0,
|
||||
UncompressedColorMapped = 1,
|
||||
UncompressedTrueColor = 2,
|
||||
UncompressedBlackWhite = 3,
|
||||
RunLengthEncodedColorMapped = 9,
|
||||
RunLengthEncodedTrueColor = 10,
|
||||
RunLengthEncodedBlackWhite = 11,
|
||||
};
|
||||
|
||||
struct TGAHeader {
|
||||
uint8_t idLength;
|
||||
uint8_t colorMapType;
|
||||
TGAImageType imageType;
|
||||
struct {
|
||||
uint64_t firstEntryIndex : 16;
|
||||
uint64_t length : 16;
|
||||
uint64_t entrySize : 8;
|
||||
} colorMap;
|
||||
uint16_t xOrigin;
|
||||
uint16_t yOrigin;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t pixelDepth;
|
||||
struct {
|
||||
uint8_t attributeBitsPerPixel : 4;
|
||||
uint8_t reserved : 1;
|
||||
uint8_t orientation : 1;
|
||||
uint8_t padding : 2;
|
||||
} imageDescriptor;
|
||||
};
|
||||
|
||||
constexpr bool WANT_DEBUG { false };
|
||||
constexpr qint64 TGA_HEADER_SIZE_BYTES { 18 };
|
||||
|
||||
// BottomLeft: 0, TopLeft: 1
|
||||
constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 };
|
||||
|
||||
TGAHeader header;
|
||||
|
||||
if (content.isSequential()) {
|
||||
qWarning(imagelogging) << "TGA - Sequential devices are not supported for reading";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) {
|
||||
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
content.read((char*)&header.idLength, 1);
|
||||
content.read((char*)&header.colorMapType, 1);
|
||||
content.read((char*)&header.imageType, 1);
|
||||
content.read((char*)&header.colorMap, 5);
|
||||
content.read((char*)&header.xOrigin, 2);
|
||||
content.read((char*)&header.yOrigin, 2);
|
||||
content.read((char*)&header.width, 2);
|
||||
content.read((char*)&header.height, 2);
|
||||
content.read((char*)&header.pixelDepth, 1);
|
||||
content.read((char*)&header.imageDescriptor, 1);
|
||||
|
||||
if (WANT_DEBUG) {
|
||||
qDebug(imagelogging) << "Id Length: " << (int)header.idLength;
|
||||
qDebug(imagelogging) << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize;
|
||||
qDebug(imagelogging) << "Color map type: " << (int)header.colorMapType;
|
||||
qDebug(imagelogging) << "Image type: " << (int)header.imageType;
|
||||
qDebug(imagelogging) << "Origin: " << header.xOrigin << header.yOrigin;
|
||||
qDebug(imagelogging) << "Size: " << header.width << header.height;
|
||||
qDebug(imagelogging) << "Depth: " << header.pixelDepth;
|
||||
qDebug(imagelogging) << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation;
|
||||
}
|
||||
|
||||
if (header.xOrigin != 0 || header.yOrigin != 0) {
|
||||
qWarning(imagelogging) << "TGA - origin not supporter";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) {
|
||||
qWarning(imagelogging) << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) {
|
||||
qWarning(imagelogging) << "TGA - Only 0 or 8 bits for the alpha channel is supported";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
char alphaMask = header.imageDescriptor.attributeBitsPerPixel == 8 ? 0x00 : 0xFF;
|
||||
int bytesPerPixel = header.pixelDepth / 8;
|
||||
|
||||
content.skip(header.idLength);
|
||||
if (header.imageType == TGAImageType::UncompressedTrueColor) {
|
||||
qint64 minimumSize = header.width * header.height * bytesPerPixel;
|
||||
if (content.bytesAvailable() < minimumSize) {
|
||||
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
QImage image{ header.width, header.height, QImage::Format_ARGB32 };
|
||||
char* line;
|
||||
for (int y = 0; y < header.height; ++y) {
|
||||
if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) {
|
||||
line = (char*)image.scanLine(header.height - y - 1);
|
||||
} else {
|
||||
line = (char*)image.scanLine(y);
|
||||
}
|
||||
for (int x = 0; x < header.width; ++x) {
|
||||
content.read(line, bytesPerPixel);
|
||||
*(line + 3) |= alphaMask;
|
||||
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
return image;
|
||||
} else if (header.imageType == TGAImageType::RunLengthEncodedTrueColor) {
|
||||
QImage image{ header.width, header.height, QImage::Format_ARGB32 };
|
||||
|
||||
for (int y = 0; y < header.height; ++y) {
|
||||
char* line;
|
||||
if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) {
|
||||
line = (char*)image.scanLine(header.height - y - 1);
|
||||
} else {
|
||||
line = (char*)image.scanLine(y);
|
||||
}
|
||||
int col = 0;
|
||||
while (col < header.width) {
|
||||
constexpr char IS_REPETITION_MASK{ (char)0x80 };
|
||||
constexpr char LENGTH_MASK{ (char)0x7f };
|
||||
char repetition;
|
||||
if (content.read(&repetition, 1) != 1) {
|
||||
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
|
||||
return QImage();
|
||||
}
|
||||
bool isRepetition = repetition & IS_REPETITION_MASK;
|
||||
|
||||
// The length in `repetition` is always 1 less than the number of following pixels,
|
||||
// so we need to increment it by 1. Because of this, the length is never 0.
|
||||
int length = (repetition & LENGTH_MASK) + 1;
|
||||
|
||||
if (isRepetition) {
|
||||
// Read into temporary buffer
|
||||
char color[4];
|
||||
if (content.read(color, bytesPerPixel) != bytesPerPixel) {
|
||||
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
|
||||
return QImage();
|
||||
}
|
||||
color[3] |= alphaMask;
|
||||
|
||||
// Copy `length` number of times
|
||||
col += length;
|
||||
while (length-- > 0) {
|
||||
*line = color[0];
|
||||
*(line + 1) = color[1];
|
||||
*(line + 2) = color[2];
|
||||
*(line + 3) = color[3];
|
||||
|
||||
line += 4;
|
||||
}
|
||||
} else {
|
||||
qint64 minimumSize = length * bytesPerPixel;
|
||||
if (content.bytesAvailable() < minimumSize) {
|
||||
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
|
||||
return QImage();
|
||||
}
|
||||
|
||||
// Read in `length` number of pixels
|
||||
col += length;
|
||||
while (length-- > 0) {
|
||||
content.read(line, bytesPerPixel);
|
||||
*(line + 3) |= alphaMask;
|
||||
|
||||
line += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
} else {
|
||||
qWarning(imagelogging) << "TGA - Unsupported image type: " << (int)header.imageType;
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
24
libraries/image/src/image/TGAReader.h
Normal file
24
libraries/image/src/image/TGAReader.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// TGAReader.h
|
||||
// image/src/image
|
||||
//
|
||||
// Created by Ryan Huffman
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_image_TGAReader_h
|
||||
#define hifi_image_TGAReader_h
|
||||
|
||||
#include <QImage>
|
||||
|
||||
namespace image {
|
||||
|
||||
// TODO Move this into a plugin that QImageReader can use
|
||||
QImage readTGA(QIODevice& contents);
|
||||
|
||||
}
|
||||
|
||||
#endif // hifi_image_TGAReader_h
|
|
@ -20,6 +20,7 @@
|
|||
#include "CalculateBlendshapeNormalsTask.h"
|
||||
#include "CalculateBlendshapeTangentsTask.h"
|
||||
#include "PrepareJointsTask.h"
|
||||
#include "ParseFlowDataTask.h"
|
||||
|
||||
namespace baker {
|
||||
|
||||
|
@ -101,7 +102,7 @@ namespace baker {
|
|||
|
||||
class BuildModelTask {
|
||||
public:
|
||||
using Input = VaryingSet5<hfm::Model::Pointer, std::vector<hfm::Mesh>, std::vector<hfm::Joint>, QMap<int, glm::quat>, QHash<QString, int>>;
|
||||
using Input = VaryingSet6<hfm::Model::Pointer, std::vector<hfm::Mesh>, std::vector<hfm::Joint>, QMap<int, glm::quat>, QHash<QString, int>, FlowData>;
|
||||
using Output = hfm::Model::Pointer;
|
||||
using JobModel = Job::ModelIO<BuildModelTask, Input, Output>;
|
||||
|
||||
|
@ -111,6 +112,7 @@ namespace baker {
|
|||
hfmModelOut->joints = QVector<hfm::Joint>::fromStdVector(input.get2());
|
||||
hfmModelOut->jointRotationOffsets = input.get3();
|
||||
hfmModelOut->jointIndices = input.get4();
|
||||
hfmModelOut->flowData = input.get5();
|
||||
output = hfmModelOut;
|
||||
}
|
||||
};
|
||||
|
@ -157,12 +159,15 @@ namespace baker {
|
|||
// Parse material mapping
|
||||
const auto materialMapping = model.addJob<ParseMaterialMappingTask>("ParseMaterialMapping", mapping);
|
||||
|
||||
// Parse flow data
|
||||
const auto flowData = model.addJob<ParseFlowDataTask>("ParseFlowData", mapping);
|
||||
|
||||
// Combine the outputs into a new hfm::Model
|
||||
const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying();
|
||||
const auto blendshapesPerMeshOut = model.addJob<BuildBlendshapesTask>("BuildBlendshapes", buildBlendshapesInputs);
|
||||
const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying();
|
||||
const auto meshesOut = model.addJob<BuildMeshesTask>("BuildMeshes", buildMeshesInputs);
|
||||
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices).asVarying();
|
||||
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData).asVarying();
|
||||
const auto hfmModelOut = model.addJob<BuildModelTask>("BuildModel", buildModelInputs);
|
||||
|
||||
output = Output(hfmModelOut, materialMapping);
|
||||
|
|
|
@ -125,8 +125,8 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics
|
|||
#if HFM_PACK_NORMALS
|
||||
const auto normal = normalizeDirForPacking(*normalIt);
|
||||
const auto tangent = normalizeDirForPacking(*tangentIt);
|
||||
const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f));
|
||||
const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f));
|
||||
const auto packedNormal = glm_packSnorm3x10_1x2(glm::vec4(normal, 0.0f));
|
||||
const auto packedTangent = glm_packSnorm3x10_1x2(glm::vec4(tangent, 0.0f));
|
||||
#else
|
||||
const auto packedNormal = *normalIt;
|
||||
const auto packedTangent = *tangentIt;
|
||||
|
|
34
libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp
Normal file
34
libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Created by Luis Cuenca on 5/3/2019
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ParseFlowDataTask.h"
|
||||
|
||||
void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mappingPair, Output& output) {
|
||||
FlowData flowData;
|
||||
static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData";
|
||||
static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData";
|
||||
auto mapping = mappingPair.second;
|
||||
for (auto mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) {
|
||||
if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) {
|
||||
QByteArray data = mappingIter.value().toByteArray();
|
||||
QJsonObject dataObject = QJsonDocument::fromJson(data).object();
|
||||
if (!dataObject.isEmpty() && dataObject.keys().size() == 1) {
|
||||
QString key = dataObject.keys()[0];
|
||||
if (dataObject[key].isObject()) {
|
||||
QVariantMap dataMap = dataObject[key].toObject().toVariantMap();
|
||||
if (mappingIter.key() == FLOW_PHYSICS_FIELD) {
|
||||
flowData._physicsConfig.insert(key, dataMap);
|
||||
} else {
|
||||
flowData._collisionsConfig.insert(key, dataMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output = flowData;
|
||||
}
|
26
libraries/model-baker/src/model-baker/ParseFlowDataTask.h
Normal file
26
libraries/model-baker/src/model-baker/ParseFlowDataTask.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Created by Luis Cuenca on 5/3/2019
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ParseFlowDataTask_h
|
||||
#define hifi_ParseFlowDataTask_h
|
||||
|
||||
#include <hfm/HFM.h>
|
||||
#include "Engine.h"
|
||||
|
||||
#include "BakerTypes.h"
|
||||
|
||||
class ParseFlowDataTask {
|
||||
public:
|
||||
using Input = baker::GeometryMappingPair;
|
||||
using Output = FlowData;
|
||||
using JobModel = baker::Job::ModelIO<ParseFlowDataTask, Input, Output>;
|
||||
|
||||
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
|
||||
};
|
||||
|
||||
#endif // hifi_ParseFlowDataTask_h
|
|
@ -29,8 +29,14 @@ QMap<QString, QString> getJointNameMapping(const QVariantHash& mapping) {
|
|||
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
||||
QMap<QString, glm::quat> jointRotationOffsets;
|
||||
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
|
||||
if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) {
|
||||
auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash();
|
||||
static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2";
|
||||
if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].type() == QVariant::Hash))) {
|
||||
QHash<QString, QVariant> offsets;
|
||||
if (mapping.contains(JOINT_ROTATION_OFFSET_FIELD)) {
|
||||
offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash();
|
||||
} else {
|
||||
offsets = mapping[JOINT_ROTATION_OFFSET2_FIELD].toHash();
|
||||
}
|
||||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||
QString jointName = itr.key();
|
||||
QString line = itr.value().toString();
|
||||
|
@ -57,6 +63,15 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
|||
auto& jointRotationOffsets = output.edit1();
|
||||
auto& jointIndices = output.edit2();
|
||||
|
||||
bool newJointRot = false;
|
||||
static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2";
|
||||
QVariantHash fstHashMap = mapping.second;
|
||||
if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) {
|
||||
newJointRot = true;
|
||||
} else {
|
||||
newJointRot = false;
|
||||
}
|
||||
|
||||
// Get joint renames
|
||||
auto jointNameMapping = getJointNameMapping(mapping.second);
|
||||
// Apply joint metadata from FST file mappings
|
||||
|
@ -64,11 +79,12 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
|||
jointsOut.push_back(jointIn);
|
||||
auto& jointOut = jointsOut.back();
|
||||
|
||||
auto jointNameMapKey = jointNameMapping.key(jointIn.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
jointOut.name = jointNameMapKey;
|
||||
if (!newJointRot) {
|
||||
auto jointNameMapKey = jointNameMapping.key(jointIn.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
jointOut.name = jointNameMapKey;
|
||||
}
|
||||
}
|
||||
|
||||
jointIndices.insert(jointOut.name, (int)jointsOut.size());
|
||||
}
|
||||
|
||||
|
@ -77,10 +93,33 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu
|
|||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||
QString jointName = itr.key();
|
||||
int jointIndex = jointIndices.value(jointName) - 1;
|
||||
if (jointIndex != -1) {
|
||||
if (jointIndex >= 0) {
|
||||
glm::quat rotationOffset = itr.value();
|
||||
jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||
qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (newJointRot) {
|
||||
for (const auto& jointIn : jointsIn) {
|
||||
|
||||
auto jointNameMapKey = jointNameMapping.key(jointIn.name);
|
||||
int mappedIndex = jointIndices.value(jointIn.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
|
||||
// delete and replace with hifi name
|
||||
jointIndices.remove(jointIn.name);
|
||||
jointIndices.insert(jointNameMapKey, mappedIndex);
|
||||
} else {
|
||||
|
||||
// nothing mapped to this fbx joint name
|
||||
if (jointNameMapping.contains(jointIn.name)) {
|
||||
// but the name is in the list of hifi names is mapped to a different joint
|
||||
int extraIndex = jointIndices.value(jointIn.name);
|
||||
jointIndices.remove(jointIn.name);
|
||||
jointIndices.insert("", extraIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -265,6 +265,7 @@ enum class EntityVersion : PacketVersion {
|
|||
WebBillboardMode,
|
||||
ModelScale,
|
||||
ReOrderParentIDProperties,
|
||||
CertificateTypeProperty,
|
||||
|
||||
// Add new versions above here
|
||||
NUM_PACKET_TYPE,
|
||||
|
|
|
@ -35,10 +35,10 @@
|
|||
#include "StencilMaskPass.h"
|
||||
#include "FadeEffect.h"
|
||||
|
||||
|
||||
|
||||
#include "DeferredLightingEffect.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
namespace gr {
|
||||
using graphics::slot::texture::Texture;
|
||||
using graphics::slot::buffer::Buffer;
|
||||
|
@ -49,13 +49,6 @@ namespace ru {
|
|||
using render_utils::slot::buffer::Buffer;
|
||||
}
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static bool DISABLE_DEFERRED = true;
|
||||
#else
|
||||
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
||||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
//#define WANT_DEBUG
|
||||
|
||||
// @note: Originally size entity::NUM_SHAPES
|
||||
|
|
|
@ -1656,9 +1656,9 @@ void packBlendshapeOffsetTo_Pos_F32_3xSN10_Nor_3xSN10_Tan_3xSN10(glm::uvec4& pac
|
|||
|
||||
packed = glm::uvec4(
|
||||
glm::floatBitsToUint(len),
|
||||
glm::packSnorm3x10_1x2(glm::vec4(normalizedPos, 0.0f)),
|
||||
glm::packSnorm3x10_1x2(glm::vec4(unpacked.normalOffset, 0.0f)),
|
||||
glm::packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffset, 0.0f))
|
||||
glm_packSnorm3x10_1x2(glm::vec4(normalizedPos, 0.0f)),
|
||||
glm_packSnorm3x10_1x2(glm::vec4(unpacked.normalOffset, 0.0f)),
|
||||
glm_packSnorm3x10_1x2(glm::vec4(unpacked.tangentOffset, 0.0f))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const {
|
|||
}
|
||||
|
||||
void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
|
||||
const glm::vec2& bounds) {
|
||||
const glm::vec2& bounds, bool forwardRendered) {
|
||||
// The font does all the OpenGL work
|
||||
if (_font) {
|
||||
_color = color;
|
||||
_font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds);
|
||||
_font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, forwardRendered);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
float getFontSize() const; // Pixel size
|
||||
|
||||
void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f),
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f));
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f), bool forwardRendered = false);
|
||||
|
||||
private:
|
||||
TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES;
|
||||
|
||||
void main() {
|
||||
_texCoord01.xy = inTexCoord0.xy;
|
||||
|
@ -26,7 +27,7 @@ void main() {
|
|||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
|
||||
<$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$>
|
||||
const vec3 normal = vec3(0, 0, 1);
|
||||
<$transformModelToWorldDir(cam, obj, normal, _normalWS)$>
|
||||
}
|
|
@ -10,7 +10,14 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
<@include DeferredBufferWrite.slh@>
|
||||
<@include DefaultMaterials.slh@>
|
||||
|
||||
<@include ForwardGlobalLight.slh@>
|
||||
<$declareEvalGlobalLightingAlphaBlended()$>
|
||||
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardCameraTransform()$>
|
||||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
LAYOUT(binding=0) uniform sampler2D Font;
|
||||
|
@ -24,12 +31,14 @@ LAYOUT(binding=0) uniform textParamsBuffer {
|
|||
TextParams params;
|
||||
};
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
|
||||
layout(location=0) out vec4 _fragColor0;
|
||||
|
||||
#define TAA_TEXTURE_LOD_BIAS -3.0
|
||||
|
||||
const float interiorCutoff = 0.8;
|
||||
|
@ -57,9 +66,24 @@ void main() {
|
|||
a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord);
|
||||
a *= 0.25;
|
||||
|
||||
packDeferredFragmentTranslucent(
|
||||
float alpha = a * params.color.a;
|
||||
if (alpha <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
vec3 fragPosition = _positionES.xyz;
|
||||
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
a * params.color.a,
|
||||
params.color.rgb,
|
||||
DEFAULT_ROUGHNESS);
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_EMISSIVE,
|
||||
DEFAULT_ROUGHNESS, alpha),
|
||||
alpha);
|
||||
}
|
|
@ -343,7 +343,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm
|
|||
}
|
||||
|
||||
void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color,
|
||||
EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds) {
|
||||
EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool forwardRendered) {
|
||||
if (str == "") {
|
||||
return;
|
||||
}
|
||||
|
@ -370,7 +370,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString
|
|||
}
|
||||
// need the gamma corrected color here
|
||||
|
||||
batch.setPipeline((color.a < 1.0f) ? _transparentPipeline : _pipeline);
|
||||
batch.setPipeline(forwardRendered || (color.a < 1.0f) ? _transparentPipeline : _pipeline);
|
||||
batch.setInputFormat(_format);
|
||||
batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride);
|
||||
batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture);
|
||||
|
|
|
@ -46,7 +46,7 @@ public:
|
|||
// Render string to batch
|
||||
void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str,
|
||||
const glm::vec4& color, EffectType effectType,
|
||||
const glm::vec2& origin, const glm::vec2& bound);
|
||||
const glm::vec2& origin, const glm::vec2& bound, bool forwardRendered);
|
||||
|
||||
static Pointer load(const QString& family);
|
||||
|
||||
|
|
24
libraries/shared/src/DisableDeferred.h
Normal file
24
libraries/shared/src/DisableDeferred.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 3/7/19.
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DisableDeferred_h
|
||||
#define hifi_DisableDeferred_h
|
||||
|
||||
#include <QString>
|
||||
#include <QProcess>
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static bool DISABLE_DEFERRED = true;
|
||||
#else
|
||||
static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" };
|
||||
static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
|
||||
#endif
|
||||
|
||||
|
||||
#endif // hifi_DisableDeferred_h
|
||||
|
|
@ -315,6 +315,42 @@ inline void glm_mat4u_mul(const glm::mat4& m1, const glm::mat4& m2, glm::mat4& r
|
|||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Fast replacement of glm::packSnorm3x10_1x2()
|
||||
// The SSE2 version quantizes using round to nearest even.
|
||||
// The glm version quantizes using round away from zero.
|
||||
//
|
||||
inline uint32_t glm_packSnorm3x10_1x2(vec4 const& v) {
|
||||
|
||||
union i10i10i10i2 {
|
||||
struct {
|
||||
int x : 10;
|
||||
int y : 10;
|
||||
int z : 10;
|
||||
int w : 2;
|
||||
} data;
|
||||
uint32_t pack;
|
||||
} Result;
|
||||
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
__m128 vclamp = _mm_min_ps(_mm_max_ps(_mm_loadu_ps((float*)&v[0]), _mm_set1_ps(-1.0f)), _mm_set1_ps(1.0f));
|
||||
__m128i vpack = _mm_cvtps_epi32(_mm_mul_ps(vclamp, _mm_setr_ps(511.f, 511.f, 511.f, 1.f)));
|
||||
|
||||
Result.data.x = _mm_cvtsi128_si32(vpack);
|
||||
Result.data.y = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(1,1,1,1)));
|
||||
Result.data.z = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(2,2,2,2)));
|
||||
Result.data.w = _mm_cvtsi128_si32(_mm_shuffle_epi32(vpack, _MM_SHUFFLE(3,3,3,3)));
|
||||
#else
|
||||
ivec4 const Pack(round(clamp(v, -1.0f, 1.0f) * vec4(511.f, 511.f, 511.f, 1.f)));
|
||||
|
||||
Result.data.x = Pack.x;
|
||||
Result.data.y = Pack.y;
|
||||
Result.data.z = Pack.z;
|
||||
Result.data.w = Pack.w;
|
||||
#endif
|
||||
return Result.pack;
|
||||
}
|
||||
|
||||
// convert float to int, using round-to-nearest-even (undefined on overflow)
|
||||
inline int fastLrintf(float x) {
|
||||
#if GLM_ARCH & GLM_ARCH_SSE2_BIT
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace hifi { namespace properties {
|
|||
const char* STEAM = "com.highfidelity.launchedFromSteam";
|
||||
const char* LOGGER = "com.highfidelity.logger";
|
||||
const char* OCULUS_STORE = "com.highfidelity.oculusStore";
|
||||
const char* STANDALONE = "com.highfidelity.standalone";
|
||||
const char* TEST = "com.highfidelity.test";
|
||||
const char* TRACING = "com.highfidelity.tracing";
|
||||
const char* HMD = "com.highfidelity.hmd";
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace hifi { namespace properties {
|
|||
extern const char* STEAM;
|
||||
extern const char* LOGGER;
|
||||
extern const char* OCULUS_STORE;
|
||||
extern const char* STANDALONE;
|
||||
extern const char* TEST;
|
||||
extern const char* TRACING;
|
||||
extern const char* HMD;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
using namespace hifi;
|
||||
|
||||
const char* OculusControllerManager::NAME = "Oculus";
|
||||
const QString OCULUS_LAYOUT = "OculusConfiguration.qml";
|
||||
|
||||
const quint64 LOST_TRACKING_DELAY = 3000000;
|
||||
|
||||
|
@ -43,6 +44,22 @@ bool OculusControllerManager::activate() {
|
|||
return true;
|
||||
}
|
||||
|
||||
QString OculusControllerManager::configurationLayout() {
|
||||
return OCULUS_LAYOUT;
|
||||
}
|
||||
|
||||
void OculusControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) {
|
||||
if (configurationSettings.contains("trackControllersInOculusHome")) {
|
||||
_touch->_trackControllersInOculusHome.set(configurationSettings["trackControllersInOculusHome"].toBool());
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject OculusControllerManager::configurationSettings() {
|
||||
QJsonObject configurationSettings;
|
||||
configurationSettings["trackControllersInOculusHome"] = _touch->_trackControllersInOculusHome.get();
|
||||
return configurationSettings;
|
||||
}
|
||||
|
||||
void OculusControllerManager::checkForConnectedDevices() {
|
||||
if (_touch && _remote) {
|
||||
return;
|
||||
|
@ -215,13 +232,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime,
|
|||
quint64 currentTime = usecTimestampNow();
|
||||
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked;
|
||||
bool hasInputFocus = ovr::hasInputFocus();
|
||||
bool trackControllersInOculusHome = _trackControllersInOculusHome.get();
|
||||
auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false);
|
||||
ovr::for_each_hand([&](ovrHandType hand) {
|
||||
++numTrackedControllers;
|
||||
int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND);
|
||||
|
||||
// Disable hand tracking while in Oculus Dash (Dash renders it's own hands)
|
||||
if (!hasInputFocus) {
|
||||
if (!hasInputFocus && !trackControllersInOculusHome) {
|
||||
_poseStateMap.erase(controller);
|
||||
_poseStateMap[controller].valid = false;
|
||||
return;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include <controllers/InputDevice.h>
|
||||
#include <plugins/InputPlugin.h>
|
||||
|
||||
|
@ -28,7 +29,11 @@ public:
|
|||
const QString getName() const override { return NAME; }
|
||||
bool isHandController() const override { return _touch != nullptr; }
|
||||
bool isHeadController() const override { return true; }
|
||||
bool configurable() override { return true; }
|
||||
QString configurationLayout() override;
|
||||
QStringList getSubdeviceNames() override;
|
||||
void setConfigurationSettings(const QJsonObject configurationSetting) override;
|
||||
QJsonObject configurationSettings() override;
|
||||
|
||||
bool activate() override;
|
||||
void deactivate() override;
|
||||
|
@ -93,6 +98,7 @@ private:
|
|||
float _leftHapticStrength { 0.0f };
|
||||
float _rightHapticDuration { 0.0f };
|
||||
float _rightHapticStrength { 0.0f };
|
||||
Setting::Handle<bool> _trackControllersInOculusHome { "trackControllersInOculusHome", false };
|
||||
mutable std::recursive_mutex _lock;
|
||||
std::map<int, bool> _lostTracking;
|
||||
std::map<int, quint64> _regainTrackingDeadline;
|
||||
|
|
|
@ -134,7 +134,7 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
|
||||
var scaleModuleName = this.hand === RIGHT_HAND ? "RightScaleEntity" : "LeftScaleEntity";
|
||||
var scaleModule = getEnabledModuleByName(scaleModuleName);
|
||||
if (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active) {
|
||||
if (scaleModule && (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active)) {
|
||||
// we're rescaling -- don't start a grab.
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
};
|
||||
|
||||
this.shouldStopTalking = function (controllerData) {
|
||||
var gripVal = controllerData.secondaryValues[this.hand];
|
||||
var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND];
|
||||
return (gripVal) ? false : true;
|
||||
};
|
||||
|
|
|
@ -247,8 +247,8 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
|
||||
this.run = function(controllerData, deltaTime) {
|
||||
this.addObjectToIgnoreList(controllerData);
|
||||
var type = this.getInteractableType(controllerData, isTriggerPressed, false);
|
||||
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE;
|
||||
var type = this.getInteractableType(controllerData, isTriggerPressed, false);
|
||||
var laserOn = isTriggerPressed || this.parameters.handLaser.alwaysOn;
|
||||
this.addObjectToIgnoreList(controllerData);
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ var CONTOLLER_SCRIPTS = [
|
|||
"controllerModules/teleport.js",
|
||||
"controllerModules/hudOverlayPointer.js",
|
||||
"controllerModules/mouseHMD.js",
|
||||
"controllerModules/scaleEntity.js",
|
||||
"controllerModules/nearGrabHyperLinkEntity.js",
|
||||
"controllerModules/nearTabletHighlight.js",
|
||||
"controllerModules/nearGrabEntity.js",
|
||||
|
|
|
@ -1492,6 +1492,8 @@ const ENTITY_SCRIPT_STATUS = {
|
|||
unloaded: "Unloaded"
|
||||
};
|
||||
|
||||
const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker";
|
||||
|
||||
const PROPERTY_NAME_DIVISION = {
|
||||
GROUP: 0,
|
||||
PROPERTY: 1,
|
||||
|
@ -1591,8 +1593,7 @@ function disableChildren(el, selector) {
|
|||
}
|
||||
|
||||
function enableProperties() {
|
||||
enableChildren(document.getElementById("properties-list"),
|
||||
"input, textarea, checkbox, .dropdown dl, .color-picker , .draggable-number.text");
|
||||
enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR);
|
||||
enableChildren(document, ".colpick");
|
||||
|
||||
let elLocked = getPropertyInputElement("locked");
|
||||
|
@ -1603,8 +1604,7 @@ function enableProperties() {
|
|||
}
|
||||
|
||||
function disableProperties() {
|
||||
disableChildren(document.getElementById("properties-list"),
|
||||
"input, textarea, checkbox, .dropdown dl, .color-picker, .draggable-number.text");
|
||||
disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR);
|
||||
disableChildren(document, ".colpick");
|
||||
for (let pickKey in colorPickers) {
|
||||
colorPickers[pickKey].colpickHide();
|
||||
|
@ -3356,8 +3356,8 @@ function loaded() {
|
|||
let shouldHide = selectedEntityProperties.certificateID !== "";
|
||||
if (shouldHide) {
|
||||
propertyValue = "** Certified **";
|
||||
property.elInput.disabled = true;
|
||||
}
|
||||
property.elInput.disabled = shouldHide;
|
||||
}
|
||||
|
||||
let isPropertyNotNumber = false;
|
||||
|
|
|
@ -176,7 +176,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) {
|
|||
this.homeButtonID = Overlays.addOverlay("circle3d", {
|
||||
name: "homeButton",
|
||||
localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
|
||||
localRotation: { x: 0, y: 1, z: 0, w: 0},
|
||||
localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}),
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
|
||||
solid: true,
|
||||
alpha: 0.0,
|
||||
|
@ -189,7 +189,7 @@ WebTablet = function (url, width, dpi, hand, location, visible) {
|
|||
this.homeButtonHighlightID = Overlays.addOverlay("circle3d", {
|
||||
name: "homeButtonHighlight",
|
||||
localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
|
||||
localRotation: { x: 0, y: 1, z: 0, w: 0},
|
||||
localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}),
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
solid: true,
|
||||
|
|
|
@ -48,10 +48,11 @@ propsAreCloneDynamic = function(props) {
|
|||
|
||||
cloneEntity = function(props) {
|
||||
var entityToClone = props.id;
|
||||
var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID;
|
||||
var props = Entities.getEntityProperties(entityToClone, ['certificateID', 'certificateType'])
|
||||
var certificateID = props.certificateID;
|
||||
// ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits
|
||||
// will now be handled by the server where the entity add will fail if limit reached
|
||||
if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) {
|
||||
if (entityIsCloneable(props) && (!!certificateID || props.certificateType.indexOf('domainUnlimited') >= 0)) {
|
||||
var cloneID = Entities.cloneEntity(entityToClone);
|
||||
return cloneID;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Declare dependencies
|
||||
macro (setup_testcase_dependencies)
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils)
|
||||
link_hifi_libraries(shared animation gpu fbx hfm graphics networking test-utils image)
|
||||
|
||||
package_libraries_for_deployment()
|
||||
endmacro ()
|
||||
|
|
|
@ -443,6 +443,28 @@ void AnimTests::testAnimPose() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// test matrix that has a negative determiant.
|
||||
glm::vec4 col0(-9.91782e-05f, -5.40349e-05f, 0.000724383f, 0.0f);
|
||||
glm::vec4 col1(-0.000155237f, 0.00071579f, 3.21398e-05f, 0.0f);
|
||||
glm::vec4 col2(0.000709614f, 0.000149036f, 0.000108273f, 0.0f);
|
||||
glm::vec4 col3(0.117922f, 0.250457f, 0.102155f, 1.0f);
|
||||
glm::mat4 m(col0, col1, col2, col3);
|
||||
AnimPose p(m);
|
||||
|
||||
glm::vec3 resultTrans = glm::vec3(col3);
|
||||
glm::quat resultRot = glm::quat(0.0530394f, 0.751549f, 0.0949531f, -0.650649f);
|
||||
glm::vec3 resultScale = glm::vec3(-0.000733135f, -0.000733135f, -0.000733135f);
|
||||
|
||||
const float TEST_EPSILON2 = 0.00001f;
|
||||
QCOMPARE_WITH_ABS_ERROR(p.trans(), resultTrans, TEST_EPSILON2);
|
||||
|
||||
if (glm::dot(p.rot(), resultRot) < 0.0f) {
|
||||
resultRot = -resultRot;
|
||||
}
|
||||
QCOMPARE_WITH_ABS_ERROR(p.rot(), resultRot, TEST_EPSILON2);
|
||||
QCOMPARE_WITH_ABS_ERROR(p.scale(), resultScale, TEST_EPSILON2);
|
||||
}
|
||||
|
||||
void AnimTests::testExpressionTokenizer() {
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
{
|
||||
"opts": {
|
||||
"template": "hifi-jsdoc-template"
|
||||
},
|
||||
"docdash": {
|
||||
"meta": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"keyword": ""
|
||||
},
|
||||
"search": [true],
|
||||
"collapse": [true],
|
||||
"typedefs": [false]
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"outputSourceFiles": false
|
||||
|
|
61
tools/jsdoc/hifi-jsdoc-template/LICENSE.md
Normal file
61
tools/jsdoc/hifi-jsdoc-template/LICENSE.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# License
|
||||
|
||||
Docdash is free software, licensed under the Apache License, Version 2.0 (the
|
||||
"License"). Commercial and non-commercial use are permitted in compliance with
|
||||
the License.
|
||||
|
||||
Copyright (c) 2016 Clement Moron <clenemt@gmail.com> and the
|
||||
[contributors to docdash](https://github.com/clenemt/docdash/graphs/contributors).
|
||||
All rights reserved.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
In addition, a copy of the License is included with this distribution.
|
||||
|
||||
As stated in Section 7, "Disclaimer of Warranty," of the License:
|
||||
|
||||
> Licensor provides the Work (and each Contributor provides its Contributions)
|
||||
> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
> express or implied, including, without limitation, any warranties or
|
||||
> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
> PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
> appropriateness of using or redistributing the Work and assume any risks
|
||||
> associated with Your exercise of permissions under this License.
|
||||
|
||||
The source code for docdash is available at:
|
||||
https://github.com/clenemt/docdash
|
||||
|
||||
# Third-Party Software
|
||||
|
||||
Docdash includes or depends upon the following third-party software, either in
|
||||
whole or in part. Each third-party software package is provided under its own
|
||||
license.
|
||||
|
||||
## JSDoc 3
|
||||
|
||||
JSDoc 3 is free software, licensed under the Apache License, Version 2.0 (the
|
||||
"License"). Commercial and non-commercial use are permitted in compliance with
|
||||
the License.
|
||||
|
||||
Copyright (c) 2011-2016 Michael Mathews <micmath@gmail.com> and the
|
||||
[contributors to JSDoc](https://github.com/jsdoc3/jsdoc/graphs/contributors).
|
||||
All rights reserved.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
In addition, a copy of the License is included with this distribution.
|
||||
|
||||
As stated in Section 7, "Disclaimer of Warranty," of the License:
|
||||
|
||||
> Licensor provides the Work (and each Contributor provides its Contributions)
|
||||
> on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
> express or implied, including, without limitation, any warranties or
|
||||
> conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
> PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
> appropriateness of using or redistributing the Work and assume any risks
|
||||
> associated with Your exercise of permissions under this License.
|
||||
|
||||
The source code for JSDoc 3 is available at:
|
||||
https://github.com/jsdoc3/jsdoc
|
42
tools/jsdoc/hifi-jsdoc-template/README.md
Normal file
42
tools/jsdoc/hifi-jsdoc-template/README.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# hifi-jsdoc-template
|
||||
The hifi-jsdoc-template is based on the [DocDash](https://github.com/clenemt/docdash) template.
|
||||
|
||||
## Usage
|
||||
Clone repository to your designated `jsdoc/node_modules` template directory.
|
||||
|
||||
In your `config.json` file, add a template option.
|
||||
|
||||
```json
|
||||
"opts": {
|
||||
"template": "node_modules/hifi-jsdoc-template"
|
||||
}
|
||||
```
|
||||
|
||||
## Sample `config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"opts": {
|
||||
"template": "node_modules/hifi-jsdoc-template"
|
||||
},
|
||||
"docdash": {
|
||||
"meta": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"keyword": ""
|
||||
},
|
||||
"search": [true],
|
||||
"collapse": [true],
|
||||
"typedefs": [false]
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"outputSourceFiles": false
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/hifi",
|
||||
"plugins/hifiJSONExport"
|
||||
]
|
||||
}
|
||||
```
|
59
tools/jsdoc/hifi-jsdoc-template/package.json
Normal file
59
tools/jsdoc/hifi-jsdoc-template/package.json
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"_from": "docdash",
|
||||
"_id": "docdash@1.0.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-HhK72PT4z55og8FDqskO/tTYXxU+LovRz+9pCDHLnUoPchkxjdIJidS+96LqW3CLrRdBmnkDRrcVrDFGLIluTw==",
|
||||
"_location": "/docdash",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "docdash",
|
||||
"name": "docdash",
|
||||
"escapedName": "docdash",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/docdash/-/docdash-1.0.0.tgz",
|
||||
"_shasum": "5b7df10fed3d341fc4416a8978c65ad561869d18",
|
||||
"_spec": "docdash",
|
||||
"_where": "D:\\hifi\\tools\\jsdoc",
|
||||
"author": {
|
||||
"name": "Clement Moron",
|
||||
"email": "clement.moron@gmail.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/clenemt/docdash/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "A clean, responsive documentation template theme for JSDoc 3 inspired by lodash and minami",
|
||||
"devDependencies": {
|
||||
"browser-sync": "latest",
|
||||
"jsdoc": "latest",
|
||||
"watch-run": "latest"
|
||||
},
|
||||
"homepage": "https://github.com/clenemt/docdash#readme",
|
||||
"keywords": [
|
||||
"jsdoc",
|
||||
"template"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "publish.js",
|
||||
"name": "docdash",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/clenemt/docdash.git"
|
||||
},
|
||||
"scripts": {
|
||||
"sync": "browser-sync start -s ../fixtures-doc -f ../fixtures-doc --reload-delay 1000 --no-ui --no-notify",
|
||||
"test": "jsdoc -c fixtures/fixtures.conf.json",
|
||||
"watch": "watch-run -d 1000 -p tmpl/**,static/** \"npm run test\""
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
772
tools/jsdoc/hifi-jsdoc-template/publish.js
Normal file
772
tools/jsdoc/hifi-jsdoc-template/publish.js
Normal file
|
@ -0,0 +1,772 @@
|
|||
/*global env: true */
|
||||
'use strict';
|
||||
|
||||
var doop = require('jsdoc/util/doop');
|
||||
var fs = require('jsdoc/fs');
|
||||
var helper = require('jsdoc/util/templateHelper');
|
||||
var logger = require('jsdoc/util/logger');
|
||||
var path = require('jsdoc/path');
|
||||
var taffy = require('taffydb').taffy;
|
||||
var template = require('jsdoc/template');
|
||||
var util = require('util');
|
||||
|
||||
var htmlsafe = helper.htmlsafe;
|
||||
var linkto = helper.linkto;
|
||||
var resolveAuthorLinks = helper.resolveAuthorLinks;
|
||||
var scopeToPunc = helper.scopeToPunc;
|
||||
var hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
|
||||
var data;
|
||||
var view;
|
||||
|
||||
var outdir = path.normalize(env.opts.destination);
|
||||
|
||||
function copyFile(source, target, cb) {
|
||||
var cbCalled = false;
|
||||
|
||||
var rd = fs.createReadStream(source);
|
||||
rd.on("error", function(err) {
|
||||
done(err);
|
||||
});
|
||||
var wr = fs.createWriteStream(target);
|
||||
wr.on("error", function(err) {
|
||||
done(err);
|
||||
});
|
||||
wr.on("close", function(ex) {
|
||||
done();
|
||||
});
|
||||
rd.pipe(wr);
|
||||
|
||||
function done(err) {
|
||||
if (!cbCalled) {
|
||||
cb(err);
|
||||
cbCalled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function find(spec) {
|
||||
return helper.find(data, spec);
|
||||
}
|
||||
|
||||
function tutoriallink(tutorial) {
|
||||
return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' });
|
||||
}
|
||||
|
||||
function getAncestorLinks(doclet) {
|
||||
return helper.getAncestorLinks(data, doclet);
|
||||
}
|
||||
|
||||
function hashToLink(doclet, hash) {
|
||||
if ( !/^(#.+)/.test(hash) ) { return hash; }
|
||||
|
||||
var url = helper.createLink(doclet);
|
||||
|
||||
url = url.replace(/(#.+|$)/, hash);
|
||||
return '<a href="' + url + '">' + hash + '</a>';
|
||||
}
|
||||
|
||||
function needsSignature(doclet) {
|
||||
var needsSig = false;
|
||||
|
||||
// function and class definitions always get a signature
|
||||
if (doclet.kind === 'function' || doclet.kind === 'class' && !doclet.hideconstructor) {
|
||||
needsSig = true;
|
||||
}
|
||||
// typedefs that contain functions get a signature, too
|
||||
else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names &&
|
||||
doclet.type.names.length) {
|
||||
for (var i = 0, l = doclet.type.names.length; i < l; i++) {
|
||||
if (doclet.type.names[i].toLowerCase() === 'function') {
|
||||
needsSig = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return needsSig;
|
||||
}
|
||||
|
||||
function getSignatureAttributes(item) {
|
||||
var attributes = [];
|
||||
|
||||
if (item.optional) {
|
||||
attributes.push('opt');
|
||||
}
|
||||
|
||||
if (item.nullable === true) {
|
||||
attributes.push('nullable');
|
||||
}
|
||||
else if (item.nullable === false) {
|
||||
attributes.push('non-null');
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
function updateItemName(item) {
|
||||
var attributes = getSignatureAttributes(item);
|
||||
var itemName = item.name || '';
|
||||
|
||||
if (item.variable) {
|
||||
itemName = '…' + itemName;
|
||||
}
|
||||
|
||||
if (attributes && attributes.length) {
|
||||
itemName = util.format( '%s<span class="signature-attributes">%s</span>', itemName,
|
||||
attributes.join(', ') );
|
||||
}
|
||||
|
||||
return itemName;
|
||||
}
|
||||
|
||||
function addParamAttributes(params) {
|
||||
return params.filter(function(param) {
|
||||
return param.name && param.name.indexOf('.') === -1;
|
||||
}).map(updateItemName);
|
||||
}
|
||||
|
||||
function buildItemTypeStrings(item) {
|
||||
var types = [];
|
||||
|
||||
if (item && item.type && item.type.names) {
|
||||
item.type.names.forEach(function(name) {
|
||||
types.push( linkto(name, htmlsafe(name)) );
|
||||
});
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
function buildAttribsString(attribs) {
|
||||
var attribsString = '';
|
||||
|
||||
if (attribs && attribs.length) {
|
||||
attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) );
|
||||
}
|
||||
|
||||
return attribsString;
|
||||
}
|
||||
|
||||
function addNonParamAttributes(items) {
|
||||
var types = [];
|
||||
|
||||
items.forEach(function(item) {
|
||||
types = types.concat( buildItemTypeStrings(item) );
|
||||
});
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
function addSignatureParams(f) {
|
||||
var params = f.params ? addParamAttributes(f.params) : [];
|
||||
f.signature = util.format( '%s( %s )', (f.signature || ''), params.join(', ') );
|
||||
}
|
||||
|
||||
function addSignatureReturns(f) {
|
||||
var attribs = [];
|
||||
var attribsString = '';
|
||||
var returnTypes = [];
|
||||
var returnTypesString = '';
|
||||
|
||||
// jam all the return-type attributes into an array. this could create odd results (for example,
|
||||
// if there are both nullable and non-nullable return types), but let's assume that most people
|
||||
// who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
|
||||
if (f.returns) {
|
||||
f.returns.forEach(function(item) {
|
||||
helper.getAttribs(item).forEach(function(attrib) {
|
||||
if (attribs.indexOf(attrib) === -1) {
|
||||
attribs.push(attrib);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
attribsString = buildAttribsString(attribs);
|
||||
}
|
||||
|
||||
if (f.returns) {
|
||||
returnTypes = addNonParamAttributes(f.returns);
|
||||
}
|
||||
if (returnTypes.length) {
|
||||
returnTypesString = util.format( ' → %s{%s}', attribsString, returnTypes.join('|') );
|
||||
}
|
||||
|
||||
f.signature = '<span class="signature">' + (f.signature || '') + '</span>' +
|
||||
'<span class="type-returns">' + returnTypesString + '</span>';
|
||||
}
|
||||
|
||||
function addSignatureTypes(f) {
|
||||
var types = f.type ? buildItemTypeStrings(f) : [];
|
||||
|
||||
f.signature = (f.signature || '') + '<span class="type-signature">' +
|
||||
(types.length ? ' :' + types.join('|') : '') + '</span>';
|
||||
}
|
||||
|
||||
function addAttribs(f) {
|
||||
var attribs = helper.getAttribs(f);
|
||||
var attribsString = buildAttribsString(attribs);
|
||||
|
||||
f.attribs = util.format('<span class="type-signature">%s</span>', attribsString);
|
||||
}
|
||||
|
||||
function shortenPaths(files, commonPrefix) {
|
||||
Object.keys(files).forEach(function(file) {
|
||||
files[file].shortened = files[file].resolved.replace(commonPrefix, '')
|
||||
// always use forward slashes
|
||||
.replace(/\\/g, '/');
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function getPathFromDoclet(doclet) {
|
||||
if (!doclet.meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doclet.meta.path && doclet.meta.path !== 'null' ?
|
||||
path.join(doclet.meta.path, doclet.meta.filename) :
|
||||
doclet.meta.filename;
|
||||
}
|
||||
|
||||
function generate(type, title, docs, filename, resolveLinks) {
|
||||
resolveLinks = resolveLinks === false ? false : true;
|
||||
|
||||
var docData = {
|
||||
type: type,
|
||||
title: title,
|
||||
docs: docs
|
||||
};
|
||||
|
||||
var outpath = path.join(outdir, filename),
|
||||
html = view.render('container.tmpl', docData);
|
||||
|
||||
if (resolveLinks) {
|
||||
html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
|
||||
}
|
||||
|
||||
fs.writeFileSync(outpath, html, 'utf8');
|
||||
}
|
||||
|
||||
function generateSourceFiles(sourceFiles, encoding) {
|
||||
encoding = encoding || 'utf8';
|
||||
Object.keys(sourceFiles).forEach(function(file) {
|
||||
var source;
|
||||
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
|
||||
var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
|
||||
helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
|
||||
|
||||
try {
|
||||
source = {
|
||||
kind: 'source',
|
||||
code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
|
||||
};
|
||||
}
|
||||
catch(e) {
|
||||
logger.error('Error while generating source file %s: %s', file, e.message);
|
||||
}
|
||||
|
||||
generate('Source', sourceFiles[file].shortened, [source], sourceOutfile, false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for classes or functions with the same name as modules (which indicates that the module
|
||||
* exports only that class or function), then attach the classes or functions to the `module`
|
||||
* property of the appropriate module doclets. The name of each class or function is also updated
|
||||
* for display purposes. This function mutates the original arrays.
|
||||
*
|
||||
* @private
|
||||
* @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
|
||||
* check.
|
||||
* @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
|
||||
*/
|
||||
function attachModuleSymbols(doclets, modules) {
|
||||
var symbols = {};
|
||||
|
||||
// build a lookup table
|
||||
doclets.forEach(function(symbol) {
|
||||
symbols[symbol.longname] = symbols[symbol.longname] || [];
|
||||
symbols[symbol.longname].push(symbol);
|
||||
});
|
||||
|
||||
return modules.map(function(module) {
|
||||
if (symbols[module.longname]) {
|
||||
module.modules = symbols[module.longname]
|
||||
// Only show symbols that have a description. Make an exception for classes, because
|
||||
// we want to show the constructor-signature heading no matter what.
|
||||
.filter(function(symbol) {
|
||||
return symbol.description || symbol.kind === 'class';
|
||||
})
|
||||
.map(function(symbol) {
|
||||
symbol = doop(symbol);
|
||||
|
||||
if (symbol.kind === 'class' || symbol.kind === 'function' && !symbol.hideconstructor) {
|
||||
symbol.name = symbol.name.replace('module:', '(require("') + '"))';
|
||||
}
|
||||
|
||||
return symbol;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) {
|
||||
var nav = '';
|
||||
|
||||
if (items && items.length) {
|
||||
var itemsNav = '';
|
||||
|
||||
items.forEach(function(item) {
|
||||
var displayName;
|
||||
var methods = find({kind:'function', memberof: item.longname});
|
||||
var signals = find({kind:'signal', memberof: item.longname});
|
||||
var members = find({kind:'member', memberof: item.longname});
|
||||
var docdash = env && env.conf && env.conf.docdash || {};
|
||||
var conf = env && env.conf || {};
|
||||
if ( !hasOwnProp.call(item, 'longname') ) {
|
||||
itemsNav += '<li>' + linktoFn('', item.name);
|
||||
itemsNav += '</li>';
|
||||
} else if ( !hasOwnProp.call(itemsSeen, item.longname) ) {
|
||||
if (conf.templates.default.useLongnameInNav) {
|
||||
displayName = item.longname;
|
||||
} else {
|
||||
displayName = item.name;
|
||||
}
|
||||
itemsNav += '<li>' + linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''));
|
||||
|
||||
if (docdash.static && members.find(function (m) { return m.scope === 'static'; } )) {
|
||||
itemsNav += "<ul class='members'>";
|
||||
|
||||
members.forEach(function (member) {
|
||||
if (!member.scope === 'static') return;
|
||||
itemsNav += "<li data-type='member'";
|
||||
if(docdash.collapse)
|
||||
itemsNav += " style='display: none;'";
|
||||
itemsNav += ">";
|
||||
itemsNav += linkto(member.longname, member.name);
|
||||
itemsNav += "</li>";
|
||||
});
|
||||
|
||||
itemsNav += "</ul>";
|
||||
}
|
||||
|
||||
if (methods.length) {
|
||||
itemsNav += "<ul class='methods'>";
|
||||
|
||||
methods.forEach(function (method) {
|
||||
itemsNav += "<li data-type='method'";
|
||||
if(docdash.collapse)
|
||||
itemsNav += " style='display: none;'";
|
||||
itemsNav += ">";
|
||||
itemsNav += linkto(method.longname, method.name);
|
||||
itemsNav += "</li>";
|
||||
});
|
||||
|
||||
itemsNav += "</ul>";
|
||||
}
|
||||
|
||||
if (signals.length) {
|
||||
itemsNav += "<ul class='methods'>";
|
||||
|
||||
signals.forEach(function (signal) {
|
||||
itemsNav += "<li data-type='method'";
|
||||
if(docdash.collapse)
|
||||
itemsNav += " style='display: none;'";
|
||||
itemsNav += ">";
|
||||
itemsNav += linkto(signal.longname, signal.name);
|
||||
itemsNav += "</li>";
|
||||
});
|
||||
|
||||
itemsNav += "</ul>";
|
||||
}
|
||||
|
||||
|
||||
itemsNav += '</li>';
|
||||
itemsSeen[item.longname] = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (itemsNav !== '') {
|
||||
nav += '<h3>' + itemHeading + '</h3><ul class="nav">' + itemsNav + '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
return nav;
|
||||
}
|
||||
|
||||
function linktoTutorial(longName, name) {
|
||||
return tutoriallink(name);
|
||||
}
|
||||
|
||||
function linktoExternal(longName, name) {
|
||||
return linkto(longName, name.replace(/(^"|"$)/g, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the navigation sidebar.
|
||||
* @param {object} members The members that will be used to create the sidebar.
|
||||
* @param {array<object>} members.classes
|
||||
* @param {array<object>} members.externals
|
||||
* @param {array<object>} members.globals
|
||||
* @param {array<object>} members.mixins
|
||||
* @param {array<object>} members.modules
|
||||
* @param {array<object>} members.namespaces
|
||||
* @param {array<object>} members.tutorials
|
||||
* @param {array<object>} members.events
|
||||
* @param {array<object>} members.interfaces
|
||||
* @return {s
|
||||
ring} The HTML for the navigation sidebar.
|
||||
*/
|
||||
|
||||
function buildNav(members) {
|
||||
var nav = '<h3><a href="index.html">Home</a></h3>';
|
||||
var seen = {};
|
||||
var seenTutorials = {};
|
||||
var docdash = env && env.conf && env.conf.docdash || {};
|
||||
if(docdash.menu){
|
||||
for(var menu in docdash.menu){
|
||||
nav += '<h2><a ';
|
||||
//add attributes
|
||||
for(var attr in docdash.menu[menu]){
|
||||
nav += attr+'="' + docdash.menu[menu][attr] + '" ';
|
||||
}
|
||||
nav += '>' + menu + '</a></h2>';
|
||||
}
|
||||
}
|
||||
var defaultOrder = [
|
||||
'Namespaces', 'Classes', 'Modules', 'Externals', 'Events', 'Mixins', 'Tutorials', 'Interfaces'
|
||||
];
|
||||
var order = docdash.sectionOrder || defaultOrder;
|
||||
var sections = {
|
||||
Namespaces: buildMemberNav(members.namespaces, 'Namespaces', seen, linkto),
|
||||
Classes: buildMemberNav(members.classes, 'Classes', seen, linkto),
|
||||
Modules: buildMemberNav(members.modules, 'Modules', {}, linkto),
|
||||
Externals: buildMemberNav(members.externals, 'Externals', seen, linktoExternal),
|
||||
Events: buildMemberNav(members.events, 'Events', seen, linkto),
|
||||
Mixins: buildMemberNav(members.mixins, 'Mixins', seen, linkto),
|
||||
Tutorials: buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial),
|
||||
Interfaces: buildMemberNav(members.interfaces, 'Interfaces', seen, linkto),
|
||||
};
|
||||
order.forEach(member => nav += sections[member]);
|
||||
|
||||
if (members.globals.length) {
|
||||
var globalNav = '';
|
||||
|
||||
members.globals.forEach(function(g) {
|
||||
if ( (docdash.typedefs || g.kind !== 'typedef') && !hasOwnProp.call(seen, g.longname) ) {
|
||||
globalNav += '<li>' + linkto(g.longname, g.name) + '</li>';
|
||||
}
|
||||
seen[g.longname] = true;
|
||||
});
|
||||
|
||||
if (!globalNav) {
|
||||
// turn the heading into a link so you can actually get to the global page
|
||||
nav += '<h3>' + linkto('global', 'Global') + '</h3>';
|
||||
}
|
||||
else {
|
||||
nav += '<h3>Globals</h3><ul>' + globalNav + '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
return nav;
|
||||
}
|
||||
|
||||
/**
|
||||
@param {TAFFY} taffyData See <http://taffydb.com/>.
|
||||
@param {object} opts
|
||||
@param {Tutorial} tutorials
|
||||
*/
|
||||
exports.publish = function(taffyData, opts, tutorials) {
|
||||
var docdash = env && env.conf && env.conf.docdash || {};
|
||||
data = taffyData;
|
||||
|
||||
var conf = env.conf.templates || {};
|
||||
conf.default = conf.default || {};
|
||||
|
||||
var templatePath = path.normalize(opts.template);
|
||||
view = new template.Template( path.join(templatePath, 'tmpl') );
|
||||
|
||||
// claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
|
||||
// doesn't try to hand them out later
|
||||
var indexUrl = helper.getUniqueFilename('index');
|
||||
// don't call registerLink() on this one! 'index' is also a valid longname
|
||||
|
||||
var globalUrl = helper.getUniqueFilename('global');
|
||||
helper.registerLink('global', globalUrl);
|
||||
|
||||
// set up templating
|
||||
view.layout = conf.default.layoutFile ?
|
||||
path.getResourcePath(path.dirname(conf.default.layoutFile),
|
||||
path.basename(conf.default.layoutFile) ) :
|
||||
'layout.tmpl';
|
||||
|
||||
// set up tutorials for helper
|
||||
helper.setTutorials(tutorials);
|
||||
|
||||
data = helper.prune(data);
|
||||
|
||||
docdash.sort !== false && data.sort('longname, version, since');
|
||||
helper.addEventListeners(data);
|
||||
|
||||
var sourceFiles = {};
|
||||
var sourceFilePaths = [];
|
||||
data().each(function(doclet) {
|
||||
if(docdash.removeQuotes){
|
||||
if(docdash.removeQuotes === "all"){
|
||||
if(doclet.name){
|
||||
doclet.name = doclet.name.replace(/"/g, '');
|
||||
doclet.name = doclet.name.replace(/'/g, '');
|
||||
}
|
||||
if(doclet.longname){
|
||||
doclet.longname = doclet.longname.replace(/"/g, '');
|
||||
doclet.longname = doclet.longname.replace(/'/g, '');
|
||||
}
|
||||
}
|
||||
else if(docdash.removeQuotes === "trim"){
|
||||
if(doclet.name){
|
||||
doclet.name = doclet.name.replace(/^"(.*)"$/, '$1');
|
||||
doclet.name = doclet.name.replace(/^'(.*)'$/, '$1');
|
||||
}
|
||||
if(doclet.longname){
|
||||
doclet.longname = doclet.longname.replace(/^"(.*)"$/, '$1');
|
||||
doclet.longname = doclet.longname.replace(/^'(.*)'$/, '$1');
|
||||
}
|
||||
}
|
||||
}
|
||||
doclet.attribs = '';
|
||||
|
||||
if (doclet.examples) {
|
||||
doclet.examples = doclet.examples.map(function(example) {
|
||||
var caption, code;
|
||||
|
||||
if (example && example.match(/^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {
|
||||
caption = RegExp.$1;
|
||||
code = RegExp.$3;
|
||||
}
|
||||
|
||||
return {
|
||||
caption: caption || '',
|
||||
code: code || example || ''
|
||||
};
|
||||
});
|
||||
}
|
||||
if (doclet.see) {
|
||||
doclet.see.forEach(function(seeItem, i) {
|
||||
doclet.see[i] = hashToLink(doclet, seeItem);
|
||||
});
|
||||
}
|
||||
|
||||
// build a list of source files
|
||||
var sourcePath;
|
||||
if (doclet.meta) {
|
||||
sourcePath = getPathFromDoclet(doclet);
|
||||
sourceFiles[sourcePath] = {
|
||||
resolved: sourcePath,
|
||||
shortened: null
|
||||
};
|
||||
if (sourceFilePaths.indexOf(sourcePath) === -1) {
|
||||
sourceFilePaths.push(sourcePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// update outdir if necessary, then create outdir
|
||||
var packageInfo = ( find({kind: 'package'}) || [] ) [0];
|
||||
if (packageInfo && packageInfo.name) {
|
||||
outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') );
|
||||
}
|
||||
fs.mkPath(outdir);
|
||||
|
||||
// copy the template's static files to outdir
|
||||
var fromDir = path.join(templatePath, 'static');
|
||||
var staticFiles = fs.ls(fromDir, 3);
|
||||
|
||||
staticFiles.forEach(function(fileName) {
|
||||
var toDir = fs.toDir( fileName.replace(fromDir, outdir) );
|
||||
fs.mkPath(toDir);
|
||||
copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);});
|
||||
});
|
||||
|
||||
// copy user-specified static files to outdir
|
||||
var staticFilePaths;
|
||||
var staticFileFilter;
|
||||
var staticFileScanner;
|
||||
if (conf.default.staticFiles) {
|
||||
// The canonical property name is `include`. We accept `paths` for backwards compatibility
|
||||
// with a bug in JSDoc 3.2.x.
|
||||
staticFilePaths = conf.default.staticFiles.include ||
|
||||
conf.default.staticFiles.paths ||
|
||||
[];
|
||||
staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf.default.staticFiles);
|
||||
staticFileScanner = new (require('jsdoc/src/scanner')).Scanner();
|
||||
|
||||
staticFilePaths.forEach(function(filePath) {
|
||||
var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
|
||||
|
||||
extraStaticFiles.forEach(function(fileName) {
|
||||
var sourcePath = fs.toDir(filePath);
|
||||
var toDir = fs.toDir( fileName.replace(sourcePath, outdir) );
|
||||
fs.mkPath(toDir);
|
||||
copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sourceFilePaths.length) {
|
||||
sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) );
|
||||
}
|
||||
data().each(function(doclet) {
|
||||
var url = helper.createLink(doclet);
|
||||
helper.registerLink(doclet.longname, url);
|
||||
|
||||
// add a shortened version of the full path
|
||||
var docletPath;
|
||||
if (doclet.meta) {
|
||||
docletPath = getPathFromDoclet(doclet);
|
||||
docletPath = sourceFiles[docletPath].shortened;
|
||||
if (docletPath) {
|
||||
doclet.meta.shortpath = docletPath;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
data().each(function(doclet) {
|
||||
var url = helper.longnameToUrl[doclet.longname];
|
||||
|
||||
if (url.indexOf('#') > -1) {
|
||||
doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
|
||||
}
|
||||
else {
|
||||
doclet.id = doclet.name;
|
||||
}
|
||||
|
||||
if ( needsSignature(doclet) ) {
|
||||
addSignatureParams(doclet);
|
||||
addSignatureReturns(doclet);
|
||||
addAttribs(doclet);
|
||||
}
|
||||
});
|
||||
|
||||
// do this after the urls have all been generated
|
||||
data().each(function(doclet) {
|
||||
doclet.ancestors = getAncestorLinks(doclet);
|
||||
|
||||
if (doclet.kind === 'member') {
|
||||
addSignatureTypes(doclet);
|
||||
addAttribs(doclet);
|
||||
}
|
||||
|
||||
if (doclet.kind === 'constant') {
|
||||
addSignatureTypes(doclet);
|
||||
addAttribs(doclet);
|
||||
doclet.kind = 'member';
|
||||
}
|
||||
});
|
||||
|
||||
var members = helper.getMembers(data);
|
||||
members.tutorials = tutorials.children;
|
||||
|
||||
// output pretty-printed source files by default
|
||||
var outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false
|
||||
? true
|
||||
: false;
|
||||
|
||||
// add template helpers
|
||||
view.find = find;
|
||||
view.linkto = linkto;
|
||||
view.resolveAuthorLinks = resolveAuthorLinks;
|
||||
view.tutoriallink = tutoriallink;
|
||||
view.htmlsafe = htmlsafe;
|
||||
view.outputSourceFiles = outputSourceFiles;
|
||||
|
||||
// once for all
|
||||
view.nav = buildNav(members);
|
||||
attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules );
|
||||
|
||||
// generate the pretty-printed source files first so other pages can link to them
|
||||
if (outputSourceFiles) {
|
||||
generateSourceFiles(sourceFiles, opts.encoding);
|
||||
}
|
||||
|
||||
if (members.globals.length) {
|
||||
generate('', 'Global', [{kind: 'globalobj'}], globalUrl);
|
||||
}
|
||||
|
||||
// index page displays information from package.json and lists files
|
||||
var files = find({kind: 'file'});
|
||||
var packages = find({kind: 'package'});
|
||||
|
||||
generate('', 'High Fidelity API Reference',
|
||||
packages.concat(
|
||||
[{kind: 'mainpage', readme: opts.readme, longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'}]
|
||||
).concat(files),
|
||||
indexUrl);
|
||||
|
||||
// set up the lists that we'll use to generate pages
|
||||
var classes = taffy(members.classes);
|
||||
var modules = taffy(members.modules);
|
||||
var namespaces = taffy(members.namespaces);
|
||||
var mixins = taffy(members.mixins);
|
||||
var externals = taffy(members.externals);
|
||||
var interfaces = taffy(members.interfaces);
|
||||
|
||||
Object.keys(helper.longnameToUrl).forEach(function(longname) {
|
||||
var myModules = helper.find(modules, {longname: longname});
|
||||
if (myModules.length) {
|
||||
generate('Module', myModules[0].name, myModules, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
var myClasses = helper.find(classes, {longname: longname});
|
||||
if (myClasses.length) {
|
||||
generate('Class', myClasses[0].name, myClasses, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
var myNamespaces = helper.find(namespaces, {longname: longname});
|
||||
if (myNamespaces.length) {
|
||||
generate('Namespace', myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
var myMixins = helper.find(mixins, {longname: longname});
|
||||
if (myMixins.length) {
|
||||
generate('Mixin', myMixins[0].name, myMixins, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
var myExternals = helper.find(externals, {longname: longname});
|
||||
if (myExternals.length) {
|
||||
generate('External', myExternals[0].name, myExternals, helper.longnameToUrl[longname]);
|
||||
}
|
||||
|
||||
var myInterfaces = helper.find(interfaces, {longname: longname});
|
||||
if (myInterfaces.length) {
|
||||
generate('Interface', myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: move the tutorial functions to templateHelper.js
|
||||
function generateTutorial(title, tutorial, filename) {
|
||||
var tutorialData = {
|
||||
title: title,
|
||||
header: tutorial.title,
|
||||
content: tutorial.parse(),
|
||||
children: tutorial.children
|
||||
};
|
||||
|
||||
var tutorialPath = path.join(outdir, filename);
|
||||
var html = view.render('tutorial.tmpl', tutorialData);
|
||||
|
||||
// yes, you can use {@link} in tutorials too!
|
||||
html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
|
||||
fs.writeFileSync(tutorialPath, html, 'utf8');
|
||||
}
|
||||
|
||||
// tutorials can have only one parent so there is no risk for loops
|
||||
function saveChildren(node) {
|
||||
node.children.forEach(function(child) {
|
||||
generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name));
|
||||
saveChildren(child);
|
||||
});
|
||||
}
|
||||
|
||||
saveChildren(tutorials);
|
||||
};
|
BIN
tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf
Normal file
BIN
tools/jsdoc/hifi-jsdoc-template/static/fonts/Cairo-Bold.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png
Normal file
BIN
tools/jsdoc/hifi-jsdoc-template/static/images/white-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
11
tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js
Normal file
11
tools/jsdoc/hifi-jsdoc-template/static/scripts/collapse.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
function hideAllButCurrent(){
|
||||
//by default all submenut items are hidden
|
||||
$("nav > ul > li > ul li").hide();
|
||||
|
||||
//only current page (if it exists) should be opened
|
||||
var file = window.location.pathname.split("/").pop();
|
||||
$("nav > ul > li > a[href^='"+file+"']").parent().find("> ul li").show();
|
||||
}
|
||||
$( document ).ready(function() {
|
||||
hideAllButCurrent();
|
||||
});
|
4
tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js
vendored
Normal file
4
tools/jsdoc/hifi-jsdoc-template/static/scripts/jquery-3.1.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
25
tools/jsdoc/hifi-jsdoc-template/static/scripts/linenumber.js
Normal file
25
tools/jsdoc/hifi-jsdoc-template/static/scripts/linenumber.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*global document */
|
||||
(function() {
|
||||
var source = document.getElementsByClassName('prettyprint source linenums');
|
||||
var i = 0;
|
||||
var lineNumber = 0;
|
||||
var lineId;
|
||||
var lines;
|
||||
var totalLines;
|
||||
var anchorHash;
|
||||
|
||||
if (source && source[0]) {
|
||||
anchorHash = document.location.hash.substring(1);
|
||||
lines = source[0].getElementsByTagName('li');
|
||||
totalLines = lines.length;
|
||||
|
||||
for (; i < totalLines; i++) {
|
||||
lineNumber++;
|
||||
lineId = 'line' + lineNumber;
|
||||
lines[i].id = lineId;
|
||||
if (lineId === anchorHash) {
|
||||
lines[i].className += ' selected';
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,2 @@
|
|||
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
|
||||
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue