Merge pull request #13 from wayne-chen/pushToTalk

rebase with master
This commit is contained in:
Wayne Chen 2019-03-08 16:10:35 -08:00 committed by GitHub
commit b3d704fa61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
138 changed files with 5894 additions and 592 deletions

9
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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>();

View 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

View file

@ -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();
}
}
}

View file

@ -815,7 +815,7 @@ ModalWindow {
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.shown = false; }
onTriggered: { canceled(); root.destroy(); }
}
}

View file

@ -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();
}
}
}

View file

@ -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));

View file

@ -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;
}
}

View file

@ -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 {}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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();
}
}

View file

@ -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
}
}

View file

@ -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") {

View file

@ -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 {

View 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);
}
}

View 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
//
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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) {

View file

@ -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;
};

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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()) {

View file

@ -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;

View file

@ -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) &ndash;
* @property {number} inputLevel - The loudness of the audio input, range <code>0.0</code> (no sound) &ndash;
* <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> &ndash; <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> &ndash; <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 &mdash; configured on the server &mdash; 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 &mdash; configured on the server &mdash; 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> &ndash;
* <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> &ndash;
* <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) &ndash; <code>1.0</code> (the
* @param {number} level - The loudness of the input audio, range <code>0.0</code> (no sound) &ndash; <code>1.0</code> (the
* onset of clipping).
* @returns {Signal}
*/

View file

@ -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
}

View file

@ -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

View file

@ -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);

View file

@ -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"

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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);

View file

@ -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");

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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 };

View file

@ -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);

View file

@ -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, "");

View file

@ -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 };

View file

@ -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

View file

@ -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) {

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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>)

View file

@ -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()) {

View 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();
}

View 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

View file

@ -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);

View file

@ -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;

View 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;
}

View 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

View file

@ -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);
}
}
}
}
}

View file

@ -265,6 +265,7 @@ enum class EntityVersion : PacketVersion {
WebBillboardMode,
ModelScale,
ReOrderParentIDProperties,
CertificateTypeProperty,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -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

View file

@ -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))
);
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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)$>
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View 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

View file

@ -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

View file

@ -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";

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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, [], []);
}

View file

@ -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;
};

View file

@ -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);

View file

@ -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",

View file

@ -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;

View file

@ -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,

View file

@ -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;
}

View file

@ -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 ()

View file

@ -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() {

View file

@ -1,4 +1,17 @@
{
"opts": {
"template": "hifi-jsdoc-template"
},
"docdash": {
"meta": {
"title": "",
"description": "",
"keyword": ""
},
"search": [true],
"collapse": [true],
"typedefs": [false]
},
"templates": {
"default": {
"outputSourceFiles": false

View 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

View 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"
]
}
```

View 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"
}

View 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 = '&hellip;' + 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( ' &rarr; %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);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View 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();
});

File diff suppressed because one or more lines are too long

View 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';
}
}
}
})();

View file

@ -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.

View file

@ -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