mirror of
https://github.com/lubosz/overte.git
synced 2025-04-17 00:57:44 +02:00
Merge branch 'master' into branching
This commit is contained in:
commit
968ffe7ec3
80 changed files with 2994 additions and 1621 deletions
|
@ -754,13 +754,13 @@ void Agent::processAgentAvatarAudio() {
|
|||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
if (_avatarSound) {
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
auto audioData = _avatarSound->getAudioData();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(audioData->rawData()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
int numAvailableBytes = (audioData->getNumBytes() - _numAvatarSoundSentBytes) > AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
? AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
: audioData->getNumBytes() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = (int16_t)numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
|
@ -773,7 +773,7 @@ void Agent::processAgentAvatarAudio() {
|
|||
}
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
if (_numAvatarSoundSentBytes == (int)audioData->getNumBytes()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
// and our sent bytes back to zero
|
||||
_avatarSound.clear();
|
||||
|
|
|
@ -83,8 +83,10 @@ SpinBox {
|
|||
}
|
||||
|
||||
validator: DoubleValidator {
|
||||
bottom: Math.min(spinBox.from, spinBox.to)
|
||||
top: Math.max(spinBox.from, spinBox.to)
|
||||
decimals: spinBox.decimals
|
||||
bottom: Math.min(spinBox.realFrom, spinBox.realTo)
|
||||
top: Math.max(spinBox.realFrom, spinBox.realTo)
|
||||
notation: DoubleValidator.StandardNotation
|
||||
}
|
||||
|
||||
textFromValue: function(value, locale) {
|
||||
|
@ -97,20 +99,37 @@ SpinBox {
|
|||
|
||||
|
||||
contentItem: TextInput {
|
||||
id: spinboxText
|
||||
z: 2
|
||||
color: isLightColorScheme
|
||||
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
selectedTextColor: hifi.colors.black
|
||||
selectionColor: hifi.colors.primaryHighlight
|
||||
text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix
|
||||
text: spinBox.textFromValue(spinBox.value, spinBox.locale)
|
||||
inputMethodHints: spinBox.inputMethodHints
|
||||
validator: spinBox.validator
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
|
||||
//rightPadding: hifi.dimensions.spinnerSize
|
||||
width: spinBox.width - hifi.dimensions.spinnerSize
|
||||
onEditingFinished: spinBox.editingFinished()
|
||||
|
||||
Text {
|
||||
id: suffixText
|
||||
x: metrics.advanceWidth(spinboxText.text + '*')
|
||||
height: spinboxText.height
|
||||
|
||||
FontMetrics {
|
||||
id: metrics
|
||||
font: spinboxText.font
|
||||
}
|
||||
|
||||
color: isLightColorScheme
|
||||
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
text: suffix
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
up.indicator: Item {
|
||||
|
|
|
@ -49,7 +49,6 @@ Item {
|
|||
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
|
||||
property int shadowHeight: 10;
|
||||
property bool hovered: false
|
||||
property bool scrolling: false
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
|
@ -238,31 +237,38 @@ Item {
|
|||
property var unhoverThunk: function () { };
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.hovered && !root.scrolling
|
||||
visible: root.hovered
|
||||
color: "transparent"
|
||||
border.width: 4
|
||||
border.color: hifiStyleConstants.colors.primaryHighlight
|
||||
z: 1
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
acceptedButtons: Qt.LeftButton;
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onContainsMouseChanged: {
|
||||
// Use onContainsMouseChanged rather than onEntered and onExited because the latter aren't always
|
||||
// triggered correctly - e.g., if drag rightwards from right hand side of a card to the next card
|
||||
// onExited doesn't fire, in which case can end up with two cards highlighted.
|
||||
if (containsMouse) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
hoverThunk();
|
||||
} else {
|
||||
unhoverThunk();
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
// Separate MouseArea for click handling so that it doesn't interfere with hovering and interaction
|
||||
// with containing ListView.
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: false
|
||||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
goFunction("hifi://" + hifiUrl);
|
||||
}
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
hoverThunk();
|
||||
}
|
||||
onExited: unhoverThunk();
|
||||
onCanceled: unhoverThunk();
|
||||
}
|
||||
MouseArea {
|
||||
// This second mouse area causes onEntered to fire on the first if you scroll just a little and the cursor stays on
|
||||
// the original card. I.e., the original card is re-highlighted if the cursor is on it after scrolling finishes.
|
||||
anchors.fill: parent
|
||||
}
|
||||
StateImage {
|
||||
id: actionIcon;
|
||||
|
|
|
@ -141,7 +141,6 @@ Column {
|
|||
textSizeSmall: root.textSizeSmall;
|
||||
stackShadowNarrowing: root.stackShadowNarrowing;
|
||||
shadowHeight: root.stackedCardShadowHeight;
|
||||
scrolling: scroll.moving
|
||||
|
||||
hoverThunk: function () {
|
||||
hovered = true;
|
||||
|
|
|
@ -256,7 +256,7 @@ Rectangle {
|
|||
color: hifi.colors.baseGrayHighlight;
|
||||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
text: "Wallet";
|
||||
text: "Secure Transactions";
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 20;
|
||||
color: hifi.colors.white;
|
||||
|
@ -287,7 +287,7 @@ Rectangle {
|
|||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
id: securityPictureText;
|
||||
text: "Wallet Security Picture";
|
||||
text: "Security Picture";
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Window 2.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.settings 1.0
|
||||
import stylesUit 1.0
|
||||
|
@ -72,6 +73,11 @@ Item {
|
|||
initialItem: inputConfiguration
|
||||
property alias messageVisible: imageMessageBox.visible
|
||||
property string selectedPlugin: ""
|
||||
|
||||
property bool keyboardEnabled: false
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
Rectangle {
|
||||
id: inputConfiguration
|
||||
anchors {
|
||||
|
@ -227,6 +233,8 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.top: inputConfiguration.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: keyboard.height
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
asynchronous: false
|
||||
|
@ -248,6 +256,29 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
onRaisedChanged: {
|
||||
if (raised) {
|
||||
// delayed execution to allow loader and its content to adjust size
|
||||
Qt.callLater(function() {
|
||||
loader.item.bringToView(Window.activeFocusItem);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
parent.keyboardEnabled = HMD.active;
|
||||
}
|
||||
}
|
||||
|
||||
function inputPlugins() {
|
||||
if (checkBox.checked) {
|
||||
|
|
|
@ -32,6 +32,18 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
function bringToView(item) {
|
||||
var yTop = item.mapToItem(contentItem, 0, 0).y;
|
||||
var yBottom = yTop + item.height;
|
||||
|
||||
var surfaceTop = contentY;
|
||||
var surfaceBottom = contentY + height;
|
||||
|
||||
if(yTop < surfaceTop || yBottom > surfaceBottom) {
|
||||
contentY = yTop - height / 2 + item.height
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
page = config.createObject(flick.contentItem);
|
||||
}
|
||||
|
@ -39,6 +51,8 @@ Flickable {
|
|||
id: config
|
||||
Rectangle {
|
||||
id: openVrConfiguration
|
||||
anchors.fill: parent
|
||||
|
||||
property int leftMargin: 75
|
||||
property int countDown: 0
|
||||
property string pluginName: ""
|
||||
|
@ -200,6 +214,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,6 +232,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -309,6 +325,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,6 +342,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -550,13 +568,15 @@ Flickable {
|
|||
width: 160
|
||||
suffix: " cm"
|
||||
label: "Arm Circumference"
|
||||
minimumValue: 0
|
||||
minimumValue: 10.0
|
||||
maximumValue: 50.0
|
||||
realStepSize: 1.0
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
realValue: 33.0
|
||||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,7 +585,8 @@ Flickable {
|
|||
width: 160
|
||||
label: "Shoulder Width"
|
||||
suffix: " cm"
|
||||
minimumValue: 0
|
||||
minimumValue: 25.0
|
||||
maximumValue: 50.0
|
||||
realStepSize: 1.0
|
||||
decimals: 1
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
@ -573,6 +594,7 @@ Flickable {
|
|||
|
||||
onEditingFinished: {
|
||||
sendConfigurationSettings();
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -743,14 +765,17 @@ Flickable {
|
|||
anchors.left: parent.left
|
||||
anchors.leftMargin: leftMargin
|
||||
|
||||
minimumValue: 5
|
||||
minimumValue: 0
|
||||
maximumValue: 5
|
||||
realValue: 5
|
||||
realStepSize: 1.0
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
onEditingFinished: {
|
||||
calibrationTimer.interval = realValue * 1000;
|
||||
openVrConfiguration.countDown = realValue;
|
||||
numberAnimation.duration = calibrationTimer.interval;
|
||||
openVrConfiguration.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4050,8 +4050,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
_snapshotSoundInjector->setOptions(options);
|
||||
_snapshotSoundInjector->restart();
|
||||
} else {
|
||||
QByteArray samples = _snapshotSound->getByteArray();
|
||||
_snapshotSoundInjector = AudioInjector::playSound(samples, options);
|
||||
_snapshotSoundInjector = AudioInjector::playSound(_snapshotSound, options);
|
||||
}
|
||||
}
|
||||
takeSnapshot(true);
|
||||
|
@ -4159,6 +4158,10 @@ void Application::focusOutEvent(QFocusEvent* event) {
|
|||
SpacemouseManager::getInstance().ManagerFocusOutEvent();
|
||||
#endif
|
||||
|
||||
synthesizeKeyReleasEvents();
|
||||
}
|
||||
|
||||
void Application::synthesizeKeyReleasEvents() {
|
||||
// synthesize events for keys currently pressed, since we may not get their release events
|
||||
// Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy,
|
||||
// clearing the existing list.
|
||||
|
@ -4784,6 +4787,7 @@ void Application::idle() {
|
|||
if (_keyboardDeviceHasFocus && activeFocusItem != offscreenUi->getRootItem()) {
|
||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||
_keyboardDeviceHasFocus = false;
|
||||
synthesizeKeyReleasEvents();
|
||||
} else if (activeFocusItem == offscreenUi->getRootItem()) {
|
||||
_keyboardDeviceHasFocus = true;
|
||||
}
|
||||
|
|
|
@ -550,6 +550,7 @@ private:
|
|||
void keyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
void focusOutEvent(QFocusEvent* event);
|
||||
void synthesizeKeyReleasEvents();
|
||||
void focusInEvent(QFocusEvent* event);
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
|
|
|
@ -141,7 +141,7 @@ Menu::Menu() {
|
|||
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
|
||||
}
|
||||
|
||||
// Edit > Package Model as .fst...
|
||||
// Edit > Package Avatar as .fst...
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
||||
qApp, SLOT(packageModel()));
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace MenuOption {
|
|||
const QString OpenVrThreadedSubmit = "OpenVR Threaded Submit";
|
||||
const QString OutputMenu = "Display";
|
||||
const QString Overlays = "Show Overlays";
|
||||
const QString PackageModel = "Package Model as .fst...";
|
||||
const QString PackageModel = "Package Avatar as .fst...";
|
||||
const QString Pair = "Pair";
|
||||
const QString PhysicsShowOwned = "Highlight Simulation Ownership";
|
||||
const QString VerboseLogging = "Verbose Logging";
|
||||
|
|
|
@ -68,7 +68,6 @@ bool ModelPackager::selectModel() {
|
|||
ModelSelector selector;
|
||||
if(selector.exec() == QDialog::Accepted) {
|
||||
_modelFile = selector.getFileInfo();
|
||||
_modelType = selector.getModelType();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -122,28 +121,26 @@ bool ModelPackager::loadModel() {
|
|||
|
||||
bool ModelPackager::editProperties() {
|
||||
// open the dialog to configure the rest
|
||||
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_hfmModel);
|
||||
ModelPropertiesDialog properties(_mapping, _modelFile.path(), *_hfmModel);
|
||||
if (properties.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
_mapping = properties.getMapping();
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << "root joint not configured for skeleton.";
|
||||
// Make sure that a mapping for the root joint has been specified
|
||||
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointRoot")) {
|
||||
qWarning() << "root joint not configured for skeleton.";
|
||||
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Packager");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled.";
|
||||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle("Model Packager");
|
||||
msgBox.setText(message);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.exec();
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -237,8 +234,6 @@ bool ModelPackager::zipModel() {
|
|||
|
||||
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const hfm::Model& hfmModel) {
|
||||
|
||||
bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL;
|
||||
|
||||
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
||||
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
||||
bool likelyMixamoFile = hfmModel.applicationName == "mixamo.com" ||
|
||||
|
@ -279,19 +274,17 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
joints.insert("jointNeck", hfmModel.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");
|
||||
}
|
||||
|
||||
if (isBodyType) {
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
|
||||
if (!joints.contains("jointHead")) {
|
||||
|
@ -301,13 +294,11 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
|
||||
if (isBodyType) {
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
|
||||
// If there are no blendshape mappings, and we detect that this is likely a mixamo file,
|
||||
|
|
|
@ -41,7 +41,6 @@ private:
|
|||
|
||||
QFileInfo _modelFile;
|
||||
QFileInfo _fbxInfo;
|
||||
FSTReader::ModelType _modelType;
|
||||
QString _texDir;
|
||||
QString _scriptDir;
|
||||
|
||||
|
|
|
@ -26,9 +26,8 @@
|
|||
#include <OffscreenUi.h>
|
||||
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(const QVariantHash& originalMapping,
|
||||
const QString& basePath, const HFMModel& hfmModel) :
|
||||
_modelType(modelType),
|
||||
_originalMapping(originalMapping),
|
||||
_basePath(basePath),
|
||||
_hfmModel(hfmModel)
|
||||
|
@ -50,36 +49,19 @@ _hfmModel(hfmModel)
|
|||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setSingleStep(0.01);
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
QHBoxLayout* translation = new QHBoxLayout();
|
||||
form->addRow("Translation:", translation);
|
||||
translation->addWidget(_translationX = createTranslationBox());
|
||||
translation->addWidget(_translationY = createTranslationBox());
|
||||
translation->addWidget(_translationZ = createTranslationBox());
|
||||
form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox());
|
||||
form->addRow("Pivot Joint:", _pivotJoint = createJointBox());
|
||||
connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint()));
|
||||
_pivotAboutCenter->setChecked(true);
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||
|
||||
} else {
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
}
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||
|
||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||
_freeJoints->addWidget(newFreeJoint);
|
||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||
}
|
||||
}
|
||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||
_freeJoints->addWidget(newFreeJoint);
|
||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
|
||||
|
@ -93,14 +75,9 @@ _hfmModel(hfmModel)
|
|||
reset();
|
||||
}
|
||||
|
||||
|
||||
QString ModelPropertiesDialog::getType() const {
|
||||
return FSTReader::getNameFromType(_modelType);
|
||||
}
|
||||
|
||||
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||
QVariantHash mapping = _originalMapping;
|
||||
mapping.insert(TYPE_FIELD, getType());
|
||||
mapping.insert(TYPE_FIELD, FSTReader::getNameFromType(FSTReader::HEAD_AND_BODY_MODEL));
|
||||
mapping.insert(NAME_FIELD, _name->text());
|
||||
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||
mapping.insert(SCRIPT_FIELD, _scriptDirectory->text());
|
||||
|
@ -113,42 +90,24 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
|
|||
}
|
||||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
glm::vec3 pivot;
|
||||
if (_pivotAboutCenter->isChecked()) {
|
||||
pivot = (_hfmModel.meshExtents.minimum + _hfmModel.meshExtents.maximum) * 0.5f;
|
||||
|
||||
} else if (_pivotJoint->currentIndex() != 0) {
|
||||
pivot = extractTranslation(_hfmModel.joints.at(_pivotJoint->currentIndex() - 1).transform);
|
||||
}
|
||||
mapping.insert(TRANSLATION_X_FIELD, -pivot.x * (float)_scale->value() + (float)_translationX->value());
|
||||
mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * (float)_scale->value() + (float)_translationY->value());
|
||||
mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * (float)_scale->value() + (float)_translationZ->value());
|
||||
|
||||
} else {
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
}
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||
|
||||
mapping.remove(FREE_JOINT_FIELD);
|
||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||
}
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
mapping.remove(FREE_JOINT_FIELD);
|
||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
@ -165,36 +124,23 @@ void ModelPropertiesDialog::reset() {
|
|||
|
||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||
|
||||
if (_modelType != FSTReader::ENTITY_MODEL) {
|
||||
if (_modelType == FSTReader::ATTACHMENT_MODEL) {
|
||||
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
|
||||
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
|
||||
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
|
||||
_pivotAboutCenter->setChecked(true);
|
||||
_pivotJoint->setCurrentIndex(0);
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
|
||||
} else {
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
}
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||
|
||||
if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) {
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||
|
||||
while (_freeJoints->count() > 1) {
|
||||
delete _freeJoints->itemAt(0)->widget();
|
||||
}
|
||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||
QString jointName = joint.toString();
|
||||
if (_hfmModel.jointIndices.contains(jointName)) {
|
||||
createNewFreeJoint(jointName);
|
||||
}
|
||||
}
|
||||
while (_freeJoints->count() > 1) {
|
||||
delete _freeJoints->itemAt(0)->widget();
|
||||
}
|
||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||
QString jointName = joint.toString();
|
||||
if (_hfmModel.jointIndices.contains(jointName)) {
|
||||
createNewFreeJoint(jointName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ class ModelPropertiesDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping,
|
||||
ModelPropertiesDialog(const QVariantHash& originalMapping,
|
||||
const QString& basePath, const HFMModel& hfmModel);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
@ -45,9 +45,7 @@ private:
|
|||
QComboBox* createJointBox(bool withNone = true) const;
|
||||
QDoubleSpinBox* createTranslationBox() const;
|
||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||
QString getType() const;
|
||||
|
||||
FSTReader::ModelType _modelType;
|
||||
QVariantHash _originalMapping;
|
||||
QString _basePath;
|
||||
HFMModel _hfmModel;
|
||||
|
@ -71,4 +69,4 @@ private:
|
|||
QVBoxLayout* _freeJoints = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelPropertiesDialog_h
|
||||
#endif // hifi_ModelPropertiesDialog_h
|
||||
|
|
|
@ -27,18 +27,11 @@ ModelSelector::ModelSelector() {
|
|||
|
||||
setWindowTitle("Select Model");
|
||||
setLayout(form);
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
_browseButton = new QPushButton("Browse", this);
|
||||
connect(_browseButton, &QPushButton::clicked, this, &ModelSelector::browse);
|
||||
form->addRow("Model File:", _browseButton);
|
||||
|
||||
_modelType = new QComboBox(this);
|
||||
|
||||
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||
form->addRow("Model Type:", _modelType);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ModelSelector::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
@ -49,19 +42,6 @@ QFileInfo ModelSelector::getFileInfo() const {
|
|||
return _modelFile;
|
||||
}
|
||||
|
||||
FSTReader::ModelType ModelSelector::getModelType() const {
|
||||
QString text = _modelType->currentText();
|
||||
|
||||
if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
return FSTReader::HEAD_AND_BODY_MODEL;
|
||||
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||
return FSTReader::ATTACHMENT_MODEL;
|
||||
} else if (text == ENTITY_MODEL_STRING) {
|
||||
return FSTReader::ENTITY_MODEL;
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
void ModelSelector::accept() {
|
||||
if (!_modelFile.isFile()) {
|
||||
return;
|
||||
|
|
|
@ -29,7 +29,6 @@ public:
|
|||
ModelSelector();
|
||||
|
||||
QFileInfo getFileInfo() const;
|
||||
FSTReader::ModelType getModelType() const;
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
@ -40,7 +39,6 @@ public:
|
|||
private:
|
||||
QFileInfo _modelFile;
|
||||
QPushButton* _browseButton;
|
||||
QComboBox* _modelType;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelSelector_h
|
||||
|
|
|
@ -562,8 +562,13 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
|||
|
||||
static const int MAX_INJECTOR_COUNT = 3;
|
||||
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
||||
auto injector = AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR,
|
||||
myAvatar->getWorldPosition());
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = myAvatar->getWorldPosition();
|
||||
options.volume = energyFactorOfFull;
|
||||
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
||||
|
||||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
_collisionInjectors.emplace_back(injector);
|
||||
}
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
|
|
|
@ -147,7 +147,11 @@ void TTSScriptingInterface::speakText(const QString& textToSpeak) {
|
|||
_lastSoundAudioInjectorUpdateTimer.stop();
|
||||
}
|
||||
|
||||
_lastSoundAudioInjector = AudioInjector::playSoundAndDelete(_lastSoundByteArray, options);
|
||||
uint32_t numChannels = 1;
|
||||
uint32_t numSamples = (uint32_t)_lastSoundByteArray.size() / sizeof(AudioData::AudioSample);
|
||||
auto samples = reinterpret_cast<AudioData::AudioSample*>(_lastSoundByteArray.data());
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, samples);
|
||||
_lastSoundAudioInjector = AudioInjector::playSoundAndDelete(newAudioData, options);
|
||||
|
||||
_lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
|
||||
#else
|
||||
|
|
|
@ -485,7 +485,7 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent
|
|||
audioOptions.position = keyWorldPosition;
|
||||
audioOptions.volume = 0.1f;
|
||||
|
||||
AudioInjector::playSound(_keySound->getByteArray(), audioOptions);
|
||||
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
|
||||
|
||||
int scanCode = key.getScanCode(_capsEnabled);
|
||||
QString keyString = key.getKeyString(_capsEnabled);
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace AudioConstants {
|
|||
const int STEREO = 2;
|
||||
const int AMBISONIC = 4;
|
||||
|
||||
typedef int16_t AudioSample;
|
||||
using AudioSample = int16_t;
|
||||
const int SAMPLE_SIZE = sizeof(AudioSample);
|
||||
|
||||
inline const char* getAudioFrameName() { return "com.highfidelity.recording.Audio"; }
|
||||
|
|
|
@ -38,12 +38,14 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
|
|||
return lhs;
|
||||
};
|
||||
|
||||
AudioInjector::AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions) :
|
||||
AudioInjector(sound.getByteArray(), injectorOptions)
|
||||
AudioInjector::AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) :
|
||||
_sound(sound),
|
||||
_audioData(sound->getAudioData()),
|
||||
_options(injectorOptions)
|
||||
{
|
||||
}
|
||||
|
||||
AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) :
|
||||
AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOptions& injectorOptions) :
|
||||
_audioData(audioData),
|
||||
_options(injectorOptions)
|
||||
{
|
||||
|
@ -154,7 +156,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj
|
|||
bool AudioInjector::injectLocally() {
|
||||
bool success = false;
|
||||
if (_localAudioInterface) {
|
||||
if (_audioData.size() > 0) {
|
||||
if (_audioData->getNumBytes() > 0) {
|
||||
|
||||
_localBuffer = new AudioInjectorLocalBuffer(_audioData);
|
||||
|
||||
|
@ -220,22 +222,12 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
if (!_currentPacket) {
|
||||
if (_currentSendOffset < 0 ||
|
||||
_currentSendOffset >= _audioData.size()) {
|
||||
_currentSendOffset >= (int)_audioData->getNumBytes()) {
|
||||
_currentSendOffset = 0;
|
||||
}
|
||||
|
||||
// make sure we actually have samples downloaded to inject
|
||||
if (_audioData.size()) {
|
||||
|
||||
int sampleSize = (_options.stereo ? 2 : 1) * sizeof(AudioConstants::AudioSample);
|
||||
auto numSamples = static_cast<int>(_audioData.size() / sampleSize);
|
||||
auto targetSize = numSamples * sampleSize;
|
||||
if (targetSize != _audioData.size()) {
|
||||
qCDebug(audio) << "Resizing audio that doesn't end at multiple of sample size, resizing from "
|
||||
<< _audioData.size() << " to " << targetSize;
|
||||
_audioData.resize(targetSize);
|
||||
}
|
||||
|
||||
if (_audioData && _audioData->getNumSamples() > 0) {
|
||||
_outgoingSequenceNumber = 0;
|
||||
_nextFrame = 0;
|
||||
|
||||
|
@ -307,19 +299,10 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
_frameTimer->restart();
|
||||
}
|
||||
|
||||
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
if (!_options.loop) {
|
||||
// If we aren't looping, let's make sure we don't read past the end
|
||||
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset);
|
||||
}
|
||||
|
||||
// Measure the loudness of this frame
|
||||
_loudness = 0.0f;
|
||||
for (int i = 0; i < totalBytesLeftToCopy; i += sizeof(int16_t)) {
|
||||
_loudness += abs(*reinterpret_cast<int16_t*>(_audioData.data() + ((_currentSendOffset + i) % _audioData.size()))) /
|
||||
(AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||
}
|
||||
_loudness /= (float)(totalBytesLeftToCopy/ sizeof(int16_t));
|
||||
assert(loopbackOptionOffset != -1);
|
||||
assert(positionOptionOffset != -1);
|
||||
assert(volumeOptionOffset != -1);
|
||||
assert(audioDataOffset != -1);
|
||||
|
||||
_currentPacket->seek(0);
|
||||
|
||||
|
@ -339,19 +322,37 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
_currentPacket->seek(audioDataOffset);
|
||||
|
||||
// This code is copying bytes from the _audioData directly into the packet, handling looping appropriately.
|
||||
// This code is copying bytes from the _sound directly into the packet, handling looping appropriately.
|
||||
// Might be a reasonable place to do the encode step here.
|
||||
QByteArray decodedAudio;
|
||||
while (totalBytesLeftToCopy > 0) {
|
||||
int bytesToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset);
|
||||
|
||||
decodedAudio.append(_audioData.data() + _currentSendOffset, bytesToCopy);
|
||||
_currentSendOffset += bytesToCopy;
|
||||
totalBytesLeftToCopy -= bytesToCopy;
|
||||
if (_options.loop && _currentSendOffset >= _audioData.size()) {
|
||||
_currentSendOffset = 0;
|
||||
}
|
||||
int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
if (!_options.loop) {
|
||||
// If we aren't looping, let's make sure we don't read past the end
|
||||
int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset;
|
||||
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead);
|
||||
}
|
||||
|
||||
auto samples = _audioData->data();
|
||||
auto currentSample = _currentSendOffset / AudioConstants::SAMPLE_SIZE;
|
||||
auto samplesLeftToCopy = totalBytesLeftToCopy / AudioConstants::SAMPLE_SIZE;
|
||||
|
||||
using AudioConstants::AudioSample;
|
||||
decodedAudio.resize(totalBytesLeftToCopy);
|
||||
auto samplesOut = reinterpret_cast<AudioSample*>(decodedAudio.data());
|
||||
|
||||
// Copy and Measure the loudness of this frame
|
||||
_loudness = 0.0f;
|
||||
for (int i = 0; i < samplesLeftToCopy; ++i) {
|
||||
auto index = (currentSample + i) % _audioData->getNumSamples();
|
||||
auto sample = samples[index];
|
||||
samplesOut[i] = sample;
|
||||
_loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
|
||||
}
|
||||
_loudness /= (float)samplesLeftToCopy;
|
||||
_currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) %
|
||||
_audioData->getNumBytes();
|
||||
|
||||
// FIXME -- good place to call codec encode here. We need to figure out how to tell the AudioInjector which
|
||||
// codec to use... possible through AbstractAudioInterface.
|
||||
QByteArray encodedAudio = decodedAudio;
|
||||
|
@ -370,7 +371,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
_outgoingSequenceNumber++;
|
||||
}
|
||||
|
||||
if (_currentSendOffset >= _audioData.size() && !_options.loop) {
|
||||
if (_currentSendOffset == 0 && !_options.loop) {
|
||||
finishNetworkInjection();
|
||||
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
||||
}
|
||||
|
@ -390,7 +391,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
// If we are falling behind by more frames than our threshold, let's skip the frames ahead
|
||||
qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames";
|
||||
_nextFrame = currentFrameBasedOnElapsedTime;
|
||||
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData.size();
|
||||
_currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData->getNumBytes();
|
||||
}
|
||||
|
||||
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
|
||||
|
@ -417,38 +418,25 @@ void AudioInjector::triggerDeleteAfterFinish() {
|
|||
}
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const float volume,
|
||||
const float stretchFactor, const glm::vec3 position) {
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
||||
AudioInjectorPointer injector = playSound(sound, options);
|
||||
|
||||
if (injector) {
|
||||
injector->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const AudioInjectorOptions& options) {
|
||||
if (!sound || !sound->isReady()) {
|
||||
return AudioInjectorPointer();
|
||||
}
|
||||
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = sound->isStereo();
|
||||
options.position = position;
|
||||
options.volume = volume;
|
||||
options.pitch = 1.0f / stretchFactor;
|
||||
|
||||
QByteArray samples = sound->getByteArray();
|
||||
|
||||
return playSoundAndDelete(samples, options);
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
AudioInjectorPointer sound = playSound(buffer, options);
|
||||
|
||||
if (sound) {
|
||||
sound->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) {
|
||||
|
||||
if (options.pitch == 1.0f) {
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options);
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(sound, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread injector";
|
||||
|
@ -456,24 +444,31 @@ AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const Au
|
|||
return injector;
|
||||
|
||||
} else {
|
||||
using AudioConstants::AudioSample;
|
||||
using AudioConstants::SAMPLE_RATE;
|
||||
const int standardRate = SAMPLE_RATE;
|
||||
// limit to 4 octaves
|
||||
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||
const int resampledRate = SAMPLE_RATE / pitch;
|
||||
|
||||
const int standardRate = AudioConstants::SAMPLE_RATE;
|
||||
const int resampledRate = AudioConstants::SAMPLE_RATE / glm::clamp(options.pitch, 1/16.0f, 16.0f); // limit to 4 octaves
|
||||
const int numChannels = options.ambisonic ? AudioConstants::AMBISONIC :
|
||||
(options.stereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
auto audioData = sound->getAudioData();
|
||||
auto numChannels = audioData->getNumChannels();
|
||||
auto numFrames = audioData->getNumFrames();
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||
|
||||
// create a resampled buffer that is guaranteed to be large enough
|
||||
const int nInputFrames = buffer.size() / (numChannels * sizeof(int16_t));
|
||||
const int maxOutputFrames = resampler.getMaxOutput(nInputFrames);
|
||||
QByteArray resampledBuffer(maxOutputFrames * numChannels * sizeof(int16_t), '\0');
|
||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||
|
||||
resampler.render(reinterpret_cast<const int16_t*>(buffer.data()),
|
||||
reinterpret_cast<int16_t*>(resampledBuffer.data()),
|
||||
nInputFrames);
|
||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(resampledBuffer, options);
|
||||
int numSamples = maxOutputFrames * numChannels;
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(newAudioData, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
||||
|
@ -481,3 +476,49 @@ AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const Au
|
|||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
||||
AudioInjectorPointer injector = playSound(audioData, options);
|
||||
|
||||
if (injector) {
|
||||
injector->_state |= AudioInjectorState::PendingDelete;
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const AudioInjectorOptions& options) {
|
||||
if (options.pitch == 1.0f) {
|
||||
AudioInjectorPointer injector = AudioInjectorPointer::create(audioData, options);
|
||||
|
||||
if (!injector->inject(&AudioInjectorManager::threadInjector)) {
|
||||
qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
|
||||
}
|
||||
return injector;
|
||||
} else {
|
||||
using AudioConstants::AudioSample;
|
||||
using AudioConstants::SAMPLE_RATE;
|
||||
const int standardRate = SAMPLE_RATE;
|
||||
// limit to 4 octaves
|
||||
const int pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
|
||||
const int resampledRate = SAMPLE_RATE / pitch;
|
||||
|
||||
auto numChannels = audioData->getNumChannels();
|
||||
auto numFrames = audioData->getNumFrames();
|
||||
|
||||
AudioSRC resampler(standardRate, resampledRate, numChannels);
|
||||
|
||||
// create a resampled buffer that is guaranteed to be large enough
|
||||
const int maxOutputFrames = resampler.getMaxOutput(numFrames);
|
||||
const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
|
||||
QByteArray resampledBuffer(maxOutputSize, '\0');
|
||||
auto bufferPtr = reinterpret_cast<AudioSample*>(resampledBuffer.data());
|
||||
|
||||
resampler.render(audioData->data(), bufferPtr, numFrames);
|
||||
|
||||
int numSamples = maxOutputFrames * numChannels;
|
||||
auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
|
||||
|
||||
return AudioInjector::playSound(newAudioData, options);
|
||||
}
|
||||
}
|
|
@ -52,8 +52,8 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
|
|||
class AudioInjector : public QObject, public QEnableSharedFromThis<AudioInjector> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(AudioDataPointer audioData, const AudioInjectorOptions& injectorOptions);
|
||||
~AudioInjector();
|
||||
|
||||
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
|
||||
|
@ -74,10 +74,11 @@ public:
|
|||
|
||||
bool stateHas(AudioInjectorState state) const ;
|
||||
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
|
||||
static AudioInjectorPointer playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||
static AudioInjectorPointer playSound(const QByteArray& buffer, const AudioInjectorOptions options);
|
||||
static AudioInjectorPointer playSound(SharedSoundPointer sound, const float volume,
|
||||
const float stretchFactor, const glm::vec3 position);
|
||||
|
||||
static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
||||
static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options);
|
||||
|
||||
public slots:
|
||||
void restart();
|
||||
|
@ -106,7 +107,8 @@ private:
|
|||
|
||||
static AbstractAudioInterface* _localAudioInterface;
|
||||
|
||||
QByteArray _audioData;
|
||||
const SharedSoundPointer _sound;
|
||||
AudioDataPointer _audioData;
|
||||
AudioInjectorOptions _options;
|
||||
AudioInjectorState _state { AudioInjectorState::NotFinished };
|
||||
bool _hasSentFirstFrame { false };
|
||||
|
|
|
@ -11,13 +11,9 @@
|
|||
|
||||
#include "AudioInjectorLocalBuffer.h"
|
||||
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) :
|
||||
_rawAudioArray(rawAudioArray),
|
||||
_shouldLoop(false),
|
||||
_isStopped(false),
|
||||
_currentOffset(0)
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) :
|
||||
_audioData(audioData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioInjectorLocalBuffer::stop() {
|
||||
|
@ -39,7 +35,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
if (!_isStopped) {
|
||||
|
||||
// first copy to the end of the raw audio
|
||||
int bytesToEnd = _rawAudioArray.size() - _currentOffset;
|
||||
int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset;
|
||||
|
||||
int bytesRead = maxSize;
|
||||
|
||||
|
@ -47,7 +43,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
bytesRead = bytesToEnd;
|
||||
}
|
||||
|
||||
memcpy(data, _rawAudioArray.data() + _currentOffset, bytesRead);
|
||||
memcpy(data, _audioData->rawData() + _currentOffset, bytesRead);
|
||||
|
||||
// now check if we are supposed to loop and if we can copy more from the beginning
|
||||
if (_shouldLoop && maxSize != bytesRead) {
|
||||
|
@ -56,7 +52,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
|
|||
_currentOffset += bytesRead;
|
||||
}
|
||||
|
||||
if (_shouldLoop && _currentOffset == _rawAudioArray.size()) {
|
||||
if (_shouldLoop && _currentOffset == (int)_audioData->getNumBytes()) {
|
||||
_currentOffset = 0;
|
||||
}
|
||||
|
||||
|
@ -70,12 +66,12 @@ qint64 AudioInjectorLocalBuffer::recursiveReadFromFront(char* data, qint64 maxSi
|
|||
// see how much we can get in this pass
|
||||
int bytesRead = maxSize;
|
||||
|
||||
if (bytesRead > _rawAudioArray.size()) {
|
||||
bytesRead = _rawAudioArray.size();
|
||||
if (bytesRead > (int)_audioData->getNumBytes()) {
|
||||
bytesRead = _audioData->getNumBytes();
|
||||
}
|
||||
|
||||
// copy that amount
|
||||
memcpy(data, _rawAudioArray.data(), bytesRead);
|
||||
memcpy(data, _audioData->rawData(), bytesRead);
|
||||
|
||||
// check if we need to call ourselves again and pull from the front again
|
||||
if (bytesRead < maxSize) {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
#include <glm/common.hpp>
|
||||
|
||||
#include "Sound.h"
|
||||
|
||||
class AudioInjectorLocalBuffer : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
|
||||
AudioInjectorLocalBuffer(AudioDataPointer audioData);
|
||||
|
||||
void stop();
|
||||
|
||||
|
@ -34,11 +36,10 @@ public:
|
|||
private:
|
||||
qint64 recursiveReadFromFront(char* data, qint64 maxSize);
|
||||
|
||||
QByteArray _rawAudioArray;
|
||||
bool _shouldLoop;
|
||||
bool _isStopped;
|
||||
|
||||
int _currentOffset;
|
||||
AudioDataPointer _audioData;
|
||||
bool _shouldLoop { false };
|
||||
bool _isStopped { false };
|
||||
int _currentOffset { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_AudioInjectorLocalBuffer_h
|
||||
|
|
|
@ -33,48 +33,59 @@
|
|||
|
||||
#include "flump3dec.h"
|
||||
|
||||
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) {
|
||||
return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership);
|
||||
int audioDataPointerMetaTypeID = qRegisterMetaType<AudioDataPointer>("AudioDataPointer");
|
||||
|
||||
using AudioConstants::AudioSample;
|
||||
|
||||
AudioDataPointer AudioData::make(uint32_t numSamples, uint32_t numChannels,
|
||||
const AudioSample* samples) {
|
||||
// Compute the amount of memory required for the audio data object
|
||||
const size_t bufferSize = numSamples * sizeof(AudioSample);
|
||||
const size_t memorySize = sizeof(AudioData) + bufferSize;
|
||||
|
||||
// Allocate the memory for the audio data object and the buffer
|
||||
void* memory = ::malloc(memorySize);
|
||||
auto audioData = reinterpret_cast<AudioData*>(memory);
|
||||
auto buffer = reinterpret_cast<AudioSample*>(audioData + 1);
|
||||
assert(((char*)buffer - (char*)audioData) == sizeof(AudioData));
|
||||
|
||||
// Use placement new to construct the audio data object at the memory allocated
|
||||
::new(audioData) AudioData(numSamples, numChannels, buffer);
|
||||
|
||||
// Copy the samples to the buffer
|
||||
memcpy(buffer, samples, bufferSize);
|
||||
|
||||
// Return shared_ptr that properly destruct the object and release the memory
|
||||
return AudioDataPointer(audioData, [](AudioData* ptr) {
|
||||
ptr->~AudioData();
|
||||
::free(ptr);
|
||||
});
|
||||
}
|
||||
|
||||
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) {
|
||||
if (auto soundInterface = qobject_cast<SoundScriptingInterface*>(object.toQObject())) {
|
||||
out = soundInterface->getSound();
|
||||
}
|
||||
}
|
||||
|
||||
SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
|
||||
// During shutdown we can sometimes get an empty sound pointer back
|
||||
if (_sound) {
|
||||
QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
|
||||
Resource(url),
|
||||
_isStereo(isStereo),
|
||||
_isAmbisonic(isAmbisonic),
|
||||
_isReady(false)
|
||||
{
|
||||
}
|
||||
AudioData::AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSample* samples)
|
||||
: _numSamples(numSamples),
|
||||
_numChannels(numChannels),
|
||||
_data(samples)
|
||||
{}
|
||||
|
||||
void Sound::downloadFinished(const QByteArray& data) {
|
||||
if (!_self) {
|
||||
soundProcessError(301, "Sound object has gone out of scope");
|
||||
return;
|
||||
}
|
||||
|
||||
// this is a QRunnable, will delete itself after it has finished running
|
||||
SoundProcessor* soundProcessor = new SoundProcessor(_url, data, _isStereo, _isAmbisonic);
|
||||
auto soundProcessor = new SoundProcessor(_self, data);
|
||||
connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess);
|
||||
connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError);
|
||||
QThreadPool::globalInstance()->start(soundProcessor);
|
||||
}
|
||||
|
||||
void Sound::soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration) {
|
||||
void Sound::soundProcessSuccess(AudioDataPointer audioData) {
|
||||
qCDebug(audio) << "Setting ready state for sound file" << _url.fileName();
|
||||
|
||||
qCDebug(audio) << "Setting ready state for sound file";
|
||||
|
||||
_byteArray = data;
|
||||
_isStereo = stereo;
|
||||
_isAmbisonic = ambisonic;
|
||||
_duration = duration;
|
||||
_isReady = true;
|
||||
_audioData = std::move(audioData);
|
||||
finishedLoading(true);
|
||||
|
||||
emit ready();
|
||||
|
@ -86,91 +97,101 @@ void Sound::soundProcessError(int error, QString str) {
|
|||
finishedLoading(false);
|
||||
}
|
||||
|
||||
|
||||
SoundProcessor::SoundProcessor(QWeakPointer<Resource> sound, QByteArray data) :
|
||||
_sound(sound),
|
||||
_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
void SoundProcessor::run() {
|
||||
auto sound = qSharedPointerCast<Sound>(_sound.lock());
|
||||
if (!sound) {
|
||||
emit onError(301, "Sound object has gone out of scope");
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(audio) << "Processing sound file";
|
||||
|
||||
// replace our byte array with the downloaded data
|
||||
QByteArray rawAudioByteArray = QByteArray(_data);
|
||||
QString fileName = _url.fileName().toLower();
|
||||
auto url = sound->getURL();
|
||||
QString fileName = url.fileName().toLower();
|
||||
qCDebug(audio) << "Processing sound file" << fileName;
|
||||
|
||||
static const QString WAV_EXTENSION = ".wav";
|
||||
static const QString MP3_EXTENSION = ".mp3";
|
||||
static const QString RAW_EXTENSION = ".raw";
|
||||
static const QString STEREO_RAW_EXTENSION = ".stereo.raw";
|
||||
QString fileType;
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
AudioProperties properties;
|
||||
|
||||
if (fileName.endsWith(WAV_EXTENSION)) {
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
|
||||
int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray);
|
||||
if (sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported WAV file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported WAV file type");
|
||||
return;
|
||||
}
|
||||
|
||||
downSample(outputAudioByteArray, sampleRate);
|
||||
|
||||
fileType = "WAV";
|
||||
properties = interpretAsWav(_data, outputAudioByteArray);
|
||||
} else if (fileName.endsWith(MP3_EXTENSION)) {
|
||||
|
||||
QByteArray outputAudioByteArray;
|
||||
|
||||
int sampleRate = interpretAsMP3(rawAudioByteArray, outputAudioByteArray);
|
||||
if (sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported MP3 file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported MP3 file type");
|
||||
return;
|
||||
}
|
||||
|
||||
downSample(outputAudioByteArray, sampleRate);
|
||||
|
||||
} else if (fileName.endsWith(RAW_EXTENSION)) {
|
||||
fileType = "MP3";
|
||||
properties = interpretAsMP3(_data, outputAudioByteArray);
|
||||
} else if (fileName.endsWith(STEREO_RAW_EXTENSION)) {
|
||||
// check if this was a stereo raw file
|
||||
// since it's raw the only way for us to know that is if the file was called .stereo.raw
|
||||
if (fileName.toLower().endsWith("stereo.raw")) {
|
||||
_isStereo = true;
|
||||
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes as stereo audio file.";
|
||||
}
|
||||
|
||||
qCDebug(audio) << "Processing sound of" << _data.size() << "bytes from" << fileName << "as stereo audio file.";
|
||||
// Process as 48khz RAW file
|
||||
downSample(rawAudioByteArray, 48000);
|
||||
|
||||
properties.numChannels = 2;
|
||||
properties.sampleRate = 48000;
|
||||
outputAudioByteArray = _data;
|
||||
} else if (fileName.endsWith(RAW_EXTENSION)) {
|
||||
// Process as 48khz RAW file
|
||||
properties.numChannels = 1;
|
||||
properties.sampleRate = 48000;
|
||||
outputAudioByteArray = _data;
|
||||
} else {
|
||||
qCWarning(audio) << "Unknown sound file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unknown sound file type");
|
||||
return;
|
||||
}
|
||||
|
||||
emit onSuccess(_data, _isStereo, _isAmbisonic, _duration);
|
||||
if (properties.sampleRate == 0) {
|
||||
qCWarning(audio) << "Unsupported" << fileType << "file type";
|
||||
emit onError(300, "Failed to load sound file, reason: unsupported " + fileType + " file type");
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = downSample(outputAudioByteArray, properties);
|
||||
|
||||
int numSamples = data.size() / AudioConstants::SAMPLE_SIZE;
|
||||
auto audioData = AudioData::make(numSamples, properties.numChannels,
|
||||
(const AudioSample*)data.constData());
|
||||
emit onSuccess(audioData);
|
||||
}
|
||||
|
||||
void SoundProcessor::downSample(const QByteArray& rawAudioByteArray, int sampleRate) {
|
||||
QByteArray SoundProcessor::downSample(const QByteArray& rawAudioByteArray,
|
||||
AudioProperties properties) {
|
||||
|
||||
// we want to convert it to the format that the audio-mixer wants
|
||||
// which is signed, 16-bit, 24Khz
|
||||
|
||||
if (sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
if (properties.sampleRate == AudioConstants::SAMPLE_RATE) {
|
||||
// no resampling needed
|
||||
_data = rawAudioByteArray;
|
||||
} else {
|
||||
|
||||
int numChannels = _isAmbisonic ? AudioConstants::AMBISONIC : (_isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
AudioSRC resampler(sampleRate, AudioConstants::SAMPLE_RATE, numChannels);
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (numChannels * sizeof(AudioConstants::AudioSample));
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_data.resize(maxDestinationBytes);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)_data.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * numChannels * sizeof(AudioConstants::AudioSample);
|
||||
_data.resize(numDestinationBytes);
|
||||
return rawAudioByteArray;
|
||||
}
|
||||
|
||||
AudioSRC resampler(properties.sampleRate, AudioConstants::SAMPLE_RATE,
|
||||
properties.numChannels);
|
||||
|
||||
// resize to max possible output
|
||||
int numSourceFrames = rawAudioByteArray.size() / (properties.numChannels * AudioConstants::SAMPLE_SIZE);
|
||||
int maxDestinationFrames = resampler.getMaxOutput(numSourceFrames);
|
||||
int maxDestinationBytes = maxDestinationFrames * properties.numChannels * AudioConstants::SAMPLE_SIZE;
|
||||
QByteArray data(maxDestinationBytes, Qt::Uninitialized);
|
||||
|
||||
int numDestinationFrames = resampler.render((int16_t*)rawAudioByteArray.data(),
|
||||
(int16_t*)data.data(),
|
||||
numSourceFrames);
|
||||
|
||||
// truncate to actual output
|
||||
int numDestinationBytes = numDestinationFrames * properties.numChannels * sizeof(AudioSample);
|
||||
data.resize(numDestinationBytes);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -218,7 +239,9 @@ struct WAVEFormat {
|
|||
};
|
||||
|
||||
// returns wavfile sample rate, used for resampling
|
||||
int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
SoundProcessor::AudioProperties SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray) {
|
||||
AudioProperties properties;
|
||||
|
||||
// Create a data stream to analyze the data
|
||||
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
|
||||
|
@ -227,7 +250,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
RIFFHeader riff;
|
||||
if (waveStream.readRawData((char*)&riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Parse the "RIFF" chunk
|
||||
|
@ -235,11 +258,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
waveStream.setByteOrder(QDataStream::LittleEndian);
|
||||
} else {
|
||||
qCWarning(audio) << "Currently not supporting big-endian audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(riff.type, "WAVE", 4) != 0) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Read chunks until the "fmt " chunk is found
|
||||
|
@ -247,7 +270,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
while (true) {
|
||||
if (waveStream.readRawData((char*)&fmt, sizeof(chunk)) != sizeof(chunk)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(fmt.id, "fmt ", 4) == 0) {
|
||||
break;
|
||||
|
@ -259,26 +282,26 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
WAVEFormat wave;
|
||||
if (waveStream.readRawData((char*)&wave, sizeof(WAVEFormat)) != sizeof(WAVEFormat)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Parse the "fmt " chunk
|
||||
if (qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_PCM &&
|
||||
qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_EXTENSIBLE) {
|
||||
qCWarning(audio) << "Currently not supporting non PCM audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (qFromLittleEndian<quint16>(wave.numChannels) == 2) {
|
||||
_isStereo = true;
|
||||
} else if (qFromLittleEndian<quint16>(wave.numChannels) == 4) {
|
||||
_isAmbisonic = true;
|
||||
} else if (qFromLittleEndian<quint16>(wave.numChannels) != 1) {
|
||||
|
||||
properties.numChannels = qFromLittleEndian<quint16>(wave.numChannels);
|
||||
if (properties.numChannels != 1 &&
|
||||
properties.numChannels != 2 &&
|
||||
properties.numChannels != 4) {
|
||||
qCWarning(audio) << "Currently not supporting audio files with other than 1/2/4 channels.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (qFromLittleEndian<quint16>(wave.bitsPerSample) != 16) {
|
||||
qCWarning(audio) << "Currently not supporting non 16bit audio files.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// Skip any extra data in the "fmt " chunk
|
||||
|
@ -289,7 +312,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
while (true) {
|
||||
if (waveStream.readRawData((char*)&data, sizeof(chunk)) != sizeof(chunk)) {
|
||||
qCWarning(audio) << "Not a valid WAVE file.";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
if (strncmp(data.id, "data", 4) == 0) {
|
||||
break;
|
||||
|
@ -300,17 +323,21 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA
|
|||
// Read the "data" chunk
|
||||
quint32 outputAudioByteArraySize = qFromLittleEndian<quint32>(data.size);
|
||||
outputAudioByteArray.resize(outputAudioByteArraySize);
|
||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
||||
auto bytesRead = waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize);
|
||||
if (bytesRead != (int)outputAudioByteArraySize) {
|
||||
qCWarning(audio) << "Error reading WAV file";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
_duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f));
|
||||
return wave.sampleRate;
|
||||
properties.sampleRate = wave.sampleRate;
|
||||
return properties;
|
||||
}
|
||||
|
||||
// returns MP3 sample rate, used for resampling
|
||||
int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||
SoundProcessor::AudioProperties SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray) {
|
||||
AudioProperties properties;
|
||||
|
||||
using namespace flump3dec;
|
||||
|
||||
static const int MP3_SAMPLES_MAX = 1152;
|
||||
|
@ -321,21 +348,19 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
// create bitstream
|
||||
Bit_stream_struc *bitstream = bs_new();
|
||||
if (bitstream == nullptr) {
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// create decoder
|
||||
mp3tl *decoder = mp3tl_new(bitstream, MP3TL_MODE_16BIT);
|
||||
if (decoder == nullptr) {
|
||||
bs_free(bitstream);
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
// initialize
|
||||
bs_set_data(bitstream, (uint8_t*)inputAudioByteArray.data(), inputAudioByteArray.size());
|
||||
int frameCount = 0;
|
||||
int sampleRate = 0;
|
||||
int numChannels = 0;
|
||||
|
||||
// skip ID3 tag, if present
|
||||
Mp3TlRetcode result = mp3tl_skip_id3(decoder);
|
||||
|
@ -357,8 +382,8 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
<< "channels =" << header->channels;
|
||||
|
||||
// save header info
|
||||
sampleRate = header->sample_rate;
|
||||
numChannels = header->channels;
|
||||
properties.sampleRate = header->sample_rate;
|
||||
properties.numChannels = header->channels;
|
||||
|
||||
// skip Xing header, if present
|
||||
result = mp3tl_skip_xing(decoder, header);
|
||||
|
@ -388,14 +413,32 @@ int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteA
|
|||
// free bitstream
|
||||
bs_free(bitstream);
|
||||
|
||||
int outputAudioByteArraySize = outputAudioByteArray.size();
|
||||
if (outputAudioByteArraySize == 0) {
|
||||
if (outputAudioByteArray.isEmpty()) {
|
||||
qCWarning(audio) << "Error decoding MP3 file";
|
||||
return 0;
|
||||
return AudioProperties();
|
||||
}
|
||||
|
||||
_isStereo = (numChannels == 2);
|
||||
_isAmbisonic = false;
|
||||
_duration = (float)outputAudioByteArraySize / (sampleRate * numChannels * sizeof(int16_t));
|
||||
return sampleRate;
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) {
|
||||
return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer& out) {
|
||||
if (auto soundInterface = qobject_cast<SoundScriptingInterface*>(object.toQObject())) {
|
||||
out = soundInterface->getSound();
|
||||
}
|
||||
}
|
||||
|
||||
SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
|
||||
// During shutdown we can sometimes get an empty sound pointer back
|
||||
if (_sound) {
|
||||
QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : Resource(url) {
|
||||
_numChannels = isAmbisonic ? 4 : (isStereo ? 2 : 1);
|
||||
}
|
||||
|
|
|
@ -19,61 +19,102 @@
|
|||
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include "AudioConstants.h"
|
||||
|
||||
class AudioData;
|
||||
using AudioDataPointer = std::shared_ptr<const AudioData>;
|
||||
|
||||
Q_DECLARE_METATYPE(AudioDataPointer);
|
||||
|
||||
// AudioData is designed to be immutable
|
||||
// All of its members and methods are const
|
||||
// This makes it perfectly safe to access from multiple threads at once
|
||||
class AudioData {
|
||||
public:
|
||||
using AudioSample = AudioConstants::AudioSample;
|
||||
|
||||
// Allocates the buffer memory contiguous with the object
|
||||
static AudioDataPointer make(uint32_t numSamples, uint32_t numChannels,
|
||||
const AudioSample* samples);
|
||||
|
||||
uint32_t getNumSamples() const { return _numSamples; }
|
||||
uint32_t getNumChannels() const { return _numChannels; }
|
||||
const AudioSample* data() const { return _data; }
|
||||
const char* rawData() const { return reinterpret_cast<const char*>(_data); }
|
||||
|
||||
float isStereo() const { return _numChannels == 2; }
|
||||
float isAmbisonic() const { return _numChannels == 4; }
|
||||
float getDuration() const { return (float)_numSamples / (_numChannels * AudioConstants::SAMPLE_RATE); }
|
||||
uint32_t getNumFrames() const { return _numSamples / _numChannels; }
|
||||
uint32_t getNumBytes() const { return _numSamples * sizeof(AudioSample); }
|
||||
|
||||
private:
|
||||
AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSample* samples);
|
||||
|
||||
const uint32_t _numSamples { 0 };
|
||||
const uint32_t _numChannels { 0 };
|
||||
const AudioSample* const _data { nullptr };
|
||||
};
|
||||
|
||||
class Sound : public Resource {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false);
|
||||
|
||||
bool isStereo() const { return _isStereo; }
|
||||
bool isAmbisonic() const { return _isAmbisonic; }
|
||||
bool isReady() const { return _isReady; }
|
||||
float getDuration() const { return _duration; }
|
||||
|
||||
const QByteArray& getByteArray() const { return _byteArray; }
|
||||
bool isReady() const { return (bool)_audioData; }
|
||||
|
||||
bool isStereo() const { return _audioData ? _audioData->isStereo() : false; }
|
||||
bool isAmbisonic() const { return _audioData ? _audioData->isAmbisonic() : false; }
|
||||
float getDuration() const { return _audioData ? _audioData->getDuration() : 0.0f; }
|
||||
|
||||
AudioDataPointer getAudioData() const { return _audioData; }
|
||||
|
||||
int getNumChannels() const { return _numChannels; }
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
|
||||
protected slots:
|
||||
void soundProcessSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void soundProcessSuccess(AudioDataPointer audioData);
|
||||
void soundProcessError(int error, QString str);
|
||||
|
||||
private:
|
||||
QByteArray _byteArray;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
bool _isReady;
|
||||
float _duration; // In seconds
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
AudioDataPointer _audioData;
|
||||
|
||||
// Only used for caching until the download has finished
|
||||
int _numChannels { 0 };
|
||||
};
|
||||
|
||||
class SoundProcessor : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SoundProcessor(const QUrl& url, const QByteArray& data, bool stereo, bool ambisonic)
|
||||
: _url(url), _data(data), _isStereo(stereo), _isAmbisonic(ambisonic)
|
||||
{
|
||||
}
|
||||
struct AudioProperties {
|
||||
uint8_t numChannels { 0 };
|
||||
uint32_t sampleRate { 0 };
|
||||
};
|
||||
|
||||
SoundProcessor(QWeakPointer<Resource> sound, QByteArray data);
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
void downSample(const QByteArray& rawAudioByteArray, int sampleRate);
|
||||
int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
int interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
|
||||
QByteArray downSample(const QByteArray& rawAudioByteArray,
|
||||
AudioProperties properties);
|
||||
AudioProperties interpretAsWav(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray);
|
||||
AudioProperties interpretAsMP3(const QByteArray& inputAudioByteArray,
|
||||
QByteArray& outputAudioByteArray);
|
||||
|
||||
signals:
|
||||
void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration);
|
||||
void onSuccess(AudioDataPointer audioData);
|
||||
void onError(int error, QString str);
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
QByteArray _data;
|
||||
bool _isStereo;
|
||||
bool _isAmbisonic;
|
||||
float _duration;
|
||||
const QWeakPointer<Resource> _sound;
|
||||
const QByteArray _data;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Sound> SharedSoundPointer;
|
||||
|
|
|
@ -1031,7 +1031,14 @@ void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entit
|
|||
// Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2)
|
||||
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
|
||||
const float stretchFactor = logf(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / logf(2.0f);
|
||||
AudioInjector::playSound(collisionSound, volume, stretchFactor, collision.contactPoint);
|
||||
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = collision.contactPoint;
|
||||
options.volume = volume;
|
||||
options.pitch = 1.0f / stretchFactor;
|
||||
|
||||
AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||
|
|
|
@ -329,6 +329,8 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
|
|||
result = GL_RGBA8_SNORM;
|
||||
break;
|
||||
case gpu::NINT2_10_10_10:
|
||||
result = GL_RGB10_A2;
|
||||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::COMPRESSED:
|
||||
|
@ -729,9 +731,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.internalFormat = GL_DEPTH_COMPONENT24;
|
||||
break;
|
||||
}
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUINT2:
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::NUM_TYPES: { // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -893,9 +895,12 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.format = GL_RGBA;
|
||||
texel.internalFormat = GL_RGBA2;
|
||||
break;
|
||||
case gpu::NINT2_10_10_10:
|
||||
texel.format = GL_RGBA;
|
||||
texel.internalFormat = GL_RGB10_A2;
|
||||
break;
|
||||
case gpu::NUINT32:
|
||||
case gpu::NINT32:
|
||||
case gpu::NINT2_10_10_10:
|
||||
case gpu::COMPRESSED:
|
||||
case gpu::NUM_TYPES: // quiet compiler
|
||||
Q_UNREACHABLE();
|
||||
|
|
|
@ -49,26 +49,7 @@ void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
#endif
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GL41Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -72,27 +72,7 @@ void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
#endif
|
||||
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GL45Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -49,28 +49,7 @@ void GLESBackend::do_draw(const Batch& batch, size_t paramOffset) {
|
|||
uint32 numVertices = batch._params[paramOffset + 1]._uint;
|
||||
uint32 startVertex = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
if (isStereo()) {
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
glDrawArraysInstanced(mode, startVertex, numVertices, 2);
|
||||
#else
|
||||
|
||||
setupStereoSide(0);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
setupStereoSide(1);
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
|
||||
#endif
|
||||
_stats._DSNumTriangles += 2 * numVertices / 3;
|
||||
_stats._DSNumDrawcalls += 2;
|
||||
|
||||
} else {
|
||||
glDrawArrays(mode, startVertex, numVertices);
|
||||
_stats._DSNumTriangles += numVertices / 3;
|
||||
_stats._DSNumDrawcalls++;
|
||||
}
|
||||
_stats._DSNumAPIDrawcalls++;
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
draw(mode, numVertices, startVertex);
|
||||
}
|
||||
|
||||
void GLESBackend::do_drawIndexed(const Batch& batch, size_t paramOffset) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include "ShaderConstants.h"
|
||||
|
||||
#include "GPULogging.h"
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "Frame.h"
|
||||
#include "GPULogging.h"
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
using namespace gpu;
|
||||
|
||||
|
@ -331,11 +332,20 @@ Size Context::getTextureResourcePopulatedGPUMemSize() {
|
|||
return Backend::textureResourcePopulatedGPUMemSize.getValue();
|
||||
}
|
||||
|
||||
PipelinePointer Context::createMipGenerationPipeline(const ShaderPointer& ps) {
|
||||
auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawViewportQuadTransformTexcoord);
|
||||
static gpu::StatePointer state(new gpu::State());
|
||||
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
// Good to go add the brand new pipeline
|
||||
return gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
||||
Size Context::getTextureResourceIdealGPUMemSize() {
|
||||
return Backend::textureResourceIdealGPUMemSize.getValue();
|
||||
}
|
||||
|
||||
|
||||
BatchPointer Context::acquireBatch(const char* name) {
|
||||
Batch* rawBatch = nullptr;
|
||||
{
|
||||
|
|
|
@ -218,6 +218,8 @@ public:
|
|||
// Same as above but grabbed at every end of a frame
|
||||
void getFrameStats(ContextStats& stats) const;
|
||||
|
||||
static PipelinePointer createMipGenerationPipeline(const ShaderPointer& pixelShader);
|
||||
|
||||
double getFrameTimerGPUAverage() const;
|
||||
double getFrameTimerBatchAverage() const;
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer {
|
|||
vec4 date;
|
||||
// Offset 16, acts as vec4 for alignment purposes
|
||||
vec3 worldPosition;
|
||||
// Offset 32, acts as vec4 for alignment purposes
|
||||
// Offset 32, acts as vec4 for alignment purposes (but not packing purposes)
|
||||
vec3 worldScale;
|
||||
// We need this float here to keep globalTime from getting pulled to offset 44
|
||||
float _spare0;
|
||||
// Offset 48
|
||||
float globalTime;
|
||||
// Offset 52
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,6 +21,8 @@
|
|||
#include "DeferredFramebuffer.h"
|
||||
#include "SurfaceGeometryPass.h"
|
||||
|
||||
#include "ssao_shared.h"
|
||||
|
||||
class AmbientOcclusionFramebuffer {
|
||||
public:
|
||||
AmbientOcclusionFramebuffer();
|
||||
|
@ -30,13 +32,23 @@ public:
|
|||
|
||||
gpu::FramebufferPointer getOcclusionBlurredFramebuffer();
|
||||
gpu::TexturePointer getOcclusionBlurredTexture();
|
||||
|
||||
|
||||
gpu::FramebufferPointer getNormalFramebuffer();
|
||||
gpu::TexturePointer getNormalTexture();
|
||||
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
gpu::FramebufferPointer getOcclusionSplitFramebuffer(int index);
|
||||
gpu::TexturePointer getOcclusionSplitTexture();
|
||||
#endif
|
||||
|
||||
// Update the source framebuffer size which will drive the allocation of all the other resources.
|
||||
void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer);
|
||||
bool update(const gpu::TexturePointer& linearDepthBuffer, int resolutionLevel, int depthResolutionLevel, bool isStereo);
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
|
||||
|
||||
bool isStereo() const { return _isStereo; }
|
||||
|
||||
protected:
|
||||
|
||||
void clear();
|
||||
void allocate();
|
||||
|
||||
|
@ -44,12 +56,22 @@ protected:
|
|||
|
||||
gpu::FramebufferPointer _occlusionFramebuffer;
|
||||
gpu::TexturePointer _occlusionTexture;
|
||||
|
||||
|
||||
gpu::FramebufferPointer _occlusionBlurredFramebuffer;
|
||||
gpu::TexturePointer _occlusionBlurredTexture;
|
||||
|
||||
|
||||
|
||||
gpu::FramebufferPointer _normalFramebuffer;
|
||||
gpu::TexturePointer _normalTexture;
|
||||
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
gpu::FramebufferPointer _occlusionSplitFramebuffers[SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT];
|
||||
gpu::TexturePointer _occlusionSplitTexture;
|
||||
#endif
|
||||
|
||||
glm::ivec2 _frameSize;
|
||||
int _resolutionLevel{ 0 };
|
||||
int _depthResolutionLevel{ 0 };
|
||||
bool _isStereo{ false };
|
||||
};
|
||||
|
||||
using AmbientOcclusionFramebufferPointer = std::shared_ptr<AmbientOcclusionFramebuffer>;
|
||||
|
@ -57,53 +79,78 @@ using AmbientOcclusionFramebufferPointer = std::shared_ptr<AmbientOcclusionFrame
|
|||
class AmbientOcclusionEffectConfig : public render::GPUJobConfig::Persistent {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool horizonBased MEMBER horizonBased NOTIFY dirty)
|
||||
Q_PROPERTY(bool ditheringEnabled MEMBER ditheringEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool borderingEnabled MEMBER borderingEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(bool fetchMipsEnabled MEMBER fetchMipsEnabled NOTIFY dirty)
|
||||
Q_PROPERTY(float radius MEMBER radius WRITE setRadius)
|
||||
Q_PROPERTY(float obscuranceLevel MEMBER obscuranceLevel WRITE setObscuranceLevel)
|
||||
Q_PROPERTY(float falloffBias MEMBER falloffBias WRITE setFalloffBias)
|
||||
Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness WRITE setEdgeSharpness)
|
||||
Q_PROPERTY(float blurDeviation MEMBER blurDeviation WRITE setBlurDeviation)
|
||||
Q_PROPERTY(float numSpiralTurns MEMBER numSpiralTurns WRITE setNumSpiralTurns)
|
||||
Q_PROPERTY(int numSamples MEMBER numSamples WRITE setNumSamples)
|
||||
Q_PROPERTY(bool jitterEnabled MEMBER jitterEnabled NOTIFY dirty)
|
||||
|
||||
Q_PROPERTY(int resolutionLevel MEMBER resolutionLevel WRITE setResolutionLevel)
|
||||
Q_PROPERTY(float edgeSharpness MEMBER edgeSharpness WRITE setEdgeSharpness)
|
||||
Q_PROPERTY(int blurRadius MEMBER blurRadius WRITE setBlurRadius)
|
||||
|
||||
// SSAO
|
||||
Q_PROPERTY(float ssaoRadius MEMBER ssaoRadius WRITE setSSAORadius)
|
||||
Q_PROPERTY(float ssaoObscuranceLevel MEMBER ssaoObscuranceLevel WRITE setSSAOObscuranceLevel)
|
||||
Q_PROPERTY(float ssaoFalloffAngle MEMBER ssaoFalloffAngle WRITE setSSAOFalloffAngle)
|
||||
Q_PROPERTY(float ssaoNumSpiralTurns MEMBER ssaoNumSpiralTurns WRITE setSSAONumSpiralTurns)
|
||||
Q_PROPERTY(int ssaoNumSamples MEMBER ssaoNumSamples WRITE setSSAONumSamples)
|
||||
|
||||
// HBAO
|
||||
Q_PROPERTY(float hbaoRadius MEMBER hbaoRadius WRITE setHBAORadius)
|
||||
Q_PROPERTY(float hbaoObscuranceLevel MEMBER hbaoObscuranceLevel WRITE setHBAOObscuranceLevel)
|
||||
Q_PROPERTY(float hbaoFalloffAngle MEMBER hbaoFalloffAngle WRITE setHBAOFalloffAngle)
|
||||
Q_PROPERTY(int hbaoNumSamples MEMBER hbaoNumSamples WRITE setHBAONumSamples)
|
||||
|
||||
public:
|
||||
AmbientOcclusionEffectConfig() : render::GPUJobConfig::Persistent(QStringList() << "Render" << "Engine" << "Ambient Occlusion", false) {}
|
||||
AmbientOcclusionEffectConfig();
|
||||
|
||||
const int MAX_RESOLUTION_LEVEL = 4;
|
||||
const int MAX_BLUR_RADIUS = 6;
|
||||
const int MAX_BLUR_RADIUS = 15;
|
||||
|
||||
void setRadius(float newRadius) { radius = std::max(0.01f, newRadius); emit dirty(); }
|
||||
void setObscuranceLevel(float level) { obscuranceLevel = std::max(0.01f, level); emit dirty(); }
|
||||
void setFalloffBias(float bias) { falloffBias = std::max(0.0f, std::min(bias, 0.2f)); emit dirty(); }
|
||||
void setEdgeSharpness(float sharpness) { edgeSharpness = std::max(0.0f, (float)sharpness); emit dirty(); }
|
||||
void setBlurDeviation(float deviation) { blurDeviation = std::max(0.0f, deviation); emit dirty(); }
|
||||
void setNumSpiralTurns(float turns) { numSpiralTurns = std::max(0.0f, (float)turns); emit dirty(); }
|
||||
void setNumSamples(int samples) { numSamples = std::max(1.0f, (float)samples); emit dirty(); }
|
||||
void setResolutionLevel(int level) { resolutionLevel = std::max(0, std::min(level, MAX_RESOLUTION_LEVEL)); emit dirty(); }
|
||||
void setBlurRadius(int radius) { blurRadius = std::max(0, std::min(MAX_BLUR_RADIUS, radius)); emit dirty(); }
|
||||
void setEdgeSharpness(float sharpness);
|
||||
void setResolutionLevel(int level);
|
||||
void setBlurRadius(int radius);
|
||||
|
||||
float radius{ 0.5f };
|
||||
float perspectiveScale{ 1.0f };
|
||||
float obscuranceLevel{ 0.5f }; // intensify or dim down the obscurance effect
|
||||
float falloffBias{ 0.01f };
|
||||
float edgeSharpness{ 1.0f };
|
||||
float blurDeviation{ 2.5f };
|
||||
float numSpiralTurns{ 7.0f }; // defining an angle span to distribute the samples ray directions
|
||||
int numSamples{ 16 };
|
||||
int resolutionLevel{ 1 };
|
||||
int blurRadius{ 4 }; // 0 means no blurring
|
||||
bool ditheringEnabled{ true }; // randomize the distribution of taps per pixel, should always be true
|
||||
bool borderingEnabled{ true }; // avoid evaluating information from non existing pixels out of the frame, should always be true
|
||||
bool fetchMipsEnabled{ true }; // fetch taps in sub mips to otpimize cache, should always be true
|
||||
void setSSAORadius(float newRadius);
|
||||
void setSSAOObscuranceLevel(float level);
|
||||
void setSSAOFalloffAngle(float bias);
|
||||
void setSSAONumSpiralTurns(float turns);
|
||||
void setSSAONumSamples(int samples);
|
||||
|
||||
void setHBAORadius(float newRadius);
|
||||
void setHBAOObscuranceLevel(float level);
|
||||
void setHBAOFalloffAngle(float bias);
|
||||
void setHBAONumSamples(int samples);
|
||||
|
||||
float perspectiveScale;
|
||||
float edgeSharpness;
|
||||
int blurRadius; // 0 means no blurring
|
||||
int resolutionLevel;
|
||||
|
||||
float ssaoRadius;
|
||||
float ssaoObscuranceLevel; // intensify or dim down the obscurance effect
|
||||
float ssaoFalloffAngle;
|
||||
float ssaoNumSpiralTurns; // defining an angle span to distribute the samples ray directions
|
||||
int ssaoNumSamples;
|
||||
|
||||
float hbaoRadius;
|
||||
float hbaoObscuranceLevel; // intensify or dim down the obscurance effect
|
||||
float hbaoFalloffAngle;
|
||||
int hbaoNumSamples;
|
||||
|
||||
bool horizonBased; // Use horizon based AO
|
||||
bool ditheringEnabled; // randomize the distribution of taps per pixel, should always be true
|
||||
bool borderingEnabled; // avoid evaluating information from non existing pixels out of the frame, should always be true
|
||||
bool fetchMipsEnabled; // fetch taps in sub mips to otpimize cache, should always be true
|
||||
bool jitterEnabled; // Add small jittering to AO samples at each frame
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
||||
#define SSAO_RANDOM_SAMPLE_COUNT 16
|
||||
|
||||
class AmbientOcclusionEffect {
|
||||
public:
|
||||
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer>;
|
||||
|
@ -115,59 +162,75 @@ public:
|
|||
|
||||
void configure(const Config& config);
|
||||
void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs);
|
||||
|
||||
|
||||
// Class describing the uniform buffer with all the parameters common to the AO shaders
|
||||
class Parameters {
|
||||
class AOParameters : public AmbientOcclusionParams {
|
||||
public:
|
||||
// Resolution info
|
||||
glm::vec4 resolutionInfo { -1.0f, 0.0f, 1.0f, 0.0f };
|
||||
// radius info is { R, R^2, 1 / R^6, ObscuranceScale}
|
||||
glm::vec4 radiusInfo{ 0.5f, 0.5f * 0.5f, 1.0f / (0.25f * 0.25f * 0.25f), 1.0f };
|
||||
// Dithering info
|
||||
glm::vec4 ditheringInfo { 0.0f, 0.0f, 0.01f, 1.0f };
|
||||
// Sampling info
|
||||
glm::vec4 sampleInfo { 11.0f, 1.0f/11.0f, 7.0f, 1.0f };
|
||||
// Blurring info
|
||||
glm::vec4 blurInfo { 1.0f, 3.0f, 2.0f, 0.0f };
|
||||
// gaussian distribution coefficients first is the sampling radius (max is 6)
|
||||
const static int GAUSSIAN_COEFS_LENGTH = 8;
|
||||
float _gaussianCoefs[GAUSSIAN_COEFS_LENGTH];
|
||||
|
||||
Parameters() {}
|
||||
|
||||
int getResolutionLevel() const { return resolutionInfo.x; }
|
||||
float getRadius() const { return radiusInfo.x; }
|
||||
float getPerspectiveScale() const { return resolutionInfo.z; }
|
||||
float getObscuranceLevel() const { return radiusInfo.w; }
|
||||
float getFalloffBias() const { return (float)ditheringInfo.z; }
|
||||
float getEdgeSharpness() const { return (float)blurInfo.x; }
|
||||
float getBlurDeviation() const { return blurInfo.z; }
|
||||
AOParameters();
|
||||
|
||||
int getResolutionLevel() const { return _resolutionInfo.x; }
|
||||
float getRadius() const { return _radiusInfo.x; }
|
||||
float getPerspectiveScale() const { return _resolutionInfo.z; }
|
||||
float getObscuranceLevel() const { return _radiusInfo.w; }
|
||||
float getFalloffAngle() const { return (float)_falloffInfo.x; }
|
||||
|
||||
float getNumSpiralTurns() const { return sampleInfo.z; }
|
||||
int getNumSamples() const { return (int)sampleInfo.x; }
|
||||
bool isFetchMipsEnabled() const { return sampleInfo.w; }
|
||||
float getNumSpiralTurns() const { return _sampleInfo.z; }
|
||||
int getNumSamples() const { return (int)_sampleInfo.x; }
|
||||
bool isFetchMipsEnabled() const { return _sampleInfo.w; }
|
||||
|
||||
bool isDitheringEnabled() const { return _ditheringInfo.x != 0.0f; }
|
||||
bool isBorderingEnabled() const { return _ditheringInfo.w != 0.0f; }
|
||||
bool isHorizonBased() const { return _resolutionInfo.y != 0.0f; }
|
||||
|
||||
int getBlurRadius() const { return (int)blurInfo.y; }
|
||||
bool isDitheringEnabled() const { return ditheringInfo.x; }
|
||||
bool isBorderingEnabled() const { return ditheringInfo.w; }
|
||||
};
|
||||
using ParametersBuffer = gpu::StructBuffer<Parameters>;
|
||||
using AOParametersBuffer = gpu::StructBuffer<AOParameters>;
|
||||
|
||||
private:
|
||||
void updateGaussianDistribution();
|
||||
|
||||
ParametersBuffer _parametersBuffer;
|
||||
|
||||
const gpu::PipelinePointer& getOcclusionPipeline();
|
||||
const gpu::PipelinePointer& getHBlurPipeline(); // first
|
||||
const gpu::PipelinePointer& getVBlurPipeline(); // second
|
||||
// Class describing the uniform buffer with all the parameters common to the bilateral blur shaders
|
||||
class BlurParameters : public AmbientOcclusionBlurParams {
|
||||
public:
|
||||
|
||||
gpu::PipelinePointer _occlusionPipeline;
|
||||
gpu::PipelinePointer _hBlurPipeline;
|
||||
gpu::PipelinePointer _vBlurPipeline;
|
||||
BlurParameters();
|
||||
|
||||
float getEdgeSharpness() const { return (float)_blurInfo.x; }
|
||||
int getBlurRadius() const { return (int)_blurInfo.w; }
|
||||
|
||||
};
|
||||
using BlurParametersBuffer = gpu::StructBuffer<BlurParameters>;
|
||||
|
||||
using FrameParametersBuffer = gpu::StructBuffer< AmbientOcclusionFrameParams>;
|
||||
|
||||
void updateBlurParameters();
|
||||
void updateFramebufferSizes();
|
||||
void updateRandomSamples();
|
||||
void updateJitterSamples();
|
||||
|
||||
int getDepthResolutionLevel() const;
|
||||
|
||||
AOParametersBuffer _aoParametersBuffer;
|
||||
FrameParametersBuffer _aoFrameParametersBuffer[SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT];
|
||||
BlurParametersBuffer _vblurParametersBuffer;
|
||||
BlurParametersBuffer _hblurParametersBuffer;
|
||||
float _blurEdgeSharpness{ 0.0f };
|
||||
|
||||
static const gpu::PipelinePointer& getOcclusionPipeline();
|
||||
static const gpu::PipelinePointer& getBilateralBlurPipeline();
|
||||
static const gpu::PipelinePointer& getMipCreationPipeline();
|
||||
static const gpu::PipelinePointer& getGatherPipeline();
|
||||
static const gpu::PipelinePointer& getBuildNormalsPipeline();
|
||||
|
||||
static gpu::PipelinePointer _occlusionPipeline;
|
||||
static gpu::PipelinePointer _bilateralBlurPipeline;
|
||||
static gpu::PipelinePointer _mipCreationPipeline;
|
||||
static gpu::PipelinePointer _gatherPipeline;
|
||||
static gpu::PipelinePointer _buildNormalsPipeline;
|
||||
|
||||
AmbientOcclusionFramebufferPointer _framebuffer;
|
||||
std::array<float, SSAO_RANDOM_SAMPLE_COUNT * SSAO_SPLIT_COUNT*SSAO_SPLIT_COUNT> _randomSamples;
|
||||
int _frameId{ 0 };
|
||||
bool _isJitterEnabled{ true };
|
||||
|
||||
gpu::RangeTimerPointer _gpuTimer;
|
||||
|
||||
|
@ -193,7 +256,7 @@ signals:
|
|||
|
||||
class DebugAmbientOcclusion {
|
||||
public:
|
||||
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer, AmbientOcclusionEffect::ParametersBuffer>;
|
||||
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer, AmbientOcclusionEffect::AOParametersBuffer>;
|
||||
using Config = DebugAmbientOcclusionConfig;
|
||||
using JobModel = render::Job::ModelI<DebugAmbientOcclusion, Inputs, Config>;
|
||||
|
||||
|
|
|
@ -220,9 +220,7 @@ static const std::string DEFAULT_DEBUG_SCATTERING_SHADER{
|
|||
|
||||
static const std::string DEFAULT_AMBIENT_OCCLUSION_SHADER{
|
||||
"vec4 getFragmentColor() {"
|
||||
" return vec4(vec3(texture(obscuranceMap, uv).x), 1.0);"
|
||||
// When drawing color " return vec4(vec3(texture(debugTexture0, uv).xyz), 1.0);"
|
||||
// when drawing normal" return vec4(normalize(texture(debugTexture0, uv).xyz * 2.0 - vec3(1.0)), 1.0);"
|
||||
" return vec4(vec3(texture(debugTexture0, uv).x), 1.0);"
|
||||
" }"
|
||||
};
|
||||
static const std::string DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER{
|
||||
|
@ -323,6 +321,8 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, const std::strin
|
|||
return DEFAULT_AMBIENT_OCCLUSION_SHADER;
|
||||
case AmbientOcclusionBlurredMode:
|
||||
return DEFAULT_AMBIENT_OCCLUSION_BLURRED_SHADER;
|
||||
case AmbientOcclusionNormalMode:
|
||||
return DEFAULT_HALF_NORMAL_SHADER;
|
||||
case VelocityMode:
|
||||
return DEFAULT_VELOCITY_SHADER;
|
||||
case CustomMode:
|
||||
|
@ -470,6 +470,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I
|
|||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionTexture());
|
||||
} else if (_mode == AmbientOcclusionBlurredMode) {
|
||||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getOcclusionBlurredTexture());
|
||||
} else if (_mode == AmbientOcclusionNormalMode) {
|
||||
batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getNormalTexture());
|
||||
}
|
||||
}
|
||||
const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
|
|
@ -91,6 +91,7 @@ protected:
|
|||
ScatteringDebugMode,
|
||||
AmbientOcclusionMode,
|
||||
AmbientOcclusionBlurredMode,
|
||||
AmbientOcclusionNormalMode,
|
||||
VelocityMode,
|
||||
CustomMode, // Needs to stay last
|
||||
|
||||
|
|
|
@ -116,17 +116,27 @@ bool isStereo() {
|
|||
float getStereoSideWidth(int resolutionLevel) {
|
||||
return float(int(frameTransform._stereoInfo.y) >> resolutionLevel);
|
||||
}
|
||||
|
||||
float getStereoSideHeight(int resolutionLevel) {
|
||||
return float(int(frameTransform._pixelInfo.w) >> resolutionLevel);
|
||||
}
|
||||
|
||||
vec2 getSideImageSize(int resolutionLevel) {
|
||||
return vec2(float(int(frameTransform._stereoInfo.y) >> resolutionLevel), float(int(frameTransform._pixelInfo.w) >> resolutionLevel));
|
||||
vec2 getStereoSideSize(int resolutionLevel) {
|
||||
return vec2(getStereoSideWidth(resolutionLevel), getStereoSideHeight(resolutionLevel));
|
||||
}
|
||||
|
||||
ivec4 getStereoSideInfoFromWidth(int xPos, int sideWidth) {
|
||||
return ivec4((1 - int(xPos < sideWidth)) * ivec2(1, sideWidth), sideWidth, isStereo());
|
||||
}
|
||||
|
||||
ivec4 getStereoSideInfo(int xPos, int resolutionLevel) {
|
||||
int sideWidth = int(getStereoSideWidth(resolutionLevel));
|
||||
return ivec4((1 - int(xPos < sideWidth)) * ivec2(1, sideWidth), sideWidth, isStereo());
|
||||
return getStereoSideInfoFromWidth(xPos, sideWidth);
|
||||
}
|
||||
|
||||
|
||||
int getStereoSide(ivec4 sideInfo) {
|
||||
return sideInfo.x;
|
||||
}
|
||||
|
||||
float evalZeyeFromZdb(float depth) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "SurfaceGeometryPass.h"
|
||||
|
||||
#include <limits>
|
||||
#include <MathUtils.h>
|
||||
|
||||
#include <gpu/Context.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
@ -28,19 +29,27 @@ namespace ru {
|
|||
LinearDepthFramebuffer::LinearDepthFramebuffer() {
|
||||
}
|
||||
|
||||
void LinearDepthFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) {
|
||||
void LinearDepthFramebuffer::update(const gpu::TexturePointer& depthBuffer, const gpu::TexturePointer& normalTexture, bool isStereo) {
|
||||
//If the depth buffer or size changed, we need to delete our FBOs
|
||||
bool reset = false;
|
||||
if ((_primaryDepthTexture != depthBuffer)) {
|
||||
if (_primaryDepthTexture != depthBuffer || _normalTexture != normalTexture) {
|
||||
_primaryDepthTexture = depthBuffer;
|
||||
_normalTexture = normalTexture;
|
||||
reset = true;
|
||||
}
|
||||
if (_primaryDepthTexture) {
|
||||
auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions());
|
||||
if (_frameSize != newFrameSize) {
|
||||
if (_frameSize != newFrameSize || _isStereo != isStereo) {
|
||||
_frameSize = newFrameSize;
|
||||
_halfFrameSize = newFrameSize >> 1;
|
||||
|
||||
_halfFrameSize = _frameSize;
|
||||
if (isStereo) {
|
||||
_halfFrameSize.x >>= 1;
|
||||
}
|
||||
_halfFrameSize >>= 1;
|
||||
if (isStereo) {
|
||||
_halfFrameSize.x <<= 1;
|
||||
}
|
||||
_isStereo = isStereo;
|
||||
reset = true;
|
||||
}
|
||||
}
|
||||
|
@ -64,16 +73,22 @@ void LinearDepthFramebuffer::allocate() {
|
|||
auto height = _frameSize.y;
|
||||
|
||||
// For Linear Depth:
|
||||
_linearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height, gpu::Texture::SINGLE_MIP,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
const uint16_t LINEAR_DEPTH_MAX_MIP_LEVEL = 5;
|
||||
// Point sampling of the depth, as well as the clamp to edge, are needed for the AmbientOcclusionEffect with HBAO
|
||||
const auto depthSamplerFull = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT, gpu::Sampler::WRAP_CLAMP);
|
||||
_linearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), width, height, LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
depthSamplerFull);
|
||||
_linearDepthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("linearDepth"));
|
||||
_linearDepthFramebuffer->setRenderBuffer(0, _linearDepthTexture);
|
||||
_linearDepthFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat());
|
||||
|
||||
// For Downsampling:
|
||||
const uint16_t HALF_LINEAR_DEPTH_MAX_MIP_LEVEL = 5;
|
||||
_halfLinearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), _halfFrameSize.x, _halfFrameSize.y, HALF_LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
// Point sampling of the depth, as well as the clamp to edge, are needed for the AmbientOcclusionEffect with HBAO
|
||||
const auto depthSamplerHalf = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_POINT, gpu::Sampler::WRAP_CLAMP);
|
||||
// The depth format here is half float as it increases performance in the AmbientOcclusion. But it might be needed elsewhere...
|
||||
_halfLinearDepthTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::SCALAR, gpu::HALF, gpu::RED), _halfFrameSize.x, _halfFrameSize.y, HALF_LINEAR_DEPTH_MAX_MIP_LEVEL,
|
||||
depthSamplerHalf);
|
||||
|
||||
_halfNormalTexture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_RGBA_32, _halfFrameSize.x, _halfFrameSize.y, gpu::Texture::SINGLE_MIP,
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT));
|
||||
|
@ -97,6 +112,10 @@ gpu::TexturePointer LinearDepthFramebuffer::getLinearDepthTexture() {
|
|||
return _linearDepthTexture;
|
||||
}
|
||||
|
||||
gpu::TexturePointer LinearDepthFramebuffer::getNormalTexture() {
|
||||
return _normalTexture;
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer LinearDepthFramebuffer::getDownsampleFramebuffer() {
|
||||
if (!_downsampleFramebuffer) {
|
||||
allocate();
|
||||
|
@ -141,11 +160,12 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con
|
|||
if (!_linearDepthFramebuffer) {
|
||||
_linearDepthFramebuffer = std::make_shared<LinearDepthFramebuffer>();
|
||||
}
|
||||
_linearDepthFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture());
|
||||
|
||||
auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture();
|
||||
auto normalTexture = deferredFramebuffer->getDeferredNormalTexture();
|
||||
|
||||
_linearDepthFramebuffer->update(depthBuffer, normalTexture, args->isStereo());
|
||||
|
||||
auto linearDepthFBO = _linearDepthFramebuffer->getLinearDepthFramebuffer();
|
||||
auto linearDepthTexture = _linearDepthFramebuffer->getLinearDepthTexture();
|
||||
|
||||
|
@ -167,32 +187,34 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con
|
|||
float clearLinearDepth = args->getViewFrustum().getFarClip() * 2.0f;
|
||||
|
||||
gpu::doInBatch("LinearDepthPass::run", args->_context, [=](gpu::Batch& batch) {
|
||||
PROFILE_RANGE_BATCH(batch, "LinearDepthPass");
|
||||
_gpuTimer->begin(batch);
|
||||
|
||||
batch.enableStereo(false);
|
||||
|
||||
batch.setViewportTransform(depthViewport);
|
||||
batch.setProjectionTransform(glm::mat4());
|
||||
batch.resetViewTransform();
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport));
|
||||
|
||||
batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer());
|
||||
|
||||
// LinearDepth
|
||||
batch.setViewportTransform(depthViewport);
|
||||
batch.setFramebuffer(linearDepthFBO);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(clearLinearDepth, 0.0f, 0.0f, 0.0f));
|
||||
batch.setPipeline(linearDepthPipeline);
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize(), depthViewport));
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, depthBuffer);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
// Downsample
|
||||
batch.setViewportTransform(halfViewport);
|
||||
|
||||
batch.setFramebuffer(downsampleFBO);
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryDepth, linearDepthTexture);
|
||||
batch.setResourceTexture(ru::Texture::SurfaceGeometryNormal, normalTexture);
|
||||
batch.setPipeline(downsamplePipeline);
|
||||
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_linearDepthFramebuffer->getDepthFrameSize() >> 1, halfViewport));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
|
||||
|
||||
_gpuTimer->end(batch);
|
||||
});
|
||||
|
||||
|
@ -244,7 +266,7 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline(const render:
|
|||
SurfaceGeometryFramebuffer::SurfaceGeometryFramebuffer() {
|
||||
}
|
||||
|
||||
void SurfaceGeometryFramebuffer::updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer) {
|
||||
void SurfaceGeometryFramebuffer::update(const gpu::TexturePointer& linearDepthBuffer) {
|
||||
//If the depth buffer or size changed, we need to delete our FBOs
|
||||
bool reset = false;
|
||||
if ((_linearDepthTexture != linearDepthBuffer)) {
|
||||
|
@ -411,7 +433,7 @@ void SurfaceGeometryPass::run(const render::RenderContextPointer& renderContext,
|
|||
if (!_surfaceGeometryFramebuffer) {
|
||||
_surfaceGeometryFramebuffer = std::make_shared<SurfaceGeometryFramebuffer>();
|
||||
}
|
||||
_surfaceGeometryFramebuffer->updateLinearDepth(linearDepthTexture);
|
||||
_surfaceGeometryFramebuffer->update(linearDepthTexture);
|
||||
|
||||
auto curvatureFramebuffer = _surfaceGeometryFramebuffer->getCurvatureFramebuffer();
|
||||
auto curvatureTexture = _surfaceGeometryFramebuffer->getCurvatureTexture();
|
||||
|
|
|
@ -28,17 +28,17 @@ public:
|
|||
|
||||
gpu::FramebufferPointer getLinearDepthFramebuffer();
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
gpu::TexturePointer getNormalTexture();
|
||||
|
||||
gpu::FramebufferPointer getDownsampleFramebuffer();
|
||||
gpu::TexturePointer getHalfLinearDepthTexture();
|
||||
gpu::TexturePointer getHalfNormalTexture();
|
||||
|
||||
// Update the depth buffer which will drive the allocation of all the other resources according to its size.
|
||||
void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer);
|
||||
gpu::TexturePointer getPrimaryDepthTexture();
|
||||
void update(const gpu::TexturePointer& depthBuffer, const gpu::TexturePointer& normalTexture, bool isStereo);
|
||||
const glm::ivec2& getDepthFrameSize() const { return _frameSize; }
|
||||
|
||||
void setResolutionLevel(int level);
|
||||
void setResolutionLevel(int level) { _resolutionLevel = std::max(0, level); }
|
||||
int getResolutionLevel() const { return _resolutionLevel; }
|
||||
|
||||
protected:
|
||||
|
@ -49,6 +49,7 @@ protected:
|
|||
|
||||
gpu::FramebufferPointer _linearDepthFramebuffer;
|
||||
gpu::TexturePointer _linearDepthTexture;
|
||||
gpu::TexturePointer _normalTexture;
|
||||
|
||||
gpu::FramebufferPointer _downsampleFramebuffer;
|
||||
gpu::TexturePointer _halfLinearDepthTexture;
|
||||
|
@ -58,6 +59,7 @@ protected:
|
|||
glm::ivec2 _frameSize;
|
||||
glm::ivec2 _halfFrameSize;
|
||||
int _resolutionLevel{ 0 };
|
||||
bool _isStereo{ false };
|
||||
};
|
||||
|
||||
using LinearDepthFramebufferPointer = std::shared_ptr<LinearDepthFramebuffer>;
|
||||
|
@ -107,7 +109,7 @@ public:
|
|||
gpu::TexturePointer getBlurringTexture();
|
||||
|
||||
// Update the source framebuffer size which will drive the allocation of all the other resources.
|
||||
void updateLinearDepth(const gpu::TexturePointer& linearDepthBuffer);
|
||||
void update(const gpu::TexturePointer& linearDepthBuffer);
|
||||
gpu::TexturePointer getLinearDepthTexture();
|
||||
const glm::ivec2& getSourceFrameSize() const { return _frameSize; }
|
||||
|
||||
|
|
|
@ -86,7 +86,10 @@
|
|||
// Ambient occlusion
|
||||
#define RENDER_UTILS_BUFFER_SSAO_PARAMS 2
|
||||
#define RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS 3
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_PYRAMID 1
|
||||
#define RENDER_UTILS_BUFFER_SSAO_BLUR_PARAMS 4
|
||||
#define RENDER_UTILS_BUFFER_SSAO_FRAME_PARAMS 5
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_DEPTH 1
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_NORMAL 2
|
||||
#define RENDER_UTILS_TEXTURE_SSAO_OCCLUSION 0
|
||||
|
||||
// Temporal anti-aliasing
|
||||
|
@ -120,7 +123,6 @@
|
|||
#define RENDER_UTILS_UNIFORM_TEXT_COLOR 0
|
||||
#define RENDER_UTILS_UNIFORM_TEXT_OUTLINE 1
|
||||
|
||||
|
||||
// Debugging
|
||||
#define RENDER_UTILS_BUFFER_DEBUG_SKYBOX 5
|
||||
#define RENDER_UTILS_DEBUG_TEXTURE0 11
|
||||
|
@ -144,7 +146,9 @@ enum Buffer {
|
|||
LightClusterContent = RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT,
|
||||
SsscParams = RENDER_UTILS_BUFFER_SSSC_PARAMS,
|
||||
SsaoParams = RENDER_UTILS_BUFFER_SSAO_PARAMS,
|
||||
SsaoFrameParams = RENDER_UTILS_BUFFER_SSAO_FRAME_PARAMS,
|
||||
SsaoDebugParams = RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS,
|
||||
SsaoBlurParams = RENDER_UTILS_BUFFER_SSAO_BLUR_PARAMS,
|
||||
LightIndex = RENDER_UTILS_BUFFER_LIGHT_INDEX,
|
||||
TaaParams = RENDER_UTILS_BUFFER_TAA_PARAMS,
|
||||
HighlightParams = RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS,
|
||||
|
@ -183,7 +187,8 @@ enum Texture {
|
|||
TaaDepth = RENDER_UTILS_TEXTURE_TAA_DEPTH,
|
||||
TaaNext = RENDER_UTILS_TEXTURE_TAA_NEXT,
|
||||
SsaoOcclusion = RENDER_UTILS_TEXTURE_SSAO_OCCLUSION,
|
||||
SsaoPyramid = RENDER_UTILS_TEXTURE_SSAO_PYRAMID,
|
||||
SsaoDepth = RENDER_UTILS_TEXTURE_SSAO_DEPTH,
|
||||
SsaoNormal = RENDER_UTILS_TEXTURE_SSAO_NORMAL,
|
||||
HighlightSceneDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH,
|
||||
HighlightDepth = RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH,
|
||||
SurfaceGeometryDepth = RENDER_UTILS_TEXTURE_SG_DEPTH,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -12,52 +12,94 @@
|
|||
<@def SSAO_SLH@>
|
||||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
<@include ssao_shared.h@>
|
||||
|
||||
<@func declarePackOcclusionDepth()@>
|
||||
|
||||
const float FAR_PLANE_Z = -300.0;
|
||||
float CSZToDepthKey(float z) {
|
||||
return clamp(z * (-1.0 / SSAO_DEPTH_KEY_SCALE), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float CSZToDephtKey(float z) {
|
||||
return clamp(z * (1.0 / FAR_PLANE_Z), 0.0, 1.0);
|
||||
}
|
||||
vec3 packOcclusionDepth(float occlusion, float depth) {
|
||||
vec4 packOcclusionOutput(float occlusion, float depth, vec3 eyeNormal) {
|
||||
depth = CSZToDepthKey(depth);
|
||||
#if SSAO_BILATERAL_BLUR_USE_NORMAL
|
||||
return vec4(occlusion, depth, eyeNormal.xy / eyeNormal.z);
|
||||
#else
|
||||
// Round to the nearest 1/256.0
|
||||
float temp = floor(depth * 256.0);
|
||||
return vec3(occlusion, temp * (1.0 / 256.0), depth * 256.0 - temp);
|
||||
depth *= 256.0;
|
||||
float temp = floor(depth);
|
||||
return vec4(occlusion, temp * (1.0 / 256.0), depth - temp, 0.0);
|
||||
#endif
|
||||
}
|
||||
vec2 unpackOcclusionDepth(vec3 raw) {
|
||||
float z = raw.y * (256.0 / 257.0) + raw.z * (1.0 / 257.0);
|
||||
return vec2(raw.x, z);
|
||||
|
||||
struct UnpackedOcclusion {
|
||||
vec3 normal;
|
||||
float depth;
|
||||
float occlusion;
|
||||
};
|
||||
|
||||
void unpackOcclusionOutput(vec4 raw, out UnpackedOcclusion result) {
|
||||
result.occlusion = raw.x;
|
||||
#if SSAO_BILATERAL_BLUR_USE_NORMAL
|
||||
result.depth = raw.y;
|
||||
result.normal = normalize(vec3(raw.zw, 1.0));
|
||||
#else
|
||||
result.depth = (raw.y + raw.z / 256.0);
|
||||
result.normal = vec3(0.0, 0.0, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
float unpackOcclusion(vec4 raw) {
|
||||
return raw.x;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
<@func declareAmbientOcclusion()@>
|
||||
<@include DeferredTransform.slh@>
|
||||
<$declareDeferredFrameTransform()$>
|
||||
|
||||
struct AmbientOcclusionParams {
|
||||
vec4 _resolutionInfo;
|
||||
vec4 _radiusInfo;
|
||||
vec4 _ditheringInfo;
|
||||
vec4 _sampleInfo;
|
||||
vec4 _blurInfo;
|
||||
float _gaussianCoefs[8];
|
||||
};
|
||||
|
||||
LAYOUT(binding=RENDER_UTILS_BUFFER_SSAO_PARAMS) uniform ambientOcclusionParamsBuffer {
|
||||
AmbientOcclusionParams params;
|
||||
};
|
||||
|
||||
LAYOUT(binding=RENDER_UTILS_BUFFER_SSAO_FRAME_PARAMS) uniform ambientOcclusionFrameParamsBuffer {
|
||||
AmbientOcclusionFrameParams frameParams;
|
||||
};
|
||||
|
||||
float getPerspectiveScale() {
|
||||
|
||||
return (params._resolutionInfo.z);
|
||||
}
|
||||
int getResolutionLevel() {
|
||||
|
||||
int getResolutionLevel() {
|
||||
return int(params._resolutionInfo.x);
|
||||
}
|
||||
|
||||
bool isHorizonBased() {
|
||||
return params._resolutionInfo.y!=0.0;
|
||||
}
|
||||
|
||||
vec2 getNormalsSideSize() {
|
||||
return params._sideSizes[0].xy;
|
||||
}
|
||||
int getNormalsResolutionLevel() {
|
||||
return int(params._sideSizes[0].z);
|
||||
}
|
||||
int getDepthResolutionLevel() {
|
||||
return int(params._sideSizes[0].w);
|
||||
}
|
||||
vec2 getOcclusionSideSize() {
|
||||
return params._sideSizes[1].xy;
|
||||
}
|
||||
vec2 getOcclusionSplitSideSize() {
|
||||
return params._sideSizes[1].zw;
|
||||
}
|
||||
|
||||
ivec2 getWidthHeightRoundUp(int resolutionLevel) {
|
||||
ivec2 fullRes = ivec2(getWidthHeight(0));
|
||||
int resolutionDivisor = 1 << resolutionLevel;
|
||||
return (fullRes + resolutionDivisor - 1) / resolutionDivisor;
|
||||
}
|
||||
|
||||
float getRadius() {
|
||||
return params._radiusInfo.x;
|
||||
}
|
||||
|
@ -65,24 +107,35 @@ float getRadius2() {
|
|||
return params._radiusInfo.y;
|
||||
}
|
||||
float getInvRadius6() {
|
||||
return mix(params._radiusInfo.z, 1.0, isHorizonBased());
|
||||
}
|
||||
float getInvRadius2() {
|
||||
return params._radiusInfo.z;
|
||||
}
|
||||
|
||||
float getObscuranceScaling() {
|
||||
return params._radiusInfo.z * params._radiusInfo.w;
|
||||
return getInvRadius6() * params._radiusInfo.w;
|
||||
}
|
||||
|
||||
float isDitheringEnabled() {
|
||||
return params._ditheringInfo.x;
|
||||
}
|
||||
float getFrameDithering() {
|
||||
return params._ditheringInfo.y;
|
||||
}
|
||||
float isBorderingEnabled() {
|
||||
return params._ditheringInfo.w;
|
||||
}
|
||||
|
||||
float getFalloffBias() {
|
||||
return params._ditheringInfo.z;
|
||||
float getFalloffCosAngle() {
|
||||
return params._falloffInfo.x;
|
||||
}
|
||||
float getFalloffCosAngleScale() {
|
||||
return params._falloffInfo.y;
|
||||
}
|
||||
|
||||
float getFalloffSinAngle() {
|
||||
return params._falloffInfo.z;
|
||||
}
|
||||
float getFalloffSinAngleScale() {
|
||||
return params._falloffInfo.w;
|
||||
}
|
||||
|
||||
float getNumSamples() {
|
||||
|
@ -99,37 +152,6 @@ int doFetchMips() {
|
|||
return int(params._sampleInfo.w);
|
||||
}
|
||||
|
||||
float getBlurEdgeSharpness() {
|
||||
return params._blurInfo.x;
|
||||
}
|
||||
|
||||
#ifdef CONSTANT_GAUSSIAN
|
||||
const int BLUR_RADIUS = 4;
|
||||
const float gaussian[BLUR_RADIUS + 1] =
|
||||
// KEEP this dead code for eventual performance improvment
|
||||
// float[](0.356642, 0.239400, 0.072410, 0.009869);
|
||||
// float[](0.398943, 0.241971, 0.053991, 0.004432, 0.000134); // stddev = 1.0
|
||||
float[](0.153170, 0.144893, 0.122649, 0.092902, 0.062970); // stddev = 2.0
|
||||
//float[](0.197413, 0.17467, 0.12098,0.065591,0.040059);
|
||||
// float[](0.111220, 0.107798, 0.098151, 0.083953, 0.067458, 0.050920, 0.036108); // stddev = 3.0
|
||||
|
||||
int getBlurRadius() {
|
||||
return BLUR_RADIUS;
|
||||
}
|
||||
|
||||
float getBlurCoef(int c) {
|
||||
return gaussian[c];
|
||||
}
|
||||
#else
|
||||
int getBlurRadius() {
|
||||
return int(params._blurInfo.y);
|
||||
}
|
||||
|
||||
float getBlurCoef(int c) {
|
||||
return params._gaussianCoefs[c];
|
||||
}
|
||||
#endif
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
<@func declareSamplingDisk()@>
|
||||
|
@ -139,43 +161,57 @@ float getAngleDitheringWorldPos(in vec3 pixelWorldPos) {
|
|||
|
||||
ivec3 pixelPos = ivec3(worldPosFract * 256.0);
|
||||
|
||||
return isDitheringEnabled() * float(((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10) + getFrameDithering();
|
||||
return isDitheringEnabled() * float(((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) + (3 * pixelPos.y ^ pixelPos.z + pixelPos.x * pixelPos.z)) * 10);
|
||||
}
|
||||
|
||||
float getAngleDitheringSplit() {
|
||||
return isDitheringEnabled() * frameParams._angleInfo.x;
|
||||
}
|
||||
|
||||
float getAngleDithering(in ivec2 pixelPos) {
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
return getAngleDitheringSplit();
|
||||
#else
|
||||
// Hash function used in the AlchemyAO paper
|
||||
return isDitheringEnabled() * float((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10) + getFrameDithering();
|
||||
return getAngleDitheringPixelPos(pixelPos);
|
||||
#endif
|
||||
}
|
||||
|
||||
float evalDiskRadius(float Zeye, vec2 imageSize) {
|
||||
float getAngleDitheringPixelPos(in ivec2 pixelPos) {
|
||||
// Hash function used in the AlchemyAO paper
|
||||
return isDitheringEnabled() * float((3 * pixelPos.x ^ pixelPos.y + pixelPos.x * pixelPos.y) * 10);
|
||||
}
|
||||
|
||||
float evalDiskRadius(float Zeye, vec2 sideImageSize) {
|
||||
// Choose the screen-space sample radius
|
||||
// proportional to the projected area of the sphere
|
||||
float ssDiskRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Zeye ) * getPerspectiveScale();
|
||||
float diskPixelRadius = -( getProjScale(getResolutionLevel()) * getRadius() / Zeye ) * getPerspectiveScale();
|
||||
|
||||
// clamp the disk to fit in the image otherwise too many unknown
|
||||
ssDiskRadius = min(ssDiskRadius, imageSize.y * 0.5);
|
||||
diskPixelRadius = min(diskPixelRadius, sideImageSize.y * 0.5);
|
||||
|
||||
return ssDiskRadius;
|
||||
return diskPixelRadius;
|
||||
}
|
||||
|
||||
const float TWO_PI = 6.28;
|
||||
const float PI = 3.1415926;
|
||||
const float TWO_PI = 6.2831852;
|
||||
|
||||
vec3 getUnitTapLocation(int sampleNumber, float spinAngle){
|
||||
vec3 getUnitTapLocation(int sampleNumber, float spiralTurns, float spinAngle, float angleRange){
|
||||
// Radius relative to ssR
|
||||
float alpha = (float(sampleNumber) + 0.5) * getInvNumSamples();
|
||||
float angle = alpha * (getNumSpiralTurns() * TWO_PI) + spinAngle;
|
||||
float alpha = float(sampleNumber) * getInvNumSamples();
|
||||
float angle = alpha * (spiralTurns * angleRange) + spinAngle;
|
||||
return vec3(cos(angle), sin(angle), alpha);
|
||||
}
|
||||
|
||||
vec3 getTapLocation(int sampleNumber, float spinAngle, float outerRadius) {
|
||||
vec3 tap = getUnitTapLocation(sampleNumber, spinAngle);
|
||||
vec3 getTapLocationSSAO(int sampleNumber, float spinAngle, float outerRadius) {
|
||||
vec3 tap = getUnitTapLocation(sampleNumber, getNumSpiralTurns(), spinAngle, TWO_PI);
|
||||
tap.xy *= tap.z;
|
||||
tap *= outerRadius;
|
||||
return tap;
|
||||
}
|
||||
|
||||
vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, vec2 pixelPos, vec2 imageSize) {
|
||||
vec3 tap = getTapLocation(sampleNumber, spinAngle, outerRadius);
|
||||
vec3 getTapLocationClampedSSAO(int sampleNumber, float spinAngle, float outerRadius, vec2 pixelPos, vec2 sideImageSize) {
|
||||
vec3 tap = getTapLocationSSAO(sampleNumber, spinAngle, outerRadius);
|
||||
vec2 tapPos = pixelPos + tap.xy;
|
||||
|
||||
if (!(isBorderingEnabled() > 0.0)) {
|
||||
|
@ -185,15 +221,15 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius,
|
|||
|
||||
{
|
||||
float check1 = float(tapPos.x < 0.5);
|
||||
float check2 = (1.0 - check1) * float(tapPos.x > imageSize.x - 0.5);
|
||||
tapPos.x = mix(tapPos.x, -tapPos.x, check1) - check2 * (imageSize.x - tapPos.x);
|
||||
float check2 = (1.0 - check1) * float(tapPos.x > sideImageSize.x - 0.5);
|
||||
tapPos.x = tapPos.x - 2.0 * tapPos.x * check1 - check2 * (sideImageSize.x - tapPos.x);
|
||||
redoTap = (check1 > 0.0 || check2 > 0.0);
|
||||
}
|
||||
|
||||
{
|
||||
float check1 = float(tapPos.y < 0.5);
|
||||
float check2 = (1.0 - check1) * float(tapPos.y > imageSize.y - 0.5);
|
||||
tapPos.y = mix(tapPos.y, -tapPos.y, check1) - check2 * (imageSize.y - tapPos.y);
|
||||
float check2 = (1.0 - check1) * float(tapPos.y > sideImageSize.y - 0.5);
|
||||
tapPos.y = tapPos.y - 2.0 * tapPos.y * check1 - check2 * (sideImageSize.y - tapPos.y);
|
||||
redoTap = (check1 > 0.0 || check2 > 0.0);
|
||||
}
|
||||
|
||||
|
@ -211,156 +247,341 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius,
|
|||
|
||||
<@func declareFetchDepthPyramidMap()@>
|
||||
|
||||
|
||||
// the depth pyramid texture
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_PYRAMID) uniform sampler2D pyramidMap;
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_DEPTH) uniform sampler2D depthPyramidTex;
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_NORMAL) uniform sampler2D normalTex;
|
||||
|
||||
float getZEye(ivec2 pixel, int level) {
|
||||
return -texelFetch(pyramidMap, pixel, level).x;
|
||||
vec2 getFramebufferUVFromSideUV(ivec4 side, vec2 uv) {
|
||||
return mix(uv, vec2((uv.x + float(getStereoSide(side))) * 0.5, uv.y), float(isStereo()));
|
||||
}
|
||||
|
||||
vec2 getSideUVFromFramebufferUV(ivec4 side, vec2 uv) {
|
||||
return mix(uv, vec2(uv.x * 2.0 - float(getStereoSide(side)), uv.y), float(isStereo()));
|
||||
}
|
||||
|
||||
vec2 getDepthTextureSize(int level) {
|
||||
return vec2(textureSize(depthPyramidTex, level));
|
||||
}
|
||||
|
||||
vec2 getDepthTextureSideSize(int level) {
|
||||
ivec2 size = textureSize(depthPyramidTex, level);
|
||||
size.x >>= int(isStereo()) & 1;
|
||||
return vec2(size);
|
||||
}
|
||||
|
||||
vec2 getStereoSideSizeRoundUp(int resolutionLevel) {
|
||||
ivec2 fullRes = ivec2(getStereoSideSize(0));
|
||||
int resolutionDivisor = 1 << resolutionLevel;
|
||||
return vec2((fullRes + resolutionDivisor - 1) / resolutionDivisor);
|
||||
}
|
||||
|
||||
float getZEyeAtUV(vec2 texCoord, float level) {
|
||||
return -textureLod(depthPyramidTex, texCoord, level).x;
|
||||
}
|
||||
|
||||
<@func getZEyeAtUVOffset(texCoord, level, texelOffset)@>
|
||||
-textureLodOffset(depthPyramidTex, <$texCoord$>, <$level$>, <$texelOffset$>).x;
|
||||
<@endfunc@>
|
||||
|
||||
float getZEyeAtUV(ivec4 side, vec2 texCoord, float level) {
|
||||
texCoord = getFramebufferUVFromSideUV(side, texCoord);
|
||||
return getZEyeAtUV(texCoord, level);
|
||||
}
|
||||
|
||||
vec3 packNormal(vec3 normal) {
|
||||
vec3 absNormal = abs(normal);
|
||||
return 0.5 + normal * 0.5 / max(absNormal.x, max(absNormal.y, absNormal.z));
|
||||
}
|
||||
|
||||
vec3 unpackNormal(vec3 packedNormal) {
|
||||
return normalize(packedNormal*2.0 - 1.0);
|
||||
}
|
||||
|
||||
vec3 getNormalEyeAtUV(vec2 texCoord, float level) {
|
||||
return unpackNormal(textureLod(normalTex, texCoord, level).xyz);
|
||||
}
|
||||
|
||||
vec3 getNormalEyeAtUV(ivec4 side, vec2 texCoord, float level) {
|
||||
texCoord = getFramebufferUVFromSideUV(side, texCoord);
|
||||
return getNormalEyeAtUV(texCoord, level);
|
||||
}
|
||||
|
||||
vec2 snapToTexel(vec2 uv, vec2 pixelSize) {
|
||||
return (floor(uv * pixelSize - 0.5) + 0.5) / pixelSize;
|
||||
}
|
||||
|
||||
const int LOG_MAX_OFFSET = 3;
|
||||
const int MAX_MIP_LEVEL = 5;
|
||||
int evalMipFromRadius(float radius) {
|
||||
// mipLevel = floor(log(ssR / MAX_OFFSET));
|
||||
const int LOG_MAX_OFFSET = 2;
|
||||
const int MAX_MIP_LEVEL = 5;
|
||||
return clamp(findMSB(int(radius)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);
|
||||
}
|
||||
|
||||
vec2 fetchTap(ivec4 side, vec2 tapUV, float tapRadius) {
|
||||
int mipLevel = evalMipFromRadius(tapRadius * float(doFetchMips()));
|
||||
|
||||
vec3 fetchTapUnfiltered(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) {
|
||||
ivec2 ssP = ivec2(tap.xy) + ssC;
|
||||
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
|
||||
vec2 fetchUV = clamp(tapUV, vec2(0), vec2(1));
|
||||
fetchUV = getFramebufferUVFromSideUV(side, fetchUV);
|
||||
|
||||
|
||||
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize;
|
||||
|
||||
vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y);
|
||||
|
||||
vec3 P;
|
||||
P.xy = tapUV;
|
||||
P.z = -texture(pyramidMap, fetchUV).x;
|
||||
|
||||
return P;
|
||||
vec2 P;
|
||||
P.x = float(mipLevel);
|
||||
P.y = -textureLod(depthPyramidTex, fetchUV, P.x).x;
|
||||
return P;
|
||||
}
|
||||
|
||||
vec3 fetchTap(ivec4 side, ivec2 ssC, vec3 tap, vec2 imageSize) {
|
||||
int mipLevel = evalMipFromRadius(tap.z * float(doFetchMips()));
|
||||
|
||||
ivec2 ssP = ivec2(tap.xy) + ssC;
|
||||
ivec2 ssPFull = ivec2(ssP.x + side.y, ssP.y);
|
||||
|
||||
// We need to divide by 2^mipLevel to read the appropriately scaled coordinate from a MIP-map.
|
||||
// Manually clamp to the texture size because texelFetch bypasses the texture unit
|
||||
// ivec2 mipSize = textureSize(pyramidMap, mipLevel);
|
||||
ivec2 mipSize = max(ivec2(imageSize) >> mipLevel, ivec2(1));
|
||||
|
||||
ivec2 mipP = clamp(ssPFull >> mipLevel, ivec2(0), mipSize - ivec2(1));
|
||||
|
||||
vec2 tapUV = (vec2(ssP) + vec2(0.5)) / imageSize;
|
||||
vec2 fetchUV = vec2(tapUV.x + float(side.w) * 0.5 * (float(side.x) - tapUV.x), tapUV.y);
|
||||
// vec2 tapUV = (vec2(mipP) + vec2(0.5)) / vec2(mipSize);
|
||||
|
||||
vec3 P;
|
||||
P.xy = tapUV;
|
||||
// P.z = -texelFetch(pyramidMap, mipP, mipLevel).x;
|
||||
P.z = -textureLod(pyramidMap, fetchUV, float(mipLevel)).x;
|
||||
|
||||
return P;
|
||||
vec3 buildPosition(ivec4 side, vec2 fragUVPos) {
|
||||
float Zeye = getZEyeAtUV(side, fragUVPos, 0.0);
|
||||
return evalEyePositionFromZeye(side.x, Zeye, fragUVPos);
|
||||
}
|
||||
|
||||
<@func buildPositionOffset(side, fragUVPos, sideFragUVPos, texelOffset, deltaUV, position)@>
|
||||
{
|
||||
float Zeye = <$getZEyeAtUVOffset($sideFragUVPos$, 0.0, $texelOffset$)$>
|
||||
<$position$> = evalEyePositionFromZeye(<$side$>.x, Zeye, <$fragUVPos$> + vec2(<$texelOffset$>)*<$deltaUV$>);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
vec3 getMinDelta(vec3 centralPoint, vec3 offsetPointPos, vec3 offsetPointNeg) {
|
||||
vec3 delta0 = offsetPointPos - centralPoint;
|
||||
vec3 delta1 = centralPoint - offsetPointNeg;
|
||||
float sqrLength0 = dot(delta0, delta0);
|
||||
float sqrLength1 = dot(delta1, delta1);
|
||||
|
||||
return mix(delta1, delta0, float(sqrLength0 < sqrLength1));
|
||||
}
|
||||
|
||||
const ivec2 UV_RIGHT = ivec2(1,0);
|
||||
const ivec2 UV_LEFT = ivec2(-1,0);
|
||||
const ivec2 UV_TOP = ivec2(0,1);
|
||||
const ivec2 UV_BOTTOM = ivec2(0,-1);
|
||||
|
||||
vec3 buildNormal(ivec4 side, vec2 fragUVPos, vec3 fragPosition, vec2 deltaDepthUV) {
|
||||
vec2 fullUVPos = getFramebufferUVFromSideUV(side, fragUVPos);
|
||||
|
||||
vec3 fragPositionDxPos;
|
||||
vec3 fragPositionDxNeg;
|
||||
vec3 fragPositionDyPos;
|
||||
vec3 fragPositionDyNeg;
|
||||
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_RIGHT, deltaDepthUV, fragPositionDxPos)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_LEFT, deltaDepthUV, fragPositionDxNeg)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_TOP, deltaDepthUV, fragPositionDyPos)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_BOTTOM, deltaDepthUV, fragPositionDyNeg)$>
|
||||
|
||||
vec3 fragDeltaDx = getMinDelta(fragPosition, fragPositionDxPos, fragPositionDxNeg);
|
||||
vec3 fragDeltaDy = getMinDelta(fragPosition, fragPositionDyPos, fragPositionDyNeg);
|
||||
|
||||
return normalize( cross(fragDeltaDx, fragDeltaDy) );
|
||||
}
|
||||
|
||||
void buildTangentBinormal(ivec4 side, vec2 fragUVPos, vec3 fragPosition, vec3 fragNormal, vec2 deltaDepthUV,
|
||||
out vec3 fragTangent, out vec3 fragBinormal) {
|
||||
vec2 fullUVPos = getFramebufferUVFromSideUV(side, fragUVPos);
|
||||
|
||||
vec3 fragPositionDxPos;
|
||||
vec3 fragPositionDxNeg;
|
||||
vec3 fragPositionDyPos;
|
||||
vec3 fragPositionDyNeg;
|
||||
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_RIGHT, deltaDepthUV, fragPositionDxPos)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_LEFT, deltaDepthUV, fragPositionDxNeg)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_TOP, deltaDepthUV, fragPositionDyPos)$>
|
||||
<$buildPositionOffset(side, fragUVPos, fullUVPos, UV_BOTTOM, deltaDepthUV, fragPositionDyNeg)$>
|
||||
|
||||
vec3 fragDeltaDx = getMinDelta(fragPosition, fragPositionDxPos, fragPositionDxNeg);
|
||||
vec3 fragDeltaDy = getMinDelta(fragPosition, fragPositionDyPos, fragPositionDyNeg);
|
||||
|
||||
//fragTangent = normalize( cross(fragDeltaDy, fragNormal) );
|
||||
//fragBinormal = normalize( cross(fragNormal, fragDeltaDx) );
|
||||
|
||||
fragTangent = fragDeltaDx;
|
||||
fragBinormal = fragDeltaDy;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func declareEvalObscurance()@>
|
||||
|
||||
float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) {
|
||||
vec3 v = Q - C;
|
||||
float vv = dot(v, v);
|
||||
float vn = dot(v, n_C);
|
||||
struct TBNFrame {
|
||||
vec3 tangent;
|
||||
vec3 binormal;
|
||||
vec3 normal;
|
||||
};
|
||||
|
||||
// Fall off function as recommended in SAO paper
|
||||
vec3 fastAcos(vec3 x) {
|
||||
// [Eberly2014] GPGPU Programming for Games and Science
|
||||
vec3 absX = abs(x);
|
||||
vec3 res = absX * (-0.156583) + vec3(PI / 2.0);
|
||||
res *= sqrt(vec3(1.0) - absX);
|
||||
return mix(res, vec3(PI) - res, greaterThanEqual(x, vec3(0)));
|
||||
}
|
||||
|
||||
float evalVisibilitySSAO(in vec3 centerPosition, in vec3 centerNormal, in vec3 tapPosition) {
|
||||
vec3 v = tapPosition - centerPosition;
|
||||
float vv = dot(v, v);
|
||||
float vn = dot(v, centerNormal);
|
||||
|
||||
// Falloff function as recommended in SSAO paper
|
||||
const float epsilon = 0.01;
|
||||
float f = max(getRadius2() - vv, 0.0);
|
||||
return f * f * f * max((vn - getFalloffBias()) / (epsilon + vv), 0.0);
|
||||
return f * f * f * max((vn - getFalloffCosAngle()) / (epsilon + vv), 0.0);
|
||||
}
|
||||
|
||||
#define HBAO_USE_COS_ANGLE 1
|
||||
#define HBAO_USE_OVERHANG_HACK 0
|
||||
|
||||
float computeWeightForHorizon(float horizonLimit, float distanceSquared) {
|
||||
return max(0.0, 1.0 - distanceSquared * getInvRadius2());
|
||||
}
|
||||
|
||||
float computeWeightedHorizon(float horizonLimit, float distanceSquared) {
|
||||
float radiusFalloff = computeWeightForHorizon(horizonLimit, distanceSquared);
|
||||
|
||||
#if !HBAO_USE_COS_ANGLE
|
||||
horizonLimit = getFalloffSinAngle() - horizonLimit;
|
||||
#endif
|
||||
horizonLimit *= radiusFalloff;
|
||||
#if !HBAO_USE_COS_ANGLE
|
||||
horizonLimit = getFalloffSinAngle() - horizonLimit;
|
||||
#endif
|
||||
|
||||
return horizonLimit;
|
||||
}
|
||||
|
||||
<@func computeHorizon()@>
|
||||
if (tapUVPos.x<0.0 || tapUVPos.y<0.0 || tapUVPos.x>=1.0 || tapUVPos.y>=1.0) {
|
||||
// Early exit because we've hit the borders of the frame
|
||||
break;
|
||||
}
|
||||
vec2 tapMipZ = fetchTap(side, tapUVPos, radius);
|
||||
vec3 tapPositionES = evalEyePositionFromZeye(side.x, tapMipZ.y, tapUVPos);
|
||||
vec3 deltaVec = tapPositionES - fragPositionES;
|
||||
float distanceSquared = dot(deltaVec, deltaVec);
|
||||
float deltaDotNormal = dot(deltaVec, fragFrameES.normal);
|
||||
#if HBAO_USE_COS_ANGLE
|
||||
float tapHorizonLimit = deltaDotNormal;
|
||||
#else
|
||||
float tapHorizonLimit = dot(deltaVec, fragFrameES.tangent);
|
||||
#endif
|
||||
tapHorizonLimit *= inversesqrt(distanceSquared);
|
||||
|
||||
if (distanceSquared < getRadius2() && deltaDotNormal>0.0) {
|
||||
#if HBAO_USE_COS_ANGLE
|
||||
float weight = computeWeightForHorizon(tapHorizonLimit, distanceSquared);
|
||||
if (tapHorizonLimit > horizonLimit) {
|
||||
occlusion += weight * (tapHorizonLimit - horizonLimit);
|
||||
horizonLimit = tapHorizonLimit;
|
||||
}
|
||||
#if HBAO_USE_OVERHANG_HACK
|
||||
else if (dot(deltaVec, fragFrameES.tangent) < 0.0) {
|
||||
// This is a hack to try to handle the case where the occlusion angle is
|
||||
// greater than 90°
|
||||
occlusion = mix(occlusion, (occlusion+1.0) * 0.5, weight);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
if (tapHorizonLimit < horizonLimit) {
|
||||
tapHorizonLimit = computeWeightedHorizon(tapHorizonLimit, distanceSquared);
|
||||
horizonLimit = min(horizonLimit, tapHorizonLimit);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func declareBlurPass(axis)@>
|
||||
#define HBAO_HORIZON_SEARCH_CONSTANT_STEP 0
|
||||
|
||||
<$declarePackOcclusionDepth()$>
|
||||
<$declareAmbientOcclusion()$>
|
||||
float computeOcclusion(ivec4 side, vec2 fragUVPos, vec3 fragPositionES, TBNFrame fragFrameES, vec2 searchDir, float searchRadius, int stepCount) {
|
||||
float occlusion = 0.0;
|
||||
#if HBAO_USE_COS_ANGLE
|
||||
float horizonLimit = getFalloffCosAngle();
|
||||
#else
|
||||
float horizonLimit = getFalloffSinAngle();
|
||||
#endif
|
||||
|
||||
// the source occlusion texture
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2D occlusionMap;
|
||||
if (stepCount>0) {
|
||||
vec2 deltaTapUV = searchDir / float(stepCount);
|
||||
vec2 tapUVPos;
|
||||
float deltaRadius = searchRadius / float(stepCount);
|
||||
vec2 sideDepthSize = getDepthTextureSideSize(0);
|
||||
|
||||
vec2 fetchOcclusionDepthRaw(ivec2 coords, out vec3 raw) {
|
||||
raw = texelFetch(occlusionMap, coords, 0).xyz;
|
||||
return unpackOcclusionDepth(raw);
|
||||
}
|
||||
#if HBAO_HORIZON_SEARCH_CONSTANT_STEP
|
||||
float radius = 0.0;
|
||||
int stepIndex;
|
||||
|
||||
vec2 fetchOcclusionDepth(ivec2 coords) {
|
||||
return unpackOcclusionDepth(texelFetch(occlusionMap, coords, 0).xyz);
|
||||
}
|
||||
for (stepIndex=0 ; stepIndex<stepCount ; stepIndex++) {
|
||||
fragUVPos += deltaTapUV;
|
||||
radius += deltaRadius;
|
||||
tapUVPos = snapToTexel(fragUVPos, sideDepthSize);
|
||||
|
||||
const int RADIUS_SCALE = 1;
|
||||
const float BLUR_WEIGHT_OFFSET = 0.05;
|
||||
const float BLUR_EDGE_SCALE = 2000.0;
|
||||
<$computeHorizon()$>
|
||||
}
|
||||
#else
|
||||
// Step is adapted to Mip level
|
||||
float radius = deltaRadius;
|
||||
float mipLevel = float(evalMipFromRadius(radius * float(doFetchMips())));
|
||||
|
||||
vec2 evalTapWeightedValue(ivec3 side, int r, ivec2 ssC, float key) {
|
||||
ivec2 tapOffset = <$axis$> * (r * RADIUS_SCALE);
|
||||
ivec2 ssP = (ssC + tapOffset);
|
||||
while (radius<=searchRadius) {
|
||||
fragUVPos += deltaTapUV;
|
||||
tapUVPos = snapToTexel(fragUVPos, sideDepthSize);
|
||||
|
||||
if ((ssP.x < side.y || ssP.x >= side.z + side.y) || (ssP.y < 0 || ssP.y >= int(getWidthHeight(getResolutionLevel()).y))) {
|
||||
return vec2(0.0);
|
||||
}
|
||||
vec2 tapOZ = fetchOcclusionDepth(ssC + tapOffset);
|
||||
<$computeHorizon()$>
|
||||
|
||||
// spatial domain: offset gaussian tap
|
||||
float weight = BLUR_WEIGHT_OFFSET + getBlurCoef(abs(r));
|
||||
|
||||
// range domain (the "bilateral" weight). As depth difference increases, decrease weight.
|
||||
weight *= max(0.0, 1.0 - (getBlurEdgeSharpness() * BLUR_EDGE_SCALE) * abs(tapOZ.y - key));
|
||||
|
||||
return vec2(tapOZ.x * weight, weight);
|
||||
}
|
||||
|
||||
vec3 getBlurredOcclusion(vec2 coord) {
|
||||
ivec2 ssC = ivec2(coord);
|
||||
|
||||
// Stereo side info
|
||||
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
|
||||
|
||||
vec3 rawSample;
|
||||
vec2 occlusionDepth = fetchOcclusionDepthRaw(ssC, rawSample);
|
||||
float key = occlusionDepth.y;
|
||||
|
||||
// Central pixel contribution
|
||||
float mainWeight = getBlurCoef(0);
|
||||
vec2 weightedSums = vec2(occlusionDepth.x * mainWeight, mainWeight);
|
||||
|
||||
// Accumulate weighted contributions along the bluring axis in the [-radius, radius] range
|
||||
int blurRadius = getBlurRadius();
|
||||
// negative side first
|
||||
for (int r = -blurRadius; r <= -1; ++r) {
|
||||
weightedSums += evalTapWeightedValue(side.xyz, r, ssC, key);
|
||||
}
|
||||
// then positive side
|
||||
for (int r = 1; r <= blurRadius; ++r) {
|
||||
weightedSums += evalTapWeightedValue(side.xyz, r, ssC, key);
|
||||
if (tapMipZ.x != mipLevel) {
|
||||
mipLevel = tapMipZ.x;
|
||||
deltaRadius *= 2.0;
|
||||
deltaTapUV *= 2.0;
|
||||
sideDepthSize = getDepthTextureSideSize(int(mipLevel));
|
||||
}
|
||||
radius += deltaRadius;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Final normalization
|
||||
const float epsilon = 0.0001;
|
||||
float result = weightedSums.x / (weightedSums.y + epsilon);
|
||||
|
||||
rawSample.x = result;
|
||||
return rawSample;
|
||||
#if HBAO_USE_COS_ANGLE
|
||||
occlusion = min(occlusion * getFalloffCosAngleScale(), 1.0);
|
||||
#else
|
||||
occlusion = horizonLimit * mix(1.0, getFalloffSinAngleScale(), horizonLimit > 0.0);
|
||||
#endif
|
||||
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
float evalVisibilityHBAO(ivec4 side, vec2 fragUVPos, vec2 invSideImageSize, vec2 deltaTap, float diskPixelRadius,
|
||||
vec3 fragPositionES, vec3 fragNormalES) {
|
||||
vec2 pixelSearchVec = deltaTap * diskPixelRadius;
|
||||
vec2 searchDir = pixelSearchVec * invSideImageSize;
|
||||
vec2 deltaTapUV = deltaTap * invSideImageSize;
|
||||
float obscuranceH1 = 0.0;
|
||||
float obscuranceH2 = 0.0;
|
||||
pixelSearchVec = abs(pixelSearchVec);
|
||||
int stepCount = int(ceil(max(pixelSearchVec.x, pixelSearchVec.y)));
|
||||
TBNFrame fragFrameES;
|
||||
|
||||
fragFrameES.tangent = vec3(0.0);
|
||||
fragFrameES.binormal = vec3(0.0);
|
||||
fragFrameES.normal = fragNormalES;
|
||||
|
||||
#if HBAO_USE_OVERHANG_HACK || !HBAO_USE_COS_ANGLE
|
||||
vec3 positionPos = buildPosition(side, fragUVPos + deltaTapUV);
|
||||
vec3 positionNeg = buildPosition(side, fragUVPos - deltaTapUV);
|
||||
|
||||
fragFrameES.tangent = getMinDelta(fragPositionES, positionPos, positionNeg);
|
||||
fragFrameES.tangent -= dot(fragNormalES, fragFrameES.tangent) * fragNormalES;
|
||||
fragFrameES.tangent = normalize(fragFrameES.tangent);
|
||||
#endif
|
||||
// Forward search for h1
|
||||
obscuranceH1 = computeOcclusion(side, fragUVPos, fragPositionES, fragFrameES, searchDir, diskPixelRadius, stepCount);
|
||||
|
||||
// Backward search for h2
|
||||
#if HBAO_USE_OVERHANG_HACK || !HBAO_USE_COS_ANGLE
|
||||
fragFrameES.tangent = -fragFrameES.tangent;
|
||||
#endif
|
||||
obscuranceH2 = computeOcclusion(side, fragUVPos, fragPositionES, fragFrameES, -searchDir, diskPixelRadius, stepCount);
|
||||
|
||||
return obscuranceH1 + obscuranceH2;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@endif@>
|
||||
<@endif@>
|
||||
|
|
135
libraries/render-utils/src/ssao_bilateralBlur.slf
Normal file
135
libraries/render-utils/src/ssao_bilateralBlur.slf
Normal file
|
@ -0,0 +1,135 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_bilateralBlur.frag
|
||||
//
|
||||
// Created by Sam Gateau on 1/1/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include ssao.slh@>
|
||||
|
||||
// Hack comment
|
||||
|
||||
<$declareAmbientOcclusion()$>
|
||||
<$declareFetchDepthPyramidMap()$>
|
||||
<$declarePackOcclusionDepth()$>
|
||||
|
||||
// the source occlusion texture
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2D occlusionMap;
|
||||
|
||||
LAYOUT(binding=RENDER_UTILS_BUFFER_SSAO_BLUR_PARAMS) uniform blurParamsBuffer {
|
||||
AmbientOcclusionBlurParams blurParams;
|
||||
};
|
||||
|
||||
vec2 getBlurOcclusionAxis() {
|
||||
return blurParams._blurAxis.xy;
|
||||
}
|
||||
|
||||
vec2 getBlurOcclusionUVLimit() {
|
||||
return blurParams._blurAxis.zw;
|
||||
}
|
||||
|
||||
vec3 getBlurScales() {
|
||||
return blurParams._blurInfo.xyz;
|
||||
}
|
||||
|
||||
int getBlurRadius() {
|
||||
return int(blurParams._blurInfo.w);
|
||||
}
|
||||
|
||||
vec4 fetchOcclusionPacked(ivec4 side, vec2 texCoord) {
|
||||
texCoord.x = isStereo() ? (texCoord.x + float(getStereoSide(side)) * getBlurOcclusionUVLimit().x) * 0.5 : texCoord.x;
|
||||
return textureLod(occlusionMap, texCoord, 0.0);
|
||||
}
|
||||
|
||||
float evalBlurCoefficient(vec3 blurScales, float radialDistance, float zDistance, float normalDistance) {
|
||||
vec3 distances = vec3(radialDistance, zDistance, normalDistance);
|
||||
return exp2(dot(blurScales, distances*distances));
|
||||
}
|
||||
|
||||
const float BLUR_EDGE_NORMAL_LIMIT = 0.25;
|
||||
|
||||
vec2 evalTapWeightedValue(vec3 blurScales, ivec4 side, int r, vec2 occlusionTexCoord, float fragDepth, vec3 fragNormal) {
|
||||
vec2 occlusionTexCoordLimits = getBlurOcclusionUVLimit();
|
||||
|
||||
if (any(lessThan(occlusionTexCoord, vec2(0.0))) || any(greaterThanEqual(occlusionTexCoord, occlusionTexCoordLimits)) ) {
|
||||
return vec2(0.0);
|
||||
}
|
||||
|
||||
vec4 tapOcclusionPacked = fetchOcclusionPacked(side, occlusionTexCoord);
|
||||
UnpackedOcclusion tap;
|
||||
unpackOcclusionOutput(tapOcclusionPacked, tap);
|
||||
|
||||
// range domain (the "bilateral" weight). As depth difference increases, decrease weight.
|
||||
float zDistance = tap.depth - fragDepth;
|
||||
#if SSAO_BILATERAL_BLUR_USE_NORMAL
|
||||
float normalDistance = BLUR_EDGE_NORMAL_LIMIT - min(BLUR_EDGE_NORMAL_LIMIT, dot(tap.normal, fragNormal));
|
||||
#else
|
||||
float normalDistance = 0.0;
|
||||
#endif
|
||||
float weight = evalBlurCoefficient(blurScales, float(abs(r)), zDistance, normalDistance);
|
||||
|
||||
return vec2(tap.occlusion * weight, weight);
|
||||
}
|
||||
|
||||
vec4 getBlurredOcclusion(ivec2 destPixelCoord, vec2 occlusionTexCoord, vec2 depthTexCoord) {
|
||||
// Stereo side info
|
||||
ivec4 side = getStereoSideInfo(destPixelCoord.x, 0);
|
||||
|
||||
float fragDepth = getZEyeAtUV(depthTexCoord, 0.0);
|
||||
float fragDepthKey = CSZToDepthKey(fragDepth);
|
||||
#if SSAO_BILATERAL_BLUR_USE_NORMAL
|
||||
vec3 fragNormal = getNormalEyeAtUV(depthTexCoord, 0.0);
|
||||
#else
|
||||
vec3 fragNormal = vec3(0.0, 0.0, 1.0);
|
||||
#endif
|
||||
vec2 weightedSums = vec2(0.0);
|
||||
|
||||
// Accumulate weighted contributions along the bluring axis in the [-radius, radius] range
|
||||
int blurRadius = getBlurRadius();
|
||||
vec3 blurScales = getBlurScales();
|
||||
int r;
|
||||
|
||||
// From now on, occlusionTexCoord is the UV pos in the side
|
||||
float sideTexCoord = occlusionTexCoord.x * 2.0 - float(getStereoSide(side)) * getBlurOcclusionUVLimit().x;
|
||||
occlusionTexCoord.x = mix(occlusionTexCoord.x, sideTexCoord, isStereo());
|
||||
|
||||
occlusionTexCoord -= getBlurOcclusionAxis() * float(blurRadius);
|
||||
|
||||
// negative side first
|
||||
for (r = -blurRadius; r <= -1; r++) {
|
||||
weightedSums += evalTapWeightedValue(blurScales, side, r, occlusionTexCoord, fragDepthKey, fragNormal);
|
||||
occlusionTexCoord += getBlurOcclusionAxis();
|
||||
}
|
||||
|
||||
// Central pixel contribution
|
||||
float mainWeight = 1.0;
|
||||
float pixelOcclusion = unpackOcclusion(fetchOcclusionPacked(side, occlusionTexCoord));
|
||||
weightedSums += vec2(pixelOcclusion * mainWeight, mainWeight);
|
||||
occlusionTexCoord += getBlurOcclusionAxis();
|
||||
|
||||
// then positive side
|
||||
for (r = 1; r <= blurRadius; ++r) {
|
||||
weightedSums += evalTapWeightedValue(blurScales, side, r, occlusionTexCoord, fragDepthKey, fragNormal);
|
||||
occlusionTexCoord += getBlurOcclusionAxis();
|
||||
}
|
||||
|
||||
// Final normalization
|
||||
const float epsilon = 0.0001;
|
||||
float result = weightedSums.x / (weightedSums.y + epsilon);
|
||||
|
||||
return packOcclusionOutput(result, fragDepth, fragNormal);
|
||||
}
|
||||
|
||||
layout(location=0) in vec4 varTexCoord0;
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = getBlurredOcclusion(ivec2(gl_FragCoord.xy), varTexCoord0.xy, varTexCoord0.zw);
|
||||
}
|
42
libraries/render-utils/src/ssao_bilateralBlur.slv
Normal file
42
libraries/render-utils/src/ssao_bilateralBlur.slv
Normal file
|
@ -0,0 +1,42 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_bilateralBlur.vert
|
||||
//
|
||||
// Draw the unit quad [-1,-1 -> 1,1] filling in
|
||||
// Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed
|
||||
//
|
||||
// Created by Olivier Prat on 9/12/2018
|
||||
// Copyright 2018 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 gpu/Transform.slh@>
|
||||
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
layout(location=0) out vec4 varTexCoord0;
|
||||
|
||||
void main(void) {
|
||||
const vec4 UNIT_QUAD[4] = vec4[4](
|
||||
vec4(-1.0, -1.0, 0.0, 1.0),
|
||||
vec4(1.0, -1.0, 0.0, 1.0),
|
||||
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||
vec4(1.0, 1.0, 0.0, 1.0)
|
||||
);
|
||||
vec4 pos = UNIT_QUAD[gl_VertexID];
|
||||
|
||||
// standard transform but applied to the Texcoord
|
||||
vec2 fullTexCoord = (pos.xy + 1.0) * 0.5;
|
||||
vec4 tc = vec4(fullTexCoord, pos.zw);
|
||||
|
||||
TransformObject obj = getTransformObject();
|
||||
<$transformModelToWorldPos(obj, tc, tc)$>
|
||||
|
||||
gl_Position = pos;
|
||||
varTexCoord0.xy = tc.xy;
|
||||
varTexCoord0.zw = fullTexCoord.xy;
|
||||
}
|
42
libraries/render-utils/src/ssao_buildNormals.slf
Normal file
42
libraries/render-utils/src/ssao_buildNormals.slf
Normal file
|
@ -0,0 +1,42 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_buildNormals.frag
|
||||
//
|
||||
// Created by Olivier Prat on 09/19/18.
|
||||
// Copyright 2018 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 ssao.slh@>
|
||||
<$declareAmbientOcclusion()$>
|
||||
<$declareFetchDepthPyramidMap()$>
|
||||
|
||||
layout(location=0) in vec2 varTexCoord0;
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
// Pixel being shaded
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
ivec2 fragPixelPos = ivec2(fragCoord.xy);
|
||||
vec2 fragUVPos = varTexCoord0;
|
||||
|
||||
// Stereo side info based on the real viewport size of this pass
|
||||
ivec2 sideNormalsSize = ivec2( getNormalsSideSize() );
|
||||
ivec4 side = getStereoSideInfoFromWidth(fragPixelPos.x, sideNormalsSize.x);
|
||||
|
||||
vec2 deltaDepthUV = vec2(1.0) / getDepthTextureSideSize(0);
|
||||
|
||||
// From now on, fragUVPos is the UV pos in the side
|
||||
fragUVPos = getSideUVFromFramebufferUV(side, fragUVPos);
|
||||
|
||||
// The position and normal of the pixel fragment in Eye space
|
||||
vec3 fragPositionES = buildPosition(side, fragUVPos);
|
||||
vec3 fragNormalES = buildNormal(side, fragUVPos, fragPositionES, deltaDepthUV);
|
||||
|
||||
outFragColor = vec4(packNormal(fragNormalES), 1.0);
|
||||
}
|
|
@ -37,90 +37,15 @@ vec2 getDebugCursorTexcoord(){
|
|||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
vec2 imageSize = getSideImageSize(getResolutionLevel());
|
||||
|
||||
// In debug adjust the correct frag pixel based on base resolution
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
if (getResolutionLevel() > 0) {
|
||||
fragCoord /= float(1 << getResolutionLevel());
|
||||
}
|
||||
|
||||
// Stereo side info based on the real viewport size of this pass
|
||||
vec2 sideDepthSize = getDepthTextureSideSize(0);
|
||||
|
||||
// Pixel Debugged
|
||||
vec2 cursorUV = getDebugCursorTexcoord();
|
||||
vec2 cursorPixelPos = cursorUV * imageSize;
|
||||
vec2 cursorPixelPos = cursorUV * sideDepthSize;
|
||||
|
||||
ivec2 ssC = ivec2(cursorPixelPos);
|
||||
|
||||
// Fetch the z under the pixel (stereo or not)
|
||||
float Zeye = getZEye(ssC, 0);
|
||||
ivec2 fragUVPos = ivec2(cursorPixelPos);
|
||||
|
||||
// Stereo side info
|
||||
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
|
||||
|
||||
// From now on, ssC is the pixel pos in the side
|
||||
ssC.x -= side.y;
|
||||
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / imageSize;
|
||||
|
||||
// The position and normal of the pixel fragment in Eye space
|
||||
vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos);
|
||||
vec3 Cn = evalEyeNormal(Cp);
|
||||
|
||||
// Choose the screen-space sample radius
|
||||
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
|
||||
|
||||
vec2 fragToCursor = cursorPixelPos - fragCoord.xy;
|
||||
if (dot(fragToCursor,fragToCursor) > ssDiskRadius * ssDiskRadius) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// Let's make noise
|
||||
//float randomPatternRotationAngle = getAngleDithering(ssC);
|
||||
vec3 wCp = (getViewInverse() * vec4(Cp, 1.0)).xyz;
|
||||
float randomPatternRotationAngle = getAngleDitheringWorldPos(wCp);
|
||||
|
||||
|
||||
// Accumulate the Obscurance for each samples
|
||||
float sum = 0.0;
|
||||
float keepTapRadius = 1.0;
|
||||
int keepedMip = -1;
|
||||
bool keep = false;
|
||||
int sampleCount = int(getNumSamples());
|
||||
for (int i = 0; i < sampleCount; ++i) {
|
||||
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, cursorPixelPos, imageSize);
|
||||
|
||||
// The occluding point in camera space
|
||||
vec2 fragToTap = vec2(ssC) + tap.xy - fragCoord.xy;
|
||||
{
|
||||
bool check = dot(fragToTap,fragToTap) < keepTapRadius;
|
||||
keep = keep || check;
|
||||
int checki = int(check);
|
||||
keepedMip = checki * evalMipFromRadius(tap.z * float(doFetchMips())) + (1 - checki) * keepedMip;
|
||||
}
|
||||
|
||||
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
|
||||
|
||||
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
|
||||
|
||||
sum += float(tap.z > 0.0) * evalAO(Cp, Cn, Q);
|
||||
}
|
||||
|
||||
|
||||
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
|
||||
|
||||
<! // KEEP IT for Debugging
|
||||
// Bilateral box-filter over a quad for free, respecting depth edges
|
||||
// (the difference that this makes is subtle)
|
||||
A -= float(abs(dFdx(Cp.z)) < 0.02) * dFdx(A) * ((ssC.x & 1) - 0.5);
|
||||
A -= float(abs(dFdy(Cp.z)) < 0.02) * dFdy(A) * ((ssC.y & 1) - 0.5);
|
||||
!>
|
||||
|
||||
outFragColor = vec4(packOcclusionDepth(A, CSZToDephtKey(Cp.z)), 1.0);
|
||||
|
||||
if ((dot(fragToCursor,fragToCursor) < (100.0 * keepTapRadius * keepTapRadius) )) {
|
||||
// outFragColor = vec4(vec3(A), 1.0);
|
||||
outFragColor = vec4(vec3(A), 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
outFragColor = mix(vec4(0.1), vec4(colorWheel(float(keepedMip) / float(MAX_MIP_LEVEL)), outFragColor.a), float(keep));
|
||||
// TODO
|
||||
outFragColor = packOcclusionOutput(0.0, 0.0, vec3(0.0, 0.0, 1.0));
|
||||
}
|
||||
|
|
36
libraries/render-utils/src/ssao_gather.slf
Normal file
36
libraries/render-utils/src/ssao_gather.slf
Normal file
|
@ -0,0 +1,36 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_gather.frag
|
||||
//
|
||||
// Created by Olivier Prat on 09/19/2018.
|
||||
// Copyright 2018 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 ssao.slh@>
|
||||
|
||||
// Hack comment
|
||||
|
||||
<$declareAmbientOcclusion()$>
|
||||
|
||||
// the source occlusion texture
|
||||
LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2DArray occlusionMaps;
|
||||
|
||||
layout(location=0) in vec4 varTexCoord0;
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
// Gather the four splits of the occlusion result back into an interleaved full size
|
||||
// result (at the resolution level, of course)
|
||||
ivec2 destPixelCoord = ivec2(gl_FragCoord.xy);
|
||||
ivec2 sourcePixelCoord = destPixelCoord >> SSAO_SPLIT_LOG2_COUNT;
|
||||
ivec2 modPixelCoord = destPixelCoord & (SSAO_SPLIT_COUNT-1);
|
||||
int occlusionMapIndex = modPixelCoord.x + (modPixelCoord.y << SSAO_SPLIT_LOG2_COUNT);
|
||||
|
||||
outFragColor = texelFetch(occlusionMaps, ivec3(sourcePixelCoord, occlusionMapIndex), 0);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_makeHorizontalBlur.frag
|
||||
//
|
||||
// Created by Sam Gateau on 1/1/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
<@include ssao.slh@>
|
||||
|
||||
const ivec2 horizontal = ivec2(1,0);
|
||||
<$declareBlurPass(horizontal)$>
|
||||
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = vec4(getBlurredOcclusion(gl_FragCoord.xy), 1.0);
|
||||
}
|
|
@ -19,66 +19,91 @@
|
|||
|
||||
<$declarePackOcclusionDepth()$>
|
||||
|
||||
#define SSAO_HBAO_MAX_RADIUS 300.0
|
||||
|
||||
layout(location=0) in vec2 varTexCoord0;
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
vec2 imageSize = getSideImageSize(getResolutionLevel());
|
||||
|
||||
// Pixel being shaded
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
ivec2 ssC = ivec2(fragCoord.xy);
|
||||
ivec2 fragPixelPos = ivec2(fragCoord.xy);
|
||||
vec2 fragUVPos = varTexCoord0;
|
||||
|
||||
// Fetch the z under the pixel (stereo or not)
|
||||
float Zeye = getZEye(ssC, 0);
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
vec3 fragNormalES = getNormalEyeAtUV(fragUVPos, 0.0);
|
||||
#endif
|
||||
|
||||
// Stereo side info
|
||||
ivec4 side = getStereoSideInfo(ssC.x, getResolutionLevel());
|
||||
|
||||
// From now on, ssC is the pixel pos in the side
|
||||
ssC.x -= side.y;
|
||||
vec2 fragPos = (vec2(ssC) + vec2(0.5)) / imageSize;
|
||||
|
||||
// The position and normal of the pixel fragment in Eye space
|
||||
vec3 Cp = evalEyePositionFromZeye(side.x, Zeye, fragPos);
|
||||
vec3 Cn = evalEyeNormal(Cp);
|
||||
|
||||
// Choose the screen-space sample radius
|
||||
float ssDiskRadius = evalDiskRadius(Cp.z, imageSize);
|
||||
|
||||
// Let's make noise
|
||||
float randomPatternRotationAngle = getAngleDithering(ssC);
|
||||
//vec3 wCp = (getViewInverse() * vec4(Cp, 1.0)).xyz;
|
||||
//float randomPatternRotationAngle = getAngleDitheringWorldPos(wCp);
|
||||
|
||||
// Accumulate the Obscurance for each samples
|
||||
float sum = 0.0;
|
||||
int sampleCount = int(getNumSamples());
|
||||
for (int i = 0; i < sampleCount; ++i) {
|
||||
vec3 tap = getTapLocationClamped(i, randomPatternRotationAngle, ssDiskRadius, vec2(ssC), imageSize);
|
||||
|
||||
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
|
||||
|
||||
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
|
||||
|
||||
sum += float(tap.z > 0.0) * evalAO(Cp, Cn, Q);
|
||||
// Stereo side info based on the real viewport size of this pass
|
||||
vec2 sideDepthSize = getDepthTextureSideSize(0);
|
||||
ivec2 sideOcclusionSize;
|
||||
if (isHorizonBased()) {
|
||||
sideOcclusionSize = ivec2( getOcclusionSplitSideSize() );
|
||||
} else {
|
||||
sideOcclusionSize = ivec2( getOcclusionSideSize() );
|
||||
}
|
||||
|
||||
float A = max(0.0, 1.0 - sum * getObscuranceScaling() * 5.0 * getInvNumSamples());
|
||||
ivec4 side = getStereoSideInfoFromWidth(fragPixelPos.x, sideOcclusionSize.x);
|
||||
// From now on, fragUVPos is the UV pos in the side
|
||||
fragUVPos = getSideUVFromFramebufferUV(side, fragUVPos);
|
||||
fragUVPos = snapToTexel(fragUVPos, sideDepthSize);
|
||||
|
||||
// KEEP IT for Debugging
|
||||
// Bilateral box-filter over a quad for free, respecting depth edges
|
||||
// (the difference that this makes is subtle)
|
||||
A -= float(abs(dFdx(Cp.z)) < 0.02) * dFdx(A) * (float(ssC.x & 1) - 0.5);
|
||||
A -= float(abs(dFdy(Cp.z)) < 0.02) * dFdy(A) * (float(ssC.y & 1) - 0.5);
|
||||
// The position and normal of the pixel fragment in Eye space
|
||||
vec2 deltaDepthUV = vec2(2.0) / sideDepthSize;
|
||||
vec3 fragPositionES = buildPosition(side, fragUVPos);
|
||||
#if !SSAO_USE_QUAD_SPLIT
|
||||
vec3 fragNormalES = buildNormal(side, fragUVPos, fragPositionES, deltaDepthUV);
|
||||
#endif
|
||||
|
||||
outFragColor = vec4(packOcclusionDepth(A, CSZToDephtKey(Cp.z)), 1.0);
|
||||
float occlusion = 1.0;
|
||||
|
||||
/* {
|
||||
vec3 tap = getTapLocationClamped(2, randomPatternRotationAngle, ssDiskRadius, ssC, imageSize);
|
||||
vec3 tapUVZ = fetchTap(side, ssC, tap, imageSize);
|
||||
vec2 fetchUV = vec2(tapUVZ.x + side.w * 0.5 * (side.x - tapUVZ.x), tapUVZ.y);
|
||||
vec3 Q = evalEyePositionFromZeye(side.x, tapUVZ.z, tapUVZ.xy);
|
||||
outFragColor = vec4(fetchUV, 0.0, 1.0);
|
||||
}*/
|
||||
if (fragPositionES.z > (1.0-getPosLinearDepthFar())) {
|
||||
// Choose the screen-space sample radius
|
||||
float diskPixelRadius = evalDiskRadius(fragPositionES.z, sideDepthSize);
|
||||
if (isHorizonBased()) {
|
||||
diskPixelRadius = min(diskPixelRadius, SSAO_HBAO_MAX_RADIUS);
|
||||
}
|
||||
|
||||
// Let's make noise
|
||||
float randomPatternRotationAngle = 0.0;
|
||||
|
||||
// Accumulate the obscurance for each samples
|
||||
float obscuranceSum = 0.0;
|
||||
int numSamples = int(getNumSamples());
|
||||
float invNumSamples = getInvNumSamples();
|
||||
|
||||
if (isHorizonBased()) {
|
||||
randomPatternRotationAngle = getAngleDithering(fragPixelPos);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i) {
|
||||
vec3 deltaTap = getUnitTapLocation(i, 1.0, randomPatternRotationAngle, PI);
|
||||
obscuranceSum += evalVisibilityHBAO(side, fragUVPos, deltaDepthUV, deltaTap.xy, diskPixelRadius, fragPositionES, fragNormalES);
|
||||
}
|
||||
obscuranceSum *= invNumSamples;
|
||||
#if HBAO_USE_COS_ANGLE
|
||||
obscuranceSum = 1.0 - obscuranceSum * getObscuranceScaling();
|
||||
#else
|
||||
obscuranceSum = mix(1.0, obscuranceSum, getObscuranceScaling());
|
||||
#endif
|
||||
} else {
|
||||
// Steps are in the depth texture resolution
|
||||
vec2 depthTexFragPixelPos = fragUVPos * sideDepthSize;
|
||||
|
||||
randomPatternRotationAngle = getAngleDitheringPixelPos(fragPixelPos) + getAngleDitheringSplit();
|
||||
|
||||
for (int i = 0; i < numSamples; ++i) {
|
||||
vec3 tap = getTapLocationClampedSSAO(i, randomPatternRotationAngle, diskPixelRadius, depthTexFragPixelPos, sideDepthSize);
|
||||
vec2 tapUV = fragUVPos + tap.xy * deltaDepthUV;
|
||||
vec2 tapMipZ = fetchTap(side, tapUV, tap.z);
|
||||
vec3 tapPositionES = evalEyePositionFromZeye(side.x, tapMipZ.y, tapUV);
|
||||
obscuranceSum += float(tap.z > 0.0) * evalVisibilitySSAO(fragPositionES, fragNormalES, tapPositionES);
|
||||
}
|
||||
obscuranceSum *= invNumSamples;
|
||||
obscuranceSum = 1.0 - obscuranceSum * getObscuranceScaling();
|
||||
}
|
||||
|
||||
occlusion = clamp(obscuranceSum, 0.0, 1.0);
|
||||
}
|
||||
outFragColor = packOcclusionOutput(occlusion, fragPositionES.z, fragNormalES);
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_makeVerticalBlur.frag
|
||||
//
|
||||
// Created by Sam Gateau on 1/1/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
<@include ssao.slh@>
|
||||
|
||||
const ivec2 vertical = ivec2(0,1);
|
||||
<$declareBlurPass(vertical)$>
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
float occlusion = getBlurredOcclusion(gl_FragCoord.xy).x;
|
||||
outFragColor = vec4(occlusion, 0.0, 0.0, occlusion);
|
||||
}
|
28
libraries/render-utils/src/ssao_mip_depth.slf
Normal file
28
libraries/render-utils/src/ssao_mip_depth.slf
Normal file
|
@ -0,0 +1,28 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// ssao_mip_depth.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Olivier Prat on 4/18/18.
|
||||
// Copyright 2018 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 gpu/ShaderConstants.h@>
|
||||
|
||||
LAYOUT(binding=GPU_TEXTURE_MIP_CREATION_INPUT) uniform sampler2D depthTexture;
|
||||
|
||||
layout(location=0) in vec2 varTexCoord0;
|
||||
|
||||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
vec4 depths = textureGather(depthTexture, varTexCoord0);
|
||||
// Keep the minimum depth
|
||||
float outZ = min(depths.w, min(depths.z, min(depths.x, depths.y)));
|
||||
|
||||
outFragColor = vec4(vec3(outZ), 1.0);
|
||||
}
|
64
libraries/render-utils/src/ssao_shared.h
Normal file
64
libraries/render-utils/src/ssao_shared.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
// <!
|
||||
// Created by Olivier Prat on 2018/09/18
|
||||
// Copyright 2013-2018 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
|
||||
// !>
|
||||
|
||||
// <@if not RENDER_UTILS_SSAO_SHARED_H@>
|
||||
// <@def RENDER_UTILS_SSAO_SHARED_H@>
|
||||
|
||||
// Hack comment to absorb the extra '//' scribe prepends
|
||||
|
||||
#ifndef RENDER_UTILS_SSAO_SHARED_H
|
||||
#define RENDER_UTILS_SSAO_SHARED_H
|
||||
|
||||
#define SSAO_USE_QUAD_SPLIT 1
|
||||
#define SSAO_BILATERAL_BLUR_USE_NORMAL 0
|
||||
|
||||
#define SSAO_DEPTH_KEY_SCALE 300.0
|
||||
|
||||
#if SSAO_USE_QUAD_SPLIT
|
||||
#define SSAO_SPLIT_LOG2_COUNT 2
|
||||
#else
|
||||
#define SSAO_SPLIT_LOG2_COUNT 0
|
||||
#endif
|
||||
#define SSAO_SPLIT_COUNT (1 << SSAO_SPLIT_LOG2_COUNT)
|
||||
|
||||
// glsl / C++ compatible source as interface for ambient occlusion
|
||||
#ifdef __cplusplus
|
||||
# define SSAO_VEC4 glm::vec4
|
||||
# define SSAO_MAT4 glm::mat4
|
||||
#else
|
||||
# define SSAO_VEC4 vec4
|
||||
# define SSAO_MAT4 mat4
|
||||
#endif
|
||||
|
||||
struct AmbientOcclusionParams {
|
||||
SSAO_VEC4 _resolutionInfo;
|
||||
SSAO_VEC4 _radiusInfo;
|
||||
SSAO_VEC4 _ditheringInfo;
|
||||
SSAO_VEC4 _sampleInfo;
|
||||
SSAO_VEC4 _falloffInfo;
|
||||
SSAO_VEC4 _sideSizes[2];
|
||||
};
|
||||
|
||||
struct AmbientOcclusionFrameParams {
|
||||
SSAO_VEC4 _angleInfo;
|
||||
};
|
||||
|
||||
struct AmbientOcclusionBlurParams {
|
||||
SSAO_VEC4 _blurInfo;
|
||||
SSAO_VEC4 _blurAxis;
|
||||
};
|
||||
|
||||
#endif // RENDER_UTILS_SHADER_CONSTANTS_H
|
||||
|
||||
// <@if 1@>
|
||||
// Trigger Scribe include
|
||||
// <@endif@> <!def that !>
|
||||
|
||||
// <@endif@>
|
||||
|
||||
// Hack Comment
|
|
@ -26,9 +26,7 @@ layout(location=1) out vec4 outNormal;
|
|||
void main(void) {
|
||||
// Gather 2 by 2 quads from texture and downsample
|
||||
|
||||
// Try different filters for Z
|
||||
vec4 Zeyes = textureGather(linearDepthMap, varTexCoord0, 0);
|
||||
// float Zeye = texture(linearDepthMap, varTexCoord0).x;
|
||||
|
||||
vec4 rawNormalsX = textureGather(normalMap, varTexCoord0, 0);
|
||||
vec4 rawNormalsY = textureGather(normalMap, varTexCoord0, 1);
|
||||
|
|
|
@ -25,7 +25,7 @@ LAYOUT(binding=0) uniform blurParamsBuffer {
|
|||
BlurParameters parameters;
|
||||
};
|
||||
|
||||
vec2 getViewportInvWidthHeight() {
|
||||
vec2 getInvWidthHeight() {
|
||||
return parameters.resolutionInfo.zw;
|
||||
}
|
||||
|
||||
|
|
|
@ -480,8 +480,8 @@ public:
|
|||
protected:
|
||||
PayloadPointer _payload;
|
||||
ItemKey _key;
|
||||
ItemCell _cell{ INVALID_CELL };
|
||||
Index _transitionId{ INVALID_INDEX };
|
||||
ItemCell _cell { INVALID_CELL };
|
||||
Index _transitionId { INVALID_INDEX };
|
||||
|
||||
friend class Scene;
|
||||
};
|
||||
|
|
|
@ -391,7 +391,12 @@ void Scene::transitionItems(const Transaction::TransitionAdds& transactions) {
|
|||
|
||||
// Remove pre-existing transition, if need be
|
||||
if (!TransitionStage::isIndexInvalid(transitionId)) {
|
||||
resetItemTransition(itemId);
|
||||
// Only remove if:
|
||||
// transitioning to something other than none or we're transitioning to none from ELEMENT_LEAVE_DOMAIN or USER_LEAVE_DOMAIN
|
||||
const auto& oldTransitionType = transitionStage->getTransition(transitionId).eventType;
|
||||
if (transitionType != Transition::NONE || !(oldTransitionType == Transition::ELEMENT_LEAVE_DOMAIN || oldTransitionType == Transition::USER_LEAVE_DOMAIN)) {
|
||||
resetItemTransition(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new one.
|
||||
|
|
|
@ -157,7 +157,7 @@ public:
|
|||
|
||||
// This next call are NOT threadsafe, you have to call them from the correct thread to avoid any potential issues
|
||||
|
||||
// Access a particular item form its ID
|
||||
// Access a particular item from its ID
|
||||
// WARNING, There is No check on the validity of the ID, so this could return a bad Item
|
||||
const Item& getItem(const ItemID& id) const { return _items[id]; }
|
||||
|
||||
|
|
|
@ -19,6 +19,6 @@ layout(location=0) in vec2 varTexCoord0;
|
|||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight());
|
||||
outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(1.0, 0.0), getInvWidthHeight());
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,6 @@ layout(location=0) in vec2 varTexCoord0;
|
|||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight());
|
||||
outFragColor = pixelShaderGaussianDepthAware(varTexCoord0, vec2(0.0, 1.0), getInvWidthHeight());
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,6 @@ layout(location=0) in vec2 varTexCoord0;
|
|||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = pixelShaderGaussian(varTexCoord0, vec2(1.0, 0.0), getViewportInvWidthHeight());
|
||||
outFragColor = pixelShaderGaussian(varTexCoord0, vec2(1.0, 0.0), getInvWidthHeight());
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,6 @@ layout(location=0) in vec2 varTexCoord0;
|
|||
layout(location=0) out vec4 outFragColor;
|
||||
|
||||
void main(void) {
|
||||
outFragColor = pixelShaderGaussian(varTexCoord0, vec2(0.0, 1.0), getViewportInvWidthHeight());
|
||||
outFragColor = pixelShaderGaussian(varTexCoord0, vec2(0.0, 1.0), getInvWidthHeight());
|
||||
}
|
||||
|
||||
|
|
|
@ -63,15 +63,15 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound
|
|||
optionsCopy.ambisonic = sound->isAmbisonic();
|
||||
optionsCopy.localOnly = optionsCopy.localOnly || sound->isAmbisonic(); // force localOnly when Ambisonic
|
||||
|
||||
auto injector = AudioInjector::playSound(sound->getByteArray(), optionsCopy);
|
||||
auto injector = AudioInjector::playSound(sound, optionsCopy);
|
||||
if (!injector) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
return new ScriptAudioInjector(injector);
|
||||
|
||||
} else {
|
||||
qCDebug(scriptengine) << "AudioScriptingInterface::playSound called with null Sound object.";
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -766,8 +766,8 @@ protected:
|
|||
*/
|
||||
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status);
|
||||
|
||||
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
|
||||
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
|
||||
EntityItemID currentEntityIdentifier; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
|
||||
QUrl currentSandboxURL; // The toplevel url string for the entity script that loaded the code being executed, else empty.
|
||||
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
|
||||
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
|
||||
|
||||
|
|
|
@ -13,3 +13,4 @@ precision highp float;
|
|||
precision highp samplerBuffer;
|
||||
precision highp sampler2DShadow;
|
||||
precision highp sampler2DArrayShadow;
|
||||
precision lowp sampler2DArray;
|
||||
|
|
21
libraries/shared/src/MathUtils.h
Normal file
21
libraries/shared/src/MathUtils.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// MathUtils.h
|
||||
// libraries/shared/src
|
||||
//
|
||||
// Created by Olivier Prat on 9/21/18.
|
||||
// Copyright 2018 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_MathUtils_h
|
||||
#define hifi_MathUtils_h
|
||||
|
||||
template <class T>
|
||||
T divideRoundUp(const T& numerator, int divisor) {
|
||||
return (numerator + divisor - T(1)) / divisor;
|
||||
}
|
||||
|
||||
#endif // hifi_MathUtils_h
|
||||
|
|
@ -211,7 +211,7 @@ void TabletScriptingInterface::playSound(TabletAudioEvents aEvent) {
|
|||
options.ambisonic = sound->isAmbisonic();
|
||||
options.localOnly = true;
|
||||
|
||||
AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound->getByteArray(), options);
|
||||
AudioInjectorPointer injector = AudioInjector::playSoundAndDelete(sound, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ void SoundEffect::play(QVariant position) {
|
|||
_injector->setOptions(options);
|
||||
_injector->restart();
|
||||
} else {
|
||||
QByteArray samples = _sound->getByteArray();
|
||||
_injector = AudioInjector::playSound(samples, options);
|
||||
_injector = AudioInjector::playSound(_sound, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,48 +7,60 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import "qrc:///qml/styles-uit"
|
||||
import "qrc:///qml/controls-uit" as HifiControls
|
||||
|
||||
import "configSlider"
|
||||
import "../lib/plotperf"
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
Rectangle {
|
||||
HifiConstants { id: hifi;}
|
||||
id: root;
|
||||
anchors.margins: hifi.dimensions.contentMargin.x
|
||||
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
Column {
|
||||
id: surfaceGeometry
|
||||
spacing: 10
|
||||
spacing: 8
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: hifi.dimensions.contentMargin.x
|
||||
|
||||
Column{
|
||||
Repeater {
|
||||
model: [
|
||||
"Radius:radius:2.0:false",
|
||||
"Level:obscuranceLevel:1.0:false",
|
||||
"Num Taps:numSamples:32:true",
|
||||
"Taps Spiral:numSpiralTurns:10.0:false",
|
||||
"Falloff Bias:falloffBias:0.2:false",
|
||||
"Edge Sharpness:edgeSharpness:1.0:false",
|
||||
"Blur Radius:blurRadius:10.0:false",
|
||||
]
|
||||
ConfigSlider {
|
||||
label: qsTr(modelData.split(":")[0])
|
||||
integral: (modelData.split(":")[3] == 'true')
|
||||
config: Render.getConfig("RenderMainView.AmbientOcclusion")
|
||||
property: modelData.split(":")[1]
|
||||
max: modelData.split(":")[2]
|
||||
min: 0.0
|
||||
}
|
||||
Repeater {
|
||||
model: [
|
||||
"Blur Edge Sharpness:edgeSharpness:1.0:false",
|
||||
"Blur Radius:blurRadius:15.0:true",
|
||||
"Resolution Downscale:resolutionLevel:2:true",
|
||||
]
|
||||
ConfigSlider {
|
||||
label: qsTr(modelData.split(":")[0])
|
||||
integral: (modelData.split(":")[3] == 'true')
|
||||
config: Render.getConfig("RenderMainView.AmbientOcclusion")
|
||||
property: modelData.split(":")[1]
|
||||
max: modelData.split(":")[2]
|
||||
min: 0.0
|
||||
height:38
|
||||
}
|
||||
}
|
||||
Row{
|
||||
|
||||
Row {
|
||||
spacing: 10
|
||||
Column {
|
||||
Repeater {
|
||||
model: [
|
||||
"resolutionLevel:resolutionLevel",
|
||||
"horizonBased:horizonBased",
|
||||
"jitterEnabled:jitterEnabled",
|
||||
"ditheringEnabled:ditheringEnabled",
|
||||
"fetchMipsEnabled:fetchMipsEnabled",
|
||||
"borderingEnabled:borderingEnabled"
|
||||
]
|
||||
CheckBox {
|
||||
HifiControls.CheckBox {
|
||||
boxSize: 20
|
||||
text: qsTr(modelData.split(":")[0])
|
||||
checked: Render.getConfig("RenderMainView.AmbientOcclusion")[modelData.split(":")[1]]
|
||||
onCheckedChanged: { Render.getConfig("RenderMainView.AmbientOcclusion")[modelData.split(":")[1]] = checked }
|
||||
|
@ -60,7 +72,8 @@ Column {
|
|||
model: [
|
||||
"debugEnabled:showCursorPixel"
|
||||
]
|
||||
CheckBox {
|
||||
HifiControls.CheckBox {
|
||||
boxSize: 20
|
||||
text: qsTr(modelData.split(":")[0])
|
||||
checked: Render.getConfig("RenderMainView.DebugAmbientOcclusion")[modelData.split(":")[1]]
|
||||
onCheckedChanged: { Render.getConfig("RenderMainView.DebugAmbientOcclusion")[modelData.split(":")[1]] = checked }
|
||||
|
@ -84,5 +97,81 @@ Column {
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
TabView {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 400
|
||||
|
||||
Tab {
|
||||
title: "SSAO"
|
||||
|
||||
Rectangle {
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: hifi.dimensions.contentMargin.x
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
"Radius:ssaoRadius:2.0:false",
|
||||
"Level:ssaoObscuranceLevel:1.0:false",
|
||||
"Num Taps:ssaoNumSamples:64:true",
|
||||
"Taps Spiral:ssaoNumSpiralTurns:10.0:false",
|
||||
"Falloff Angle:ssaoFalloffAngle:1.0:false",
|
||||
]
|
||||
ConfigSlider {
|
||||
label: qsTr(modelData.split(":")[0])
|
||||
integral: (modelData.split(":")[3] == 'true')
|
||||
config: Render.getConfig("RenderMainView.AmbientOcclusion")
|
||||
property: modelData.split(":")[1]
|
||||
max: modelData.split(":")[2]
|
||||
min: 0.0
|
||||
height:38
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "HBAO"
|
||||
|
||||
Rectangle {
|
||||
color: hifi.colors.baseGray;
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: hifi.dimensions.contentMargin.x
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
"Radius:hbaoRadius:2.0:false",
|
||||
"Level:hbaoObscuranceLevel:1.0:false",
|
||||
"Num Taps:hbaoNumSamples:6:true",
|
||||
"Falloff Angle:hbaoFalloffAngle:1.0:false",
|
||||
]
|
||||
ConfigSlider {
|
||||
label: qsTr(modelData.split(":")[0])
|
||||
integral: (modelData.split(":")[3] == 'true')
|
||||
config: Render.getConfig("RenderMainView.AmbientOcclusion")
|
||||
property: modelData.split(":")[1]
|
||||
max: modelData.split(":")[2]
|
||||
min: 0.0
|
||||
height:38
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,53 @@
|
|||
"use strict";
|
||||
|
||||
//
|
||||
// debugSurfaceGeometryPass.js
|
||||
// debugAmbientOcclusionPass.js
|
||||
// tablet-sample-app
|
||||
//
|
||||
// Created by Sam Gateau on 6/6/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
// Created by Olivier Prat on April 19 2018.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// Set up the qml ui
|
||||
var qml = Script.resolvePath('ambientOcclusionPass.qml');
|
||||
var window = new OverlayWindow({
|
||||
title: 'Ambient Occlusion Pass',
|
||||
source: qml,
|
||||
width: 400, height: 300,
|
||||
});
|
||||
window.setPosition(Window.innerWidth - 420, 50 + 550 + 50);
|
||||
window.closed.connect(function() { Script.stop(); });
|
||||
(function() {
|
||||
var AppUi = Script.require('appUi');
|
||||
|
||||
var onMousePressEvent = function (e) {
|
||||
};
|
||||
Controller.mousePressEvent.connect(onMousePressEvent);
|
||||
|
||||
var moveDebugCursor = false;
|
||||
Controller.mousePressEvent.connect(function (e) {
|
||||
if (e.isMiddleButton) {
|
||||
moveDebugCursor = true;
|
||||
setDebugCursor(e.x, e.y);
|
||||
var onMouseReleaseEvent = function () {
|
||||
};
|
||||
Controller.mouseReleaseEvent.connect(onMouseReleaseEvent);
|
||||
|
||||
var onMouseMoveEvent = function (e) {
|
||||
};
|
||||
Controller.mouseMoveEvent.connect(onMouseMoveEvent);
|
||||
|
||||
function fromQml(message) {
|
||||
|
||||
}
|
||||
});
|
||||
Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; });
|
||||
Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); });
|
||||
|
||||
|
||||
function setDebugCursor(x, y) {
|
||||
nx = (x / Window.innerWidth);
|
||||
ny = 1.0 - ((y) / (Window.innerHeight - 32));
|
||||
|
||||
Render.getConfig("RenderMainView").getConfig("DebugAmbientOcclusion").debugCursorTexcoord = { x: nx, y: ny };
|
||||
}
|
||||
var ui;
|
||||
function startup() {
|
||||
ui = new AppUi({
|
||||
buttonName: "AO",
|
||||
home: Script.resolvePath("ambientOcclusionPass.qml"),
|
||||
onMessage: fromQml,
|
||||
//normalButton: Script.resolvePath("../../../system/assets/images/ao-i.svg"),
|
||||
//activeButton: Script.resolvePath("../../../system/assets/images/ao-a.svg")
|
||||
});
|
||||
}
|
||||
startup();
|
||||
Script.scriptEnding.connect(function () {
|
||||
Controller.mousePressEvent.disconnect(onMousePressEvent);
|
||||
Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent);
|
||||
Controller.mouseMoveEvent.disconnect(onMouseMoveEvent);
|
||||
pages.clear();
|
||||
// killEngineInspectorView();
|
||||
// killCullInspectorView();
|
||||
// killEngineLODWindow();
|
||||
});
|
||||
}());
|
||||
|
|
|
@ -204,6 +204,7 @@ Rectangle {
|
|||
ListElement { text: "Debug Scattering"; color: "White" }
|
||||
ListElement { text: "Ambient Occlusion"; color: "White" }
|
||||
ListElement { text: "Ambient Occlusion Blurred"; color: "White" }
|
||||
ListElement { text: "Ambient Occlusion Normal"; color: "White" }
|
||||
ListElement { text: "Velocity"; color: "White" }
|
||||
ListElement { text: "Custom"; color: "White" }
|
||||
}
|
||||
|
|
|
@ -252,7 +252,7 @@ input.search:focus {
|
|||
box-shadow: 0 0 0 1px #00b4ef;
|
||||
}
|
||||
|
||||
input:disabled, textarea:disabled {
|
||||
input:disabled, textarea:disabled, .draggable-number.text[disabled="disabled"] {
|
||||
background-color: #383838;
|
||||
color: #afafaf;
|
||||
}
|
||||
|
@ -889,6 +889,9 @@ div.refresh input[type="button"] {
|
|||
border-color: #afafaf;
|
||||
}
|
||||
|
||||
.colpick {
|
||||
z-index: 3;
|
||||
}
|
||||
.colpick[disabled="disabled"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -911,6 +914,79 @@ div.refresh input[type="button"] {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
.draggable-number {
|
||||
position: relative;
|
||||
}
|
||||
.draggable-number div {
|
||||
height: 28px;
|
||||
width: 92px;
|
||||
}
|
||||
.draggable-number.text {
|
||||
display: inline-block;
|
||||
color: #afafaf;
|
||||
background-color: #252525;
|
||||
font-family: FiraSans-SemiBold;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
height: 28px;
|
||||
width: 100%;
|
||||
line-height: 2;
|
||||
}
|
||||
.draggable-number.text:hover {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.draggable-number span {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
font-family: HiFi-Glyphs;
|
||||
font-size: 20px;
|
||||
z-index: 2;
|
||||
}
|
||||
.draggable-number span:hover {
|
||||
cursor: default;
|
||||
}
|
||||
.draggable-number.left-arrow {
|
||||
top: -5px;
|
||||
right: 106px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.draggable-number.right-arrow {
|
||||
top: -5px;
|
||||
left: 106px;
|
||||
}
|
||||
.draggable-number input[type=number] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.draggable-number input[type=button] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
.draggable-number input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
.draggable-number.fstuple {
|
||||
height: 28px;
|
||||
width: 124px;
|
||||
left: 12px;
|
||||
}
|
||||
.draggable-number.fstuple + .draggable-number.fstuple {
|
||||
padding-left: 28px;
|
||||
}
|
||||
.draggable-number.fstuple input {
|
||||
right: -10px;
|
||||
}
|
||||
.draggable-number.fstuple .sublabel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -16px;
|
||||
font-family: FiraSans-SemiBold;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.row .property {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
|
@ -927,10 +1003,10 @@ div.refresh input[type="button"] {
|
|||
.property.texture {
|
||||
display: block;
|
||||
}
|
||||
.property.texture input{
|
||||
.property.texture input {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
.texture-image img{
|
||||
.texture-image img {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
@ -1362,17 +1438,12 @@ input[type=button]#export {
|
|||
}
|
||||
|
||||
input#property-scale-button-rescale {
|
||||
margin-top: 6px;
|
||||
min-width: 50px;
|
||||
left: 152px;
|
||||
}
|
||||
input#property-scale-button-reset {
|
||||
margin-top: 6px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#property-userData-button-edit,
|
||||
#property-materialData-button-clear {
|
||||
margin: 6px 0 6px 0;
|
||||
left: 250px;
|
||||
}
|
||||
|
||||
#property-userData-static,
|
||||
|
@ -1557,6 +1628,10 @@ input.number-slider {
|
|||
flex-flow: column;
|
||||
}
|
||||
|
||||
.flex-column + .flex-column {
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/createAppTooltip.js"></script>
|
||||
<script type="text/javascript" src="js/draggableNumber.js"></script>
|
||||
<script type="text/javascript" src="js/entityProperties.js"></script>
|
||||
<script src="js/jsoneditor.min.js"></script>
|
||||
</head>
|
||||
|
|
158
scripts/system/html/js/draggableNumber.js
Normal file
158
scripts/system/html/js/draggableNumber.js
Normal file
|
@ -0,0 +1,158 @@
|
|||
// draggableNumber.js
|
||||
//
|
||||
// Created by David Back on 7 Nov 2018
|
||||
// Copyright 2018 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
|
||||
|
||||
const DELTA_X_FOCUS_THRESHOLD = 1;
|
||||
|
||||
function DraggableNumber(min, max, step, decimals) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step !== undefined ? step : 1;
|
||||
this.decimals = decimals;
|
||||
this.initialMouseEvent = null;
|
||||
this.lastMouseEvent = null;
|
||||
this.valueChangeFunction = null;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
DraggableNumber.prototype = {
|
||||
mouseDown: function(event) {
|
||||
if (event.target === this.elText) {
|
||||
this.initialMouseEvent = event;
|
||||
this.lastMouseEvent = event;
|
||||
document.addEventListener("mousemove", this.onDocumentMouseMove);
|
||||
document.addEventListener("mouseup", this.onDocumentMouseUp);
|
||||
}
|
||||
},
|
||||
|
||||
mouseUp: function(event) {
|
||||
if (event.target === this.elText && this.initialMouseEvent) {
|
||||
let dx = event.clientX - this.initialMouseEvent.clientX;
|
||||
if (dx <= DELTA_X_FOCUS_THRESHOLD) {
|
||||
this.elInput.style.visibility = "visible";
|
||||
this.elText.style.visibility = "hidden";
|
||||
}
|
||||
this.initialMouseEvent = null;
|
||||
}
|
||||
},
|
||||
|
||||
documentMouseMove: function(event) {
|
||||
if (this.lastMouseEvent) {
|
||||
let initialValue = this.elInput.value;
|
||||
let dx = event.clientX - this.lastMouseEvent.clientX;
|
||||
let changeValue = dx !== 0;
|
||||
if (changeValue) {
|
||||
while (dx !== 0) {
|
||||
if (dx > 0) {
|
||||
this.stepUp();
|
||||
--dx;
|
||||
} else {
|
||||
this.stepDown();
|
||||
++dx;
|
||||
}
|
||||
}
|
||||
if (this.valueChangeFunction) {
|
||||
this.valueChangeFunction();
|
||||
}
|
||||
}
|
||||
this.lastMouseEvent = event;
|
||||
}
|
||||
},
|
||||
|
||||
documentMouseUp: function(event) {
|
||||
this.lastMouseEvent = null;
|
||||
document.removeEventListener("mousemove", this.onDocumentMouseMove);
|
||||
document.removeEventListener("mouseup", this.onDocumentMouseUp);
|
||||
},
|
||||
|
||||
stepUp: function() {
|
||||
this.elInput.stepUp();
|
||||
this.inputChange();
|
||||
},
|
||||
|
||||
stepDown: function() {
|
||||
this.elInput.stepDown();
|
||||
this.inputChange();
|
||||
},
|
||||
|
||||
setValue: function(newValue) {
|
||||
if (newValue !== "" && this.decimals !== undefined) {
|
||||
this.elInput.value = parseFloat(newValue).toFixed(this.decimals);
|
||||
} else {
|
||||
this.elInput.value = newValue;
|
||||
}
|
||||
this.elText.firstChild.data = this.elInput.value;
|
||||
},
|
||||
|
||||
setValueChangeFunction: function(valueChangeFunction) {
|
||||
if (this.valueChangeFunction) {
|
||||
this.elInput.removeEventListener("change", this.valueChangeFunction);
|
||||
}
|
||||
this.valueChangeFunction = valueChangeFunction.bind(this.elInput);
|
||||
this.elInput.addEventListener("change", this.valueChangeFunction);
|
||||
},
|
||||
|
||||
inputChange: function() {
|
||||
this.setValue(this.elInput.value);
|
||||
},
|
||||
|
||||
inputBlur: function() {
|
||||
this.elInput.style.visibility = "hidden";
|
||||
this.elText.style.visibility = "visible";
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.onMouseDown = this.mouseDown.bind(this);
|
||||
this.onMouseUp = this.mouseUp.bind(this);
|
||||
this.onDocumentMouseMove = this.documentMouseMove.bind(this);
|
||||
this.onDocumentMouseUp = this.documentMouseUp.bind(this);
|
||||
this.onStepUp = this.stepUp.bind(this);
|
||||
this.onStepDown = this.stepDown.bind(this);
|
||||
this.onInputChange = this.inputChange.bind(this);
|
||||
this.onInputBlur = this.inputBlur.bind(this);
|
||||
|
||||
this.elDiv = document.createElement('div');
|
||||
this.elDiv.className = "draggable-number";
|
||||
|
||||
this.elText = document.createElement('label');
|
||||
this.elText.className = "draggable-number text";
|
||||
this.elText.innerText = " ";
|
||||
this.elText.style.visibility = "visible";
|
||||
this.elText.addEventListener("mousedown", this.onMouseDown);
|
||||
this.elText.addEventListener("mouseup", this.onMouseUp);
|
||||
|
||||
this.elLeftArrow = document.createElement('span');
|
||||
this.elRightArrow = document.createElement('span');
|
||||
this.elLeftArrow.className = 'draggable-number left-arrow';
|
||||
this.elLeftArrow.innerHTML = 'D';
|
||||
this.elLeftArrow.addEventListener("click", this.onStepDown);
|
||||
this.elRightArrow.className = 'draggable-number right-arrow';
|
||||
this.elRightArrow.innerHTML = 'D';
|
||||
this.elRightArrow.addEventListener("click", this.onStepUp);
|
||||
|
||||
this.elInput = document.createElement('input');
|
||||
this.elInput.className = "draggable-number input";
|
||||
this.elInput.setAttribute("type", "number");
|
||||
if (this.min !== undefined) {
|
||||
this.elInput.setAttribute("min", this.min);
|
||||
}
|
||||
if (this.max !== undefined) {
|
||||
this.elInput.setAttribute("max", this.max);
|
||||
}
|
||||
if (this.step !== undefined) {
|
||||
this.elInput.setAttribute("step", this.step);
|
||||
}
|
||||
this.elInput.style.visibility = "hidden";
|
||||
this.elInput.addEventListener("change", this.onInputChange);
|
||||
this.elInput.addEventListener("blur", this.onInputBlur);
|
||||
|
||||
this.elText.appendChild(this.elLeftArrow);
|
||||
this.elText.appendChild(this.elInput);
|
||||
this.elText.appendChild(this.elRightArrow);
|
||||
this.elDiv.appendChild(this.elText);
|
||||
}
|
||||
};
|
|
@ -260,7 +260,7 @@ const GROUPS = [
|
|||
buttons: [ { id: "copy", label: "Copy from Skybox",
|
||||
className: "black", onClick: copySkyboxURLToAmbientURL } ],
|
||||
propertyID: "copyURLToAmbient",
|
||||
showPropertyRule: { "skyboxMode": "enabled" },
|
||||
showPropertyRule: { "ambientLightMode": "enabled" },
|
||||
},
|
||||
{
|
||||
label: "Haze",
|
||||
|
@ -315,7 +315,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Background Blend",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -337,7 +337,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Glare Angle",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -353,7 +353,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Intensity",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -363,7 +363,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Threshold",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -373,7 +373,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Size",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
|
@ -621,7 +621,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Lifespan",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
unit: "s",
|
||||
min: 0.01,
|
||||
max: 10,
|
||||
|
@ -631,7 +631,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Max Particles",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 10000,
|
||||
step: 1,
|
||||
|
@ -652,7 +652,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Emit Rate",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 1000,
|
||||
step: 1,
|
||||
|
@ -660,7 +660,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Emit Speed",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 0.01,
|
||||
|
@ -669,7 +669,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Speed Spread",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 0.01,
|
||||
|
@ -688,7 +688,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Emit Radius Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -723,7 +723,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -733,7 +733,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -742,7 +742,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -754,7 +754,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Size Spread",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -810,7 +810,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -820,7 +820,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -829,7 +829,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -841,7 +841,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Alpha Spread",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -886,7 +886,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -898,7 +898,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -909,7 +909,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -923,7 +923,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Spin Spread",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -950,7 +950,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: -180,
|
||||
max: 0,
|
||||
step: 1,
|
||||
|
@ -961,7 +961,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -978,7 +978,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -989,7 +989,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "slider",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -1264,6 +1264,7 @@ const GROUPS = [
|
|||
label: "Linear Velocity",
|
||||
type: "vec3",
|
||||
vec3Type: "xyz",
|
||||
step: 0.1,
|
||||
decimals: 4,
|
||||
subLabels: [ "x", "y", "z" ],
|
||||
unit: "m/s",
|
||||
|
@ -1272,6 +1273,9 @@ const GROUPS = [
|
|||
{
|
||||
label: "Linear Damping",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
decimals: 2,
|
||||
propertyID: "damping",
|
||||
},
|
||||
|
@ -1288,24 +1292,36 @@ const GROUPS = [
|
|||
{
|
||||
label: "Angular Damping",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
decimals: 4,
|
||||
propertyID: "angularDamping",
|
||||
},
|
||||
{
|
||||
label: "Bounciness",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
decimals: 4,
|
||||
propertyID: "restitution",
|
||||
},
|
||||
{
|
||||
label: "Friction",
|
||||
type: "number",
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
decimals: 4,
|
||||
propertyID: "friction",
|
||||
},
|
||||
{
|
||||
label: "Density",
|
||||
type: "number",
|
||||
min: 100,
|
||||
max: 10000,
|
||||
step: 1,
|
||||
decimals: 4,
|
||||
propertyID: "density",
|
||||
},
|
||||
|
@ -1314,6 +1330,8 @@ const GROUPS = [
|
|||
type: "vec3",
|
||||
vec3Type: "xyz",
|
||||
subLabels: [ "x", "y", "z" ],
|
||||
step: 0.1,
|
||||
decimals: 4,
|
||||
unit: "m/s<sup>2</sup>",
|
||||
propertyID: "gravity",
|
||||
},
|
||||
|
@ -1374,26 +1392,16 @@ const PROPERTY_NAME_DIVISION = {
|
|||
};
|
||||
|
||||
const VECTOR_ELEMENTS = {
|
||||
X_INPUT: 0,
|
||||
Y_INPUT: 1,
|
||||
Z_INPUT: 2,
|
||||
X_NUMBER: 0,
|
||||
Y_NUMBER: 1,
|
||||
Z_NUMBER: 2,
|
||||
};
|
||||
|
||||
const COLOR_ELEMENTS = {
|
||||
COLOR_PICKER: 0,
|
||||
RED_INPUT: 1,
|
||||
GREEN_INPUT: 2,
|
||||
BLUE_INPUT: 3,
|
||||
};
|
||||
|
||||
const SLIDER_ELEMENTS = {
|
||||
SLIDER: 0,
|
||||
NUMBER_INPUT: 1,
|
||||
};
|
||||
|
||||
const ICON_ELEMENTS = {
|
||||
ICON: 0,
|
||||
LABEL: 1,
|
||||
RED_NUMBER: 1,
|
||||
GREEN_NUMBER: 2,
|
||||
BLUE_NUMBER: 3,
|
||||
};
|
||||
|
||||
const TEXTURE_ELEMENTS = {
|
||||
|
@ -1427,17 +1435,17 @@ function getPropertyInputElement(propertyID) {
|
|||
switch (property.data.type) {
|
||||
case 'string':
|
||||
case 'bool':
|
||||
case 'number':
|
||||
case 'slider':
|
||||
case 'dropdown':
|
||||
case 'textarea':
|
||||
case 'texture':
|
||||
return property.elInput;
|
||||
case 'number':
|
||||
return property.elNumber.elInput;
|
||||
case 'vec3':
|
||||
case 'vec2':
|
||||
return { x: property.elInputX, y: property.elInputY, z: property.elInputZ };
|
||||
return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput };
|
||||
case 'color':
|
||||
return { red: property.elInputR, green: property.elInputG, blue: property.elInputB };
|
||||
return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput };
|
||||
case 'icon':
|
||||
return property.elLabel;
|
||||
default:
|
||||
|
@ -1460,10 +1468,11 @@ function disableChildren(el, selector) {
|
|||
}
|
||||
|
||||
function enableProperties() {
|
||||
enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker");
|
||||
enableChildren(document.getElementById("properties-list"),
|
||||
"input, textarea, checkbox, .dropdown dl, .color-picker , .draggable-number.text");
|
||||
enableChildren(document, ".colpick");
|
||||
|
||||
let elLocked = getPropertyInputElement("locked");
|
||||
|
||||
if (elLocked.checked === false) {
|
||||
removeStaticUserData();
|
||||
removeStaticMaterialData();
|
||||
|
@ -1471,13 +1480,14 @@ function enableProperties() {
|
|||
}
|
||||
|
||||
function disableProperties() {
|
||||
disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker");
|
||||
disableChildren(document.getElementById("properties-list"),
|
||||
"input, textarea, checkbox, .dropdown dl, .color-picker, .draggable-number.text");
|
||||
disableChildren(document, ".colpick");
|
||||
for (let pickKey in colorPickers) {
|
||||
colorPickers[pickKey].colpickHide();
|
||||
}
|
||||
|
||||
let elLocked = getPropertyInputElement("locked");
|
||||
|
||||
if (elLocked.checked === true) {
|
||||
if ($('#property-userData-editor').css('display') === "block") {
|
||||
showStaticUserData();
|
||||
|
@ -1507,32 +1517,28 @@ function resetProperties() {
|
|||
property.elInput.checked = false;
|
||||
break;
|
||||
}
|
||||
case 'number':
|
||||
case 'slider': {
|
||||
case 'number': {
|
||||
if (propertyData.defaultValue !== undefined) {
|
||||
property.elInput.value = propertyData.defaultValue;
|
||||
property.elNumber.setValue(propertyData.defaultValue);
|
||||
} else {
|
||||
property.elInput.value = "";
|
||||
}
|
||||
if (property.elSlider !== undefined) {
|
||||
property.elSlider.value = property.elInput.value;
|
||||
property.elNumber.setValue("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'vec3':
|
||||
case 'vec2': {
|
||||
property.elInputX.value = "";
|
||||
property.elInputY.value = "";
|
||||
if (property.elInputZ !== undefined) {
|
||||
property.elInputZ.value = "";
|
||||
property.elNumberX.setValue("");
|
||||
property.elNumberY.setValue("");
|
||||
if (property.elNumberZ !== undefined) {
|
||||
property.elNumberZ.setValue("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'color': {
|
||||
property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")";
|
||||
property.elInputR.value = "";
|
||||
property.elInputG.value = "";
|
||||
property.elInputB.value = "";
|
||||
property.elNumberR.setValue("");
|
||||
property.elNumberG.setValue("");
|
||||
property.elNumberB.setValue("");
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
|
@ -1611,6 +1617,20 @@ function getPropertyValue(originalPropertyName) {
|
|||
return propertyValue;
|
||||
}
|
||||
|
||||
function updateVisibleSpaceModeProperties() {
|
||||
for (let propertyID in properties) {
|
||||
if (properties.hasOwnProperty(propertyID)) {
|
||||
let property = properties[propertyID];
|
||||
let propertySpaceMode = property.spaceMode;
|
||||
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL) {
|
||||
showPropertyElement(propertyID, propertySpaceMode === currentSpaceMode);
|
||||
} else {
|
||||
showPropertyElement(propertyID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PROPERTY UPDATE FUNCTIONS
|
||||
|
@ -1797,10 +1817,12 @@ function createBoolProperty(property, elProperty) {
|
|||
let subPropertyOf = propertyData.subPropertyOf;
|
||||
if (subPropertyOf !== undefined) {
|
||||
elInput.addEventListener('change', function() {
|
||||
updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName, property.isParticleProperty);
|
||||
updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf],
|
||||
elInput, propertyName, property.isParticleProperty);
|
||||
});
|
||||
} else {
|
||||
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, property.isParticleProperty));
|
||||
elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse,
|
||||
property.isParticleProperty));
|
||||
}
|
||||
|
||||
return elInput;
|
||||
|
@ -1811,98 +1833,28 @@ function createNumberProperty(property, elProperty) {
|
|||
let elementID = property.elementID;
|
||||
let propertyData = property.data;
|
||||
|
||||
elProperty.className = "number";
|
||||
elProperty.className = "draggable-number";
|
||||
|
||||
let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max,
|
||||
propertyData.step, propertyData.decimals);
|
||||
|
||||
let elInput = document.createElement('input');
|
||||
elInput.setAttribute("id", elementID);
|
||||
elInput.setAttribute("type", "number");
|
||||
if (propertyData.min !== undefined) {
|
||||
elInput.setAttribute("min", propertyData.min);
|
||||
}
|
||||
if (propertyData.max !== undefined) {
|
||||
elInput.setAttribute("max", propertyData.max);
|
||||
}
|
||||
if (propertyData.step !== undefined) {
|
||||
elInput.setAttribute("step", propertyData.step);
|
||||
let defaultValue = propertyData.defaultValue;
|
||||
if (defaultValue !== undefined) {
|
||||
elDraggableNumber.elInput.value = defaultValue;
|
||||
}
|
||||
|
||||
let valueChangeFunction = createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier,
|
||||
property.isParticleProperty);
|
||||
elDraggableNumber.setValueChangeFunction(valueChangeFunction);
|
||||
|
||||
let defaultValue = propertyData.defaultValue;
|
||||
if (defaultValue !== undefined) {
|
||||
elInput.value = defaultValue;
|
||||
}
|
||||
|
||||
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals, property.isParticleProperty));
|
||||
|
||||
elProperty.appendChild(elInput);
|
||||
|
||||
elDraggableNumber.elInput.setAttribute("id", elementID);
|
||||
elProperty.appendChild(elDraggableNumber.elDiv);
|
||||
|
||||
if (propertyData.buttons !== undefined) {
|
||||
addButtons(elProperty, elementID, propertyData.buttons, true);
|
||||
addButtons(elDraggableNumber.elText, elementID, propertyData.buttons, false);
|
||||
}
|
||||
|
||||
return elInput;
|
||||
}
|
||||
|
||||
function createSliderProperty(property, elProperty) {
|
||||
let propertyData = property.data;
|
||||
|
||||
elProperty.className = "range";
|
||||
|
||||
let elDiv = document.createElement("div");
|
||||
elDiv.className = "slider-wrapper";
|
||||
|
||||
let elSlider = document.createElement("input");
|
||||
elSlider.setAttribute("type", "range");
|
||||
|
||||
let elInput = document.createElement("input");
|
||||
elInput.setAttribute("type", "number");
|
||||
|
||||
if (propertyData.min !== undefined) {
|
||||
elInput.setAttribute("min", propertyData.min);
|
||||
elSlider.setAttribute("min", propertyData.min);
|
||||
}
|
||||
if (propertyData.max !== undefined) {
|
||||
elInput.setAttribute("max", propertyData.max);
|
||||
elSlider.setAttribute("max", propertyData.max);
|
||||
elSlider.setAttribute("data-max", propertyData.max);
|
||||
}
|
||||
if (propertyData.step !== undefined) {
|
||||
elInput.setAttribute("step", propertyData.step);
|
||||
elSlider.setAttribute("step", propertyData.step);
|
||||
}
|
||||
|
||||
elInput.onchange = function (event) {
|
||||
let inputValue = event.target.value;
|
||||
elSlider.value = inputValue;
|
||||
if (propertyData.multiplier !== undefined) {
|
||||
inputValue *= propertyData.multiplier;
|
||||
}
|
||||
updateProperty(property.name, inputValue, property.isParticleProperty);
|
||||
};
|
||||
elSlider.oninput = function (event) {
|
||||
let sliderValue = event.target.value;
|
||||
if (propertyData.step === 1) {
|
||||
if (sliderValue > 0) {
|
||||
elInput.value = Math.floor(sliderValue);
|
||||
} else {
|
||||
elInput.value = Math.ceil(sliderValue);
|
||||
}
|
||||
} else {
|
||||
elInput.value = sliderValue;
|
||||
}
|
||||
if (propertyData.multiplier !== undefined) {
|
||||
sliderValue *= propertyData.multiplier;
|
||||
}
|
||||
updateProperty(property.name, sliderValue, property.isParticleProperty);
|
||||
};
|
||||
|
||||
elDiv.appendChild(elSlider);
|
||||
elDiv.appendChild(elInput);
|
||||
elProperty.appendChild(elDiv);
|
||||
|
||||
let elResult = [];
|
||||
elResult[SLIDER_ELEMENTS.SLIDER] = elSlider;
|
||||
elResult[SLIDER_ELEMENTS.NUMBER_INPUT] = elInput;
|
||||
return elResult;
|
||||
return elDraggableNumber;
|
||||
}
|
||||
|
||||
function createVec3Property(property, elProperty) {
|
||||
|
@ -1912,23 +1864,24 @@ function createVec3Property(property, elProperty) {
|
|||
|
||||
elProperty.className = propertyData.vec3Type + " fstuple";
|
||||
|
||||
let elInputX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT],
|
||||
propertyData.min, propertyData.max, propertyData.step);
|
||||
let elInputY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT],
|
||||
propertyData.min, propertyData.max, propertyData.step);
|
||||
let elInputZ = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT],
|
||||
propertyData.min, propertyData.max, propertyData.step);
|
||||
let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER],
|
||||
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals);
|
||||
let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER],
|
||||
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals);
|
||||
let elNumberZ = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER],
|
||||
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals);
|
||||
|
||||
let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ,
|
||||
propertyData.multiplier, property.isParticleProperty);
|
||||
elInputX.addEventListener('change', inputChangeFunction);
|
||||
elInputY.addEventListener('change', inputChangeFunction);
|
||||
elInputZ.addEventListener('change', inputChangeFunction);
|
||||
let valueChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput,
|
||||
elNumberZ.elInput, propertyData.multiplier,
|
||||
property.isParticleProperty);
|
||||
elNumberX.setValueChangeFunction(valueChangeFunction);
|
||||
elNumberY.setValueChangeFunction(valueChangeFunction);
|
||||
elNumberZ.setValueChangeFunction(valueChangeFunction);
|
||||
|
||||
let elResult = [];
|
||||
elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX;
|
||||
elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY;
|
||||
elResult[VECTOR_ELEMENTS.Z_INPUT] = elInputZ;
|
||||
elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX;
|
||||
elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY;
|
||||
elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberZ;
|
||||
return elResult;
|
||||
}
|
||||
|
||||
|
@ -1944,19 +1897,19 @@ function createVec2Property(property, elProperty) {
|
|||
|
||||
elProperty.appendChild(elTuple);
|
||||
|
||||
let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT],
|
||||
propertyData.min, propertyData.max, propertyData.step);
|
||||
let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT],
|
||||
propertyData.min, propertyData.max, propertyData.step);
|
||||
let elNumberX = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER],
|
||||
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals);
|
||||
let elNumberY = createTupleNumberInput(elProperty, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER],
|
||||
propertyData.min, propertyData.max, propertyData.step, propertyData.decimals);
|
||||
|
||||
let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY,
|
||||
let valueChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elNumberX.elInput, elNumberY.elInput,
|
||||
propertyData.multiplier, property.isParticleProperty);
|
||||
elInputX.addEventListener('change', inputChangeFunction);
|
||||
elInputY.addEventListener('change', inputChangeFunction);
|
||||
elNumberX.setValueChangeFunction(valueChangeFunction);
|
||||
elNumberY.setValueChangeFunction(valueChangeFunction);
|
||||
|
||||
let elResult = [];
|
||||
elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX;
|
||||
elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY;
|
||||
elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX;
|
||||
elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY;
|
||||
return elResult;
|
||||
}
|
||||
|
||||
|
@ -1976,15 +1929,15 @@ function createColorProperty(property, elProperty) {
|
|||
elProperty.appendChild(elColorPicker);
|
||||
elProperty.appendChild(elTuple);
|
||||
|
||||
let elInputR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
let elNumberR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
let elNumberG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
let elNumberB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP);
|
||||
|
||||
let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB,
|
||||
property.isParticleProperty);
|
||||
elInputR.addEventListener('change', inputChangeFunction);
|
||||
elInputG.addEventListener('change', inputChangeFunction);
|
||||
elInputB.addEventListener('change', inputChangeFunction);
|
||||
let valueChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elNumberR.elInput, elNumberG.elInput,
|
||||
elNumberB.elInput, property.isParticleProperty);
|
||||
elNumberR.setValueChangeFunction(valueChangeFunction);
|
||||
elNumberG.setValueChangeFunction(valueChangeFunction);
|
||||
elNumberB.setValueChangeFunction(valueChangeFunction);
|
||||
|
||||
let colorPickerID = "#" + elementID;
|
||||
colorPickers[colorPickerID] = $(colorPickerID).colpick({
|
||||
|
@ -1997,9 +1950,9 @@ function createColorProperty(property, elProperty) {
|
|||
// The original color preview within the picker needs to be updated on show because
|
||||
// prior to the picker being shown we don't have access to the selections' starting color.
|
||||
colorPickers[colorPickerID].colpickSetColor({
|
||||
"r": elInputR.value,
|
||||
"g": elInputG.value,
|
||||
"b": elInputB.value
|
||||
"r": elNumberR.elInput.value,
|
||||
"g": elNumberG.elInput.value,
|
||||
"b": elNumberB.elInput.value
|
||||
});
|
||||
},
|
||||
onHide: function(colpick) {
|
||||
|
@ -2013,9 +1966,9 @@ function createColorProperty(property, elProperty) {
|
|||
|
||||
let elResult = [];
|
||||
elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker;
|
||||
elResult[COLOR_ELEMENTS.RED_INPUT] = elInputR;
|
||||
elResult[COLOR_ELEMENTS.GREEN_INPUT] = elInputG;
|
||||
elResult[COLOR_ELEMENTS.BLUE_INPUT] = elInputB;
|
||||
elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR;
|
||||
elResult[COLOR_ELEMENTS.GREEN_NUMBER] = elNumberG;
|
||||
elResult[COLOR_ELEMENTS.BLUE_NUMBER] = elNumberB;
|
||||
return elResult;
|
||||
}
|
||||
|
||||
|
@ -2141,37 +2094,30 @@ function createButtonsProperty(property, elProperty, elLabel) {
|
|||
let propertyData = property.data;
|
||||
|
||||
elProperty.className = "text";
|
||||
|
||||
let hasLabel = propertyData.label !== undefined;
|
||||
|
||||
if (propertyData.buttons !== undefined) {
|
||||
addButtons(elProperty, elementID, propertyData.buttons, hasLabel);
|
||||
addButtons(elProperty, elementID, propertyData.buttons, false);
|
||||
}
|
||||
|
||||
return elProperty;
|
||||
}
|
||||
|
||||
function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step) {
|
||||
function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step, decimals) {
|
||||
let elementID = propertyElementID + "-" + subLabel.toLowerCase();
|
||||
|
||||
let elDiv = document.createElement('div');
|
||||
let elLabel = document.createElement('label');
|
||||
elLabel.className = subLabel;
|
||||
elLabel.className = "sublabel " + subLabel;
|
||||
elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1);
|
||||
elLabel.setAttribute("for", elementID);
|
||||
elLabel.style.visibility = "visible";
|
||||
|
||||
let elInput = document.createElement('input');
|
||||
elInput.className = subLabel + " number-slider";
|
||||
elInput.setAttribute("id", elementID);
|
||||
elInput.setAttribute("type", "number");
|
||||
elInput.setAttribute("min", min);
|
||||
elInput.setAttribute("max", max);
|
||||
elInput.setAttribute("step", step);
|
||||
let elDraggableNumber = new DraggableNumber(min, max, step, decimals);
|
||||
elDraggableNumber.elInput.setAttribute("id", elementID);
|
||||
elDraggableNumber.elDiv.className += " fstuple";
|
||||
elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
|
||||
elTuple.appendChild(elDraggableNumber.elDiv);
|
||||
|
||||
elDiv.appendChild(elLabel);
|
||||
elDiv.appendChild(elInput);
|
||||
elTuple.appendChild(elDiv);
|
||||
|
||||
return elInput;
|
||||
return elDraggableNumber;
|
||||
}
|
||||
|
||||
function addButtons(elProperty, propertyID, buttons, newRow) {
|
||||
|
@ -2198,6 +2144,85 @@ function addButtons(elProperty, propertyID, buttons, newRow) {
|
|||
}
|
||||
}
|
||||
|
||||
function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) {
|
||||
let property = {
|
||||
data: propertyData,
|
||||
elementID: propertyElementID,
|
||||
name: propertyName,
|
||||
elProperty: elProperty,
|
||||
};
|
||||
let propertyType = propertyData.type;
|
||||
|
||||
switch (propertyType) {
|
||||
case 'string': {
|
||||
property.elInput = createStringProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'bool': {
|
||||
property.elInput = createBoolProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
property.elNumber = createNumberProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'vec3': {
|
||||
let elVec3 = createVec3Property(property, elProperty);
|
||||
property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER];
|
||||
property.elNumberY = elVec3[VECTOR_ELEMENTS.Y_NUMBER];
|
||||
property.elNumberZ = elVec3[VECTOR_ELEMENTS.Z_NUMBER];
|
||||
break;
|
||||
}
|
||||
case 'vec2': {
|
||||
let elVec2 = createVec2Property(property, elProperty);
|
||||
property.elNumberX = elVec2[VECTOR_ELEMENTS.X_NUMBER];
|
||||
property.elNumberY = elVec2[VECTOR_ELEMENTS.Y_NUMBER];
|
||||
break;
|
||||
}
|
||||
case 'color': {
|
||||
let elColor = createColorProperty(property, elProperty);
|
||||
property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER];
|
||||
property.elNumberR = elColor[COLOR_ELEMENTS.RED_NUMBER];
|
||||
property.elNumberG = elColor[COLOR_ELEMENTS.GREEN_NUMBER];
|
||||
property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER];
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
property.elInput = createDropdownProperty(property, propertyID, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'textarea': {
|
||||
property.elInput = createTextareaProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'icon': {
|
||||
property.elSpan = createIconProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'texture': {
|
||||
let elTexture = createTextureProperty(property, elProperty);
|
||||
property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE];
|
||||
property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'buttons': {
|
||||
property.elProperty = createButtonsProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'placeholder':
|
||||
case 'sub-header': {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log("EntityProperties - Unknown property type " +
|
||||
propertyType + " set to property " + propertyID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BUTTON CALLBACKS
|
||||
|
@ -2717,104 +2742,6 @@ function showParentMaterialNameBox(number, elNumber, elString) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateVisibleSpaceModeProperties() {
|
||||
for (let propertyID in properties) {
|
||||
if (properties.hasOwnProperty(propertyID)) {
|
||||
let property = properties[propertyID];
|
||||
let propertySpaceMode = property.spaceMode;
|
||||
if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL) {
|
||||
showPropertyElement(propertyID, propertySpaceMode === currentSpaceMode);
|
||||
} else {
|
||||
showPropertyElement(propertyID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) {
|
||||
let property = {
|
||||
data: propertyData,
|
||||
elementID: propertyElementID,
|
||||
name: propertyName,
|
||||
elProperty: elProperty,
|
||||
};
|
||||
let propertyType = propertyData.type;
|
||||
|
||||
switch (propertyType) {
|
||||
case 'string': {
|
||||
property.elInput = createStringProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'bool': {
|
||||
property.elInput = createBoolProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
property.elInput = createNumberProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'slider': {
|
||||
let elSlider = createSliderProperty(property, elProperty);
|
||||
property.elSlider = elSlider[SLIDER_ELEMENTS.SLIDER];
|
||||
property.elInput = elSlider[SLIDER_ELEMENTS.NUMBER_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'vec3': {
|
||||
let elVec3 = createVec3Property(property, elProperty);
|
||||
property.elInputX = elVec3[VECTOR_ELEMENTS.X_INPUT];
|
||||
property.elInputY = elVec3[VECTOR_ELEMENTS.Y_INPUT];
|
||||
property.elInputZ = elVec3[VECTOR_ELEMENTS.Z_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'vec2': {
|
||||
let elVec2 = createVec2Property(property, elProperty);
|
||||
property.elInputX = elVec2[VECTOR_ELEMENTS.X_INPUT];
|
||||
property.elInputY = elVec2[VECTOR_ELEMENTS.Y_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'color': {
|
||||
let elColor = createColorProperty(property, elProperty);
|
||||
property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER];
|
||||
property.elInputR = elColor[COLOR_ELEMENTS.RED_INPUT];
|
||||
property.elInputG = elColor[COLOR_ELEMENTS.GREEN_INPUT];
|
||||
property.elInputB = elColor[COLOR_ELEMENTS.BLUE_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
property.elInput = createDropdownProperty(property, propertyID, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'textarea': {
|
||||
property.elInput = createTextareaProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'icon': {
|
||||
property.elSpan = createIconProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'texture': {
|
||||
let elTexture = createTextureProperty(property, elProperty);
|
||||
property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE];
|
||||
property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT];
|
||||
break;
|
||||
}
|
||||
case 'buttons': {
|
||||
property.elProperty = createButtonsProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'placeholder':
|
||||
case 'sub-header': {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log("EntityProperties - Unknown property type " +
|
||||
propertyType + " set to property " + propertyID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
function loaded() {
|
||||
openEventBridge(function() {
|
||||
|
@ -2896,17 +2823,16 @@ function loaded() {
|
|||
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
|
||||
let elSpan = document.createElement('span');
|
||||
elSpan.className = 'indented';
|
||||
elSpan.innerText = propertyData.label;
|
||||
elSpan.innerText = propertyData.label !== undefined ? propertyData.label : "";
|
||||
elLabel.appendChild(elSpan);
|
||||
} else {
|
||||
elLabel.innerText = propertyData.label;
|
||||
elLabel.innerText = propertyData.label !== undefined ? propertyData.label : "";
|
||||
}
|
||||
elContainer.appendChild(elLabel);
|
||||
} else {
|
||||
elContainer = document.getElementById(propertyData.replaceID);
|
||||
}
|
||||
|
||||
|
||||
if (elLabel) {
|
||||
createAppTooltip.registerTooltipElement(elLabel, propertyID);
|
||||
}
|
||||
|
@ -3056,7 +2982,6 @@ function loaded() {
|
|||
let typeProperty = properties["type"];
|
||||
typeProperty.elSpan.innerHTML = typeProperty.data.icons[type];
|
||||
typeProperty.elSpan.style.display = "inline-block";
|
||||
typeProperty.elLabel.innerHTML = type + " (" + data.selections.length + ")";
|
||||
|
||||
disableProperties();
|
||||
} else {
|
||||
|
@ -3103,7 +3028,6 @@ function loaded() {
|
|||
let isPropertyNotNumber = false;
|
||||
switch (propertyData.type) {
|
||||
case 'number':
|
||||
case 'slider':
|
||||
isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null;
|
||||
break;
|
||||
case 'vec3':
|
||||
|
@ -3135,21 +3059,13 @@ function loaded() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'number':
|
||||
case 'slider': {
|
||||
case 'number': {
|
||||
let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
|
||||
let value = propertyValue / multiplier;
|
||||
if (propertyData.round !== undefined) {
|
||||
value = Math.round(value.round) / propertyData.round;
|
||||
}
|
||||
if (propertyData.decimals !== undefined) {
|
||||
property.elInput.value = value.toFixed(propertyData.decimals);
|
||||
} else {
|
||||
property.elInput.value = value;
|
||||
}
|
||||
if (property.elSlider !== undefined) {
|
||||
property.elSlider.value = property.elInput.value;
|
||||
}
|
||||
property.elNumber.setValue(value);
|
||||
break;
|
||||
}
|
||||
case 'vec3':
|
||||
|
@ -3164,16 +3080,16 @@ function loaded() {
|
|||
valueZ = Math.round(valueZ * propertyData.round) / propertyData.round;
|
||||
}
|
||||
if (propertyData.decimals !== undefined) {
|
||||
property.elInputX.value = valueX.toFixed(propertyData.decimals);
|
||||
property.elInputY.value = valueY.toFixed(propertyData.decimals);
|
||||
if (property.elInputZ !== undefined) {
|
||||
property.elInputZ.value = valueZ.toFixed(propertyData.decimals);
|
||||
property.elNumberX.setValue(valueX.toFixed(propertyData.decimals));
|
||||
property.elNumberY.setValue(valueY.toFixed(propertyData.decimals));
|
||||
if (property.elNumberZ !== undefined) {
|
||||
property.elNumberZ.setValue(valueZ.toFixed(propertyData.decimals));
|
||||
}
|
||||
} else {
|
||||
property.elInputX.value = valueX;
|
||||
property.elInputY.value = valueY;
|
||||
if (property.elInputZ !== undefined) {
|
||||
property.elInputZ.value = valueZ;
|
||||
property.elNumberX.setValue(valueX);
|
||||
property.elNumberY.setValue(valueY);
|
||||
if (property.elNumberZ !== undefined) {
|
||||
property.elNumberZ.setValue(valueZ);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -3182,9 +3098,9 @@ function loaded() {
|
|||
property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," +
|
||||
propertyValue.green + "," +
|
||||
propertyValue.blue + ")";
|
||||
property.elInputR.value = propertyValue.red;
|
||||
property.elInputG.value = propertyValue.green;
|
||||
property.elInputB.value = propertyValue.blue;
|
||||
property.elNumberR.setValue(propertyValue.red);
|
||||
property.elNumberG.setValue(propertyValue.green);
|
||||
property.elNumberB.setValue(propertyValue.blue);
|
||||
break;
|
||||
}
|
||||
case 'dropdown': {
|
||||
|
|
Loading…
Reference in a new issue