merged with master

This commit is contained in:
amantley 2019-01-31 09:56:46 -08:00
commit f68ee4e7ce
272 changed files with 12598 additions and 4276 deletions

4
.gitignore vendored
View file

@ -104,4 +104,6 @@ tools/unity-avatar-exporter/Logs
tools/unity-avatar-exporter/Packages
tools/unity-avatar-exporter/ProjectSettings
tools/unity-avatar-exporter/Temp
server-console/package-lock.json
vcpkg/
/tools/nitpick/compiledResources

View file

@ -108,8 +108,10 @@ set(PLATFORM_QT_GL OpenGL)
if (USE_GLES)
add_definitions(-DUSE_GLES)
add_definitions(-DGPU_POINTER_STORAGE_SHARED)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
else()
add_definitions(-DGPU_POINTER_STORAGE_RAW)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
endif()

View file

@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ
### Install
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt`
### Packaging
@ -14,17 +15,67 @@ To produce an installer, run the `package` target.
To produce an executable installer on Windows, the following are required:
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
1. [7-zip](<https://www.7-zip.org/download.html>)
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
1. [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.04
Install using defaults (will install to `C:\Program Files (x86)\NSIS`)
1. [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
1. Extract Zip
1. Copy `UAC.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\x86-ansi\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\x86-unicode\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 (use the link marked **nsProcess_1_6.7z**)
1. Extract Zip
1. Copy `Include\nsProcess.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\nsProcess.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\nsProcessW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
1. Extract Zip
1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
1. Extract Zip
1. Copy `NSISpre.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `NSISpre.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. [nsisSlideshow Plug-in for Nullsoft](<http://wiz0u.free.fr/prog/nsisSlideshow/>) - 1.7
1. Extract Zip
1. Copy `bin\nsisSlideshow.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `bin\nsisSlideshowW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
1. Download both Zips and unzip
1. Copy `nsisunz\Release\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `NSISunzU\Plugin unicode\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [ApplicationID plug-in for Nullsoft]() - 1.0
1. Download [`Pre-built DLLs`](<https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip>)
1. Extract Zip
1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [npm](<https://www.npmjs.com/get-npm>)
1. Install version 10.15.0 LTS
1. Perform a clean cmake from a new terminal.
1. Open the `hifi.sln` Solution and select the Release configuration.
1. Build the Solution.
1. Build `packaged-server-console` (found under **Server Console**)
This will add 2 folders to `build\server-console\` -
`server-console-win32-x64` and `x64`
1. Build CMakeTargets->PACKAGE
Installer is now available in `build\_CPack_Packages\win64\NSIS`
#### OS X
Run the `package` target to create an Apple Disk Image (.dmg).
1. [npm](<https://www.npmjs.com/get-npm>)
Install version 10.15.0 LTS
1. Perform a clean cmake.
1. Perform a Release build of ALL_BUILD
1. Perform a Release build of `packaged-server-console`
This will add a folder to `build\server-console\` -
Sandbox-darwin-x64
1. Perform a Release build of `package`
Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop

View file

@ -0,0 +1,5 @@
set(TARGET_NAME framePlayer)
setup_hifi_library(AndroidExtras)
link_hifi_libraries(shared ktx shaders qml gpu gl ${PLATFORM_GL_BACKEND})
target_link_libraries(${TARGET_NAME} android log m)
target_opengl()

View file

@ -0,0 +1,50 @@
apply plugin: 'com.android.application'
android {
signingConfigs {
release {
keyAlias 'key0'
keyPassword 'password'
storeFile file('C:/android/keystore.jks')
storePassword 'password'
}
}
compileSdkVersion 28
defaultConfig {
applicationId "io.highfidelity.frameplayer"
minSdkVersion 25
targetSdkVersion 28
ndk { abiFilters 'arm64-v8a' }
externalNativeBuild {
cmake {
arguments '-DHIFI_ANDROID=1',
'-DHIFI_ANDROID_APP=framePlayer',
'-DANDROID_TOOLCHAIN=clang',
'-DANDROID_STL=c++_shared',
'-DCMAKE_VERBOSE_MAKEFILE=ON'
targets = ['framePlayer']
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
externalNativeBuild.cmake.path '../../../CMakeLists.txt'
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
//implementation project(':oculus')
implementation project(':qt')
}

View file

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Android\SDK/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.highfidelity.frameplayer"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto">
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:label="Frame Viewer"
android:allowBackup="false"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
<activity
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:excludeFromRecents="false"
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode">
<!-- JNI nonsense -->
<meta-data android:name="android.app.lib_name" android:value="framePlayer"/>
<!-- Qt nonsense -->
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,6 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<file>qml/main.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,91 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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 "PlayerWindow.h"
#include <QtCore/QFileInfo>
#include <QtGui/QImageReader>
#include <QtQml/QQmlContext>
#include <QtQuick/QQuickItem>
#include <gpu/Frame.h>
#include <gpu/FrameIO.h>
PlayerWindow::PlayerWindow() {
setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
setSurfaceType(QSurface::OpenGLSurface);
create();
showFullScreen();
// Make sure the window has been created by processing events
QCoreApplication::processEvents();
// Start the rendering thread
_renderThread.initialize(this, &_surface);
// Start the UI
_surface.resize(size());
connect(&_surface, &hifi::qml::OffscreenSurface::rootContextCreated, this, [](QQmlContext* context){
context->setContextProperty("FRAMES_FOLDER", "file:assets:/frames");
});
_surface.load("qrc:///qml/main.qml");
// Connect the UI handler
QObject::connect(_surface.getRootItem(), SIGNAL(loadFile(QString)),
this, SLOT(loadFile(QString))
);
// Turn on UI input events
installEventFilter(&_surface);
}
PlayerWindow::~PlayerWindow() {
}
// static const char* FRAME_FILE = "assets:/frames/20190110_1635.json";
static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
QImage image;
QImageReader(filename.c_str()).read(&image);
if (layer > 0) {
return;
}
texture->assignStoredMip(0, image.byteCount(), image.constBits());
}
void PlayerWindow::loadFile(QString filename) {
QString realFilename = QUrl(filename).toLocalFile();
if (QFileInfo(realFilename).exists()) {
auto frame = gpu::readFrame(realFilename.toStdString(), _renderThread._externalTexture, &textureLoader);
_surface.pause();
_renderThread.submitFrame(frame);
}
}
void PlayerWindow::touchEvent(QTouchEvent* event) {
// Super basic input handling when the 3D scene is active.... tap with two finders to return to the
// QML UI
static size_t touches = 0;
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
touches = std::max<size_t>(touches, event->touchPoints().size());
break;
case QEvent::TouchEnd:
if (touches >= 2) {
_renderThread.submitFrame(nullptr);
_surface.resume();
}
touches = 0;
break;
default:
break;
}
}

View file

@ -0,0 +1,35 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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
//
#pragma once
#include <QtGui/QWindow>
#include <QtCore/QSettings>
#include <qml/OffscreenSurface.h>
#include <gpu/Forward.h>
#include "RenderThread.h"
// Create a simple OpenGL window that renders text in various ways
class PlayerWindow : public QWindow {
Q_OBJECT
public:
PlayerWindow();
virtual ~PlayerWindow();
protected:
void touchEvent(QTouchEvent *ev) override;
public slots:
void loadFile(QString filename);
private:
hifi::qml::OffscreenSurface _surface;
QSettings _settings;
RenderThread _renderThread;
};

View file

@ -0,0 +1,162 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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 "RenderThread.h"
#include <QtGui/QWindow>
void RenderThread::submitFrame(const gpu::FramePointer& frame) {
std::unique_lock<std::mutex> lock(_frameLock);
_pendingFrames.push(frame);
}
void RenderThread::move(const glm::vec3& v) {
std::unique_lock<std::mutex> lock(_frameLock);
_correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
}
void RenderThread::setup() {
// Wait until the context has been moved to this thread
{ std::unique_lock<std::mutex> lock(_frameLock); }
makeCurrent();
// Disable vsync for profiling
::gl::setSwapInterval(0);
glClearColor(1, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
// GPU library init
gpu::Context::init<gpu::gl::GLBackend>();
_gpuContext = std::make_shared<gpu::Context>();
_backend = _gpuContext->getBackend();
_gpuContext->beginFrame();
_gpuContext->endFrame();
makeCurrent();
glGenFramebuffers(1, &_uiFbo);
glGenTextures(1, &_externalTexture);
glBindTexture(GL_TEXTURE_2D, _externalTexture);
static const glm::u8vec4 color{ 0 };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
glClearColor(0, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
}
void RenderThread::initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen) {
std::unique_lock<std::mutex> lock(_frameLock);
setObjectName("RenderThread");
Parent::initialize();
_offscreen = offscreen;
_window = window;
_glContext.setWindow(_window);
_glContext.create();
_glContext.makeCurrent();
hifi::qml::OffscreenSurface::setSharedContext(_glContext.qglContext());
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
_glContext.doneCurrent();
_glContext.moveToThread(_thread);
_thread->setObjectName("RenderThread");
}
void RenderThread::shutdown() {
_activeFrame.reset();
while (!_pendingFrames.empty()) {
_gpuContext->consumeFrameUpdates(_pendingFrames.front());
_pendingFrames.pop();
}
_gpuContext->shutdown();
_gpuContext.reset();
}
void RenderThread::renderFrame() {
auto windowSize = _window->geometry().size();
uvec2 readFboSize;
uint32_t readFbo{ 0 };
if (_activeFrame) {
const auto &frame = _activeFrame;
_backend->recycle();
_backend->syncCache();
_gpuContext->enableStereo(frame->stereoState._enable);
if (frame && !frame->batches.empty()) {
_gpuContext->executeFrame(frame);
}
auto &glBackend = static_cast<gpu::gl::GLBackend&>(*_backend);
readFbo = glBackend.getFramebufferID(frame->framebuffer);
readFboSize = frame->framebuffer->getSize();
CHECK_GL_ERROR();
} else {
hifi::qml::OffscreenSurface::TextureAndFence newTextureAndFence;
if (_offscreen->fetchTexture(newTextureAndFence)) {
if (_uiTexture != 0) {
auto readFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_offscreen->getDiscardLambda()(_uiTexture, readFence);
_uiTexture = 0;
}
glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)newTextureAndFence.second);
_uiTexture = newTextureAndFence.first;
glBindFramebuffer(GL_READ_FRAMEBUFFER, _uiFbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _uiTexture, 0);
}
if (_uiTexture != 0) {
readFbo = _uiFbo;
readFboSize = { windowSize.width(), windowSize.height() };
}
}
if (readFbo) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
glBlitFramebuffer(
0, 0, readFboSize.x, readFboSize.y,
0, 0, windowSize.width(), windowSize.height(),
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
} else {
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
_glContext.swapBuffers();
}
void RenderThread::updateFrame() {
std::queue<gpu::FramePointer> pendingFrames;
{
std::unique_lock<std::mutex> lock(_frameLock);
pendingFrames.swap(_pendingFrames);
}
while (!pendingFrames.empty()) {
_activeFrame = pendingFrames.front();
pendingFrames.pop();
if (_activeFrame) {
_gpuContext->consumeFrameUpdates(_activeFrame);
}
}
}
bool RenderThread::process() {
updateFrame();
makeCurrent();
renderFrame();
return true;
}

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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
//
#pragma once
#include <GenericThread.h>
#include <gl/Context.h>
#include <gpu/gl/GLBackend.h>
#include <qml/OffscreenSurface.h>
class RenderThread : public GenericThread {
using Parent = GenericThread;
public:
QWindow* _window{ nullptr };
std::mutex _mutex;
gpu::ContextPointer _gpuContext; // initialized during window creation
std::shared_ptr<gpu::Backend> _backend;
std::atomic<size_t> _presentCount{ 0 };
std::mutex _frameLock;
std::queue<gpu::FramePointer> _pendingFrames;
gpu::FramePointer _activeFrame;
uint32_t _externalTexture{ 0 };
glm::mat4 _correction;
hifi::qml::OffscreenSurface* _offscreen{ nullptr };
gl::Context _glContext;
uint32_t _uiTexture{ 0 };
uint32_t _uiFbo{ 0 };
void move(const glm::vec3& v);
void setup() override;
bool process() override;
void shutdown() override;
void initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen);
void submitFrame(const gpu::FramePointer& frame);
void updateFrame();
void renderFrame();
bool makeCurrent() {
return _glContext.makeCurrent();
}
void doneCurrent() {
_glContext.doneCurrent();
}
};

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2018/11/22
// Copyright 2014 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 <android/log.h>
#include <QtGui/QGuiApplication>
#include <QtCore/QTimer>
#include <QtCore/QFileInfo>
#include <Trace.h>
#include "PlayerWindow.h"
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
const char * local=message.toStdString().c_str();
switch (type) {
case QtDebugMsg:
__android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
break;
case QtInfoMsg:
__android_log_write(ANDROID_LOG_INFO,"Interface",local);
break;
case QtWarningMsg:
__android_log_write(ANDROID_LOG_WARN,"Interface",local);
break;
case QtCriticalMsg:
__android_log_write(ANDROID_LOG_ERROR,"Interface",local);
break;
case QtFatalMsg:
default:
__android_log_write(ANDROID_LOG_FATAL,"Interface",local);
abort();
}
}
}
int main(int argc, char** argv) {
setupHifiApplication("gpuFramePlayer");
QGuiApplication app(argc, argv);
auto oldMessageHandler = qInstallMessageHandler(messageHandler);
DependencyManager::set<tracing::Tracer>();
PlayerWindow window;
app.exec();
qInstallMessageHandler(oldMessageHandler);
return 0;
}

View file

@ -0,0 +1,36 @@
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import Qt.labs.folderlistmodel 2.11
Item {
id: root
width: 640
height: 480
ListView {
anchors.fill: parent
FolderListModel {
id: folderModel
folder: FRAMES_FOLDER
nameFilters: ["*.json"]
}
Component {
id: fileDelegate
Text {
text: fileName
font.pointSize: 36
MouseArea {
anchors.fill: parent
onClicked: root.loadFile(folderModel.folder + "/" + fileName);
}
}
}
model: folderModel
delegate: fileDelegate
}
signal loadFile(string filename);
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidUnknownAttribute -->
<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="192"
android:viewportHeight="192"
android:width="192dp"
android:height="192dp">
<path
android:pathData="M189.5 96.5A93.5 93.5 0 0 1 96 190 93.5 93.5 0 0 1 2.5 96.5 93.5 93.5 0 0 1 96 3 93.5 93.5 0 0 1 189.5 96.5Z"
android:fillColor="#333333" />
<path
android:pathData="M96.2 173.1c-10.3 0 -20.4 -2.1 -29.8 -6 -9.2 -3.8 -17.3 -9.4 -24.3 -16.4 -7 -7 -12.6 -15.2 -16.4 -24.3 -4.1 -9.6 -6.2 -19.6 -6.2 -30 0 -10.3 2.1 -20.4 6 -29.8 3.8 -9.2 9.4 -17.3 16.4 -24.3 7 -7 15.2 -12.6 24.3 -16.4 9.5 -4 19.5 -6 29.8 -6 10.3 0 20.4 2.1 29.8 6 9.2 3.8 17.3 9.4 24.3 16.4 7 7 12.6 15.2 16.4 24.3 4 9.5 6 19.5 6 29.8 0 10.3 -2.1 20.4 -6 29.8 -3.8 9.2 -9.4 17.3 -16.4 24.3 -7 7 -15.2 12.6 -24.3 16.4 -9.2 4.1 -19.3 6.2 -29.6 6.2zm0 -145.3c-37.8 0 -68.6 30.8 -68.6 68.6 0 37.8 30.8 68.6 68.6 68.6 37.8 0 68.6 -30.8 68.6 -68.6 0 -37.8 -30.8 -68.6 -68.6 -68.6z"
android:fillColor="#00b4f0" />
<path
android:pathData="M119.6 129l0 -53.8c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 25L79 83.8 79 64c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 54.1c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.7 -2.4 -6.9 -5.8 -8l0 -27.3 35 16.3 0 22.2c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.8 -2.4 -6.9 -5.8 -8z"
android:fillColor="#00b4f0" />
</vector>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name" translatable="false">GPU Frame Player</string>
</resources>

View file

@ -0,0 +1,44 @@
{
"avatars": [
{
"name": "Wooden Mannequin",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
"url": "qrc:////meshes/defaultAvatar_full.fst"
},
{
"name": "Anime-Styled Boy",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
"url": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst"
},
{
"name": "Anime-Styled Girl",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e7e43f6-1757-44d3-baa4-756827d96311/large/hifi-mp-1e7e43f6-1757-44d3-baa4-756827d96311.jpg",
"url": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst"
},
{
"name": "Last Legends: Male Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/28569047-6f1a-4100-af67-8054ec397cc3/thumbnail/hifi-mp-28569047-6f1a-4100-af67-8054ec397cc3.jpg",
"url": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst"
},
{
"name": "Last Legends: Female Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8d823be5-6197-4418-b984-eb94160ed956/thumbnail/hifi-mp-8d823be5-6197-4418-b984-eb94160ed956.jpg",
"url": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg"
},
{
"name": "Matthew: Photo-real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/b652081b-a199-425e-ae5c-7815721bdc09/thumbnail/hifi-mp-b652081b-a199-425e-ae5c-7815721bdc09.jpg",
"url": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst"
},
{
"name": "Priscilla: Photo real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339/thumbnail/hifi-mp-e7565f93-8bc5-47c2-b6eb-b3b31d4a1339.jpg",
"url": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst"
},
{
"name": "H1-F1 Optical Interpreter bot",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/469c8b66-e3c2-47fb-9820-e306b1dd15c4/large/hifi-mp-469c8b66-e3c2-47fb-9820-e306b1dd15c4.jpg",
"url": "http://mpassets.highfidelity.com/469c8b66-e3c2-47fb-9820-e306b1dd15c4-v1/optical_interpreter[1].fst"
}
]
}

View file

@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env,
jobject instance) {
QString displayName = AndroidHelper::instance().getDisplayName();
return env->NewStringUTF(displayName.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env,
jobject instance,
jstring name_) {
const char *c_name = env->GetStringUTFChars(name_, 0);
const QString name = QString::fromUtf8(c_name);
env->ReleaseStringUTFChars(name_, c_name);
AndroidHelper::instance().setDisplayName(name);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env,
jobject instance,
jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
QString avatarUrl = QString::fromUtf8(url);
AndroidHelper::instance().setMyAvatarUrl(avatarUrl);
env->ReleaseStringUTFChars(url_, url);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
DependencyManager::get<AccountManager>()->logout();

View file

@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso;
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.fragment.ProfileFragment;
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
import io.highfidelity.hifiinterface.fragment.SignupFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
HomeFragment.OnHomeInteractionListener,
FriendsFragment.OnHomeInteractionListener {
FriendsFragment.OnHomeInteractionListener,
ProfileFragment.OnProfileInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private View mProfilePanel;
private TextView mLogoutOption;
private MenuItem mPeopleMenuItem;
private MenuItem mProfileMenuItem;
private boolean backToScene;
private String backToUrl;
@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
updateDebugMenu(mNavigationView.getMenu());
Toolbar toolbar = findViewById(R.id.toolbar);
@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
}
private void loadProfileFragment() {
Fragment fragment = ProfileFragment.newInstance();
loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true);
}
private void loadSettingsFragment() {
SettingsFragment fragment = SettingsFragment.newInstance();
@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
case R.id.action_people:
loadPeopleFragment();
return true;
case R.id.action_profile:
loadProfileFragment();
break;
case R.id.action_debug_settings:
loadSettingsFragment();
return true;
@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
goToUser(username);
}
@Override
public void onCancelProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onCompleteProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onAvatarChosen() {
loadHomeFragment(false);
}
private class RoundProfilePictureCallback implements Callback {
@Override
public void onSuccess() {

View file

@ -0,0 +1,126 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.TextView;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
public class ProfileFragment extends Fragment {
private TextView mDisplayName;
private Button mOkButton;
private OnProfileInteractionListener mListener;
private AvatarProvider mAvatarsProvider;
private native String getDisplayName();
private native void setDisplayName(String name);
private native void setAvatarUrl(String url);
public ProfileFragment() {
// Required empty public constructor
}
public static ProfileFragment newInstance() {
ProfileFragment fragment = new ProfileFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
mDisplayName = rootView.findViewById(R.id.displayName);
mDisplayName.setText(getDisplayName());
mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent));
mOkButton = rootView.findViewById(R.id.okButton);
mOkButton.setOnClickListener(view -> onOkButtonClicked());
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit());
RecyclerView avatarsView = rootView.findViewById(R.id.gridview);
int numberOfColumns = 1;
mAvatarsProvider = new AvatarProvider(getContext());
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
avatarsView.setLayoutManager(gridLayoutMgr);
AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider);
avatarsView.setAdapter(avatarAdapter);
avatarAdapter.loadAvatars();
avatarAdapter.setClickListener((view, position, avatar) -> {
setAvatarUrl(avatar.avatarUrl);
if (mListener != null) {
mListener.onAvatarChosen();
}
});
return rootView;
}
private void onOkButtonClicked() {
setDisplayName(mDisplayName.getText().toString());
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCompleteProfileEdit();
}
}
private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mOkButton.performClick();
return true;
}
return false;
}
private void onCancelProfileEdit() {
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCancelProfileEdit();
}
}
/**
* Processes the back pressed event and returns true if it was managed by this Fragment
* @return
*/
public boolean onBackPressed() {
return false;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnProfileInteractionListener) {
mListener = (OnProfileInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnProfileInteractionListener");
}
}
public interface OnProfileInteractionListener {
void onCancelProfileEdit();
void onCompleteProfileEdit();
void onAvatarChosen();
}
}

View file

@ -0,0 +1,70 @@
package io.highfidelity.hifiinterface.provider;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarProvider {
private static final String AVATARS_JSON = "avatars.json";
private static final String JSON_FIELD_NAME = "name";
private static final String JSON_FIELD_URL = "url";
private static final String JSON_FIELD_IMAGE = "preview_image";
private static final String JSON_FIELD_AVATARS_ARRAY = "avatars";
private final Context mContext;
public interface AvatarsCallback {
void retrieveOk(List<AvatarAdapter.Avatar> avatars);
void retrieveError(Exception e, String message);
}
public AvatarProvider(Context context) {
mContext = context;
}
public void retrieve(AvatarsCallback avatarsCallback)
{
try {
JSONObject obj = new JSONObject(loadJSONFromAssets());
JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY);
ArrayList<AvatarAdapter.Avatar> avatars = new ArrayList<>();
for (int i = 0; i < m_jArry.length(); i++) {
JSONObject jo_inside = m_jArry.getJSONObject(i);
AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar();
anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME);
anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE);
anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL);
avatars.add(anAvatar);
}
avatarsCallback.retrieveOk(avatars);
} catch (IOException e) {
avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON");
} catch (JSONException e) {
avatarsCallback.retrieveError(e, "Failed parsing avatar JSON");
}
}
private String loadJSONFromAssets() throws IOException {
String json = null;
InputStream is = mContext.getAssets().open(AVATARS_JSON);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
return json;
}
}

View file

@ -0,0 +1,111 @@
package io.highfidelity.hifiinterface.view;
import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.ViewHolder> {
private static final String TAG = "Interface";
private final Context mContext;
private final LayoutInflater mInflater;
private final AvatarProvider mProvider;
private List<Avatar> mAvatars = new ArrayList<>();
private ItemClickListener mClickListener;
public AvatarAdapter(Context context, AvatarProvider provider) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mProvider = provider;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.avatar_item, parent, false);
return new AvatarAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AvatarAdapter.Avatar anAvatar = mAvatars.get(position);
assert(holder.mName != null);
holder.mName.setText(anAvatar.avatarName);
Uri uri = Uri.parse(anAvatar.avatarPreviewUrl);
Picasso.get().load(uri).into(holder.mPreviewImage);
}
@Override
public int getItemCount() {
return mAvatars.size();
}
public void loadAvatars() {
mProvider.retrieve(new AvatarProvider.AvatarsCallback() {
@Override
public void retrieveOk(List<AvatarAdapter.Avatar> avatars) {
mAvatars = new ArrayList<>(avatars);
notifyDataSetChanged();
}
@Override
public void retrieveError(Exception e, String message) {
Log.e(TAG, message, e);
}
});
}
public void setClickListener(ItemClickListener clickListener) {
mClickListener = clickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position, Avatar avatar);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView mName;
ImageView mPreviewImage;
public ViewHolder(View itemView) {
super(itemView);
mName = itemView.findViewById(R.id.avatarName);
assert (mName != null);
mPreviewImage = itemView.findViewById(R.id.avatarPreview);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int position= getAdapterPosition();
if (mClickListener != null) {
mClickListener.onItemClick(view, position, mAvatars.get(position));
}
}
}
public static class Avatar {
public String avatarName;
public String avatarUrl;
public String avatarPreviewUrl;
public Avatar() { }
}
}

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/avatarName"
android:fontFamily="@font/raleway_bold"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/colorButton1" />
<ImageView
android:id="@+id/avatarPreview"
android:background="@color/backgroundDark"
android:layout_width="match_parent"
android:layout_height="190dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toBottomOf="@id/avatarName"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/encourage_login_background"
android:scaleType="fitXY" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#B2000000" />
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<android.support.constraint.ConstraintLayout
android:id="@+id/profileForm"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/profile_margin_top"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="visible">
<EditText
android:id="@+id/displayName"
android:layout_width="match_parent"
android:layout_height="27dp"
android:layout_marginLeft="@dimen/profile_margin"
android:layout_marginRight="@dimen/profile_margin"
android:layout_marginBottom="@dimen/profile_margin"
android:background="@color/white_opaque"
android:paddingLeft="@dimen/edit_text_padding"
android:ems="10"
android:fontFamily="sans-serif"
android:textSize="@dimen/login_edit_text_size"
android:inputType="textCapWords"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="left|center_vertical"
android:hint="@string/displayName"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/displayName"
app:layout_constraintBottom_toTopOf="@id/okButton"
android:layout_marginTop="@dimen/profile_margin_avatars_top"
android:layout_marginBottom="@dimen/profile_margin_avatars_bottom"
android:layout_marginLeft="@dimen/profile_margin"
android:layout_marginRight="@dimen/profile_margin">
<android.support.v7.widget.RecyclerView
android:id="@+id/gridview"
android:paddingTop="@dimen/list_vertical_padding"
android:paddingBottom="@dimen/list_vertical_padding"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<TextView
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
app:layout_constraintLeft_toLeftOf="@id/displayName"
app:layout_constraintTop_toTopOf="@id/okButton"
app:layout_constraintBottom_toBottomOf="@id/okButton"
app:layout_constraintRight_toLeftOf="@id/okButton"
android:textColor="@color/white_opaque"
android:fontFamily="@font/raleway_bold"
android:textSize="@dimen/button_medium_text_size"
android:text="@string/cancel_uppercase" />
<Button
android:id="@+id/okButton"
android:layout_width="@dimen/button_medium_width"
android:layout_height="@dimen/button_medium_height"
android:background="@drawable/rounded_button_color3"
android:fontFamily="@font/raleway_bold"
android:layout_marginTop="@dimen/button_medium_margin"
android:layout_marginBottom="@dimen/button_medium_margin"
android:text="@string/ok"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="@dimen/button_medium_text_size"
app:layout_constraintRight_toRightOf="@id/displayName"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -9,6 +9,10 @@
android:id="@+id/action_people"
android:title="@string/people"
/>
<item
android:id="@+id/action_profile"
android:title="@string/profile"
/>
<item
android:id="@+id/action_debug_settings"
android:title="@string/settings"

View file

@ -54,5 +54,8 @@
<dimen name="login_menu_button_margin_top">77dp</dimen>
<dimen name="login_menu_text_size">20sp</dimen>
<dimen name="login_edit_text_padding">14dp</dimen>
<dimen name="profile_margin_top">20dp</dimen>
<dimen name="profile_margin">35dp</dimen>
<dimen name="profile_margin_avatars_top">40dp</dimen>
<dimen name="profile_margin_avatars_bottom">70dp</dimen>
</resources>

View file

@ -43,6 +43,7 @@
<string name="tagFragmentSignup">tagFragmentSignup</string>
<string name="tagFragmentPolicy">tagFragmentPolicy</string>
<string name="tagFragmentPeople">tagFragmentPeople</string>
<string name="tagFragmentProfile">tagFragmentProfile</string>
<string name="tagSettings">tagSettings</string>
<string name="tagFragmentSignedIn">tagFragmentSignedIn</string>
<string name="settings">Settings</string>
@ -54,4 +55,7 @@
<string name="take_me_in_world">No thanks, take me in-world!</string>
<string name="be_anywere">BE ANYWHERE, WITH ANYONE \nRIGHT NOW</string>
<string name="steam_log_in">STEAM LOG IN</string>
<string name="displayName">Name to display</string>
<string name="ok">OK</string>
<string name="profile">Avatar</string>
</resources>

View file

@ -7,15 +7,12 @@ docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/D
# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION
# So make sure we use VERSION_CODE consistently
if [-z "$VERSION_CODE"]; then
export VERSION_CODE=$VERSION
fi
test -z "$VERSION_CODE" && export VERSION_CODE=$VERSION
docker run \
--rm \
--security-opt seccomp:unconfined \
-v "${WORKSPACE}":/home/jenkins/hifi \
-v /home/jenkins/.gradle:/home/jenkins/.gradle \
-e RELEASE_NUMBER \
-e RELEASE_TYPE \
-e ANDROID_APP \

View file

@ -3,3 +3,6 @@ project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
include ':interface'
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
//include ':framePlayer'
//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')

View file

@ -743,7 +743,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok);
if (ok && coefficient >= 0.0f && coefficient <= 1.0f &&
if (ok && coefficient <= 1.0f &&
itSource != end(_audioZones) &&
itListener != end(_audioZones)) {

View file

@ -770,13 +770,29 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo
break;
}
}
// translate the zone setting to gain per log2(distance)
float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, EPSILON, 1.0f);
// calculate the attenuation using the distance to this node
// reference attenuation of 0dB at distance = 1.0m
gain *= fastExp2f(fastLog2f(g) * fastLog2f(std::max(distance, HRTF_NEARFIELD_MIN)));
gain = std::min(gain, 1.0f / HRTF_NEARFIELD_MIN);
if (attenuationPerDoublingInDistance < 0.0f) {
// translate a negative zone setting to distance limit
const float MIN_DISTANCE_LIMIT = ATTN_DISTANCE_REF + 1.0f; // silent after 1m
float distanceLimit = std::max(-attenuationPerDoublingInDistance, MIN_DISTANCE_LIMIT);
// calculate the LINEAR attenuation using the distance to this node
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = distance - ATTN_DISTANCE_REF;
gain *= std::max(1.0f - d / (distanceLimit - ATTN_DISTANCE_REF), 0.0f);
gain = std::min(gain, ATTN_GAIN_MAX);
} else {
// translate a positive zone setting to gain per log2(distance)
const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance)
float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f);
// calculate the LOGARITHMIC attenuation using the distance to this node
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN);
gain *= fastExp2f(fastLog2f(g) * fastLog2f(d));
gain = std::min(gain, ATTN_GAIN_MAX);
}
return gain;
}

View file

@ -7,6 +7,7 @@
#
macro(TARGET_GLAD)
if (ANDROID)
include(SelectLibraryConfigurations)
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/glad)
set(GLAD_INCLUDE_DIRS "${INSTALL_DIR}/include")
set(GLAD_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libglad_d.a)
@ -31,8 +32,8 @@ macro(TARGET_GLAD)
set(GLAD_INCLUDE_DIRS ${${GLAD_UPPER}_INCLUDE_DIRS})
set(GLAD_LIBRARY ${${GLAD_UPPER}_LIBRARY})
endif()
target_include_directories(${TARGET_NAME} PUBLIC ${GLAD_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${GLAD_LIBRARY})
target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES})
target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES})
endmacro()

View file

@ -6,8 +6,13 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
macro(TARGET_ZLIB)
# using VCPKG for zlib
find_package(ZLIB REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES})
if (ANDROID)
# zlib is part of the NDK
target_link_libraries(${TARGET_NAME} z)
else()
# using VCPKG for zlib
find_package(ZLIB REQUIRED)
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES})
endif()
endmacro()

View file

@ -1,3 +1,3 @@
Source: nvtt
Version: 8c7e6b40ee5095f227b75880fabd89c99d6f34c0
Version: 330c4d56274a0f602a5c70596e2eb670a4ed56c2
Description: Texture processing tools with support for Direct3D 10 and 11 formats.

View file

@ -10,8 +10,8 @@ include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO highfidelity/nvidia-texture-tools
REF 8c7e6b40ee5095f227b75880fabd89c99d6f34c0
SHA512 f107d19dbbd6651ef2126b1422a5db8db291bf70311ac4fb1dbacb5ceaa8752fee38becbd32964f57596f0b84e1223bb2c3ff9d9c4fdc65c3e77a47836657cef
REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2
SHA512 4c0bc2f369120d696cc27710b6d33086b27eef55f537ec66b9a5c8b1839bc2426c0413670b0f65be52c5d353468f0126dfe024be1f0690611d4d7e33ac530127
HEAD_REF master
)

View file

@ -246,6 +246,7 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) {
qCWarning(asset_backup) << "Current mappings not yet loaded.";
_backups.emplace_back(backupName, AssetUtils::Mappings(), true);
return;
}

View file

@ -15,8 +15,8 @@ print = functools.partial(print, flush=True)
# Encapsulates the vcpkg system
class VcpkgRepo:
CMAKE_TEMPLATE = """
set(CMAKE_TOOLCHAIN_FILE "{}" CACHE FILEPATH "Toolchain file")
set(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}")
get_filename_component(CMAKE_TOOLCHAIN_FILE "{}" ABSOLUTE CACHE)
get_filename_component(CMAKE_TOOLCHAIN_FILE_UNCACHED "{}" ABSOLUTE)
set(VCPKG_INSTALL_ROOT "{}")
set(VCPKG_TOOLS_DIR "{}")
"""

View file

@ -120,6 +120,6 @@ FocusScope {
}
Component.onCompleted: {
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -22,11 +22,16 @@ Item {
width: root.width
height: root.height
readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
readonly property string termsContainerOculusText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
readonly property int textFieldHeight: 31
readonly property string fontFamily: "Raleway"
readonly property int fontSize: 15
readonly property bool fontBold: true
readonly property int textFieldFontSize: 18
readonly property var passwordImageRatio: 16 / 23
readonly property bool withSteam: withSteam
property bool withOculus: withOculus
property bool withSteam: withSteam
property string errorString: errorString
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -61,15 +66,20 @@ Item {
Item {
id: contentItem
anchors.fill: parent
width: parent.width
height: errorContainer.height + fields.height + buttons.height + additionalTextContainer.height +
termsContainer.height
anchors.top: parent.top
anchors.topMargin: root.bannerHeight + 0.25 * parent.height
anchors.left: parent.left
Item {
id: errorContainer
width: parent.width
width: root.bannerWidth
height: loginErrorMessageTextMetrics.height
anchors {
bottom: buttons.top;
bottomMargin: hifi.dimensions.contentSpacing.y;
bottom: completeProfileBody.withOculus ? fields.top : buttons.top;
bottomMargin: 1.5 * hifi.dimensions.contentSpacing.y;
left: buttons.left;
}
TextMetrics {
@ -79,8 +89,8 @@ Item {
}
Text {
id: loginErrorMessage;
width: root.bannerWidth
color: "red";
width: root.bannerWidth;
font.family: completeProfileBody.fontFamily
font.pixelSize: 18
font.bold: completeProfileBody.fontBold
@ -88,13 +98,196 @@ Item {
horizontalAlignment: Text.AlignHCenter
text: completeProfileBody.errorString
visible: true
onTextChanged: {
mainContainer.recalculateErrorMessage();
}
Component.onCompleted: {
mainContainer.recalculateErrorMessage();
}
}
Component.onCompleted: {
if (loginErrorMessageTextMetrics.width > root.bannerWidth && root.isTablet) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = 3 * loginErrorMessageTextMetrics.height;
}
Item {
id: fields
width: root.bannerWidth
height: 3 * completeProfileBody.textFieldHeight + 2 * hifi.dimensions.contentSpacing.y
visible: completeProfileBody.withOculus
anchors {
left: parent.left
leftMargin: (parent.width - root.bannerWidth) / 2
bottom: buttons.top
bottomMargin: hifi.dimensions.contentSpacing.y
}
HifiControlsUit.TextField {
id: usernameField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
placeholderText: "Username"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
anchors {
top: parent.top
}
Keys.onPressed: {
if (!usernameField.visible) {
return;
}
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
passwordField.focus = true;
} else {
emailField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
passwordField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
onFocusChanged: {
root.text = "";
if (focus) {
root.isPassword = false;
}
}
Component.onCompleted: {
var userID = "";
if (completeProfileBody.withOculus) {
userID = loginDialog.oculusUserID();
}
usernameField.text = userID;
}
}
HifiControlsUit.TextField {
id: emailField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
anchors {
top: usernameField.bottom
topMargin: hifi.dimensions.contentSpacing.y
}
placeholderText: "Email"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
activeFocusOnPress: true
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
usernameField.focus = true;
} else {
passwordField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
usernameField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
onFocusChanged: {
root.text = "";
if (focus) {
root.isPassword = false;
}
}
}
HifiControlsUit.TextField {
id: passwordField
width: root.bannerWidth
height: completeProfileBody.textFieldHeight
placeholderText: "Password (optional)"
font.pixelSize: completeProfileBody.textFieldFontSize
styleRenderType: Text.QtRendering
activeFocusOnPress: true
echoMode: passwordFieldMouseArea.showPassword ? TextInput.Normal : TextInput.Password
anchors {
top: emailField.bottom
topMargin: hifi.dimensions.contentSpacing.y
}
onFocusChanged: {
root.text = "";
root.isPassword = focus;
}
Item {
id: showPasswordContainer
z: 10
// width + image's rightMargin
width: showPasswordImage.width + 8
height: parent.height
anchors {
right: parent.right
}
Image {
id: showPasswordImage
width: passwordField.height * passwordImageRatio
height: passwordField.height * passwordImageRatio
anchors {
right: parent.right
rightMargin: 8
top: parent.top
topMargin: passwordFieldMouseArea.showPassword ? 6 : 8
bottom: parent.bottom
bottomMargin: passwordFieldMouseArea.showPassword ? 5 : 8
}
source: passwordFieldMouseArea.showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg"
MouseArea {
id: passwordFieldMouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
property bool showPassword: false
onClicked: {
showPassword = !showPassword;
}
}
}
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Tab:
event.accepted = true;
if (event.modifiers === Qt.ShiftModifier) {
emailField.focus = true;
} else if (usernameField.visible) {
usernameField.focus = true;
} else {
emailField.focus = true;
}
break;
case Qt.Key_Backtab:
event.accepted = true;
emailField.focus = true;
break;
case Qt.Key_Enter:
case Qt.Key_Return:
event.accepted = true;
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
break;
}
}
}
}
@ -105,7 +298,7 @@ Item {
height: d.minHeightButton
anchors {
top: parent.top
topMargin: (parent.height - additionalTextContainer.height) / 2 - hifi.dimensions.contentSpacing.y
topMargin: (parent.height - additionalTextContainer.height + fields.height) / 2 - hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - root.bannerWidth) / 2
}
@ -144,7 +337,7 @@ Item {
width: (parent.width - hifi.dimensions.contentSpacing.x) / 2
height: d.minHeightButton
text: qsTr("Create your profile")
text: completeProfileBody.withOculus ? qsTr("Sign Up") : qsTr("Create your profile")
color: hifi.buttons.blue
fontFamily: completeProfileBody.fontFamily
@ -158,55 +351,12 @@ Item {
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginErrorMessage.visible = false;
loginDialog.createAccountFromSteam();
}
}
}
Item {
id: additionalTextContainer
width: parent.width
height: additionalTextMetrics.height
anchors {
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: additionalTextMetrics
font: additionalText.font
text: "Already have a High Fidelity profile? Link to an existing profile here."
}
HifiStylesUit.ShortcutText {
id: additionalText
text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.NoWrap
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: {
loginDialog.isLogIn = true;
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "withSteam": true, "linkSteam": true });
}
Component.onCompleted: {
if (additionalTextMetrics.width > root.bannerWidth && root.isTablet) {
additionalText.width = root.bannerWidth;
additionalText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
additionalTextContainer.anchors.left = buttons.left;
} else {
additionalText.anchors.centerIn = additionalTextContainer;
if (completeProfileBody.withOculus) {
loginDialog.createAccountFromOculus(emailField.text, usernameField.text, passwordField.text);
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"linkSteam": false, "withOculus": completeProfileBody.withOculus, "linkOculus": false, "createOculus": true });
} else if (completeProfileBody.withSteam) {
loginDialog.createAccountFromSteam();
}
}
}
@ -217,29 +367,33 @@ Item {
width: parent.width
height: termsTextMetrics.height
anchors {
top: additionalTextContainer.bottom
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
topMargin: hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: completeProfileBody.termsContainerText
text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
if (completeProfileBody.withOculus) {
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
} else {
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
}
HifiStylesUit.InfoItem {
id: termsText
text: completeProfileBody.termsContainerText
text: completeProfileBody.withOculus ? completeProfileBody.termsContainerOculusText : completeProfileBody.termsContainerText
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.lightGray
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
@ -247,7 +401,7 @@ Item {
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth && root.isTablet) {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
@ -260,14 +414,86 @@ Item {
}
}
}
Item {
id: additionalTextContainer
width: parent.width
height: additionalTextMetrics.height
anchors {
top: termsContainer.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: additionalTextMetrics
font: additionalText.font
text: "Already have a High Fidelity profile? Link to an existing profile here."
}
HifiStylesUit.ShortcutText {
id: additionalText
text: "<a href='https://fake.link'>Already have a High Fidelity profile? Link to an existing profile here.</a>"
width: root.bannerWidth;
font.family: completeProfileBody.fontFamily
font.pixelSize: completeProfileBody.fontSize
font.bold: completeProfileBody.fontBold
wrapMode: Text.NoWrap
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
linkColor: hifi.colors.blueAccent
onLinkActivated: {
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "",
"withSteam": completeProfileBody.withSteam, "linkSteam": completeProfileBody.withSteam, "withOculus": completeProfileBody.withOculus,
"linkOculus": completeProfileBody.withOculus });
}
Component.onCompleted: {
if (additionalTextMetrics.width > root.bannerWidth) {
additionalText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
additionalTextContainer.height = (additionalTextMetrics.width / root.bannerWidth) * additionalTextMetrics.height;
additionalTextContainer.anchors.left = buttons.left;
} else {
additionalText.anchors.centerIn = additionalTextContainer;
}
}
}
}
}
function recalculateErrorMessage() {
if (completeProfileBody.errorString !== "") {
loginErrorMessage.visible = true;
var errorLength = completeProfileBody.errorString.split(/\r\n|\r|\n/).length;
var errorStringEdited = completeProfileBody.errorString.replace(/[\n\r]+/g, "\n");
loginErrorMessage.text = errorStringEdited;
if (errorLength > 1.0) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
} else if (loginErrorMessageTextMetrics.width > root.bannerWidth) {
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = (loginErrorMessageTextMetrics.width / root.bannerWidth) * loginErrorMessageTextMetrics.height;
} else {
loginErrorMessage.wrapMode = Text.NoWrap;
loginErrorMessage.verticalAlignment = Text.AlignVCenter;
loginErrorMessage.horizontalAlignment = Text.AlignHCenter;
errorContainer.height = loginErrorMessageTextMetrics.height;
}
}
}
}
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
console.log("Create Succeeded");
if (completeProfileBody.withSteam) {
if (completeProfileBody.loginDialogPoppedUp) {
var data = {
@ -277,20 +503,24 @@ Item {
}
loginDialog.loginThroughSteam();
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false });
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false,
"withOculus": completeProfileBody.withOculus, "linkOculus": false });
}
onHandleCreateFailed: {
console.log("Create Failed: " + error);
if (completeProfileBody.withSteam) {
if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
if (completeProfileBody.loginDialogPoppedUp) {
action = completeProfileBody.withSteam ? "Steam" : "Oculus";
var data = {
"action": "user failed to create a profile with Steam from the complete profile screen"
"action": "user failed to create a profile with " + action + " from the complete profile screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
}
bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam });
if (!completeProfileBody.withOculus) {
bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam,
"withOculus": completeProfileBody.withOculus });
}
}
}
@ -302,5 +532,6 @@ Item {
}
d.resize();
root.text = "";
usernameField.forceActiveFocus();
}
}

View file

@ -36,9 +36,10 @@ Item {
property bool keyboardRaised: false
property bool punctuationMode: false
property bool withSteam: false
property bool withSteam: withSteam
property bool linkSteam: linkSteam
property bool withOculus: false
property bool withOculus: withOculus
property bool linkOculus: linkOculus
property string errorString: errorString
property bool lostFocus: false
@ -83,23 +84,24 @@ Item {
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
"withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
}
function init() {
// going to/from sign in/up dialog.
loginDialog.isLogIn = true;
loginErrorMessage.text = linkAccountBody.errorString;
loginErrorMessage.visible = (linkAccountBody.errorString !== "");
loginButton.text = !linkAccountBody.linkSteam ? "Log In" : "Link Account";
if (loginErrorMessageTextMetrics.width > emailField.width) {
loginErrorMessage.wrapMode = Text.WordWrap;
errorContainer.height = (loginErrorMessageTextMetrics.width / emailField.width) * loginErrorMessageTextMetrics.height;
}
loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account";
loginButton.color = hifi.buttons.blue;
emailField.placeholderText = "Username or Email";
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
if (linkAccountBody.linkSteam) {
steamInfoText.anchors.top = passwordField.bottom;
keepMeLoggedInCheckbox.anchors.top = steamInfoText.bottom;
if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2;
loginButton.anchors.right = emailField.right;
} else {
@ -125,7 +127,7 @@ Item {
id: loginContainer
width: emailField.width
height: errorContainer.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y +
keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height + steamInfoTextMetrics.height
keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height
anchors {
top: parent.top
topMargin: root.bannerHeight + 0.25 * parent.height
@ -135,7 +137,7 @@ Item {
Item {
id: errorContainer
width: loginErrorMessageTextMetrics.width
width: parent.width
height: loginErrorMessageTextMetrics.height
anchors {
bottom: emailField.top;
@ -304,7 +306,7 @@ Item {
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
color: hifi.buttons.noneBorderlessWhite;
visible: linkAccountBody.linkSteam
visible: linkAccountBody.linkSteam || linkAccountBody.linkOculus
anchors {
top: keepMeLoggedInCheckbox.bottom
topMargin: hifi.dimensions.contentSpacing.y
@ -315,10 +317,9 @@ Item {
"action": "user clicked cancel at link account screen"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
loginDialog.dismissLoginDialog();
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
"withOculus": linkAccountBody.withOculus, "errorString": "" });
}
}
HifiControlsUit.Button {
@ -337,33 +338,6 @@ Item {
linkAccountBody.login();
}
}
TextMetrics {
id: steamInfoTextMetrics
font: steamInfoText.font
text: steamInfoText.text
}
Text {
id: steamInfoText
width: root.bannerWidth
visible: linkAccountBody.linkSteam
anchors {
top: loginButton.bottom
topMargin: hifi.dimensions.contentSpacing.y
left: emailField.left
}
font.family: linkAccountBody.fontFamily
font.pixelSize: linkAccountBody.textFieldFontSize
color: "white"
text: qsTr("Your Steam account information will not be exposed to others.");
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
Component.onCompleted: {
if (steamInfoTextMetrics.width > root.bannerWidth) {
steamInfoText.wrapMode = Text.WordWrap;
}
}
}
TextMetrics {
id: cantAccessTextMetrics
font: cantAccessText.font
@ -372,7 +346,7 @@ Item {
HifiStylesUit.ShortcutText {
id: cantAccessText
z: 10
visible: !linkAccountBody.linkSteam
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
anchors {
top: loginButton.bottom
topMargin: hifi.dimensions.contentSpacing.y
@ -423,10 +397,10 @@ Item {
buttonGlyphSize: 24
buttonGlyphRightMargin: 10
onClicked: {
// if (loginDialog.isOculusStoreRunning()) {
// linkAccountBody.withOculus = true;
// loginDialog.loginThroughSteam();
// } else
if (loginDialog.isOculusRunning()) {
linkAccountBody.withOculus = true;
loginDialog.loginThroughOculus();
} else
if (loginDialog.isSteamRunning()) {
linkAccountBody.withSteam = true;
loginDialog.loginThroughSteam();
@ -446,18 +420,17 @@ Item {
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
"withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam });
"withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus });
}
Component.onCompleted: {
if (linkAccountBody.linkSteam) {
if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
continueButton.visible = false;
return;
}
// if (loginDialog.isOculusStoreRunning()) {
// continueButton.text = qsTr("CONTINUE WITH OCULUS");
// continueButton.buttonGlyph = hifi.glyphs.oculus;
// } else
if (loginDialog.isSteamRunning()) {
if (loginDialog.isOculusRunning()) {
continueButton.text = qsTr("CONTINUE WITH OCULUS");
continueButton.buttonGlyph = hifi.glyphs.oculus;
} else if (loginDialog.isSteamRunning()) {
continueButton.text = qsTr("CONTINUE WITH STEAM");
continueButton.buttonGlyph = hifi.glyphs.steamSquare;
} else {
@ -470,7 +443,7 @@ Item {
id: signUpContainer
width: loginContainer.width
height: signUpTextMetrics.height
visible: !linkAccountBody.linkSteam
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus
anchors {
left: loginContainer.left
top: loginContainer.bottom
@ -519,7 +492,7 @@ Item {
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader,
"errorString": "", "linkSteam": linkAccountBody.linkSteam });
"errorString": "" });
}
}
}
@ -543,7 +516,7 @@ Item {
fontFamily: linkAccountBody.fontFamily
fontSize: linkAccountBody.fontSize
fontBold: linkAccountBody.fontBold
visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam;
visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam && !linkAccountBody.linkOculus;
onClicked: {
if (linkAccountBody.loginDialogPoppedUp) {
var data = {

View file

@ -29,6 +29,8 @@ Item {
property bool withSteam: withSteam
property bool withOculus: withOculus
property bool linkSteam: linkSteam
property bool linkOculus: linkOculus
property bool createOculus: createOculus
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -75,15 +77,25 @@ Item {
}
}
Timer {
id: oculusSuccessTimer
interval: 500;
running: false;
repeat: false;
onTriggered: {
loginDialog.loginThroughOculus();
init();
}
}
function init() {
// For the process of logging in.
loggingInText.wrapMode = Text.NoWrap;
if (loggingInBody.linkSteam) {
if (loggingInBody.createOculus) {
loggingInGlyph.text = hifi.glyphs.oculus;
loggingInGlyph.visible = true;
loggingInText.text = "Linking to Steam";
loggingInText.text = "Creating account with Oculus";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkSteam();
} else if (loggingInBody.withSteam) {
loggingInGlyph.visible = true;
loggingInText.text = "Logging in to Steam";
@ -100,12 +112,18 @@ Item {
loggingInSpinner.visible = true;
}
function loadingSuccess() {
loggingInSpinner.visible = false;
if (loggingInBody.linkSteam) {
loggingInText.text = "Linking to Steam";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkSteam();
return;
} else if (loggingInBody.linkOculus) {
loggingInText.text = "Linking to Oculus";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
loginDialog.linkOculus();
return;
}
loggingInSpinner.visible = false;
if (loggingInBody.withSteam) {
// reset the flag.
loggingInGlyph.visible = false;
@ -246,6 +264,26 @@ Item {
verticalAlignment: Text.AlignVCenter;
visible: false;
}
HifiControlsUit.Button {
id: okButton;
width: d.minWidthButton
height: d.minHeightButton
text: qsTr("OK")
color: hifi.buttons.white
anchors {
top: loggedInGlyph.bottom
topMargin: 3 * hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - width) / 2;
}
onClicked: {
root.tryDestroy();
if (loginDialog.getLoginDialogPoppedUp()) {
loginDialog.dismissLoginDialog();
}
}
visible: false
}
}
}
}
@ -257,6 +295,34 @@ Item {
Connections {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded")
if (loggingInBody.withOculus) {
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user created Oculus account successfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loggingInBody.createOculus = false;
loggingInText.text = "Account created!";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
oculusSuccessTimer.start();
}
}
onHandleCreateFailed: {
console.log("Create Failed: " + error);
if (loggingInBody.withOculus) {
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user created Oculus account unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "errorString": error });
}
}
onHandleLinkCompleted: {
console.log("Link Succeeded");
if (loggingInBody.linkSteam) {
@ -267,21 +333,40 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loggingInBody.loadingSuccess();
} else if (loggingInBody.linkOculus) {
loggingInBody.linkOculus = false;
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Oculus with their hifi account credentials successfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
}
loggingInBody.loadingSuccess();
}
onHandleLinkFailed: {
console.log("Link Failed: " + error);
if (loggingInBody.linkSteam) {
loggingInSpinner.visible = false;
if (loggingInBody.linkOculus) {
loggingInText.text = "Oculus failed to link";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Oculus unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
okButton.visible = true;
} else if (loggingInBody.linkSteam){
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user linked Steam unsuccessfully"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
} else {
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": loggingInBody.linkSteam,
"linkOculus": loggingInBody.linkOculus, "errorString": error });
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error });
}
onHandleLoginCompleted: {
@ -292,8 +377,19 @@ Item {
onHandleLoginFailed: {
console.log("Login Failed")
loggingInSpinner.visible = false;
loggingInGlyph.visible = false;
var errorString = "";
if (loggingInBody.linkSteam && loggingInBody.withSteam) {
if (loggingInBody.linkOculus && loggingInBody.withOculus) {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user failed to link Oculus with their hifi account credentials"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.linkSteam && loggingInBody.withSteam) {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
@ -301,9 +397,9 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString });
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.withSteam) {
loggingInGlyph.visible = false;
errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {
@ -311,19 +407,19 @@ Item {
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else if (loggingInBody.withOculus) {
loggingInGlyph.visible = false;
errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again."
errorString = "Your Oculus account is not connected to an existing High Fidelity account. Please create a new one."
if (loggingInBody.loginDialogPoppedUp) {
var data = {
"action": "user failed to authenticate with Oculus to log in"
};
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString });
}
else {
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam,
"withOculus": loggingInBody.withOculus, "linkSteam": loggingInBody.linkSteam, "linkOculus": loggingInBody.linkOculus, "errorString": errorString });
} else {
errorString = "Username or password is incorrect.";
if (loggingInBody.loginDialogPoppedUp) {
var data = {

View file

@ -23,6 +23,7 @@ Item {
clip: true
height: root.height
width: root.width
readonly property string termsContainerText: qsTr("By signing up, you agree to High Fidelity's Terms of Service")
property int textFieldHeight: 31
property string fontFamily: "Raleway"
property int fontSize: 15
@ -37,7 +38,6 @@ Item {
onKeyboardRaisedChanged: d.resize();
property string errorString: errorString
property bool linkSteam: linkSteam
property bool lostFocus: false
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -73,7 +73,6 @@ Item {
function init() {
// going to/from sign in/up dialog.
loginDialog.isLogIn = false;
emailField.placeholderText = "Email";
emailField.text = "";
emailField.anchors.top = usernameField.bottom;
@ -353,7 +352,7 @@ Item {
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam });
bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
}
}
HifiControlsUit.Button {
@ -380,6 +379,54 @@ Item {
signUpBody.signup();
}
}
Item {
id: termsContainer
width: parent.width
height: termsTextMetrics.height
anchors {
top: signUpButton.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: signUpBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By signing up, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
HifiStylesUit.InfoItem {
id: termsText
text: signUpBody.termsContainerText
font.family: signUpBody.fontFamily
font.pixelSize: signUpBody.fontSize
font.bold: signUpBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
termsContainer.anchors.left = buttons.left;
} else {
termsText.anchors.centerIn = termsContainer;
}
}
}
}
}
}
@ -433,14 +480,15 @@ Item {
if (errorString !== "") {
loginErrorMessage.visible = true;
var errorLength = errorString.split(/\r\n|\r|\n/).length;
var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n");
loginErrorMessage.text = errorStringEdited;
loginErrorMessageTextMetrics.text = errorString;
if (loginErrorMessageTextMetrics.width > usernameField.width) {
if (errorLength > 1.0) {
loginErrorMessage.width = root.bannerWidth;
loginErrorMessage.wrapMode = Text.WordWrap;
loginErrorMessage.verticalAlignment = Text.AlignLeft;
loginErrorMessage.horizontalAlignment = Text.AlignLeft;
errorContainer.height = (loginErrorMessageTextMetrics.width / usernameField.width) * loginErrorMessageTextMetrics.height;
errorContainer.height = errorLength * loginErrorMessageTextMetrics.height;
}
errorContainer.anchors.bottom = usernameField.top;
errorContainer.anchors.bottomMargin = hifi.dimensions.contentSpacing.y;

View file

@ -19,6 +19,7 @@ import TabletScriptingInterface 1.0
Item {
id: usernameCollisionBody
clip: true
readonly property string termsContainerText: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service")
width: root.width
height: root.height
readonly property string fontFamily: "Raleway"
@ -26,13 +27,18 @@ Item {
readonly property int textFieldFontSize: 18
readonly property bool fontBold: true
readonly property bool withSteam: withSteam
property bool withSteam: withSteam
property bool withOculus: withOculus
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
function create() {
mainTextContainer.visible = false
loginDialog.createAccountFromSteam(textField.text);
if (usernameCollisionBody.withOculus) {
loginDialog.createAccountFromOculus(textField.text);
} else if (usernameCollisionBody.withSteam) {
loginDialog.createAccountFromSteam(textField.text);
}
}
property bool keyboardEnabled: false
@ -90,12 +96,19 @@ Item {
font.family: usernameCollisionBody.fontFamily
font.pixelSize: usernameCollisionBody.fontSize
font.bold: usernameCollisionBody.fontBold
text: qsTr("Your Steam username is not available.");
text: qsTr("");
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
horizontalAlignment: Text.AlignHCenter
Component.onCompleted: {
if (usernameCollisionBody.withOculus) {
text = qsTr("Your Oculus username is not available.");
} else if (usernameCollisionBody.withSteam) {
text = qsTr("Your Steam username is not available.");
}
}
}
@ -164,7 +177,8 @@ Item {
fontSize: usernameCollisionBody.fontSize
fontBold: usernameCollisionBody.fontBold
onClicked: {
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "" });
bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
"withOculus": usernameCollisionBody.withOculus, "errorString": "" });
}
}
HifiControlsUit.Button {
@ -187,6 +201,55 @@ Item {
}
}
}
Item {
id: termsContainer
width: parent.width
height: termsTextMetrics.height
anchors {
top: buttons.bottom
horizontalCenter: parent.horizontalCenter
topMargin: 2 * hifi.dimensions.contentSpacing.y
left: parent.left
leftMargin: (parent.width - buttons.width) / 2
}
TextMetrics {
id: termsTextMetrics
font: termsText.font
text: usernameCollisionBody.termsContainerText
Component.onCompleted: {
// with the link.
termsText.text = qsTr("By creating this user profile, you agree to <a href='https://highfidelity.com/terms'>High Fidelity's Terms of Service</a>")
}
}
HifiStylesUit.InfoItem {
id: termsText
text: usernameCollisionBody.termsContainerText
font.family: usernameCollisionBody.fontFamily
font.pixelSize: usernameCollisionBody.fontSize
font.bold: usernameCollisionBody.fontBold
wrapMode: Text.WordWrap
color: hifi.colors.white
linkColor: hifi.colors.blueAccent
lineHeight: 1
lineHeightMode: Text.ProportionalHeight
onLinkActivated: loginDialog.openUrl(link);
Component.onCompleted: {
if (termsTextMetrics.width > root.bannerWidth) {
termsText.width = root.bannerWidth;
termsText.wrapMode = Text.WordWrap;
additionalText.verticalAlignment = Text.AlignLeft;
additionalText.horizontalAlignment = Text.AlignLeft;
termsContainer.height = (termsTextMetrics.width / root.bannerWidth) * termsTextMetrics.height;
termsContainer.anchors.left = buttons.left;
} else {
termsText.anchors.centerIn = termsContainer;
}
}
}
}
}
Component.onCompleted: {
@ -201,18 +264,25 @@ Item {
target: loginDialog
onHandleCreateCompleted: {
console.log("Create Succeeded");
if (usernameCollisionBody.withSteam) {
if (usernameCollisionBody.withOculus) {
if (usernameCollisionBody.loginDialogPoppedUp) {
var data = {
"action": "user created a profile with Oculus successfully in the username collision screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginDialog.loginThroughOculus();
} else if (usernameCollisionBody.withSteam) {
if (usernameCollisionBody.loginDialogPoppedUp) {
var data = {
"action": "user created a profile with Steam successfully in the username collision screen"
}
UserActivityLogger.logAction("encourageLoginDialog", data);
}
loginDialog.loginThroughSteam();
}
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false })
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam,
"withOculus": usernameCollisionBody.withOculus, "linkSteam": false, "linkOculus": false })
}
onHandleCreateFailed: {
console.log("Create Failed: " + error)

View file

@ -150,6 +150,6 @@ FocusScope {
Component.onCompleted: {
keyboardTimer.start();
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -0,0 +1,26 @@
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import Qt.labs.folderlistmodel 2.11
Item {
width: 640
height: 480
ListView {
width: 200; height: 400
FolderListModel {
id: folderModel
folder: "assets:/frames/"
nameFilters: ["*.json"]
}
Component {
id: fileDelegate
Text { text: fileName }
}
model: folderModel
delegate: fileDelegate
}
}

View file

@ -57,16 +57,23 @@ SpinBox {
locale: Qt.locale("en_US")
onValueModified: realValue = value/factor
onValueChanged: realValue = value/factor
onValueModified: {
realValue = value / factor
}
onValueChanged: {
realValue = value / factor
spinBox.editingFinished();
}
onRealValueChanged: {
var newValue = Math.round(realValue*factor);
var newValue = Math.round(realValue * factor);
if(value != newValue) {
value = newValue;
}
}
stepSize: realStepSize*factor
stepSize: realStepSize * factor
to : realTo*factor
from : realFrom*factor
@ -90,11 +97,11 @@ SpinBox {
}
textFromValue: function(value, locale) {
return parseFloat(value/factor).toFixed(decimals);
return parseFloat(value / factor).toFixed(decimals);
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text)*factor;
return Number.fromLocaleString(locale, text) * factor;
}
@ -102,7 +109,7 @@ SpinBox {
id: spinboxText
z: 2
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray)
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
@ -112,8 +119,6 @@ SpinBox {
verticalAlignment: Qt.AlignVCenter
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
width: spinBox.width - hifi.dimensions.spinnerSize
onEditingFinished: spinBox.editingFinished()
Text {
id: suffixText
x: metrics.advanceWidth(spinboxText.text + '*')
@ -125,7 +130,7 @@ SpinBox {
}
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.lightGray)
? (spinBox.activeFocus ? hifi.colors.black : hifi.colors.faintGray)
: (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText)
text: suffix
verticalAlignment: Qt.AlignVCenter
@ -170,6 +175,22 @@ SpinBox {
}
}
Keys.onPressed: {
if (event.key === Qt.Key_Return) {
if (!spinboxText.acceptableInput) {
var number = spinBox.valueFromText(spinboxText.text, spinBox.locale) / spinBox.factor
if (number < spinBox.minimumValue) {
number = spinBox.minimumValue;
} else if (number > maximumValue) {
number = spinBox.maximumValue;
}
spinboxText.text = spinBox.textFromValue(Math.round(number * factor), spinBox.locale)
}
}
}
HifiControls.Label {
id: spinBoxLabel
text: spinBox.label

View file

@ -14,6 +14,8 @@ import QtQuick 2.5
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
import "../LoginDialog"
FocusScope {
@ -25,10 +27,9 @@ FocusScope {
width: parent.width
height: parent.height
signal sendToScript(var message);
signal canceled();
property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system");
property bool isHMD: false
property bool isHMD: HMD.active
property bool gotoPreviousApp: false;
property bool keyboardEnabled: false
@ -52,6 +53,7 @@ FocusScope {
}
function tryDestroy() {
tabletProxy.gotoHomeScreen();
}
MouseArea {
@ -76,7 +78,7 @@ FocusScope {
interval: 200
onTriggered: {
if (MenuInterface.isOptionChecked("Use 3D Keyboard")) {
if (MenuInterface.isOptionChecked("Use 3D Keyboard") && root.isHMD) {
KeyboardScriptingInterface.raised = true;
}
}
@ -169,11 +171,13 @@ FocusScope {
Component.onDestruction: {
loginKeyboard.raised = false;
KeyboardScriptingInterface.raised = false;
if (root.isHMD) {
KeyboardScriptingInterface.raised = false;
}
}
Component.onCompleted: {
keyboardTimer.start();
bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false });
bodyLoader.setSource("../LoginDialog/LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": false, "linkOculus": false });
}
}

View file

@ -254,6 +254,7 @@ Rectangle {
onSaveClicked: function() {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
hmdAvatarAlignmentType : settings.hmdAvatarAlignmentTypeIsEyes ? 'eyes' : 'head',
collisionsEnabled : settings.environmentCollisionsOn,
otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn,
animGraphOverrideUrl : settings.avatarAnimationOverrideJSON,

View file

@ -1215,7 +1215,7 @@ Rectangle {
if (userIndex !== -1) {
['userName', 'admin', 'connection', 'profileUrl', 'placeName'].forEach(function (name) {
var value = message.params[name];
if (value === undefined) {
if (value === undefined || value == "") {
return;
}
nearbyUserModel.setProperty(userIndex, name, value);

View file

@ -37,6 +37,7 @@ Rectangle {
property alias dominantHandIsLeft: leftHandRadioButton.checked
property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledRadiobutton.checked
property alias environmentCollisionsOn: environmentCollisionsEnabledRadiobutton.checked
property alias hmdAvatarAlignmentTypeIsEyes: eyesRadioButton.checked
property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text
property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
@ -65,6 +66,11 @@ Rectangle {
} else {
environmentCollisionsDisabledRadiobutton.checked = true;
}
if (settings.hmdAvatarAlignmentType === 'eyes') {
eyesRadioButton.checked = true;
} else {
headRadioButton.checked = true;
}
avatarAnimationJSON = settings.animGraphUrl;
avatarAnimationOverrideJSON = settings.animGraphOverrideUrl;
@ -210,7 +216,7 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
rows: 2
rows: 4
rowSpacing: 25
columns: 3
@ -233,7 +239,7 @@ Rectangle {
Layout.row: 0
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: leftRight
checked: true
@ -249,7 +255,7 @@ Rectangle {
id: rightHandRadioButton
Layout.row: 0
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: leftRight
@ -260,7 +266,7 @@ Rectangle {
text: "Right"
boxSize: 20
}
HifiConstants {
id: hifi
}
@ -272,17 +278,17 @@ Rectangle {
Layout.column: 0
text: "Avatar to avatar collision"
}
ButtonGroup {
id: otherAvatarsOnOff
}
HifiControlsUit.RadioButton {
id: otherAvatarsCollisionsEnabledRadiobutton
Layout.row: 1
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: otherAvatarsOnOff
@ -297,7 +303,7 @@ Rectangle {
id: otherAvatarsCollisionsDisabledRadiobutton
Layout.row: 1
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: otherAvatarsOnOff
@ -320,13 +326,13 @@ Rectangle {
ButtonGroup {
id: worldOnOff
}
HifiControlsUit.RadioButton {
id: environmentCollisionsEnabledRadiobutton
Layout.row: 2
Layout.column: 1
Layout.leftMargin: -20
Layout.leftMargin: -15
ButtonGroup.group: worldOnOff
@ -341,7 +347,7 @@ Rectangle {
id: environmentCollisionsDisabledRadiobutton
Layout.row: 2
Layout.column: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: worldOnOff
@ -352,6 +358,52 @@ Rectangle {
text: "Off"
boxSize: 20
}
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 3
Layout.column: 0
text: "HMD Alignment"
}
ButtonGroup {
id: headEyes
}
HifiControlsUit.RadioButton {
id: headRadioButton
Layout.row: 3
Layout.column: 1
Layout.leftMargin: -15
ButtonGroup.group: headEyes
checked: true
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Head"
boxSize: 20
}
HifiControlsUit.RadioButton {
id: eyesRadioButton
Layout.row: 3
Layout.column: 2
Layout.rightMargin: -15
ButtonGroup.group: headEyes
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Eyes"
boxSize: 20
}
}
ColumnLayout {

View file

@ -21,11 +21,11 @@ import "../../../../controls" as HifiControls
import "../" as HifiCommerceCommon
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Item {
Rectangle {
HifiConstants { id: hifi; }
id: root;
color: hifi.colors.baseGray
property int parentAppTitleBarHeight;
property int parentAppNavBarHeight;
property string currentActiveView: "sendAssetHome";

View file

@ -70,7 +70,7 @@ Flickable {
readonly property bool hmdDesktop: hmdInDesktop.checked
property int state: buttonState.disabled
property var lastConfiguration: null
property var lastConfiguration: null
HifiConstants { id: hifi }
@ -90,7 +90,6 @@ Flickable {
anchors.fill: parent
propagateComposedEvents: true
onPressed: {
parent.forceActiveFocus()
mouse.accepted = false;
}
}
@ -169,9 +168,7 @@ Flickable {
boxRadius: 7
visible: viveInDesktop.checked
anchors.top: viveInDesktop.bottom
anchors.topMargin: 5
anchors.left: openVrConfiguration.left
anchors.leftMargin: leftMargin + 10
onClicked: {
@ -214,13 +211,13 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
HifiControls.SpinBox {
id: headZOffset
z: 10
width: 112
label: "Z Offset"
minimumValue: -50
@ -232,7 +229,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -326,7 +322,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
@ -344,7 +339,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -578,7 +572,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
@ -596,7 +589,6 @@ Flickable {
onRealValueChanged: {
sendConfigurationSettings();
openVrConfiguration.forceActiveFocus();
}
}
}
@ -747,8 +739,8 @@ Flickable {
}
Component.onCompleted: {
InputConfiguration.calibrationStatus.connect(calibrationStatusInfo);
lastConfiguration = composeConfigurationSettings();
InputConfiguration.calibrationStatus.connect(calibrationStatusInfo);
}
Component.onDestruction: {
@ -777,7 +769,6 @@ Flickable {
calibrationTimer.interval = realValue * 1000;
openVrConfiguration.countDown = realValue;
numberAnimation.duration = calibrationTimer.interval;
openVrConfiguration.forceActiveFocus();
}
}
@ -1048,6 +1039,9 @@ Flickable {
}
function updateButtonState() {
if (lastConfiguration === null) {
lastConfiguration = composeConfigurationSettings();
}
var settings = composeConfigurationSettings();
var bodySetting = settings["bodyConfiguration"];
var headSetting = settings["headConfiguration"];

View file

@ -15,6 +15,7 @@
#include <src/ui/LoginDialog.h>
#include "Application.h"
#include "Constants.h"
#include "avatar/AvatarManager.h"
#if defined(qApp)
#undef qApp
@ -152,3 +153,16 @@ void AndroidHelper::signupFailed(QNetworkReply* reply) {
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
}
QString AndroidHelper::getDisplayName() {
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getDisplayName();
}
void AndroidHelper::setDisplayName(const QString &displayName) {
DependencyManager::get<AvatarManager>()->getMyAvatar()->setDisplayName(displayName);
}
void AndroidHelper::setMyAvatarUrl(const QString &avatarUrl) {
QUrl url = QUrl(avatarUrl);
DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(url);
}

View file

@ -41,6 +41,9 @@ public:
void operator=(AndroidHelper const&) = delete;
void signup(QString email, QString username, QString password);
QString getDisplayName();
void setDisplayName(const QString &displayName);
void setMyAvatarUrl(const QString &avatarUrl);
public slots:
void showLoginDialog(QUrl url);

View file

@ -120,6 +120,7 @@
#include <plugins/PluginManager.h>
#include <plugins/PluginUtils.h>
#include <plugins/SteamClientPlugin.h>
#include <plugins/OculusPlatformPlugin.h>
#include <plugins/InputConfiguration.h>
#include <RecordingScriptingInterface.h>
#include <render/EngineStats.h>
@ -160,6 +161,7 @@
#include "avatar/AvatarManager.h"
#include "avatar/MyHead.h"
#include "avatar/AvatarPackager.h"
#include "avatar/MyCharacterController.h"
#include "CrashRecoveryHandler.h"
#include "CrashHandler.h"
#include "devices/DdeFaceTracker.h"
@ -800,6 +802,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
if (auto steamClient = pluginManager->getSteamClientPlugin()) {
steamClient->init();
}
if (auto oculusPlatform = pluginManager->getOculusPlatformPlugin()) {
oculusPlatform->init();
}
PROFILE_SET_THREAD_NAME("Main Thread");
@ -2417,7 +2422,6 @@ void Application::updateVerboseLogging() {
bool enable = menu->isOptionChecked(MenuOption::VerboseLogging);
QString rules =
"hifi.*.debug=%1\n"
"hifi.*.info=%1\n"
"hifi.audio-stream.debug=false\n"
"hifi.audio-stream.info=false";
@ -2714,7 +2718,14 @@ Application::~Application() {
avatarManager->handleProcessedPhysicsTransaction(transaction);
avatarManager->deleteAllAvatars();
auto myCharacterController = getMyAvatar()->getCharacterController();
myCharacterController->clearDetailedMotionStates();
myCharacterController->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myCharacterController->handleProcessedPhysicsTransaction(transaction);
_physicsEngine->setCharacterController(nullptr);
// the _shapeManager should have zero references
@ -2737,6 +2748,11 @@ Application::~Application() {
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->shutdown();
}
if (auto oculusPlatform = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatform->shutdown();
}
DependencyManager::destroy<PluginManager>();
DependencyManager::destroy<CompositorHelper>(); // must be destroyed before the FramebufferCache
@ -3543,8 +3559,10 @@ void Application::resizeGL() {
auto renderConfig = _graphicsEngine.getRenderEngine()->getConfiguration();
assert(renderConfig);
auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask");
assert(mainView);
mainView->setProperty("resolutionScale", renderResolutionScale);
// mainView can be null if we're rendering in forward mode
if (mainView) {
mainView->setProperty("resolutionScale", renderResolutionScale);
}
displayPlugin->setRenderResolutionScale(renderResolutionScale);
}
@ -4092,6 +4110,19 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
break;
case Qt::Key_G:
if (isShifted && isMeta && Menu::getInstance() && Menu::getInstance()->getMenu("Developer")->isVisible()) {
static const QString HIFI_FRAMES_FOLDER_VAR = "HIFI_FRAMES_FOLDER";
static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR)
? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR)
: "hifiFrames";
static QString GPU_FRAME_TEMPLATE = GPU_FRAME_FOLDER + "/{DATE}_{TIME}";
QString fullPath = FileUtils::computeDocumentPath(FileUtils::replaceDateTimeTokens(GPU_FRAME_TEMPLATE));
if (FileUtils::canCreateFile(fullPath)) {
getActiveDisplayPlugin()->captureFrame(fullPath.toStdString());
}
}
break;
case Qt::Key_X:
if (isShifted && isMeta) {
auto offscreenUi = getOffscreenUI();
@ -4163,7 +4194,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) {
AudioInjectorOptions options;
options.localOnly = true;
options.positionSet = false; // system sound
options.stereo = true;
Setting::Handle<bool> notificationSounds{ MenuOption::NotificationSounds, true };
Setting::Handle<bool> notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true };
if (notificationSounds.get() && notificationSoundSnapshot.get()) {
@ -4873,6 +4906,10 @@ void Application::idle() {
steamClient->runCallbacks();
}
if (auto oculusPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlugin->handleOVREvents();
}
float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
_lastTimeUpdated.start();
@ -6057,6 +6094,13 @@ void Application::update(float deltaTime) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
controller::HmdAvatarAlignmentType hmdAvatarAlignmentType;
if (myAvatar->getHmdAvatarAlignmentType() == "eyes") {
hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Eyes;
} else {
hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Head;
}
controller::InputCalibrationData calibrationData = {
myAvatar->getSensorToWorldMatrix(),
createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()),
@ -6070,7 +6114,8 @@ void Application::update(float deltaTime) {
myAvatar->getRightArmCalibrationMat(),
myAvatar->getLeftArmCalibrationMat(),
myAvatar->getRightHandCalibrationMat(),
myAvatar->getLeftHandCalibrationMat()
myAvatar->getLeftHandCalibrationMat(),
hmdAvatarAlignmentType
};
InputPluginPointer keyboardMousePlugin;
@ -6260,7 +6305,9 @@ void Application::update(float deltaTime) {
avatarManager->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
avatarManager->handleProcessedPhysicsTransaction(transaction);
myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
_physicsEngine->processTransaction(transaction);
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
myAvatar->prepareForPhysicsSimulation();
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
dynamic->prepareForPhysicsSimulation();
@ -8233,7 +8280,18 @@ void Application::toggleLogDialog() {
return;
}
if (! _logDialog) {
bool keepOnTop =_keepLogWindowOnTop.get();
#ifdef Q_OS_WIN
_logDialog = new LogDialog(keepOnTop ? qApp->getWindow() : nullptr, getLogger());
#else
_logDialog = new LogDialog(nullptr, getLogger());
if (keepOnTop) {
Qt::WindowFlags flags = _logDialog->windowFlags() | Qt::Tool;
_logDialog->setWindowFlags(flags);
}
#endif
}
if (_logDialog->isVisible()) {
@ -8243,6 +8301,19 @@ void Application::toggleLogDialog() {
}
}
void Application::recreateLogWindow(int keepOnTop) {
_keepLogWindowOnTop.set(keepOnTop != 0);
if (_logDialog) {
bool toggle = _logDialog->isVisible();
_logDialog->close();
_logDialog = nullptr;
if (toggle) {
toggleLogDialog();
}
}
}
void Application::toggleEntityScriptServerLogDialog() {
if (! _entityScriptServerLogDialog) {
_entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr);

View file

@ -217,6 +217,8 @@ public:
void setDesktopTabletScale(float desktopTabletScale);
bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); }
bool getLogWindowOnTopSetting() { return _keepLogWindowOnTop.get(); }
void setLogWindowOnTopSetting(bool keepOnTop) { _keepLogWindowOnTop.set(keepOnTop); }
void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value);
@ -365,6 +367,7 @@ public slots:
Q_INVOKABLE void loadDialog();
Q_INVOKABLE void loadScriptURLDialog() const;
void toggleLogDialog();
void recreateLogWindow(int);
void toggleEntityScriptServerLogDialog();
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
Q_INVOKABLE void loadAddAvatarBookmarkDialog() const;
@ -656,6 +659,7 @@ private:
Setting::Handle<bool> _constrainToolbarPosition;
Setting::Handle<QString> _preferredCursor;
Setting::Handle<bool> _miniTabletEnabledSetting;
Setting::Handle<bool> _keepLogWindowOnTop { "keepLogWindowOnTop", false };
float _scaleMirror;
float _mirrorYawOffset;

View file

@ -288,39 +288,37 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
_measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity;
if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) {
_measuredLinearVelocitiesIndex = 0;
_measuredLinearVelocitiesIndex %= AvatarActionHold::velocitySmoothFrames;
}
if (_kinematicSetVelocity) {
glm::vec3 measuredLinearVelocity = _measuredLinearVelocities[0];
for (int i = 1; i < AvatarActionHold::velocitySmoothFrames; i++) {
// there is a bit of lag between when someone releases the trigger and when the software reacts to
// the release. we calculate the velocity from previous frames but we don't include several
// of the most recent.
//
// if _measuredLinearVelocitiesIndex is
// 0 -- ignore i of 3 4 5
// 1 -- ignore i of 4 5 0
// 2 -- ignore i of 5 0 1
// 3 -- ignore i of 0 1 2
// 4 -- ignore i of 1 2 3
// 5 -- ignore i of 2 3 4
// This code is now disabled, but I'm leaving it commented-out because I suspect it will come back.
// if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {
// continue;
// }
measuredLinearVelocity += _measuredLinearVelocities[i];
}
}
glm::vec3 measuredLinearVelocity;
for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) {
// there is a bit of lag between when someone releases the trigger and when the software reacts to
// the release. we calculate the velocity from previous frames but we don't include several
// of the most recent.
//
// if _measuredLinearVelocitiesIndex is
// 0 -- ignore i of 3 4 5
// 1 -- ignore i of 4 5 0
// 2 -- ignore i of 5 0 1
// 3 -- ignore i of 0 1 2
// 4 -- ignore i of 1 2 3
// 5 -- ignore i of 2 3 4
// This code is now disabled, but I'm leaving it commented-out because I suspect it will come back.
// if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
// (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {
// continue;
// }
measuredLinearVelocity += _measuredLinearVelocities[i];
}
measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames
measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames
// - 3 // 3 because of the 3 we skipped, above
);
if (_kinematicSetVelocity) {
rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity));
rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget));
}

View file

@ -43,6 +43,7 @@
#include "InterfaceLogging.h"
#include "Menu.h"
#include "MyAvatar.h"
#include "DebugDraw.h"
#include "SceneScriptingInterface.h"
// 50 times per second - target is 45hz, but this helps account for any small deviations
@ -396,21 +397,45 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact
if (isInPhysics) {
transaction.objectsToRemove.push_back(avatar->_motionState);
avatar->_motionState = nullptr;
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
transaction.objectsToRemove.push_back(mState);
}
avatar->resetDetailedMotionStates();
} else {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
motionState->setMass(avatar->computeMass());
avatar->_motionState = motionState;
transaction.objectsToAdd.push_back(motionState);
if (avatar->getDetailedMotionStates().size() == 0) {
avatar->createDetailedMotionStates(avatar);
for (auto dMotionState : avatar->getDetailedMotionStates()) {
transaction.objectsToAdd.push_back(dMotionState);
}
}
if (avatar->getDetailedMotionStates().size() > 0) {
ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
motionState->setMass(avatar->computeMass());
avatar->_motionState = motionState;
transaction.objectsToAdd.push_back(motionState);
} else {
failedShapeBuilds.insert(avatar);
}
} else {
failedShapeBuilds.insert(avatar);
}
}
} else if (isInPhysics) {
transaction.objectsToChange.push_back(avatar->_motionState);
auto& detailedMotionStates = avatar->getDetailedMotionStates();
for (auto& mState : detailedMotionStates) {
if (mState) {
transaction.objectsToChange.push_back(mState);
}
}
}
}
_avatarsToChangeInPhysics.swap(failedShapeBuilds);
@ -450,6 +475,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
_spaceProxiesToDelete.push_back(avatar->getSpaceIndex());
}
AvatarHashMap::handleRemovedAvatar(avatar, removalReason);
avatar->tearDownGrabs();
avatar->die();
queuePhysicsChange(avatar);
@ -509,6 +535,7 @@ void AvatarManager::deleteAllAvatars() {
if (avatar != _myAvatar) {
auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
assert(!otherAvatar->_motionState);
assert(otherAvatar->getDetailedMotionStates().size() == 0);
}
}
}
@ -601,103 +628,135 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude,
const QScriptValue& avatarIdsToDiscard) {
const QScriptValue& avatarIdsToDiscard,
bool pickAgainstMesh) {
QVector<EntityItemID> avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude);
QVector<EntityItemID> avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard);
return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard);
return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard, pickAgainstMesh);
}
RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard) {
const QVector<EntityItemID>& avatarsToDiscard,
bool pickAgainstMesh) {
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findRayIntersectionVector",
Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
Q_ARG(const PickRay&, ray),
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard),
Q_ARG(bool, pickAgainstMesh));
return result;
}
// It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
PROFILE_RANGE(simulation_physics, __FUNCTION__);
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results
glm::vec3 rayDirection = glm::normalize(ray.direction);
std::vector<MyCharacterController::RayAvatarResult> physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(rayDirection), distance, QVector<uint>());
if (physicsResults.size() > 0) {
glm::vec3 rayDirectionInv = { rayDirection.x != 0.0f ? 1.0f / rayDirection.x : INFINITY,
rayDirection.y != 0.0f ? 1.0f / rayDirection.y : INFINITY,
rayDirection.z != 0.0f ? 1.0f / rayDirection.z : INFINITY };
std::vector<SortedAvatar> sortedAvatars;
auto avatarHashCopy = getHashCopy();
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
continue;
}
float distance = FLT_MAX;
#if 0
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
AABox avatarBounds = avatarModel->getRenderableMeshBound();
if (avatarBounds.contains(ray.origin)) {
distance = 0.0f;
} else {
float boundDistance = FLT_MAX;
BoxFace face;
glm::vec3 surfaceNormal;
if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) {
distance = boundDistance;
}
}
#else
glm::vec3 start;
glm::vec3 end;
float radius;
avatar->getCapsule(start, end, radius);
findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance);
#endif
if (distance < FLT_MAX) {
sortedAvatars.emplace_back(distance, avatar);
}
}
if (sortedAvatars.size() > 1) {
static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; };
std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator);
}
for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) {
const SortedAvatar& sortedAvatar = *it;
// We can exit once avatarCapsuleDistance > bestDistance
if (sortedAvatar.first > result.distance) {
break;
}
float distance = FLT_MAX;
BoxFace face;
MyCharacterController::RayAvatarResult rayAvatarResult;
AvatarPointer avatar = nullptr;
BoxFace face = BoxFace::UNKNOWN_FACE;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel();
if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) {
if (distance < result.distance) {
result.intersects = true;
result.avatarID = sortedAvatar.second->getID();
result.distance = distance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
for (auto &hit : physicsResults) {
auto avatarID = hit._intersectWithAvatar;
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID))) {
continue;
}
if (_myAvatar->getSessionUUID() != avatarID) {
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.find(avatarID);
if (itr != avatarMap.end()) {
avatar = std::static_pointer_cast<Avatar>(*itr);
}
} else {
avatar = _myAvatar;
}
if (!hit._isBound) {
rayAvatarResult = hit;
} else if (avatar) {
auto &multiSpheres = avatar->getMultiSphereShapes();
if (multiSpheres.size() > 0) {
std::vector<MyCharacterController::RayAvatarResult> boxHits;
for (size_t i = 0; i < hit._boundJoints.size(); i++) {
assert(hit._boundJoints[i] < multiSpheres.size());
auto &mSphere = multiSpheres[hit._boundJoints[i]];
if (mSphere.isValid()) {
float boundDistance = FLT_MAX;
BoxFace face;
glm::vec3 surfaceNormal;
auto &bbox = mSphere.getBoundingBox();
if (bbox.findRayIntersection(ray.origin, rayDirection, rayDirectionInv, boundDistance, face, surfaceNormal)) {
MyCharacterController::RayAvatarResult boxHit;
boxHit._distance = boundDistance;
boxHit._intersect = true;
boxHit._intersectionNormal = surfaceNormal;
boxHit._intersectionPoint = ray.origin + boundDistance * rayDirection;
boxHit._intersectWithAvatar = avatarID;
boxHit._intersectWithJoint = mSphere.getJointIndex();
boxHits.push_back(boxHit);
}
}
}
if (boxHits.size() > 0) {
if (boxHits.size() > 1) {
std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA,
const MyCharacterController::RayAvatarResult& hitB) {
return hitA._distance < hitB._distance;
});
}
rayAvatarResult = boxHits[0];
}
}
}
if (pickAgainstMesh) {
glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint);
glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + rayDirection, rayAvatarResult._intersectWithJoint);
auto avatarOrientation = avatar->getWorldOrientation();
auto avatarPosition = avatar->getWorldPosition();
auto jointOrientation = avatarOrientation * avatar->getAbsoluteDefaultJointRotationInObjectFrame(rayAvatarResult._intersectWithJoint);
auto jointPosition = avatarPosition + (avatarOrientation * avatar->getAbsoluteDefaultJointTranslationInObjectFrame(rayAvatarResult._intersectWithJoint));
auto defaultFrameRayOrigin = jointPosition + jointOrientation * localRayOrigin;
auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint;
auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin;
if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, distance, face, surfaceNormal, extraInfo, true, false)) {
auto newDistance = glm::length(vec3FromVariant(extraInfo["worldIntersectionPoint"]) - defaultFrameRayOrigin);
rayAvatarResult._distance = newDistance;
rayAvatarResult._intersectionPoint = ray.origin + newDistance * rayDirection;
rayAvatarResult._intersectionNormal = surfaceNormal;
extraInfo["worldIntersectionPoint"] = vec3toVariant(rayAvatarResult._intersectionPoint);
break;
}
} else if (rayAvatarResult._intersect){
break;
}
}
if (rayAvatarResult._intersect) {
result.intersects = true;
result.avatarID = rayAvatarResult._intersectWithAvatar;
result.distance = rayAvatarResult._distance;
result.surfaceNormal = rayAvatarResult._intersectionNormal;
result.jointIndex = rayAvatarResult._intersectWithJoint;
result.intersection = ray.origin + rayAvatarResult._distance * rayDirection;
result.extraInfo = extraInfo;
result.face = face;
}
}
if (result.intersects) {
result.intersection = ray.origin + ray.direction * result.distance;
}
return result;
}

View file

@ -29,6 +29,7 @@
#include <EntitySimulation.h> // for SetOfEntities
#include "AvatarMotionState.h"
#include "DetailedMotionState.h"
#include "MyAvatar.h"
#include "OtherAvatar.h"
@ -136,21 +137,25 @@ public:
* @param {PickRay} ray
* @param {Uuid[]} [avatarsToInclude=[]]
* @param {Uuid[]} [avatarsToDiscard=[]]
* @param {boolean} pickAgainstMesh
* @returns {RayToAvatarIntersectionResult}
*/
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude = QScriptValue(),
const QScriptValue& avatarIdsToDiscard = QScriptValue());
const QScriptValue& avatarIdsToDiscard = QScriptValue(),
bool pickAgainstMesh = true);
/**jsdoc
* @function AvatarManager.findRayIntersectionVector
* @param {PickRay} ray
* @param {Uuid[]} avatarsToInclude
* @param {Uuid[]} avatarsToDiscard
* @param {boolean} pickAgainstMesh
* @returns {RayToAvatarIntersectionResult}
*/
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
const QVector<EntityItemID>& avatarsToDiscard,
bool pickAgainstMesh);
/**jsdoc
* @function AvatarManager.findParabolaIntersectionVector

View file

@ -0,0 +1,188 @@
//
// DetailedMotionState.cpp
// interface/src/avatar/
//
// Created by Luis Cuenca 1/11/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "DetailedMotionState.h"
#include <PhysicsCollisionGroups.h>
#include <PhysicsEngine.h>
#include <PhysicsHelpers.h>
#include "MyAvatar.h"
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) :
ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) {
assert(_avatar);
if (!_avatar->isMyAvatar()) {
_otherAvatar = std::static_pointer_cast<OtherAvatar>(_avatar);
}
_type = MOTIONSTATE_TYPE_DETAILED;
}
void DetailedMotionState::handleEasyChanges(uint32_t& flags) {
ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) {
_body->activate();
}
}
bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
DetailedMotionState::~DetailedMotionState() {
assert(_avatar);
_avatar = nullptr;
}
// virtual
uint32_t DetailedMotionState::getIncomingDirtyFlags() {
return _body ? _dirtyFlags : 0;
}
void DetailedMotionState::clearIncomingDirtyFlags() {
if (_body) {
_dirtyFlags = 0;
}
}
PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const {
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
return MOTION_TYPE_KINEMATIC;
}
// virtual and protected
const btCollisionShape* DetailedMotionState::computeNewShape() {
btCollisionShape* shape = nullptr;
if (!_avatar->isMyAvatar()) {
if (_otherAvatar != nullptr) {
shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints);
}
} else {
std::shared_ptr<MyAvatar> myAvatar = std::static_pointer_cast<MyAvatar>(_avatar);
if (myAvatar) {
shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex);
}
}
return shape;
}
// virtual
bool DetailedMotionState::isMoving() const {
return false;
}
// virtual
void DetailedMotionState::getWorldTransform(btTransform& worldTrans) const {
worldTrans.setOrigin(glmToBullet(getObjectPosition()));
worldTrans.setRotation(glmToBullet(getObjectRotation()));
}
// virtual
void DetailedMotionState::setWorldTransform(const btTransform& worldTrans) {
_body->setWorldTransform(worldTrans);
}
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// virtual
float DetailedMotionState::getObjectRestitution() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectFriction() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectLinearDamping() const {
return 0.5f;
}
// virtual
float DetailedMotionState::getObjectAngularDamping() const {
return 0.5f;
}
// virtual
glm::vec3 DetailedMotionState::getObjectPosition() const {
if (_otherAvatar != nullptr) {
auto bodyLOD = _otherAvatar->getBodyLOD();
if (bodyLOD == OtherAvatar::BodyLOD::Sphere) {
return _avatar->getFitBounds().calcCenter();
}
}
return _avatar->getJointPosition(_jointIndex);
}
// virtual
glm::quat DetailedMotionState::getObjectRotation() const {
return _avatar->getWorldOrientation() * _avatar->getAbsoluteJointRotationInObjectFrame(_jointIndex);
}
// virtual
glm::vec3 DetailedMotionState::getObjectLinearVelocity() const {
return glm::vec3(0.0f);
}
// virtual
glm::vec3 DetailedMotionState::getObjectAngularVelocity() const {
return glm::vec3(0.0f);
}
// virtual
glm::vec3 DetailedMotionState::getObjectGravity() const {
return glm::vec3(0.0f);
}
// virtual
const QUuid DetailedMotionState::getObjectID() const {
return _avatar->getSessionUUID();
}
QString DetailedMotionState::getName() const {
return _avatar->getName() + "_" + _jointIndex;
}
// virtual
QUuid DetailedMotionState::getSimulatorID() const {
return _avatar->getSessionUUID();
}
// virtual
void DetailedMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = BULLET_COLLISION_GROUP_DETAILED_AVATAR;
mask = Physics::getDefaultCollisionMask(group);
}
// virtual
float DetailedMotionState::getMass() const {
return 0.0f;
}
void DetailedMotionState::setRigidBody(btRigidBody* body) {
ObjectMotionState::setRigidBody(body);
if (_body) {
// remove angular dynamics from this body
_body->setAngularFactor(0.0f);
}
}
void DetailedMotionState::setShape(const btCollisionShape* shape) {
ObjectMotionState::setShape(shape);
}
void DetailedMotionState::forceActive() {
if (_body && !_body->isActive()) {
_body->setActivationState(ACTIVE_TAG);
}
}

View file

@ -0,0 +1,100 @@
//
// DetailedMotionState.h
// interface/src/avatar/
//
// Created by Luis Cuenca 1/11/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_DetailedMotionState_h
#define hifi_DetailedMotionState_h
#include <QSet>
#include <ObjectMotionState.h>
#include <BulletUtil.h>
#include "OtherAvatar.h"
class DetailedMotionState : public ObjectMotionState {
public:
DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex);
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
virtual uint32_t getIncomingDirtyFlags() override;
virtual void clearIncomingDirtyFlags() override;
virtual PhysicsMotionType computePhysicsMotionType() const override;
virtual bool isMoving() const override;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const override;
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
// These pure virtual methods must be implemented for each MotionState type
// and make it possible to implement more complicated methods in this base class.
// pure virtual overrides from ObjectMotionState
virtual float getObjectRestitution() const override;
virtual float getObjectFriction() const override;
virtual float getObjectLinearDamping() const override;
virtual float getObjectAngularDamping() const override;
virtual glm::vec3 getObjectPosition() const override;
virtual glm::quat getObjectRotation() const override;
virtual glm::vec3 getObjectLinearVelocity() const override;
virtual glm::vec3 getObjectAngularVelocity() const override;
virtual glm::vec3 getObjectGravity() const override;
virtual const QUuid getObjectID() const override;
virtual QString getName() const override;
virtual QUuid getSimulatorID() const override;
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() const override;
void forceActive();
QUuid getAvatarID() const { return _avatar->getID(); }
int getJointIndex() const { return _jointIndex; }
void setIsBound(bool isBound, std::vector<int> boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
bool getIsBound(std::vector<int>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
friend class AvatarManager;
friend class Avatar;
protected:
void setRigidBody(btRigidBody* body) override;
void setShape(const btCollisionShape* shape) override;
// the dtor had been made protected to force the compiler to verify that it is only
// ever called by the Avatar class dtor.
~DetailedMotionState();
virtual bool isReadyToComputeShape() const override { return true; }
virtual const btCollisionShape* computeNewShape() override;
AvatarPointer _avatar;
float _diameter { 0.0f };
uint32_t _dirtyFlags;
int _jointIndex { -1 };
OtherAvatarPointer _otherAvatar { nullptr };
bool _isBound { false };
std::vector<int> _boundJoints;
};
#endif // hifi_DetailedMotionState_h

View file

@ -95,6 +95,37 @@ const float CENTIMETERS_PER_METER = 100.0f;
const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" };
static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit");
static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand");
static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto");
static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean");
MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) {
if (str == USER_RECENTER_MODEL_FORCE_SIT) {
return MyAvatar::ForceSit;
} else if (str == USER_RECENTER_MODEL_FORCE_STAND) {
return MyAvatar::ForceStand;
} else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) {
return MyAvatar::DisableHMDLean;
} else {
return MyAvatar::Auto;
}
}
QString userRecenterModelToString(MyAvatar::SitStandModelType model) {
switch (model) {
case MyAvatar::ForceSit:
return USER_RECENTER_MODEL_FORCE_SIT;
case MyAvatar::ForceStand:
return USER_RECENTER_MODEL_FORCE_STAND;
case MyAvatar::DisableHMDLean:
return USER_RECENTER_MODEL_DISABLE_HMD_LEAN;
case MyAvatar::Auto:
default:
return USER_RECENTER_MODEL_AUTO;
}
}
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
_yawSpeed(YAW_SPEED_DEFAULT),
@ -103,7 +134,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
_scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE),
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
_characterController(this),
_characterController(std::shared_ptr<MyAvatar>(this)),
_eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
@ -125,6 +156,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_prevShouldDrawHead(true),
_audioListenerMode(FROM_HEAD),
_dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
_hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE),
_headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
_scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
_yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed),
@ -138,7 +170,8 @@ MyAvatar::MyAvatar(QThread* thread) :
_useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
_userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0)
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0),
_userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO)
{
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
@ -286,10 +319,25 @@ MyAvatar::~MyAvatar() {
_myScriptEngine = nullptr;
}
QString MyAvatar::getDominantHand() const {
return _dominantHand.get();
}
void MyAvatar::setDominantHand(const QString& hand) {
if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) {
_dominantHand = hand;
emit dominantHandChanged(_dominantHand);
_dominantHand.set(hand);
emit dominantHandChanged(hand);
}
}
QString MyAvatar::getHmdAvatarAlignmentType() const {
return _hmdAvatarAlignmentType.get();
}
void MyAvatar::setHmdAvatarAlignmentType(const QString& type) {
if (type != _hmdAvatarAlignmentType.get()) {
_hmdAvatarAlignmentType.set(type);
emit hmdAvatarAlignmentTypeChanged(type);
}
}
@ -377,6 +425,7 @@ void MyAvatar::resetSensorsAndBody() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "resetSensorsAndBody");
return;
}
qApp->getActiveDisplayPlugin()->resetSensors();
@ -817,7 +866,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
// and all of its joints, now update our attachements.
Avatar::simulateAttachments(deltaTime);
relayJointDataToChildren();
if (updateGrabs()) {
if (applyGrabChanges()) {
_cauterizationNeedsUpdate = true;
}
@ -1122,77 +1171,6 @@ controller::Pose MyAvatar::getRightHandTipPose() const {
return pose;
}
glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
// virtual
void MyAvatar::render(RenderArgs* renderArgs) {
// don't render if we've been asked to disable local rendering
@ -1277,7 +1255,8 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
}
void MyAvatar::saveData() {
_dominantHandSetting.set(_dominantHand);
_dominantHandSetting.set(getDominantHand());
_hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType());
_headPitchSetting.set(getHead()->getBasePitch());
_scaleSetting.set(_targetScale);
_yawSpeedSetting.set(_yawSpeed);
@ -1300,6 +1279,7 @@ void MyAvatar::saveData() {
_useSnapTurnSetting.set(_useSnapTurn);
_userHeightSetting.set(getUserHeight());
_flyingHMDSetting.set(getFlyingHMDPref());
_userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel()));
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
saveAvatarEntityDataToSettings();
@ -1882,9 +1862,12 @@ void MyAvatar::loadData() {
setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
setSnapTurn(_useSnapTurnSetting.get());
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower());
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
setTargetScale(_scaleSetting.get());
setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO)));
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
setEnableDebugDrawBaseOfSupport(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawBaseOfSupport));
@ -3060,16 +3043,15 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
if (_skeletonModel && _skeletonModel->isLoaded()) {
const Rig& rig = _skeletonModel->getRig();
const HFMModel& hfmModel = _skeletonModel->getHFMModel();
for (int i = 0; i < rig.getJointStateCount(); i++) {
AnimPose jointPose;
rig.getAbsoluteJointPoseInRigFrame(i, jointPose);
const HFMJointShapeInfo& shapeInfo = hfmModel.joints[i].shapeInfo;
const AnimPose pose = rigToWorldPose * jointPose;
for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) {
glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]);
glm::vec3 pointB = pose.xformPoint(shapeInfo.debugLines[2 * j + 1]);
DebugDraw::getInstance().drawRay(pointA, pointB, DEBUG_COLORS[i % NUM_DEBUG_COLORS]);
int jointCount = rig.getJointStateCount();
if (jointCount == (int)_multiSphereShapes.size()) {
for (int i = 0; i < jointCount; i++) {
AnimPose jointPose;
rig.getAbsoluteJointPoseInRigFrame(i, jointPose);
const AnimPose pose = rigToWorldPose * jointPose;
auto &multiSphere = _multiSphereShapes[i];
auto debugLines = multiSphere.getDebugLines();
DebugDraw::getInstance().drawRays(debugLines, DEBUG_COLORS[i % NUM_DEBUG_COLORS], pose.trans(), pose.rot());
}
}
}
@ -4761,7 +4743,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float CYLINDER_TOP = 0.1f;
const float CYLINDER_TOP = 2.0f;
const float CYLINDER_BOTTOM = -1.5f;
const float SITTING_BOTTOM = -0.02f;
@ -5319,7 +5301,7 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabData.remove(grabID)) {
_deletedAvatarGrabs.insert(grabID);
_grabsToDelete.push_back(grabID);
tellHandler = true;
}
});
@ -5329,3 +5311,4 @@ void MyAvatar::releaseGrab(const QUuid& grabID) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID);
}
}

View file

@ -37,6 +37,7 @@
class AvatarActionHold;
class ModelItemID;
class MyHead;
class DetailedMotionState;
enum eyeContactTarget {
LEFT_EYE,
@ -252,6 +253,7 @@ class MyAvatar : public Avatar {
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
const QString DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE = "head";
using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point;
@ -519,7 +521,18 @@ public:
* @function MyAvatar.getDominantHand
* @returns {string}
*/
Q_INVOKABLE QString getDominantHand() const { return _dominantHand; }
Q_INVOKABLE QString getDominantHand() const;
/**jsdoc
* @function MyAvatar.setHmdAvatarAlignmentType
* @param {string} hand
*/
Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& hand);
/**jsdoc
* @function MyAvatar.setHmdAvatarAlignmentType
* @returns {string}
*/
Q_INVOKABLE QString getHmdAvatarAlignmentType() const;
/**jsdoc
* @function MyAvatar.setCenterOfGravityModelEnabled
@ -790,56 +803,6 @@ public:
*/
Q_INVOKABLE controller::Pose getRightHandTipPose() const;
// world-space to avatar-space rigconversion functions
/**jsdoc
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldPoint
* @param {vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
void updateLookAtTargetAvatar();
void computeMyLookAtTarget(const AvatarHash& hash);
@ -1585,6 +1548,13 @@ signals:
*/
void dominantHandChanged(const QString& hand);
/**jsdoc
* @function MyAvatar.hmdAvatarAlignmentTypeChanged
* @param {string} type
* @returns {Signal}
*/
void hmdAvatarAlignmentTypeChanged(const QString& type);
/**jsdoc
* @function MyAvatar.sensorToWorldScaleChanged
* @param {number} scale
@ -1773,7 +1743,8 @@ private:
ThreadSafeValueCache<QUrl> _prefOverrideAnimGraphUrl;
QUrl _fstAnimGraphOverrideUrl;
bool _useSnapTurn { true };
QString _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE };
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees
const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec
@ -1946,6 +1917,7 @@ private:
TimePoint _nextTraitsSendWindow;
Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<QString> _hmdAvatarAlignmentTypeSetting;
Setting::Handle<float> _headPitchSetting;
Setting::Handle<float> _scaleSetting;
Setting::Handle<float> _yawSpeedSetting;
@ -1962,6 +1934,7 @@ private:
Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true };
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
Setting::Handle<QString> _userRecenterModelSetting;
// AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute

View file

@ -12,8 +12,10 @@
#include "MyCharacterController.h"
#include <BulletUtil.h>
#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h"
#include "MyAvatar.h"
#include "DetailedMotionState.h"
// TODO: make avatars stand on steep slope
// TODO: make avatars not snag on low ceilings
@ -24,7 +26,7 @@ void MyCharacterController::RayShotgunResult::reset() {
walkable = true;
}
MyCharacterController::MyCharacterController(MyAvatar* avatar) {
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) {
assert(avatar);
_avatar = avatar;
@ -44,7 +46,6 @@ void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
void MyCharacterController::updateShapeIfNecessary() {
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
if (_radius > 0.0f) {
// create RigidBody if it doesn't exist
if (!_rigidBody) {
@ -375,3 +376,121 @@ void MyCharacterController::updateMassProperties() {
_rigidBody->setMassProps(mass, inertia);
}
btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int jointIndex) {
ShapeInfo shapeInfo;
_avatar->computeDetailedShapeInfo(shapeInfo, jointIndex);
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
return shape;
}
return nullptr;
}
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int jointIndex) {
auto shape = createDetailedCollisionShapeForJoint(jointIndex);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(_avatar, shape, jointIndex);
motionState->setMass(_avatar->computeMass());
return motionState;
}
return nullptr;
}
void MyCharacterController::clearDetailedMotionStates() {
_pendingFlags |= PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
// We make sure we don't add them again
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
}
void MyCharacterController::resetDetailedMotionStates() {
_detailedMotionStates.clear();
}
void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i]->forceActive();
}
if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
transaction.objectsToRemove.push_back(_detailedMotionStates[i]);
}
_detailedMotionStates.clear();
}
if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
for (int i = 0; i < _avatar->getJointCount(); i++) {
auto dMotionState = createDetailedMotionStateForJoint(i);
if (dMotionState) {
_detailedMotionStates.push_back(dMotionState);
transaction.objectsToAdd.push_back(dMotionState);
}
}
}
}
void MyCharacterController::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
// things on objectsToRemove are ready for delete
for (auto object : transaction.objectsToRemove) {
delete object;
}
transaction.clear();
}
class DetailedRayResultCallback : public btCollisionWorld::AllHitsRayResultCallback {
public:
DetailedRayResultCallback()
: btCollisionWorld::AllHitsRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
// the RayResultCallback's group and mask must match MY_AVATAR
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DETAILED_RAY;
m_collisionFilterMask = BULLET_COLLISION_MASK_DETAILED_RAY;
}
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
return AllHitsRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
}
};
std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction,
const btScalar& length, const QVector<uint>& jointsToExclude) const {
std::vector<RayAvatarResult> foundAvatars;
if (_dynamicsWorld) {
btVector3 end = origin + length * direction;
DetailedRayResultCallback rayCallback = DetailedRayResultCallback();
rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal;
rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest;
_dynamicsWorld->rayTest(origin, end, rayCallback);
if (rayCallback.m_hitFractions.size() > 0) {
foundAvatars.reserve(rayCallback.m_hitFractions.size());
for (int i = 0; i < rayCallback.m_hitFractions.size(); i++) {
auto object = rayCallback.m_collisionObjects[i];
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(object->getUserPointer());
if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) {
DetailedMotionState* detailedMotionState = dynamic_cast<DetailedMotionState*>(motionState);
MyCharacterController::RayAvatarResult result;
result._intersect = true;
result._intersectWithAvatar = detailedMotionState->getAvatarID();
result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[i]);
result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[i]);
result._distance = length * rayCallback.m_hitFractions[i];
result._intersectWithJoint = detailedMotionState->getJointIndex();
result._isBound = detailedMotionState->getIsBound(result._boundJoints);
btVector3 center;
btScalar radius;
detailedMotionState->getShape()->getBoundingSphere(center, radius);
result._maxDistance = (float)radius;
foundAvatars.push_back(result);
}
}
std::sort(foundAvatars.begin(), foundAvatars.end(), [](const RayAvatarResult& resultA, const RayAvatarResult& resultB) {
return resultA._distance < resultB._distance;
});
}
}
return foundAvatars;
}

33
interface/src/avatar/MyCharacterController.h Executable file → Normal file
View file

@ -15,13 +15,15 @@
#include <CharacterController.h>
//#include <SharedUtil.h>
#include <PhysicsEngine.h>
class btCollisionShape;
class MyAvatar;
class DetailedMotionState;
class MyCharacterController : public CharacterController {
public:
explicit MyCharacterController(MyAvatar* avatar);
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar);
~MyCharacterController ();
void setDynamicsWorld(btDynamicsWorld* world) override;
@ -42,6 +44,31 @@ public:
void setDensity(btScalar density) { _density = density; }
btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex);
DetailedMotionState* createDetailedMotionStateForJoint(int jointIndex);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void clearDetailedMotionStates();
void resetDetailedMotionStates();
void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction);
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
struct RayAvatarResult {
bool _intersect { false };
bool _isBound { false };
QUuid _intersectWithAvatar;
int _intersectWithJoint { -1 };
float _distance { 0.0f };
float _maxDistance { 0.0f };
QVariantMap _extraInfo;
glm::vec3 _intersectionPoint;
glm::vec3 _intersectionNormal;
std::vector<int> _boundJoints;
};
std::vector<RayAvatarResult> rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length,
const QVector<uint>& jointsToExclude) const;
int32_t computeCollisionMask() const override;
void handleChangedCollisionMask() override;
@ -56,12 +83,14 @@ private:
btConvexHullShape* computeShape() const;
protected:
MyAvatar* _avatar { nullptr };
std::shared_ptr<MyAvatar> _avatar { nullptr };
// shotgun scan data
btAlignedObjectArray<btVector3> _topPoints;
btAlignedObjectArray<btVector3> _bottomPoints;
btScalar _density { 1.0f };
std::vector<DetailedMotionState*> _detailedMotionStates;
};
#endif // hifi_MyCharacterController_h

View file

@ -15,6 +15,7 @@
#include "Application.h"
#include "AvatarMotionState.h"
#include "DetailedMotionState.h"
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
@ -110,29 +111,155 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {
int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i]->forceActive();
}
if (_moving && _motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
}
return bytesRead;
}
btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints) {
ShapeInfo shapeInfo;
isBound = false;
QString jointName = "";
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
jointName = _multiSphereShapes[jointIndex].getJointName();
}
switch (_bodyLOD) {
case BodyLOD::Sphere:
shapeInfo.setSphere(0.5f * getFitBounds().getDimensions().y);
boundJoints.clear();
for (auto &spheres : _multiSphereShapes) {
if (spheres.isValid()) {
boundJoints.push_back(spheres.getJointIndex());
}
}
isBound = true;
break;
case BodyLOD::MultiSphereLow:
if (jointName.contains("RightHand", Qt::CaseInsensitive) || jointName.contains("LeftHand", Qt::CaseInsensitive)) {
if (jointName.size() <= QString("RightHand").size()) {
AABox handBound;
for (auto &spheres : _multiSphereShapes) {
if (spheres.isValid() && spheres.getJointName().contains(jointName, Qt::CaseInsensitive)) {
boundJoints.push_back(spheres.getJointIndex());
handBound += spheres.getBoundingBox();
}
}
shapeInfo.setSphere(0.5f * handBound.getLargestDimension());
glm::vec3 jointPosition;
glm::quat jointRotation;
_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition);
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation);
glm::vec3 positionOffset = glm::inverse(jointRotation) * (handBound.calcCenter() - jointPosition);
shapeInfo.setOffset(positionOffset);
isBound = true;
}
break;
}
case BodyLOD::MultiSphereHigh:
computeDetailedShapeInfo(shapeInfo, jointIndex);
break;
default:
break;
}
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
auto shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) {
shape->setMargin(0.001f);
}
return shape;
}
return nullptr;
}
DetailedMotionState* OtherAvatar::createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex) {
bool isBound = false;
std::vector<int> boundJoints;
btCollisionShape* shape = createCollisionShape(jointIndex, isBound, boundJoints);
if (shape) {
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
motionState->setMass(computeMass());
motionState->setIsBound(isBound, boundJoints);
return motionState;
}
return nullptr;
}
void OtherAvatar::resetDetailedMotionStates() {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
_detailedMotionStates[i] = nullptr;
}
_detailedMotionStates.clear();
}
void OtherAvatar::setWorkloadRegion(uint8_t region) {
_workloadRegion = region;
QString printRegion = "";
if (region == workload::Region::R1) {
printRegion = "R1";
} else if (region == workload::Region::R2) {
printRegion = "R2";
} else if (region == workload::Region::R3) {
printRegion = "R3";
} else {
printRegion = "invalid";
}
qCDebug(avatars) << "Setting workload region to " << printRegion;
computeShapeLOD();
}
void OtherAvatar::computeShapeLOD() {
// auto newBodyLOD = _workloadRegion < workload::Region::R3 ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape;
// auto newBodyLOD = BodyLOD::CapsuleShape;
BodyLOD newLOD;
switch (_workloadRegion) {
case workload::Region::R1:
newLOD = BodyLOD::MultiSphereHigh;
break;
case workload::Region::R2:
newLOD = BodyLOD::MultiSphereLow;
break;
case workload::Region::UNKNOWN:
case workload::Region::INVALID:
case workload::Region::R3:
default:
newLOD = BodyLOD::Sphere;
break;
}
if (newLOD != _bodyLOD) {
_bodyLOD = newLOD;
if (isInPhysicsSimulation()) {
qCDebug(avatars) << "Changing to body LOD " << newLOD;
_needsReinsertion = true;
}
}
}
bool OtherAvatar::isInPhysicsSimulation() const {
return _motionState != nullptr && _detailedMotionStates.size() > 0;
}
bool OtherAvatar::shouldBeInPhysicsSimulation() const {
return (_workloadRegion < workload::Region::R3 && !isDead());
return !isDead() && !(isInPhysicsSimulation() && _needsReinsertion);
}
bool OtherAvatar::needsPhysicsUpdate() const {
constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP;
return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST));
return (_needsReinsertion || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)));
}
void OtherAvatar::rebuildCollisionShape() {
if (_motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
if (_detailedMotionStates[i]) {
_detailedMotionStates[i]->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
}
}
}
void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
@ -141,6 +268,25 @@ void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
}
}
void OtherAvatar::createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar) {
auto& detailedMotionStates = getDetailedMotionStates();
assert(detailedMotionStates.empty());
if (_bodyLOD == BodyLOD::Sphere) {
auto dMotionState = createMotionState(avatar, -1);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
} else {
for (int i = 0; i < getJointCount(); i++) {
auto dMotionState = createMotionState(avatar, i);
if (dMotionState) {
detailedMotionStates.push_back(dMotionState);
}
}
}
_needsReinsertion = false;
}
void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
@ -217,7 +363,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
applyGrabChanges();
}
updateFadingStatus();

22
interface/src/avatar/OtherAvatar.h Executable file → Normal file
View file

@ -21,12 +21,19 @@
class AvatarManager;
class AvatarMotionState;
class DetailedMotionState;
class OtherAvatar : public Avatar {
public:
explicit OtherAvatar(QThread* thread);
virtual ~OtherAvatar();
enum BodyLOD {
Sphere = 0,
MultiSphereLow, // No finger joints
MultiSphereHigh // All joints
};
virtual void instantiableAvatar() override { };
virtual void createOrb() override;
virtual void indicateLoadingStatus(LoadingStatus loadingStatus) override;
@ -39,13 +46,22 @@ public:
int parseDataFromBuffer(const QByteArray& buffer) override;
bool isInPhysicsSimulation() const { return _motionState != nullptr; }
bool isInPhysicsSimulation() const;
void rebuildCollisionShape() override;
void setWorkloadRegion(uint8_t region);
bool shouldBeInPhysicsSimulation() const;
bool needsPhysicsUpdate() const;
btCollisionShape* createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints);
DetailedMotionState* createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex);
void createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar);
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
void resetDetailedMotionStates();
BodyLOD getBodyLOD() { return _bodyLOD; }
void computeShapeLOD();
void updateCollisionGroup(bool myAvatarCollide);
bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; }
void setCollisionWithOtherAvatarsFlags() override;
@ -64,10 +80,14 @@ protected:
std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr };
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
AvatarMotionState* _motionState { nullptr };
std::vector<DetailedMotionState*> _detailedMotionStates;
int32_t _spaceIndex { -1 };
uint8_t _workloadRegion { workload::Region::INVALID };
BodyLOD _bodyLOD { BodyLOD::Sphere };
bool _needsReinsertion { false };
};
using OtherAvatarPointer = std::shared_ptr<OtherAvatar>;
using AvatarPointer = std::shared_ptr<Avatar>;
#endif // hifi_OtherAvatar_h

View file

@ -56,7 +56,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
}
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), true);
if (avatarRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
} else {

View file

@ -20,9 +20,9 @@
#include <PathUtils.h>
const int TOP_BAR_HEIGHT = 124;
const int INITIAL_WIDTH = 720;
const int INITIAL_WIDTH = 800;
const int INITIAL_HEIGHT = 480;
const int MINIMAL_WIDTH = 700;
const int MINIMAL_WIDTH = 780;
const int SEARCH_BUTTON_LEFT = 25;
const int SEARCH_BUTTON_WIDTH = 20;
const int SEARCH_TOGGLE_BUTTON_WIDTH = 50;

View file

@ -19,6 +19,9 @@
#include <shared/AbstractLoggerInterface.h>
#include "Application.h"
#include "MainWindow.h"
const int REVEAL_BUTTON_WIDTH = 122;
const int ALL_LOGS_BUTTON_WIDTH = 90;
const int MARGIN_LEFT = 25;
@ -148,6 +151,16 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog
_messageCount->setObjectName("messageCount");
_messageCount->show();
_keepOnTopBox = new QCheckBox(" Keep window on top", this);
bool isOnTop = qApp-> getLogWindowOnTopSetting();
_keepOnTopBox->setCheckState(isOnTop ? Qt::Checked : Qt::Unchecked);
#ifdef Q_OS_WIN
connect(_keepOnTopBox, &QCheckBox::stateChanged, qApp, &Application::recreateLogWindow);
#else
connect(_keepOnTopBox, &QCheckBox::stateChanged, this, &LogDialog::handleKeepWindowOnTop);
#endif
_keepOnTopBox->show();
_extraDebuggingBox = new QCheckBox("Extra debugging", this);
if (_logger->extraDebugging()) {
_extraDebuggingBox->setCheckState(Qt::Checked);
@ -183,6 +196,11 @@ void LogDialog::resizeEvent(QResizeEvent* event) {
THIRD_ROW,
COMBOBOX_WIDTH,
ELEMENT_HEIGHT);
_keepOnTopBox->setGeometry(width() - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN - ALL_LOGS_BUTTON_WIDTH - ELEMENT_MARGIN - COMBOBOX_WIDTH - ELEMENT_MARGIN,
THIRD_ROW,
COMBOBOX_WIDTH,
ELEMENT_HEIGHT);
_messageCount->setGeometry(_leftPad,
THIRD_ROW,
COMBOBOX_WIDTH,
@ -234,6 +252,23 @@ void LogDialog::handleInfoPrintBox(int state) {
printLogFile();
}
void LogDialog::handleKeepWindowOnTop(int state) {
bool keepOnTop = (state != 0);
Qt::WindowFlags flags = windowFlags();
if (keepOnTop) {
flags |= Qt::Tool;
} else {
flags &= ~Qt::Tool;
}
setWindowFlags(flags);
qApp->setLogWindowOnTopSetting(keepOnTop);
show();
}
void LogDialog::handleCriticalPrintBox(int state) {
_logger->setCriticalPrint(state != 0);
printLogFile();

View file

@ -34,6 +34,7 @@ public slots:
private slots:
void handleRevealButton();
void handleExtraDebuggingCheckbox(int);
void handleKeepWindowOnTop(int);
void handleDebugPrintBox(int);
void handleInfoPrintBox(int);
void handleCriticalPrintBox(int);
@ -55,6 +56,7 @@ protected:
private:
QCheckBox* _extraDebuggingBox;
QCheckBox* _keepOnTopBox;
QPushButton* _revealLogButton;
QPushButton* _allLogsButton;
QCheckBox* _debugPrintBox;

View file

@ -18,6 +18,7 @@
#include <plugins/PluginManager.h>
#include <plugins/SteamClientPlugin.h>
#include <plugins/OculusPlatformPlugin.h>
#include <shared/GlobalAppProperties.h>
#include <ui/TabletScriptingInterface.h>
#include <UserActivityLogger.h>
@ -109,8 +110,16 @@ bool LoginDialog::isSteamRunning() const {
return steamClient && steamClient->isRunning();
}
bool LoginDialog::isOculusStoreRunning() const {
return qApp->property(hifi::properties::OCULUS_STORE).toBool();
bool LoginDialog::isOculusRunning() const {
auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin();
return (oculusPlatformPlugin && oculusPlatformPlugin->isRunning());
}
QString LoginDialog::oculusUserID() const {
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
return oculusPlatformPlugin->getOculusUserID();
}
return "";
}
void LoginDialog::dismissLoginDialog() {
@ -126,6 +135,79 @@ void LoginDialog::login(const QString& username, const QString& password) const
DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
}
void LoginDialog::loginThroughOculus() {
qDebug() << "Attempting to login through Oculus";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
DependencyManager::get<AccountManager>()->requestAccessTokenWithOculus(nonce, oculusID);
});
}
}
void LoginDialog::linkOculus() {
qDebug() << "Attempting to link Oculus account";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this] (QString nonce, QString oculusID) {
if (nonce.isEmpty() || oculusID.isEmpty()) {
emit handleLoginFailed();
return;
}
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "linkCompleted";
callbackParams.errorCallbackMethod = "linkFailed";
const QString LINK_OCULUS_PATH = "api/v1/user/oculus/link";
QJsonObject payload;
payload["oculus_nonce"] = nonce;
payload["oculus_id"] = oculusID;
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(LINK_OCULUS_PATH, AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
});
}
}
void LoginDialog::createAccountFromOculus(QString email, QString username, QString password) {
qDebug() << "Attempting to create account from Oculus info";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
oculusPlatformPlugin->requestNonceAndUserID([this, email, username, password] (QString nonce, QString oculusID) {
if (nonce.isEmpty() || oculusID.isEmpty()) {
emit handleLoginFailed();
return;
}
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "createCompleted";
callbackParams.errorCallbackMethod = "createFailed";
const QString CREATE_ACCOUNT_FROM_OCULUS_PATH = "api/v1/user/oculus/create";
QJsonObject payload;
payload["oculus_nonce"] = nonce;
payload["oculus_id"] = oculusID;
if (!email.isEmpty()) {
payload["email"] = email;
}
if (!username.isEmpty()) {
payload["username"] = username;
}
if (!password.isEmpty()) {
payload["password"] = password;
}
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(CREATE_ACCOUNT_FROM_OCULUS_PATH, AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
});
}
}
void LoginDialog::loginThroughSteam() {
qDebug() << "Attempting to login through Steam";
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -157,7 +239,7 @@ void LoginDialog::linkSteam() {
const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
QJsonObject payload;
payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(LINK_STEAM_PATH, AccountManagerAuth::Required,
@ -184,9 +266,9 @@ void LoginDialog::createAccountFromSteam(QString username) {
const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
QJsonObject payload;
payload.insert("steam_auth_ticket", QJsonValue::fromVariant(QVariant(ticket)));
payload["steam_auth_ticket"] = QJsonValue::fromVariant(QVariant(ticket));
if (!username.isEmpty()) {
payload.insert("username", QJsonValue::fromVariant(QVariant(username)));
payload["username"] = username;
}
auto accountManager = DependencyManager::get<AccountManager>();
@ -214,6 +296,45 @@ void LoginDialog::createCompleted(QNetworkReply* reply) {
}
void LoginDialog::createFailed(QNetworkReply* reply) {
if (isOculusRunning()) {
auto replyData = reply->readAll();
QJsonParseError parseError;
auto doc = QJsonDocument::fromJson(replyData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
emit handleCreateFailed(reply->errorString());
return;
}
auto data = doc["data"];
auto error = data["error"];
auto oculusError = data["oculus"];
auto user = error["username"].toArray();
auto uid = error["uid"].toArray();
auto email = error["email"].toArray();
auto password = error["password"].toArray();
QString reply;
if (uid[0].isString()) {
emit handleCreateFailed("Oculus ID " + uid[0].toString() + ".");
return;
}
if (user[0].isString()) {
reply = "Username " + user[0].toString() + ".";
}
if (email[0].isString()) {
reply.append((!reply.isEmpty()) ? "\n" : "");
reply.append("Email " + email[0].toString() + ".");
}
if (password[0].isString()) {
reply.append((!reply.isEmpty()) ? "\n" : "");
reply.append("Password " + password[0].toString() + ".");
}
if (!oculusError.isNull() && !oculusError.isUndefined()) {
emit handleCreateFailed("Could not verify token with Oculus. Please try again.");
return;
} else {
emit handleCreateFailed(reply);
return;
}
}
emit handleCreateFailed(reply->errorString());
}

View file

@ -22,7 +22,6 @@ extern const QUrl OVERLAY_LOGIN_DIALOG;
class LoginDialog : public OffscreenQmlDialog {
Q_OBJECT
Q_PROPERTY(bool isLogIn READ getIsLogIn WRITE setIsLogIn)
HIFI_QML_DECL
public:
@ -67,24 +66,23 @@ protected slots:
Q_INVOKABLE void dismissLoginDialog();
Q_INVOKABLE bool isSteamRunning() const;
Q_INVOKABLE bool isOculusStoreRunning() const;
Q_INVOKABLE bool isOculusRunning() const;
Q_INVOKABLE QString oculusUserID() const;
Q_INVOKABLE void login(const QString& username, const QString& password) const;
Q_INVOKABLE void loginThroughSteam();
Q_INVOKABLE void linkSteam();
Q_INVOKABLE void createAccountFromSteam(QString username = QString());
Q_INVOKABLE void loginThroughOculus();
Q_INVOKABLE void linkOculus();
Q_INVOKABLE void createAccountFromOculus(QString email = QString(), QString username = QString(), QString password = QString());
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE void openUrl(const QString& url) const;
Q_INVOKABLE bool getLoginDialogPoppedUp() const;
private:
bool getIsLogIn() const { return _isLogIn; }
void setIsLogIn(const bool isLogIn) { _isLogIn = isLogIn; }
bool _isLogIn{ false };
};
#endif // hifi_LoginDialog_h

View file

@ -1310,10 +1310,17 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
float gain = injector->getVolume();
if (injector->isAmbisonic()) {
// no distance attenuation
float gain = injector->getVolume();
if (injector->isPositionSet()) {
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
//
// Calculate the soundfield orientation relative to the listener.
@ -1327,33 +1334,49 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
float qy = -relativeOrientation.x;
float qz = relativeOrientation.y;
// Ambisonic gets spatialized into mixBuffer
// spatialize into mixBuffer
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else if (injector->isStereo()) {
// calculate distance, gain
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = gainForSource(distance, injector->getVolume());
if (injector->isPositionSet()) {
// stereo gets directly mixed into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
} else {
// direct mix into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
mixBuffer[2*i+0] += convertToFloat(_localScratchBuffer[2*i+0]) * gain;
mixBuffer[2*i+1] += convertToFloat(_localScratchBuffer[2*i+1]) * gain;
}
// calculate distance, gain and azimuth for hrtf
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = gainForSource(distance, injector->getVolume());
float azimuth = azimuthForSource(relativePosition);
} else { // injector is mono
// mono gets spatialized into mixBuffer
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
if (injector->isPositionSet()) {
// distance attenuation
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
float azimuth = azimuthForSource(relativePosition);
// spatialize into mixBuffer
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
} else {
// direct mix into mixBuffer
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
float sample = convertToFloat(_localScratchBuffer[i]) * gain;
mixBuffer[2*i+0] += sample;
mixBuffer[2*i+1] += sample;
}
}
}
} else {
@ -1956,8 +1979,10 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) {
float AudioClient::gainForSource(float distance, float volume) {
// attenuation = -6dB * log2(distance)
// reference attenuation of 0dB at distance = 1.0m
float gain = volume / std::max(distance, HRTF_NEARFIELD_MIN);
// reference attenuation of 0dB at distance = ATTN_DISTANCE_REF
float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN);
float gain = volume / d;
gain = std::min(gain, ATTN_GAIN_MAX);
return gain;
}

View file

@ -30,6 +30,10 @@ static const float HRTF_NEARFIELD_MAX = 1.0f; // distance in meters
static const float HRTF_NEARFIELD_MIN = 0.125f; // distance in meters
static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters
// Distance attenuation
static const float ATTN_DISTANCE_REF = 2.0f; // distance where attn is 0dB
static const float ATTN_GAIN_MAX = 16.0f; // max gain allowed by distance attn (+24dB)
class AudioHRTF {
public:

View file

@ -67,6 +67,7 @@ public:
bool isLocalOnly() const { return _options.localOnly; }
float getVolume() const { return _options.volume; }
bool isPositionSet() const { return _options.positionSet; }
glm::vec3 getPosition() const { return _options.position; }
glm::quat getOrientation() const { return _options.orientation; }
bool isStereo() const { return _options.stereo; }

View file

@ -19,6 +19,7 @@
AudioInjectorOptions::AudioInjectorOptions() :
position(0.0f, 0.0f, 0.0f),
positionSet(true), // default to legacy behavior
volume(1.0f),
loop(false),
orientation(glm::vec3(0.0f, 0.0f, 0.0f)),
@ -29,12 +30,13 @@ AudioInjectorOptions::AudioInjectorOptions() :
secondOffset(0.0f),
pitch(1.0f)
{
}
QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInjectorOptions& injectorOptions) {
QScriptValue obj = engine->newObject();
obj.setProperty("position", vec3ToScriptValue(engine, injectorOptions.position));
if (injectorOptions.positionSet) {
obj.setProperty("position", vec3ToScriptValue(engine, injectorOptions.position));
}
obj.setProperty("volume", injectorOptions.volume);
obj.setProperty("loop", injectorOptions.loop);
obj.setProperty("orientation", quatToScriptValue(engine, injectorOptions.orientation));
@ -68,12 +70,18 @@ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOpt
return;
}
if (injectorOptions.positionSet == false) {
qWarning() << "Audio injector options: injectorOptionsFromScriptValue() called more than once?";
}
injectorOptions.positionSet = false;
QScriptValueIterator it(object);
while (it.hasNext()) {
it.next();
if (it.name() == "position") {
vec3FromScriptValue(object.property("position"), injectorOptions.position);
injectorOptions.positionSet = true;
} else if (it.name() == "orientation") {
quatFromScriptValue(object.property("orientation"), injectorOptions.orientation);
} else if (it.name() == "volume") {

View file

@ -21,6 +21,7 @@ class AudioInjectorOptions {
public:
AudioInjectorOptions();
glm::vec3 position;
bool positionSet;
float volume;
bool loop;
glm::quat orientation;

View file

@ -298,7 +298,9 @@ void Avatar::setTargetScale(float targetScale) {
_scaleChanged = usecTimestampNow();
_avatarScaleChanged = _scaleChanged;
_isAnimatingScale = true;
for (auto& sphere : _multiSphereShapes) {
sphere.setScale(_targetScale);
}
emit targetScaleChanged(targetScale);
}
}
@ -324,88 +326,79 @@ void Avatar::removeAvatarEntitiesFromTree() {
}
}
bool Avatar::updateGrabs() {
bool Avatar::applyGrabChanges() {
if (!_avatarGrabDataChanged && _grabsToChange.empty() && _grabsToDelete.empty()) {
// early exit for most common case: nothing to do
return false;
}
bool grabAddedOrRemoved = false;
// update the Grabs according to any changes in _avatarGrabData
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabDataChanged) {
// collect changes in _avatarGrabData
foreach (auto grabID, _avatarGrabData.keys()) {
AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID);
if (grabItr == _avatarGrabs.end()) {
MapOfGrabs::iterator itr = _avatarGrabs.find(grabID);
if (itr == _avatarGrabs.end()) {
GrabPointer grab = std::make_shared<Grab>();
grab->fromByteArray(_avatarGrabData.value(grabID));
_avatarGrabs[grabID] = grab;
_changedAvatarGrabs.insert(grabID);
_grabsToChange.insert(grabID);
} else {
GrabPointer grab = grabItr.value();
bool changed = grab->fromByteArray(_avatarGrabData.value(grabID));
bool changed = itr->second->fromByteArray(_avatarGrabData.value(grabID));
if (changed) {
_changedAvatarGrabs.insert(grabID);
_grabsToChange.insert(grabID);
}
}
}
_avatarGrabDataChanged = false;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr;
auto sessionID = DependencyManager::get<NodeList>()->getSessionUUID();
QMutableSetIterator<QUuid> delItr(_deletedAvatarGrabs);
while (delItr.hasNext()) {
QUuid grabID = delItr.next();
GrabPointer grab = _avatarGrabs[grabID];
if (!grab) {
delItr.remove();
// delete _avatarGrabs
VectorOfIDs undeleted;
for (const auto& id : _grabsToDelete) {
MapOfGrabs::iterator itr = _avatarGrabs.find(id);
if (itr == _avatarGrabs.end()) {
continue;
}
bool success;
const GrabPointer& grab = itr->second;
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
// only clear this entry from the _deletedAvatarGrabs if we found the entity.
if (success && target) {
bool iShouldTellServer = target->getEditSenderID() == sessionID;
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(target);
if (entity && entity->isAvatarEntity() && (entity->getOwningAvatarID() == sessionID ||
entity->getOwningAvatarID() == AVATAR_SELF_ID)) {
// this is our own avatar-entity, so we always tell the server about the release
iShouldTellServer = true;
}
target->removeGrab(grab);
delItr.remove();
// in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server
// about the final position.
if (entityTree) {
bool force = true;
entityTree->withWriteLock([&] {
entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer);
});
}
_avatarGrabs.erase(itr);
grabAddedOrRemoved = true;
} else {
undeleted.push_back(id);
}
_avatarGrabs.remove(grabID);
_changedAvatarGrabs.remove(grabID);
}
_grabsToDelete = std::move(undeleted);
QMutableSetIterator<QUuid> changeItr(_changedAvatarGrabs);
while (changeItr.hasNext()) {
QUuid grabID = changeItr.next();
GrabPointer& grab = _avatarGrabs[grabID];
// change _avatarGrabs and add Actions to target
SetOfIDs unchanged;
for (const auto& id : _grabsToChange) {
MapOfGrabs::iterator itr = _avatarGrabs.find(id);
if (itr == _avatarGrabs.end()) {
continue;
}
bool success;
const GrabPointer& grab = itr->second;
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
if (success && target) {
target->addGrab(grab);
// only clear this entry from the _changedAvatarGrabs if we found the entity.
changeItr.remove();
if (isMyAvatar()) {
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(target);
if (entity) {
entity->upgradeScriptSimulationPriority(PERSONAL_SIMULATION_PRIORITY);
}
}
grabAddedOrRemoved = true;
} else {
unchanged.insert(id);
}
}
_grabsToChange = std::move(unchanged);
});
return grabAddedOrRemoved;
}
@ -413,8 +406,8 @@ bool Avatar::updateGrabs() {
void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) {
// relay avatar's joint position to grabbed target in a way that allows for averaging
_avatarGrabsLock.withReadLock([&] {
foreach (auto grabID, _avatarGrabs.keys()) {
const GrabPointer& grab = _avatarGrabs.value(grabID);
for (const auto& entry : _avatarGrabs) {
const GrabPointer& grab = entry.second;
if (!grab || !grab->getActionID().isNull()) {
continue; // the accumulated value isn't used, in this case.
@ -432,6 +425,20 @@ void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& g
});
}
void Avatar::tearDownGrabs() {
_avatarGrabsLock.withWriteLock([&] {
for (const auto& entry : _avatarGrabs) {
_grabsToDelete.push_back(entry.first);
}
_grabsToChange.clear();
});
applyGrabChanges();
if (!_grabsToDelete.empty()) {
// some grabs failed to delete, which is a possible "leak", so we log about it
qWarning() << "Failed to tearDown" << _grabsToDelete.size() << "grabs for Avatar" << getID();
}
}
void Avatar::relayJointDataToChildren() {
forEachChild([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
@ -717,6 +724,7 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
}
fixupModelsInScene(scene);
updateFitBoundingBox();
}
void Avatar::render(RenderArgs* renderArgs) {
@ -1283,6 +1291,79 @@ glm::vec3 Avatar::getAbsoluteJointScaleInObjectFrame(int index) const {
}
}
glm::vec3 Avatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 modelOffset = position - jointPos;
glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset;
return jointSpacePosition;
}
glm::vec3 Avatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir;
return jointSpaceDir;
}
glm::quat Avatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot;
return jointSpaceRot;
}
glm::vec3 Avatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
} else {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
}
glm::vec3 worldOffset = jointRot * jointSpacePos;
glm::vec3 worldPos = jointPos + worldOffset;
return worldPos;
}
glm::vec3 Avatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::vec3 worldDir = jointRot * jointSpaceDir;
return worldDir;
}
glm::quat Avatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
glm::quat worldRot = jointRot * jointSpaceRot;
return worldRot;
}
void Avatar::invalidateJointIndicesCache() const {
QWriteLocker writeLock(&_modelJointIndicesCacheLock);
_modelJointsCached = false;
@ -1421,6 +1502,7 @@ void Avatar::setModelURLFinished(bool success) {
void Avatar::rigReady() {
buildUnscaledEyeHeightCache();
buildSpine2SplineRatioCache();
computeMultiSphereShapes();
}
// rig has been reset.
@ -1429,6 +1511,48 @@ void Avatar::rigReset() {
clearSpine2SplineRatioCache();
}
void Avatar::computeMultiSphereShapes() {
const Rig& rig = getSkeletonModel()->getRig();
glm::vec3 scale = extractScale(rig.getGeometryOffsetPose());
const HFMModel& geometry = getSkeletonModel()->getHFMModel();
int jointCount = rig.getJointStateCount();
_multiSphereShapes.clear();
_multiSphereShapes.reserve(jointCount);
for (int i = 0; i < jointCount; i++) {
const HFMJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo;
std::vector<btVector3> btPoints;
int lineCount = (int)shapeInfo.debugLines.size();
btPoints.reserve(lineCount);
for (int j = 0; j < lineCount; j++) {
const glm::vec3 &point = shapeInfo.debugLines[j];
auto rigPoint = scale * point;
btVector3 btPoint = glmToBullet(rigPoint);
btPoints.push_back(btPoint);
}
auto jointName = rig.nameOfJoint(i).toUpper();
MultiSphereShape multiSphereShape;
if (multiSphereShape.computeMultiSphereShape(i, jointName, btPoints)) {
multiSphereShape.calculateDebugLines();
multiSphereShape.setScale(_targetScale);
}
_multiSphereShapes.push_back(multiSphereShape);
}
}
void Avatar::updateFitBoundingBox() {
_fitBoundingBox = AABox();
if (getJointCount() == (int)_multiSphereShapes.size()) {
for (int i = 0; i < getJointCount(); i++) {
auto &shape = _multiSphereShapes[i];
glm::vec3 jointPosition;
glm::quat jointRotation;
_skeletonModel->getJointPositionInWorldFrame(i, jointPosition);
_skeletonModel->getJointRotationInWorldFrame(i, jointRotation);
_fitBoundingBox += shape.updateBoundingBox(jointPosition, jointRotation);
}
}
}
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) {
if (isSoft) {
@ -1608,6 +1732,21 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
shapeInfo.setOffset(offset);
}
void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) {
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
auto& data = _multiSphereShapes[jointIndex].getSpheresData();
std::vector<glm::vec3> positions;
std::vector<btScalar> radiuses;
positions.reserve(data.size());
radiuses.reserve(data.size());
for (auto& sphere : data) {
positions.push_back(sphere._position);
radiuses.push_back(sphere._radius);
}
shapeInfo.setMultiSphere(positions, radiuses);
}
}
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
@ -1974,3 +2113,12 @@ scriptable::ScriptableModelBase Avatar::getScriptableModel() {
}
return result;
}
void Avatar::clearAvatarGrabData(const QUuid& id) {
AvatarData::clearAvatarGrabData(id);
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabs.find(id) != _avatarGrabs.end()) {
_grabsToDelete.push_back(id);
}
});
}

View file

@ -14,6 +14,9 @@
#include <functional>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <map>
#include <set>
#include <vector>
#include <QtCore/QUuid>
@ -24,12 +27,15 @@
#include <GLMHelpers.h>
#include <CubicHermiteSpline.h>
#include <Grab.h>
#include <ThreadSafeValueCache.h>
#include "Head.h"
#include "SkeletonModel.h"
#include "Rig.h"
#include <ThreadSafeValueCache.h>
#include "MetaModelPayload.h"
#include "MultiSphereShape.h"
namespace render {
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar);
@ -223,15 +229,65 @@ public:
*/
Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const;
virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override;
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; }
virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; }
// world-space to avatar-space rigconversion functions
/**jsdoc
* @function MyAvatar.worldToJointPoint
* @param {Vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.worldToJointRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldPoint
* @param {vec3} position
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldDirection
* @param {Vec3} direction
* @param {number} [jointIndex=-1]
* @returns {Vec3}
*/
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
* @function MyAvatar.jointToWorldRotation
* @param {Quat} rotation
* @param {number} [jointIndex=-1]
* @returns {Quat}
*/
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
@ -319,6 +375,8 @@ public:
virtual void rebuildCollisionShape() = 0;
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
virtual void computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex);
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
float computeMass();
/**jsdoc
@ -397,6 +455,7 @@ public:
float getBoundingRadius() const;
AABox getRenderBounds() const; // THis call is accessible from rendering thread only to report the bounding box of the avatar during the frame.
AABox getFitBounds() const { return _fitBoundingBox; }
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
@ -440,6 +499,9 @@ public:
void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators);
const std::vector<MultiSphereShape>& getMultiSphereShapes() const { return _multiSphereShapes; }
void tearDownGrabs();
signals:
void targetScaleChanged(float targetScale);
@ -509,6 +571,8 @@ protected:
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
QString _empty{};
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
void computeMultiSphereShapes();
void updateFitBoundingBox();
SkeletonModelPointer _skeletonModel;
@ -544,7 +608,7 @@ protected:
// protected methods...
bool isLookingAtMe(AvatarSharedPointer avatar) const;
bool updateGrabs();
bool applyGrabChanges();
void relayJointDataToChildren();
void fade(render::Transaction& transaction, render::Transition::Type type);
@ -633,8 +697,18 @@ protected:
static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs);
std::vector<MultiSphereShape> _multiSphereShapes;
AABox _fitBoundingBox;
void clearAvatarGrabData(const QUuid& grabID) override;
AvatarGrabMap _avatarGrabs;
using SetOfIDs = std::set<QUuid>;
using VectorOfIDs = std::vector<QUuid>;
using MapOfGrabs = std::map<QUuid, GrabPointer>;
MapOfGrabs _avatarGrabs;
SetOfIDs _grabsToChange; // updated grab IDs -- changes needed to entities or physics
VectorOfIDs _grabsToDelete; // deleted grab IDs -- changes needed to entities or physics
};
#endif // hifi_Avatar_h

View file

@ -632,9 +632,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
// include jointData if there is room for the most minimal section. i.e. no translations or rotations.
IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) {
// Allow for faux joints + translation bit-vector:
const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat)
+ jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE;
// Minimum space required for another rotation joint -
// size of joint + following translation bit-vector + translation scale + faux joints:
const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize +
sizeof(float) + AvatarDataPacket::FAUX_JOINTS_SIZE;
auto startSection = destinationBuffer;
// compute maxTranslationDimension before we send any joint data.
@ -724,6 +726,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
const JointData& data = joints[i];
const JointData& last = lastSentJointData[i];
// Note minSizeForJoint is conservative since there isn't a following bit-vector + scale.
if (packetEnd - destinationBuffer >= minSizeForJoint) {
if (!data.translationIsDefaultPose) {
if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation)
@ -2900,6 +2903,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
obj.setProperty("jointIndex", value.jointIndex);
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
return obj;
}
@ -2919,6 +2923,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
if (surfaceNormal.isValid()) {
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
}
value.jointIndex = object.property("jointIndex").toInt32();
value.extraInfo = object.property("extraInfo").toVariant().toMap();
}
@ -3010,7 +3015,6 @@ void AvatarData::clearAvatarGrabData(const QUuid& grabID) {
_avatarGrabsLock.withWriteLock([&] {
if (_avatarGrabData.remove(grabID)) {
_avatarGrabDataChanged = true;
_deletedAvatarGrabs.insert(grabID);
}
});
}

View file

@ -54,7 +54,6 @@
#include "AvatarTraits.h"
#include "HeadData.h"
#include "PathUtils.h"
#include "Grab.h"
#include <graphics/Material.h>
@ -67,8 +66,6 @@ using PackedAvatarEntityMap = QMap<QUuid, QByteArray>; // similar to AvatarEntit
using AvatarEntityIDs = QSet<QUuid>;
using AvatarGrabDataMap = QMap<QUuid, QByteArray>;
using AvatarGrabIDs = QSet<QUuid>;
using AvatarGrabMap = QMap<QUuid, GrabPointer>;
using AvatarDataSequenceNumber = uint16_t;
@ -1475,8 +1472,6 @@ protected:
mutable ReadWriteLockable _avatarGrabsLock;
AvatarGrabDataMap _avatarGrabData;
bool _avatarGrabDataChanged { false }; // by network
AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics
AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
@ -1539,7 +1534,7 @@ protected:
}
bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData);
void clearAvatarGrabData(const QUuid& grabID);
virtual void clearAvatarGrabData(const QUuid& grabID);
private:
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
@ -1620,6 +1615,7 @@ public:
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
int jointIndex { -1 };
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)

View file

@ -15,6 +15,11 @@
namespace controller {
enum class HmdAvatarAlignmentType {
Eyes = 0, // align the user's eyes with the avatars eyes
Head // align the user's head with the avatars head
};
struct InputCalibrationData {
glm::mat4 sensorToWorldMat; // sensor to world
glm::mat4 avatarMat; // avatar to world
@ -29,6 +34,7 @@ struct InputCalibrationData {
glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space
glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space
glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space
HmdAvatarAlignmentType hmdAvatarAlignmentType;
};
enum class ChannelType {

View file

@ -31,6 +31,8 @@ public:
virtual void compositeExtra() override;
virtual void pluginUpdate() override {};
protected:
mutable bool _isThrottled = false;

View file

@ -20,6 +20,7 @@ public:
QImage getScreenshot(float aspectRatio = 0.0f) const override;
QImage getSecondaryCameraScreenshot() const override;
void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {};
void pluginUpdate() override {};
private:
static const QString NAME;
};

View file

@ -15,8 +15,10 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtCore/QFileInfo>
#include <QtGui/QImage>
#include <QtGui/QImageWriter>
#include <QtGui/QOpenGLFramebufferObject>
#include <NumericalConstants.h>
@ -30,6 +32,7 @@
#include <gl/OffscreenGLCanvas.h>
#include <gpu/Texture.h>
#include <gpu/FrameIO.h>
#include <shaders/Shaders.h>
#include <gpu/gl/GLShared.h>
#include <gpu/gl/GLBackend.h>
@ -465,11 +468,43 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
});
}
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) {
renderFromTexture(batch, texture, viewport, scissor, gpu::FramebufferPointer());
void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const {
withOtherThreadContext([&] {
using namespace gpu;
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
FramebufferPointer framebuffer{ Framebuffer::create("captureFramebuffer") };
TextureCapturer captureLambda = [&](const std::string& filename, const gpu::TexturePointer& texture, uint16 layer) {
QImage image;
if (texture->getUsageType() == TextureUsageType::STRICT_RESOURCE) {
image = QImage{ 1, 1, QImage::Format_ARGB32 };
auto storedImage = texture->accessStoredMipFace(0, 0);
memcpy(image.bits(), storedImage->data(), image.sizeInBytes());
//if (texture == textureCache->getWhiteTexture()) {
//} else if (texture == textureCache->getBlackTexture()) {
//} else if (texture == textureCache->getBlueTexture()) {
//} else if (texture == textureCache->getGrayTexture()) {
} else {
ivec4 rect = { 0, 0, texture->getWidth(), texture->getHeight() };
framebuffer->setRenderBuffer(0, texture, layer);
glBackend->syncGPUObject(*framebuffer);
image = QImage{ rect.z, rect.w, QImage::Format_ARGB32 };
glBackend->downloadFramebuffer(framebuffer, rect, image);
}
QImageWriter(filename.c_str()).write(image);
};
if (_currentFrame) {
gpu::writeFrame(filename, _currentFrame, captureLambda);
}
});
}
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer copyFbo /*=gpu::FramebufferPointer()*/) {
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) {
renderFromTexture(batch, texture, viewport, scissor, nullptr);
}
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) {
auto fbo = gpu::FramebufferPointer();
batch.enableStereo(false);
batch.resetViewTransform();

View file

@ -47,7 +47,7 @@ public:
void endSession() override final;
bool eventFilter(QObject* receiver, QEvent* event) override;
bool isDisplayVisible() const override { return true; }
void captureFrame(const std::string& outputName) const override;
void submitFrame(const gpu::FramePointer& newFrame) override;
glm::uvec2 getRecommendedRenderSize() const override {
@ -113,8 +113,8 @@ protected:
// Plugin specific functionality to send the composed scene to the output window or device
virtual void internalPresent();
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor, gpu::FramebufferPointer fbo);
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo);
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor);
virtual void updateFrameData();
virtual glm::mat4 getViewCorrection() { return glm::mat4(); }

View file

@ -69,7 +69,10 @@ bool DebugHmdDisplayPlugin::internalActivate() {
_eyeInverseProjections[1] = glm::inverse(_eyeProjections[1]);
_eyeOffsets[0][3] = vec4{ -0.0327499993, 0.0, -0.0149999997, 1.0 };
_eyeOffsets[1][3] = vec4{ 0.0327499993, 0.0, -0.0149999997, 1.0 };
_renderTargetSize = { 3024, 1680 };
// Test HMD per-eye resolution
_renderTargetSize = uvec2{ 1214 * 2 , 1344 };
// uncomment to capture a quarter size frame
//_renderTargetSize /= 2;
_cullingProjection = _eyeProjections[0];
// This must come after the initialization, so that the values calculated
// above are available during the customizeContext call (when not running

View file

@ -46,6 +46,8 @@ public:
virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
void pluginUpdate() override {};
signals:
void hmdMountedChanged();
void hmdVisibleChanged(bool visible);

View file

@ -28,6 +28,8 @@ public:
// to the HMD plugins.
//virtual glm::mat4 getEyeToHeadTransform(Eye eye) const override;
virtual void pluginUpdate() override {};
protected:
virtual bool internalActivate() override;
virtual void internalDeactivate() override;

View file

@ -14,20 +14,20 @@
#include <ObjectMotionState.h>
#include "RenderableLightEntityItem.h"
#include "RenderableLineEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderablePolyLineEntityItem.h"
#include "RenderableShapeEntityItem.h"
#include "RenderableModelEntityItem.h"
#include "RenderableTextEntityItem.h"
#include "RenderableImageEntityItem.h"
#include "RenderableWebEntityItem.h"
#include "RenderableParticleEffectEntityItem.h"
#include "RenderableLineEntityItem.h"
#include "RenderablePolyLineEntityItem.h"
#include "RenderablePolyVoxEntityItem.h"
#include "RenderableGridEntityItem.h"
#include "RenderableGizmoEntityItem.h"
#include "RenderableLightEntityItem.h"
#include "RenderableZoneEntityItem.h"
#include "RenderableMaterialEntityItem.h"
#include "RenderableImageEntityItem.h"
#include "RenderableGridEntityItem.h"
using namespace render;
using namespace render::entities;
@ -141,7 +141,7 @@ std::shared_ptr<T> make_renderer(const EntityItemPointer& entity) {
return std::shared_ptr<T>(new T(entity), [](T* ptr) { ptr->deleteLater(); });
}
EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) {
EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) {
connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] {
_needsRenderUpdate = true;
emit requestRenderUpdate();
@ -284,6 +284,10 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer,
result = make_renderer<GridEntityRenderer>(entity);
break;
case Type::Gizmo:
result = make_renderer<GizmoEntityRenderer>(entity);
break;
case Type::Light:
result = make_renderer<LightEntityRenderer>(entity);
break;
@ -468,3 +472,32 @@ void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const st
std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].remove(material);
}
glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start) {
if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) {
return color;
}
float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND);
float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin();
float outPulse = (1.0f - pulse);
glm::vec4 result = color;
if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) {
result.r *= pulse;
result.g *= pulse;
result.b *= pulse;
} else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) {
result.r *= outPulse;
result.g *= outPulse;
result.b *= outPulse;
}
if (pulseProperties.getAlphaMode() == PulseMode::IN_PHASE) {
result.a *= pulse;
} else if (pulseProperties.getAlphaMode() == PulseMode::OUT_PHASE) {
result.a *= outPulse;
}
return result;
}

View file

@ -62,6 +62,8 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); }
static glm::vec4 calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start);
protected:
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
virtual void onAddToScene(const EntityItemPointer& entity);
@ -151,6 +153,8 @@ protected:
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;
quint64 _created;
private:
// The base class relies on comparing the model transform to the entity transform in order
// to trigger an update, so the member must not be visible to derived classes as a modifiable

View file

@ -0,0 +1,289 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RenderableGizmoEntityItem.h"
#include <DependencyManager.h>
#include <GeometryCache.h>
using namespace render;
using namespace render::entities;
GizmoEntityRenderer::GizmoEntityRenderer(const EntityItemPointer& entity) : Parent(entity)
{
}
GizmoEntityRenderer::~GizmoEntityRenderer() {
auto geometryCache = DependencyManager::get<GeometryCache>();
if (geometryCache) {
if (_ringGeometryID) {
geometryCache->releaseID(_ringGeometryID);
}
if (_majorTicksGeometryID) {
geometryCache->releaseID(_majorTicksGeometryID);
}
if (_minorTicksGeometryID) {
geometryCache->releaseID(_minorTicksGeometryID);
}
}
}
bool GizmoEntityRenderer::isTransparent() const {
bool ringTransparent = _gizmoType == GizmoType::RING && (_ringProperties.getInnerStartAlpha() < 1.0f ||
_ringProperties.getInnerEndAlpha() < 1.0f || _ringProperties.getOuterStartAlpha() < 1.0f ||
_ringProperties.getOuterEndAlpha() < 1.0f);
return Parent::isTransparent() || ringTransparent;
}
bool GizmoEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
bool needsUpdate = resultWithReadLock<bool>([&] {
if (_gizmoType != entity->getGizmoType()) {
return true;
}
if (_ringProperties != entity->getRingProperties()) {
return true;
}
return false;
});
return needsUpdate;
}
void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) {
bool dirty = false;
RingGizmoPropertyGroup ringProperties = entity->getRingProperties();
withWriteLock([&] {
_gizmoType = entity->getGizmoType();
if (_ringProperties != ringProperties) {
_ringProperties = ringProperties;
dirty = true;
}
});
if (dirty || _prevPrimitiveMode != _primitiveMode || !_ringGeometryID || !_majorTicksGeometryID || !_minorTicksGeometryID) {
_prevPrimitiveMode = _primitiveMode;
auto geometryCache = DependencyManager::get<GeometryCache>();
if (!_ringGeometryID) {
_ringGeometryID = geometryCache->allocateID();
}
const float FULL_CIRCLE = 360.0f;
const float SLICES = 180.0f;
const float SLICE_ANGLE_RADIANS = glm::radians(FULL_CIRCLE / SLICES);
if (_primitiveMode == PrimitiveMode::SOLID) {
QVector<glm::vec3> points;
QVector<glm::vec4> colors;
vec4 innerStartColor = vec4(toGlm(ringProperties.getInnerStartColor()), ringProperties.getInnerStartAlpha());
vec4 outerStartColor = vec4(toGlm(ringProperties.getOuterStartColor()), ringProperties.getOuterStartAlpha());
vec4 innerEndColor = vec4(toGlm(ringProperties.getInnerEndColor()), ringProperties.getInnerEndAlpha());
vec4 outerEndColor = vec4(toGlm(ringProperties.getOuterEndColor()), ringProperties.getOuterEndAlpha());
float startAtRadians = glm::radians(ringProperties.getStartAngle());
float endAtRadians = glm::radians(ringProperties.getEndAngle());
const auto totalRange = endAtRadians - startAtRadians;
if (ringProperties.getInnerRadius() <= 0) {
_solidPrimitive = gpu::TRIANGLE_FAN;
points << vec3();
colors << innerStartColor;
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
points << 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << 0.5f * glm::vec3(cosf(endAtRadians), 0.0f, sinf(endAtRadians));
colors << outerEndColor;
} else {
_solidPrimitive = gpu::TRIANGLE_STRIP;
for (float angleRadians = startAtRadians; angleRadians < endAtRadians; angleRadians += SLICE_ANGLE_RADIANS) {
float range = (angleRadians - startAtRadians) / totalRange;
points << 0.5f * glm::vec3(cosf(angleRadians) * ringProperties.getInnerRadius(), 0.0f, sinf(angleRadians) * ringProperties.getInnerRadius());
colors << glm::mix(innerStartColor, innerEndColor, range);
points << 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
colors << glm::mix(outerStartColor, outerEndColor, range);
}
points << 0.5f * glm::vec3(cosf(endAtRadians) * ringProperties.getInnerRadius(), 0.0f, sinf(endAtRadians) * ringProperties.getInnerRadius());
colors << innerEndColor;
points << 0.5f * glm::vec3(cosf(endAtRadians), 0.0f, sinf(endAtRadians));
colors << outerEndColor;
}
geometryCache->updateVertices(_ringGeometryID, points, colors);
} else {
_solidPrimitive = gpu::LINE_STRIP;
QVector<glm::vec3> points;
float startAtRadians = glm::radians(ringProperties.getStartAngle());
float endAtRadians = glm::radians(ringProperties.getEndAngle());
float angleRadians = startAtRadians;
glm::vec3 firstPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << firstPoint;
while (angleRadians < endAtRadians) {
angleRadians += SLICE_ANGLE_RADIANS;
glm::vec3 thisPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << thisPoint;
}
// get the last slice portion....
angleRadians = endAtRadians;
glm::vec3 lastPoint = 0.5f * glm::vec3(cosf(angleRadians), 0.0f, sinf(angleRadians));
points << lastPoint;
geometryCache->updateVertices(_ringGeometryID, points, glm::vec4(toGlm(ringProperties.getOuterStartColor()), ringProperties.getOuterStartAlpha()));
}
if (ringProperties.getHasTickMarks()) {
if (ringProperties.getMajorTickMarksAngle() > 0.0f && ringProperties.getMajorTickMarksLength() != 0.0f) {
QVector<glm::vec3> points;
if (!_majorTicksGeometryID) {
_majorTicksGeometryID = geometryCache->allocateID();
}
float startAngle = ringProperties.getStartAngle();
float tickMarkAngle = ringProperties.getMajorTickMarksAngle();
float angle = startAngle - fmodf(startAngle, tickMarkAngle) + tickMarkAngle;
float tickMarkLength = 0.5f * ringProperties.getMajorTickMarksLength();
float startRadius = (tickMarkLength > 0.0f) ? 0.5f * ringProperties.getInnerRadius() : 0.5f;
float endRadius = startRadius + tickMarkLength;
while (angle <= ringProperties.getEndAngle()) {
float angleInRadians = glm::radians(angle);
glm::vec3 thisPointA = startRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
glm::vec3 thisPointB = endRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
points << thisPointA << thisPointB;
angle += tickMarkAngle;
}
glm::vec4 color(toGlm(ringProperties.getMajorTickMarksColor()), 1.0f); // TODO: alpha
geometryCache->updateVertices(_majorTicksGeometryID, points, color);
}
if (ringProperties.getMinorTickMarksAngle() > 0.0f && ringProperties.getMinorTickMarksLength() != 0.0f) {
QVector<glm::vec3> points;
if (!_minorTicksGeometryID) {
_minorTicksGeometryID = geometryCache->allocateID();
}
float startAngle = ringProperties.getStartAngle();
float tickMarkAngle = ringProperties.getMinorTickMarksAngle();
float angle = startAngle - fmodf(startAngle, tickMarkAngle) + tickMarkAngle;
float tickMarkLength = 0.5f * ringProperties.getMinorTickMarksLength();
float startRadius = (tickMarkLength > 0.0f) ? 0.5f * ringProperties.getInnerRadius() : 0.5f;
float endRadius = startRadius + tickMarkLength;
while (angle <= ringProperties.getEndAngle()) {
float angleInRadians = glm::radians(angle);
glm::vec3 thisPointA = startRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
glm::vec3 thisPointB = endRadius * glm::vec3(cosf(angleInRadians), 0.0f, sinf(angleInRadians));
points << thisPointA << thisPointB;
angle += tickMarkAngle;
}
glm::vec4 color(toGlm(ringProperties.getMinorTickMarksColor()), 1.0f); // TODO: alpha
geometryCache->updateVertices(_minorTicksGeometryID, points, color);
}
}
}
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() {
withWriteLock([&] {
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});
});
}
Item::Bound GizmoEntityRenderer::getBound() {
auto bound = Parent::getBound();
if (_ringProperties.getHasTickMarks()) {
glm::vec3 scale = bound.getScale();
for (int i = 0; i < 3; i += 2) {
if (_ringProperties.getMajorTickMarksLength() + _ringProperties.getInnerRadius() > 1.0f) {
scale[i] *= _ringProperties.getMajorTickMarksLength() + _ringProperties.getInnerRadius();
} else if (_ringProperties.getMajorTickMarksLength() < -2.0f) {
scale[i] *= -_ringProperties.getMajorTickMarksLength() - 1.0f;
}
if (_ringProperties.getMinorTickMarksLength() + _ringProperties.getInnerRadius() > 1.0f) {
scale[i] *= _ringProperties.getMinorTickMarksLength() + _ringProperties.getInnerRadius();
} else if (_ringProperties.getMinorTickMarksLength() < -2.0f) {
scale[i] *= -_ringProperties.getMinorTickMarksLength() - 1.0f;
}
}
bound.setScaleStayCentered(scale);
return bound;
}
return bound;
}
ShapeKey GizmoEntityRenderer::getShapeKey() {
auto builder = render::ShapeKey::Builder().withoutCullFace();
if (isTransparent()) {
builder.withTranslucent();
}
if (_primitiveMode == PrimitiveMode::LINES) {
builder.withUnlit().withDepthBias();
}
return builder.build();
}
void GizmoEntityRenderer::doRender(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableGizmoEntityItem::render");
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
if (_gizmoType == GizmoType::RING) {
Transform transform;
bool hasTickMarks;
glm::vec4 tickProperties;
withReadLock([&] {
transform = _renderTransform;
hasTickMarks = _ringProperties.getHasTickMarks();
tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
});
bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, _renderLayer != RenderLayer::WORLD);
batch.setModelTransform(transform);
// Background circle
geometryCache->renderVertices(batch, wireframe ? gpu::LINE_STRIP : _solidPrimitive, _ringGeometryID);
// Ticks
if (hasTickMarks) {
if (tickProperties.x > 0.0f && tickProperties.y != 0.0f) {
geometryCache->renderVertices(batch, gpu::LINES, _majorTicksGeometryID);
}
if (tickProperties.z > 0.0f && tickProperties.w != 0.0f) {
geometryCache->renderVertices(batch, gpu::LINES, _minorTicksGeometryID);
}
}
}
}

View file

@ -0,0 +1,48 @@
//
// Created by Sam Gondelman on 1/22/19
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RenderableGizmoEntityItem_h
#define hifi_RenderableGizmoEntityItem_h
#include "RenderableEntityItem.h"
#include <GizmoEntityItem.h>
namespace render { namespace entities {
class GizmoEntityRenderer : public TypedEntityRenderer<GizmoEntityItem> {
using Parent = TypedEntityRenderer<GizmoEntityItem>;
using Pointer = std::shared_ptr<GizmoEntityRenderer>;
public:
GizmoEntityRenderer(const EntityItemPointer& entity);
~GizmoEntityRenderer();
protected:
Item::Bound getBound() override;
ShapeKey getShapeKey() override;
bool isTransparent() const override;
private:
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
virtual void doRender(RenderArgs* args) override;
GizmoType _gizmoType;
RingGizmoPropertyGroup _ringProperties;
PrimitiveMode _prevPrimitiveMode;
int _ringGeometryID { 0 };
int _majorTicksGeometryID { 0 };
int _minorTicksGeometryID { 0 };
gpu::Primitive _solidPrimitive { gpu::TRIANGLE_FAN };
};
} }
#endif // hifi_RenderableGizmoEntityItem_h

View file

@ -26,7 +26,7 @@ GridEntityRenderer::~GridEntityRenderer() {
}
bool GridEntityRenderer::isTransparent() const {
return Parent::isTransparent() || _alpha < 1.0f;
return Parent::isTransparent() || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool GridEntityRenderer::needsRenderUpdate() const {
@ -55,6 +55,10 @@ bool GridEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
});
@ -65,6 +69,7 @@ void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
withWriteLock([&] {
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
_followCamera = entity->getFollowCamera();
_majorGridEvery = entity->getMajorGridEvery();
@ -105,11 +110,12 @@ ShapeKey GridEntityRenderer::getShapeKey() {
}
void GridEntityRenderer::doRender(RenderArgs* args) {
glm::u8vec3 color;
glm::vec4 color;
glm::vec3 dimensions;
Transform renderTransform;
withReadLock([&] {
color = _color;
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
renderTransform = _renderTransform;
});
@ -141,12 +147,11 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
float majorGridColDivisions = dimensions.y / _majorGridEvery;
float minorGridRowDivisions = dimensions.x / _minorGridEvery;
float minorGridColDivisions = dimensions.y / _minorGridEvery;
glm::vec4 gridColor(toGlm(color), _alpha);
const float MINOR_GRID_EDGE = 0.0025f;
const float MAJOR_GRID_EDGE = 0.005f;
DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner,
minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE,
majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE,
gridColor, _geometryId);
color, _geometryId);
}

View file

@ -36,6 +36,7 @@ private:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
bool _followCamera;
uint32_t _majorGridEvery;

View file

@ -26,7 +26,7 @@ ImageEntityRenderer::~ImageEntityRenderer() {
}
bool ImageEntityRenderer::isTransparent() const {
return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f;
return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE;
}
bool ImageEntityRenderer::needsRenderUpdate() const {
@ -71,6 +71,10 @@ bool ImageEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
return false;
});
@ -97,6 +101,7 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_color = entity->getColor();
_alpha = entity->getAlpha();
_pulseProperties = entity->getPulseProperties();
if (!_textureIsLoaded && _texture && _texture->isLoaded()) {
_textureIsLoaded = true;
@ -135,13 +140,14 @@ ShapeKey ImageEntityRenderer::getShapeKey() {
void ImageEntityRenderer::doRender(RenderArgs* args) {
NetworkTexturePointer texture;
QRect subImage;
glm::u8vec3 color;
glm::vec4 color;
glm::vec3 dimensions;
Transform transform;
withReadLock([&] {
texture = _texture;
subImage = _subImage;
color = _color;
color = glm::vec4(toGlm(_color), _alpha);
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
dimensions = _dimensions;
transform = _renderTransform;
});
@ -211,11 +217,9 @@ void ImageEntityRenderer::doRender(RenderArgs* args) {
glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth,
(fromImage.y() + fromImage.height() - 0.5f) / imageHeight);
glm::vec4 imageColor(toGlm(color), _alpha);
DependencyManager::get<GeometryCache>()->renderQuad(
*batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
imageColor, _geometryId
color, _geometryId
);
batch->setResourceTexture(0, nullptr);

View file

@ -44,6 +44,7 @@ private:
glm::u8vec3 _color;
float _alpha;
PulsePropertyGroup _pulseProperties;
glm::vec3 _dimensions;

View file

@ -71,8 +71,11 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE
return true;
}
auto particleProperties = entity->getParticleProperties();
if (particleProperties != _particleProperties) {
if (_particleProperties != entity->getParticleProperties()) {
return true;
}
if (_pulseProperties != entity->getPulseProperties()) {
return true;
}
@ -95,6 +98,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
}
});
}
withWriteLock([&] {
_pulseProperties = entity->getPulseProperties();
});
_emitting = entity->getIsEmitting();
bool hasTexture = resultWithReadLock<bool>([&]{ return _particleProperties.textures.isEmpty(); });
@ -142,10 +149,6 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
particleUniforms.radius.middle = _particleProperties.radius.gradient.target;
particleUniforms.radius.finish = _particleProperties.radius.range.finish;
particleUniforms.radius.spread = _particleProperties.radius.gradient.spread;
particleUniforms.color.start = _particleProperties.getColorStart();
particleUniforms.color.middle = _particleProperties.getColorMiddle();
particleUniforms.color.finish = _particleProperties.getColorFinish();
particleUniforms.color.spread = _particleProperties.getColorSpread();
particleUniforms.spin.start = _particleProperties.spin.range.start;
particleUniforms.spin.middle = _particleProperties.spin.gradient.target;
particleUniforms.spin.finish = _particleProperties.spin.range.finish;
@ -158,6 +161,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
}
ItemKey ParticleEffectEntityRenderer::getKey() {
// FIXME: implement isTransparent() for particles and an opaque pipeline
if (_visible) {
return ItemKey::Builder::transparentShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer());
} else {
@ -334,12 +338,18 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
gpu::Batch& batch = *args->_batch;
batch.setResourceTexture(0, _networkTexture->getGPUTexture());
Transform transform;
Transform transform;
// The particles are in world space, so the transform is unused, except for the rotation, which we use
// if the particles are marked rotateWithEntity
withReadLock([&] {
transform.setRotation(_renderTransform.getRotation());
auto& color = _uniformBuffer.edit<ParticleUniforms>().color;
color.start = EntityRenderer::calculatePulseColor(_particleProperties.getColorStart(), _pulseProperties, _created);
color.middle = EntityRenderer::calculatePulseColor(_particleProperties.getColorMiddle(), _pulseProperties, _created);
color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created);
color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created);
});
batch.setModelTransform(transform);
batch.setUniformBuffer(0, _uniformBuffer);
batch.setInputFormat(_vertexFormat);

View file

@ -94,6 +94,8 @@ private:
BufferView _uniformBuffer;
quint64 _lastSimulated { 0 };
PulsePropertyGroup _pulseProperties;
NetworkTexturePointer _networkTexture;
ScenePointer _scene;
};

Some files were not shown because too many files have changed in this diff Show more