Merge branch 'master' into 21314
# Conflicts: # interface/resources/qml/controls/TabletWebView.qml
|
@ -16,7 +16,6 @@ Item {
|
|||
property var parentStackItem: null
|
||||
property int headerHeight: 70
|
||||
property string url
|
||||
property alias address: displayUrl.text //for compatibility
|
||||
property string scriptURL
|
||||
property alias eventBridge: eventBridgeWrapper.eventBridge
|
||||
property bool keyboardEnabled: false
|
||||
|
@ -82,6 +81,7 @@ Item {
|
|||
color: hifi.colors.baseGray
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignLeft
|
||||
text: webview.url
|
||||
anchors {
|
||||
top: nav.bottom
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
|
@ -159,7 +159,6 @@ Item {
|
|||
function loadUrl(url) {
|
||||
webview.url = url
|
||||
web.url = webview.url;
|
||||
web.address = webview.url;
|
||||
}
|
||||
|
||||
function onInitialPage(url) {
|
||||
|
@ -253,7 +252,6 @@ Item {
|
|||
});
|
||||
|
||||
webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
|
||||
web.address = url;
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
|
@ -279,7 +277,6 @@ Item {
|
|||
}
|
||||
|
||||
if (WebEngineView.LoadSucceededStatus == loadRequest.status) {
|
||||
web.address = webview.url;
|
||||
if (startingUp) {
|
||||
web.initialPage = webview.url;
|
||||
startingUp = false;
|
||||
|
@ -297,6 +294,7 @@ Item {
|
|||
HiFiControls.Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
||||
numeric: parent.punctuationMode
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
|
@ -308,7 +306,6 @@ Item {
|
|||
Component.onCompleted: {
|
||||
web.isDesktop = (typeof desktop !== "undefined");
|
||||
keyboardEnabled = HMD.active;
|
||||
address = url;
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
|
|
|
@ -44,6 +44,7 @@ Rectangle {
|
|||
property var activeTab: "nearbyTab";
|
||||
property bool currentlyEditingDisplayName: false
|
||||
property bool punctuationMode: false;
|
||||
property var eventBridge;
|
||||
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
|
@ -1036,139 +1037,16 @@ Rectangle {
|
|||
}
|
||||
} // Keyboard
|
||||
|
||||
Item {
|
||||
id: webViewContainer;
|
||||
anchors.fill: parent;
|
||||
|
||||
Rectangle {
|
||||
id: navigationContainer;
|
||||
visible: userInfoViewer.visible;
|
||||
height: 60;
|
||||
anchors {
|
||||
top: parent.top;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
color: hifi.colors.faintGray;
|
||||
|
||||
Item {
|
||||
id: backButton
|
||||
anchors {
|
||||
top: parent.top;
|
||||
left: parent.left;
|
||||
}
|
||||
height: parent.height - addressBar.height;
|
||||
width: parent.width/2;
|
||||
|
||||
FiraSansSemiBold {
|
||||
// Properties
|
||||
text: "BACK";
|
||||
elide: Text.ElideRight;
|
||||
// Anchors
|
||||
anchors.fill: parent;
|
||||
// Text Size
|
||||
size: 16;
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
// Style
|
||||
color: backButtonMouseArea.containsMouse || !userInfoViewer.canGoBack ? hifi.colors.lightGray : hifi.colors.darkGray;
|
||||
MouseArea {
|
||||
id: backButtonMouseArea;
|
||||
anchors.fill: parent
|
||||
hoverEnabled: enabled
|
||||
onClicked: {
|
||||
if (userInfoViewer.canGoBack) {
|
||||
userInfoViewer.goBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: closeButtonContainer
|
||||
anchors {
|
||||
top: parent.top;
|
||||
right: parent.right;
|
||||
}
|
||||
height: parent.height - addressBar.height;
|
||||
width: parent.width/2;
|
||||
|
||||
FiraSansSemiBold {
|
||||
id: closeButton;
|
||||
// Properties
|
||||
text: "CLOSE";
|
||||
elide: Text.ElideRight;
|
||||
// Anchors
|
||||
anchors.fill: parent;
|
||||
// Text Size
|
||||
size: 16;
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
// Style
|
||||
color: hifi.colors.redHighlight;
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: enabled
|
||||
onClicked: userInfoViewer.visible = false;
|
||||
onEntered: closeButton.color = hifi.colors.redAccent;
|
||||
onExited: closeButton.color = hifi.colors.redHighlight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: addressBar
|
||||
anchors {
|
||||
top: closeButtonContainer.bottom;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
height: 30;
|
||||
width: parent.width;
|
||||
|
||||
FiraSansRegular {
|
||||
// Properties
|
||||
text: userInfoViewer.url;
|
||||
elide: Text.ElideRight;
|
||||
// Anchors
|
||||
anchors.fill: parent;
|
||||
anchors.leftMargin: 5;
|
||||
// Text Size
|
||||
size: 14;
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft;
|
||||
// Style
|
||||
color: hifi.colors.lightGray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: webViewBackground;
|
||||
color: "white";
|
||||
visible: userInfoViewer.visible;
|
||||
anchors {
|
||||
top: navigationContainer.bottom;
|
||||
bottom: parent.bottom;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.WebView {
|
||||
id: userInfoViewer;
|
||||
anchors {
|
||||
top: navigationContainer.bottom;
|
||||
bottom: parent.bottom;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
visible: false;
|
||||
HifiControls.TabletWebView {
|
||||
eventBridge: pal.eventBridge;
|
||||
id: userInfoViewer;
|
||||
anchors {
|
||||
top: parent.top;
|
||||
bottom: parent.bottom;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
visible: false;
|
||||
}
|
||||
|
||||
// Timer used when selecting nearbyTable rows that aren't yet present in the model
|
||||
|
|
|
@ -2526,6 +2526,11 @@ bool Application::event(QEvent* event) {
|
|||
|
||||
isPaintingThrottled = false;
|
||||
|
||||
return true;
|
||||
} else if ((int)event->type() == (int)Idle) {
|
||||
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
|
||||
idle(nsecsElapsed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -6491,10 +6496,22 @@ void Application::activeChanged(Qt::ApplicationState state) {
|
|||
}
|
||||
|
||||
void Application::windowMinimizedChanged(bool minimized) {
|
||||
// initialize the _minimizedWindowTimer
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
connect(&_minimizedWindowTimer, &QTimer::timeout, this, [] {
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), new QEvent(static_cast<QEvent::Type>(Idle)), Qt::HighEventPriority);
|
||||
});
|
||||
});
|
||||
|
||||
// avoid rendering to the display plugin but continue posting Idle events,
|
||||
// so that physics continues to simulate and the deadlock watchdog knows we're alive
|
||||
if (!minimized && !getActiveDisplayPlugin()->isActive()) {
|
||||
_minimizedWindowTimer.stop();
|
||||
getActiveDisplayPlugin()->activate();
|
||||
} else if (minimized && getActiveDisplayPlugin()->isActive()) {
|
||||
getActiveDisplayPlugin()->deactivate();
|
||||
_minimizedWindowTimer.start(THROTTLED_SIM_FRAME_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -137,8 +137,9 @@ public:
|
|||
|
||||
enum Event {
|
||||
Present = DisplayPlugin::Present,
|
||||
Paint = Present + 1,
|
||||
Lambda = Paint + 1
|
||||
Paint,
|
||||
Idle,
|
||||
Lambda
|
||||
};
|
||||
|
||||
// FIXME? Empty methods, do we still need them?
|
||||
|
@ -536,6 +537,7 @@ private:
|
|||
RateCounter<> _avatarSimCounter;
|
||||
RateCounter<> _simCounter;
|
||||
|
||||
QTimer _minimizedWindowTimer;
|
||||
QElapsedTimer _timerStart;
|
||||
QElapsedTimer _lastTimeUpdated;
|
||||
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include <ObjectActionSpring.h>
|
||||
#include <ObjectActionTravelOriented.h>
|
||||
#include <ObjectConstraintHinge.h>
|
||||
#include <ObjectConstraintSlider.h>
|
||||
#include <ObjectConstraintBallSocket.h>
|
||||
#include <ObjectConstraintConeTwist.h>
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "InterfaceDynamicFactory.h"
|
||||
|
@ -38,9 +41,15 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid
|
|||
return std::make_shared<ObjectConstraintHinge>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_FAR_GRAB:
|
||||
return std::make_shared<AvatarActionFarGrab>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_SLIDER:
|
||||
return std::make_shared<ObjectConstraintSlider>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_BALL_SOCKET:
|
||||
return std::make_shared<ObjectConstraintBallSocket>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_CONE_TWIST:
|
||||
return std::make_shared<ObjectConstraintConeTwist>(id, ownerEntity);
|
||||
}
|
||||
|
||||
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type");
|
||||
qDebug() << "Unknown entity dynamic type";
|
||||
return EntityDynamicPointer();
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,8 @@ void AvatarActionHold::prepareForPhysicsSimulation() {
|
|||
}
|
||||
|
||||
bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity) {
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale) {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
|
||||
|
||||
|
@ -213,6 +214,9 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
|
||||
// update linearVelocity based on offset via _relativePosition;
|
||||
linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition);
|
||||
|
||||
linearTimeScale = _linearTimeScale;
|
||||
angularTimeScale = _angularTimeScale;
|
||||
});
|
||||
|
||||
return true;
|
||||
|
|
|
@ -38,7 +38,8 @@ public:
|
|||
|
||||
bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity) override;
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale) override;
|
||||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
|
|||
auto dataObject = doc.object().value("data").toObject();
|
||||
QString thumbnailUrl = dataObject.value("thumbnail_url").toString();
|
||||
QString imageUrl = dataObject.value("image_url").toString();
|
||||
QString snapshotID = dataObject.value("id").toString();
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host.
|
||||
QString currentPath = _inWorldLocation.path();
|
||||
|
@ -43,6 +44,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
|
|||
if (dataObject.contains("shareable_url")) {
|
||||
detailsObject.insert("shareable_url", dataObject.value("shareable_url").toString());
|
||||
}
|
||||
detailsObject.insert("snapshot_id", snapshotID);
|
||||
QString pickledDetails = QJsonDocument(detailsObject).toJson();
|
||||
userStoryObject.insert("details", pickledDetails);
|
||||
userStoryObject.insert("thumbnail_url", thumbnailUrl);
|
||||
|
|
|
@ -117,6 +117,15 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT
|
|||
if (normalizedDynamicTypeString == "fargrab") {
|
||||
return DYNAMIC_TYPE_FAR_GRAB;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "slider") {
|
||||
return DYNAMIC_TYPE_SLIDER;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "ballsocket") {
|
||||
return DYNAMIC_TYPE_BALL_SOCKET;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "conetwist") {
|
||||
return DYNAMIC_TYPE_CONE_TWIST;
|
||||
}
|
||||
|
||||
qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name"
|
||||
<< dynamicTypeString;
|
||||
|
@ -139,6 +148,12 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp
|
|||
return "hinge";
|
||||
case DYNAMIC_TYPE_FAR_GRAB:
|
||||
return "far-grab";
|
||||
case DYNAMIC_TYPE_SLIDER:
|
||||
return "slider";
|
||||
case DYNAMIC_TYPE_BALL_SOCKET:
|
||||
return "ball-socket";
|
||||
case DYNAMIC_TYPE_CONE_TWIST:
|
||||
return "cone-twist";
|
||||
}
|
||||
assert(false);
|
||||
return "none";
|
||||
|
|
|
@ -31,7 +31,10 @@ enum EntityDynamicType {
|
|||
DYNAMIC_TYPE_HOLD = 3000,
|
||||
DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000,
|
||||
DYNAMIC_TYPE_HINGE = 5000,
|
||||
DYNAMIC_TYPE_FAR_GRAB = 6000
|
||||
DYNAMIC_TYPE_FAR_GRAB = 6000,
|
||||
DYNAMIC_TYPE_SLIDER = 7000,
|
||||
DYNAMIC_TYPE_BALL_SOCKET = 8000,
|
||||
DYNAMIC_TYPE_CONE_TWIST = 9000
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ Q_DECLARE_LOGGING_CATEGORY(gpugllogging)
|
|||
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl)
|
||||
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl_detail)
|
||||
|
||||
#define BUFFER_OFFSET(bytes) ((GLubyte*) nullptr + (bytes))
|
||||
|
||||
namespace gpu { namespace gl {
|
||||
|
||||
// Create a fence and inject a GPU wait on the fence
|
||||
|
|
|
@ -11,6 +11,20 @@
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
bool GLTexelFormat::isCompressed() const {
|
||||
switch (internalFormat) {
|
||||
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_RED_RGTC1:
|
||||
case GL_COMPRESSED_RG_RGTC2:
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
|
||||
GLenum result = GL_RGBA8;
|
||||
|
|
|
@ -18,6 +18,11 @@ public:
|
|||
GLenum format;
|
||||
GLenum type;
|
||||
|
||||
GLTexelFormat(GLenum glinternalFormat, GLenum glformat, GLenum gltype) : internalFormat(glinternalFormat), format(glformat), type(gltype) {}
|
||||
GLTexelFormat(GLenum glinternalFormat) : internalFormat(glinternalFormat) {}
|
||||
|
||||
bool isCompressed() const;
|
||||
|
||||
static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) {
|
||||
return evalGLTexelFormat(dstFormat, dstFormat);
|
||||
}
|
||||
|
|
|
@ -102,7 +102,8 @@ const std::vector<GLenum>& GLTexture::getFaceTargets(GLenum target) {
|
|||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id) :
|
||||
GLObject(backend, texture, id),
|
||||
_source(texture.source()),
|
||||
_target(getGLTextureType(texture))
|
||||
_target(getGLTextureType(texture)),
|
||||
_texelFormat(GLTexelFormat::evalGLTexelFormatInternal(texture.getTexelFormat()))
|
||||
{
|
||||
Backend::setGPUObject(texture, this);
|
||||
}
|
||||
|
@ -150,6 +151,7 @@ GLExternalTexture::~GLExternalTexture() {
|
|||
// Variable sized textures
|
||||
using MemoryPressureState = GLVariableAllocationSupport::MemoryPressureState;
|
||||
using WorkQueue = GLVariableAllocationSupport::WorkQueue;
|
||||
using TransferJobPointer = GLVariableAllocationSupport::TransferJobPointer;
|
||||
|
||||
std::list<TextureWeakPointer> GLVariableAllocationSupport::_memoryManagedTextures;
|
||||
MemoryPressureState GLVariableAllocationSupport::_memoryPressureState { MemoryPressureState::Idle };
|
||||
|
@ -159,6 +161,7 @@ WorkQueue GLVariableAllocationSupport::_transferQueue;
|
|||
WorkQueue GLVariableAllocationSupport::_promoteQueue;
|
||||
WorkQueue GLVariableAllocationSupport::_demoteQueue;
|
||||
TexturePointer GLVariableAllocationSupport::_currentTransferTexture;
|
||||
TransferJobPointer GLVariableAllocationSupport::_currentTransferJob;
|
||||
size_t GLVariableAllocationSupport::_frameTexturesCreated { 0 };
|
||||
|
||||
#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f
|
||||
|
@ -553,9 +556,15 @@ void GLVariableAllocationSupport::executeNextTransfer(const TexturePointer& curr
|
|||
if (!_pendingTransfers.empty()) {
|
||||
// Keeping hold of a strong pointer during the transfer ensures that the transfer thread cannot try to access a destroyed texture
|
||||
_currentTransferTexture = currentTexture;
|
||||
if (_pendingTransfers.front()->tryTransfer()) {
|
||||
// Keeping hold of a strong pointer to the transfer job ensures that if the pending transfer queue is rebuilt, the transfer job
|
||||
// doesn't leave scope, causing a crash in the buffering thread
|
||||
_currentTransferJob = _pendingTransfers.front();
|
||||
// transfer jobs use asynchronous buffering of the texture data because it may involve disk IO, so we execute a try here to determine if the buffering
|
||||
// is complete
|
||||
if (_currentTransferJob->tryTransfer()) {
|
||||
_pendingTransfers.pop();
|
||||
_currentTransferTexture.reset();
|
||||
_currentTransferJob.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,8 @@ public:
|
|||
void transfer();
|
||||
};
|
||||
|
||||
using TransferQueue = std::queue<std::unique_ptr<TransferJob>>;
|
||||
using TransferJobPointer = std::shared_ptr<TransferJob>;
|
||||
using TransferQueue = std::queue<TransferJobPointer>;
|
||||
static MemoryPressureState _memoryPressureState;
|
||||
|
||||
public:
|
||||
|
@ -100,6 +101,7 @@ protected:
|
|||
static WorkQueue _promoteQueue;
|
||||
static WorkQueue _demoteQueue;
|
||||
static TexturePointer _currentTransferTexture;
|
||||
static TransferJobPointer _currentTransferJob;
|
||||
static const uvec3 INITIAL_MIP_TRANSFER_DIMENSIONS;
|
||||
static const uvec3 MAX_TRANSFER_DIMENSIONS;
|
||||
static const size_t MAX_TRANSFER_SIZE;
|
||||
|
@ -153,6 +155,7 @@ public:
|
|||
const GLuint& _texture { _id };
|
||||
const std::string _source;
|
||||
const GLenum _target;
|
||||
GLTexelFormat _texelFormat;
|
||||
|
||||
static const std::vector<GLenum>& getFaceTargets(GLenum textureType);
|
||||
static uint8_t getFaceCount(GLenum textureType);
|
||||
|
|
|
@ -18,6 +18,8 @@ Q_LOGGING_CATEGORY(gpugl41logging, "hifi.gpu.gl41")
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl41;
|
||||
|
||||
const std::string GL41Backend::GL41_VERSION { "GL41" };
|
||||
|
||||
void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) {
|
||||
Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint;
|
||||
GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType];
|
||||
|
|
|
@ -37,12 +37,16 @@ class GL41Backend : public GLBackend {
|
|||
public:
|
||||
static const GLint TRANSFORM_OBJECT_SLOT { 31 };
|
||||
static const GLint RESOURCE_TRANSFER_TEX_UNIT { 32 };
|
||||
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 33 };
|
||||
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 34 };
|
||||
static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 };
|
||||
static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 };
|
||||
static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 };
|
||||
|
||||
explicit GL41Backend(bool syncCache) : Parent(syncCache) {}
|
||||
GL41Backend() : Parent() {}
|
||||
|
||||
static const std::string GL41_VERSION;
|
||||
const std::string& getVersion() const override { return GL41_VERSION; }
|
||||
|
||||
class GL41Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL41Backend;
|
||||
|
|
|
@ -240,7 +240,9 @@ GL41StrictResourceTexture::GL41StrictResourceTexture(const std::weak_ptr<GLBacke
|
|||
|
||||
using GL41VariableAllocationTexture = GL41Backend::GL41VariableAllocationTexture;
|
||||
|
||||
GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL41Texture(backend, texture) {
|
||||
GL41VariableAllocationTexture::GL41VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) :
|
||||
GL41Texture(backend, texture)
|
||||
{
|
||||
auto mipLevels = texture.getNumMips();
|
||||
_allocatedMip = mipLevels;
|
||||
_maxAllocatedMip = _populatedMip = mipLevels;
|
||||
|
@ -306,6 +308,129 @@ void GL41VariableAllocationTexture::syncSampler() const {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
void copyUncompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {
|
||||
// DestID must be bound to the GL41Backend::RESOURCE_TRANSFER_TEX_UNIT
|
||||
|
||||
GLuint fbo { 0 };
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
|
||||
uint16_t mips = numMips;
|
||||
// copy pre-existing mips
|
||||
for (uint16_t mip = populatedMips; mip < mips; ++mip) {
|
||||
auto mipDimensions = texture.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - destMipOffset;
|
||||
uint16_t sourceMip = mip - srcMipOffset;
|
||||
for (GLenum target : GLTexture::getFaceTargets(texTarget)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, srcId, sourceMip);
|
||||
(void)CHECK_GL_ERROR();
|
||||
glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
// destroy the transfer framebuffer
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
}
|
||||
|
||||
void copyCompressedTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {
|
||||
// DestID must be bound to the GL41Backend::RESOURCE_TRANSFER_TEX_UNIT
|
||||
|
||||
struct MipDesc {
|
||||
GLint _faceSize;
|
||||
GLint _size;
|
||||
GLint _offset;
|
||||
GLint _width;
|
||||
GLint _height;
|
||||
};
|
||||
std::vector<MipDesc> sourceMips(numMips);
|
||||
|
||||
std::vector<GLubyte> bytes;
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_TRANSFER_EXTRA_TEX_UNIT);
|
||||
glBindTexture(texTarget, srcId);
|
||||
const auto& faceTargets = GLTexture::getFaceTargets(texTarget);
|
||||
GLint internalFormat { 0 };
|
||||
|
||||
// Collect the mip description from the source texture
|
||||
GLint bufferOffset { 0 };
|
||||
for (uint16_t mip = populatedMips; mip < numMips; ++mip) {
|
||||
auto& sourceMip = sourceMips[mip];
|
||||
|
||||
uint16_t sourceLevel = mip - srcMipOffset;
|
||||
|
||||
// Grab internal format once
|
||||
if (internalFormat == 0) {
|
||||
glGetTexLevelParameteriv(faceTargets[0], sourceLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
|
||||
}
|
||||
|
||||
// Collect the size of the first face, and then compute the total size offset needed for this mip level
|
||||
auto mipDimensions = texture.evalMipDimensions(mip);
|
||||
sourceMip._width = mipDimensions.x;
|
||||
sourceMip._height = mipDimensions.y;
|
||||
#ifdef DEBUG_COPY
|
||||
glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_WIDTH, &sourceMip._width);
|
||||
glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_HEIGHT, &sourceMip._height);
|
||||
#endif
|
||||
glGetTexLevelParameteriv(faceTargets.front(), sourceLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &sourceMip._faceSize);
|
||||
sourceMip._size = (GLint)faceTargets.size() * sourceMip._faceSize;
|
||||
sourceMip._offset = bufferOffset;
|
||||
bufferOffset += sourceMip._size;
|
||||
gpu::gl::checkGLError();
|
||||
}
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
// Allocate the PBO to accomodate for all the mips to copy
|
||||
GLuint pbo { 0 };
|
||||
glGenBuffers(1, &pbo);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, bufferOffset, nullptr, GL_STATIC_COPY);
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
// Transfer from source texture to pbo
|
||||
for (uint16_t mip = populatedMips; mip < numMips; ++mip) {
|
||||
auto& sourceMip = sourceMips[mip];
|
||||
|
||||
uint16_t sourceLevel = mip - srcMipOffset;
|
||||
|
||||
for (GLint f = 0; f < (GLint)faceTargets.size(); f++) {
|
||||
glGetCompressedTexImage(faceTargets[f], sourceLevel, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize));
|
||||
}
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
// Now populate the new texture from the pbo
|
||||
glBindTexture(texTarget, 0);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_TRANSFER_TEX_UNIT);
|
||||
|
||||
// Transfer from pbo to new texture
|
||||
for (uint16_t mip = populatedMips; mip < numMips; ++mip) {
|
||||
auto& sourceMip = sourceMips[mip];
|
||||
|
||||
uint16_t destLevel = mip - destMipOffset;
|
||||
|
||||
for (GLint f = 0; f < (GLint)faceTargets.size(); f++) {
|
||||
#ifdef DEBUG_COPY
|
||||
GLint destWidth, destHeight, destSize;
|
||||
glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_WIDTH, &destWidth);
|
||||
glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_HEIGHT, &destHeight);
|
||||
glGetTexLevelParameteriv(faceTargets.front(), destLevel, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &destSize);
|
||||
#endif
|
||||
glCompressedTexSubImage2D(faceTargets[f], destLevel, 0, 0, sourceMip._width, sourceMip._height, internalFormat,
|
||||
sourceMip._faceSize, BUFFER_OFFSET(sourceMip._offset + f * sourceMip._faceSize));
|
||||
gpu::gl::checkGLError();
|
||||
}
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glDeleteBuffers(1, &pbo);
|
||||
}
|
||||
|
||||
void GL41VariableAllocationTexture::promote() {
|
||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
||||
Q_ASSERT(_allocatedMip > 0);
|
||||
|
@ -315,36 +440,22 @@ void GL41VariableAllocationTexture::promote() {
|
|||
|
||||
GLuint oldId = _id;
|
||||
auto oldSize = _size;
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
|
||||
// create new texture
|
||||
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
|
||||
// allocate storage for new level
|
||||
allocateStorage(targetAllocatedMip);
|
||||
|
||||
// copy pre-existing mips
|
||||
uint16_t numMips = _gpuObject.getNumMips();
|
||||
withPreservedTexture([&] {
|
||||
GLuint fbo { 0 };
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
|
||||
uint16_t mips = _gpuObject.getNumMips();
|
||||
// copy pre-existing mips
|
||||
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
||||
auto mipDimensions = _gpuObject.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - _allocatedMip;
|
||||
uint16_t sourceMip = mip - oldAllocatedMip;
|
||||
for (GLenum target : getFaceTargets(_target)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip);
|
||||
(void)CHECK_GL_ERROR();
|
||||
glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
if (_texelFormat.isCompressed()) {
|
||||
copyCompressedTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
} else {
|
||||
copyUncompressedTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
}
|
||||
|
||||
// destroy the transfer framebuffer
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
|
||||
syncSampler();
|
||||
});
|
||||
|
||||
|
@ -360,34 +471,21 @@ void GL41VariableAllocationTexture::demote() {
|
|||
Q_ASSERT(_allocatedMip < _maxAllocatedMip);
|
||||
auto oldId = _id;
|
||||
auto oldSize = _size;
|
||||
|
||||
// allocate new texture
|
||||
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
allocateStorage(_allocatedMip + 1);
|
||||
_populatedMip = std::max(_populatedMip, _allocatedMip);
|
||||
|
||||
// copy pre-existing mips
|
||||
uint16_t numMips = _gpuObject.getNumMips();
|
||||
withPreservedTexture([&] {
|
||||
GLuint fbo { 0 };
|
||||
glCreateFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
|
||||
uint16_t mips = _gpuObject.getNumMips();
|
||||
// copy pre-existing mips
|
||||
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
||||
auto mipDimensions = _gpuObject.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - _allocatedMip;
|
||||
uint16_t sourceMip = mip - oldAllocatedMip;
|
||||
for (GLenum target : getFaceTargets(_target)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, oldId, sourceMip);
|
||||
(void)CHECK_GL_ERROR();
|
||||
glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, mipDimensions.x, mipDimensions.y);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
if (_texelFormat.isCompressed()) {
|
||||
copyCompressedTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
} else {
|
||||
copyUncompressedTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
}
|
||||
|
||||
// destroy the transfer framebuffer
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
|
||||
syncSampler();
|
||||
});
|
||||
|
||||
|
@ -460,4 +558,3 @@ GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend
|
|||
GL41ResourceTexture::~GL41ResourceTexture() {
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ Q_LOGGING_CATEGORY(gpugl45logging, "hifi.gpu.gl45")
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl45;
|
||||
|
||||
const std::string GL45Backend::GL45_VERSION { "GL45" };
|
||||
|
||||
void GL45Backend::recycle() const {
|
||||
Parent::recycle();
|
||||
}
|
||||
|
|
|
@ -41,6 +41,9 @@ public:
|
|||
explicit GL45Backend(bool syncCache) : Parent(syncCache) {}
|
||||
GL45Backend() : Parent() {}
|
||||
|
||||
static const std::string GL45_VERSION;
|
||||
const std::string& getVersion() const override { return GL45_VERSION; }
|
||||
|
||||
class GL45Texture : public GLTexture {
|
||||
using Parent = GLTexture;
|
||||
friend class GL45Backend;
|
||||
|
|
|
@ -97,6 +97,24 @@ void GL45ResourceTexture::syncSampler() const {
|
|||
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
|
||||
}
|
||||
|
||||
|
||||
void copyTexGPUMem(const gpu::Texture& texture, GLenum texTarget, GLuint srcId, GLuint destId, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) {
|
||||
for (uint16_t mip = populatedMips; mip < numMips; ++mip) {
|
||||
auto mipDimensions = texture.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - destMipOffset;
|
||||
uint16_t sourceMip = mip - srcMipOffset;
|
||||
auto faces = GLTexture::getFaceCount(texTarget);
|
||||
for (uint8_t face = 0; face < faces; ++face) {
|
||||
glCopyImageSubData(
|
||||
srcId, texTarget, sourceMip, 0, 0, face,
|
||||
destId, texTarget, targetMip, 0, 0, face,
|
||||
mipDimensions.x, mipDimensions.y, 1
|
||||
);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GL45ResourceTexture::promote() {
|
||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
|
||||
Q_ASSERT(_allocatedMip > 0);
|
||||
|
@ -106,27 +124,18 @@ void GL45ResourceTexture::promote() {
|
|||
|
||||
GLuint oldId = _id;
|
||||
auto oldSize = _size;
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
|
||||
// create new texture
|
||||
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
|
||||
// allocate storage for new level
|
||||
allocateStorage(targetAllocatedMip);
|
||||
uint16_t mips = _gpuObject.getNumMips();
|
||||
|
||||
// copy pre-existing mips
|
||||
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
||||
auto mipDimensions = _gpuObject.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - _allocatedMip;
|
||||
uint16_t sourceMip = mip - oldAllocatedMip;
|
||||
auto faces = getFaceCount(_target);
|
||||
for (uint8_t face = 0; face < faces; ++face) {
|
||||
glCopyImageSubData(
|
||||
oldId, _target, sourceMip, 0, 0, face,
|
||||
_id, _target, targetMip, 0, 0, face,
|
||||
mipDimensions.x, mipDimensions.y, 1
|
||||
);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
}
|
||||
uint16_t numMips = _gpuObject.getNumMips();
|
||||
copyTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
|
||||
// destroy the old texture
|
||||
glDeleteTextures(1, &oldId);
|
||||
// update the memory usage
|
||||
|
@ -140,25 +149,17 @@ void GL45ResourceTexture::demote() {
|
|||
Q_ASSERT(_allocatedMip < _maxAllocatedMip);
|
||||
auto oldId = _id;
|
||||
auto oldSize = _size;
|
||||
|
||||
// allocate new texture
|
||||
const_cast<GLuint&>(_id) = allocate(_gpuObject);
|
||||
uint16_t oldAllocatedMip = _allocatedMip;
|
||||
allocateStorage(_allocatedMip + 1);
|
||||
_populatedMip = std::max(_populatedMip, _allocatedMip);
|
||||
uint16_t mips = _gpuObject.getNumMips();
|
||||
|
||||
// copy pre-existing mips
|
||||
for (uint16_t mip = _populatedMip; mip < mips; ++mip) {
|
||||
auto mipDimensions = _gpuObject.evalMipDimensions(mip);
|
||||
uint16_t targetMip = mip - _allocatedMip;
|
||||
uint16_t sourceMip = targetMip + 1;
|
||||
auto faces = getFaceCount(_target);
|
||||
for (uint8_t face = 0; face < faces; ++face) {
|
||||
glCopyImageSubData(
|
||||
oldId, _target, sourceMip, 0, 0, face,
|
||||
_id, _target, targetMip, 0, 0, face,
|
||||
mipDimensions.x, mipDimensions.y, 1
|
||||
);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
}
|
||||
uint16_t numMips = _gpuObject.getNumMips();
|
||||
copyTexGPUMem(_gpuObject, _target, oldId, _id, numMips, oldAllocatedMip, _allocatedMip, _populatedMip);
|
||||
|
||||
// destroy the old texture
|
||||
glDeleteTextures(1, &oldId);
|
||||
// update the memory usage
|
||||
|
|
|
@ -50,6 +50,10 @@ Context::Context(const Context& context) {
|
|||
Context::~Context() {
|
||||
}
|
||||
|
||||
const std::string& Context::getBackendVersion() const {
|
||||
return _backend->getVersion();
|
||||
}
|
||||
|
||||
void Context::beginFrame(const glm::mat4& renderPose) {
|
||||
assert(!_frameActive);
|
||||
_frameActive = true;
|
||||
|
|
|
@ -54,6 +54,9 @@ class Backend {
|
|||
public:
|
||||
virtual~ Backend() {};
|
||||
|
||||
|
||||
virtual const std::string& getVersion() const = 0;
|
||||
|
||||
void setStereoState(const StereoState& stereo) { _stereo = stereo; }
|
||||
|
||||
virtual void render(const Batch& batch) = 0;
|
||||
|
@ -153,6 +156,8 @@ public:
|
|||
Context();
|
||||
~Context();
|
||||
|
||||
const std::string& getBackendVersion() const;
|
||||
|
||||
void beginFrame(const glm::mat4& renderPose = glm::mat4());
|
||||
void appendFrameBatch(Batch& batch);
|
||||
FramePointer endFrame();
|
||||
|
|
|
@ -216,6 +216,7 @@ void Texture::MemoryStorage::assignMipFaceData(uint16 level, uint8 face, const s
|
|||
TexturePointer Texture::createExternal(const ExternalRecycler& recycler, const Sampler& sampler) {
|
||||
TexturePointer tex = std::make_shared<Texture>(TextureUsageType::EXTERNAL);
|
||||
tex->_type = TEX_2D;
|
||||
tex->_texelFormat = Element::COLOR_RGBA_32;
|
||||
tex->_maxMipLevel = 0;
|
||||
tex->_sampler = sampler;
|
||||
tex->setExternalRecycler(recycler);
|
||||
|
@ -407,8 +408,12 @@ void Texture::setStoredMipFormat(const Element& format) {
|
|||
_storage->setFormat(format);
|
||||
}
|
||||
|
||||
const Element& Texture::getStoredMipFormat() const {
|
||||
return _storage->getFormat();
|
||||
Element Texture::getStoredMipFormat() const {
|
||||
if (_storage) {
|
||||
return _storage->getFormat();
|
||||
} else {
|
||||
return Element();
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::assignStoredMip(uint16 level, Size size, const Byte* bytes) {
|
||||
|
|
|
@ -285,7 +285,7 @@ public:
|
|||
Stamp bumpStamp() { return ++_stamp; }
|
||||
|
||||
void setFormat(const Element& format) { _format = format; }
|
||||
const Element& getFormat() const { return _format; }
|
||||
Element getFormat() const { return _format; }
|
||||
|
||||
private:
|
||||
Stamp _stamp { 0 };
|
||||
|
@ -324,11 +324,11 @@ public:
|
|||
void reset() override { }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<storage::FileStorage> maybeOpenFile();
|
||||
std::shared_ptr<storage::FileStorage> maybeOpenFile() const;
|
||||
|
||||
std::mutex _cacheFileCreateMutex;
|
||||
std::mutex _cacheFileWriteMutex;
|
||||
std::weak_ptr<storage::FileStorage> _cacheFile;
|
||||
mutable std::mutex _cacheFileCreateMutex;
|
||||
mutable std::mutex _cacheFileWriteMutex;
|
||||
mutable std::weak_ptr<storage::FileStorage> _cacheFile;
|
||||
|
||||
std::string _filename;
|
||||
std::atomic<uint8_t> _minMipLevelAvailable;
|
||||
|
@ -372,7 +372,7 @@ public:
|
|||
bool isColorRenderTarget() const;
|
||||
bool isDepthStencilRenderTarget() const;
|
||||
|
||||
const Element& getTexelFormat() const { return _texelFormat; }
|
||||
Element getTexelFormat() const { return _texelFormat; }
|
||||
|
||||
Vec3u getDimensions() const { return Vec3u(_width, _height, _depth); }
|
||||
uint16 getWidth() const { return _width; }
|
||||
|
@ -468,7 +468,7 @@ public:
|
|||
|
||||
// Mip storage format is constant across all mips
|
||||
void setStoredMipFormat(const Element& format);
|
||||
const Element& getStoredMipFormat() const;
|
||||
Element getStoredMipFormat() const;
|
||||
|
||||
// Manually allocate the mips down until the specified maxMip
|
||||
// this is just allocating the sysmem version of it
|
||||
|
|
|
@ -128,7 +128,7 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
|
|||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() {
|
||||
std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() const {
|
||||
std::shared_ptr<storage::FileStorage> file = _cacheFile.lock();
|
||||
if (file) {
|
||||
return file;
|
||||
|
@ -154,7 +154,8 @@ PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
|
|||
auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
|
||||
auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
|
||||
if (faceSize != 0 && faceOffset != 0) {
|
||||
result = std::make_shared<storage::FileStorage>(_filename.c_str())->createView(faceSize, faceOffset)->toMemoryStorage();
|
||||
auto file = maybeOpenFile();
|
||||
result = file->createView(faceSize, faceOffset)->toMemoryStorage();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -334,6 +334,22 @@ private:
|
|||
int _maxNumPixels;
|
||||
};
|
||||
|
||||
NetworkTexture::~NetworkTexture() {
|
||||
if (_ktxHeaderRequest || _ktxMipRequest) {
|
||||
if (_ktxHeaderRequest) {
|
||||
_ktxHeaderRequest->disconnect(this);
|
||||
_ktxHeaderRequest->deleteLater();
|
||||
_ktxHeaderRequest = nullptr;
|
||||
}
|
||||
if (_ktxMipRequest) {
|
||||
_ktxMipRequest->disconnect(this);
|
||||
_ktxMipRequest->deleteLater();
|
||||
_ktxMipRequest = nullptr;
|
||||
}
|
||||
TextureCache::requestCompleted(_self);
|
||||
}
|
||||
}
|
||||
|
||||
const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits<uint16_t>::max();
|
||||
void NetworkTexture::makeRequest() {
|
||||
if (!_sourceIsKTX) {
|
||||
|
@ -398,13 +414,18 @@ void NetworkTexture::startRequestForNextMipLevel() {
|
|||
}
|
||||
|
||||
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
|
||||
auto self = _self.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ktxResourceState = PENDING_MIP_REQUEST;
|
||||
|
||||
init();
|
||||
float priority = -(float)_originalKtxDescriptor->header.numberOfMipmapLevels + (float)_lowestKnownPopulatedMip;
|
||||
setLoadPriority(this, priority);
|
||||
_url.setFragment(QString::number(_lowestKnownPopulatedMip - 1));
|
||||
TextureCache::attemptRequest(_self);
|
||||
TextureCache::attemptRequest(self);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,19 +494,16 @@ void NetworkTexture::ktxMipRequestFinished() {
|
|||
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
|
||||
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
|
||||
_lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qWarning(networking) << "Trying to update mips but texture is null";
|
||||
}
|
||||
finishedLoading(true);
|
||||
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
finishedLoading(false);
|
||||
if (handleFailedRequest(_ktxMipRequest->getResult())) {
|
||||
_ktxResourceState = PENDING_MIP_REQUEST;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qWarning(networking) << "Failed to load mip: " << _url;
|
||||
_ktxResourceState = FAILED_TO_LOAD;
|
||||
}
|
||||
|
@ -497,8 +515,7 @@ void NetworkTexture::ktxMipRequestFinished() {
|
|||
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST && _lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
|
||||
startRequestForNextMipLevel();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ class NetworkTexture : public Resource, public Texture {
|
|||
|
||||
public:
|
||||
NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
|
||||
~NetworkTexture() override;
|
||||
|
||||
QString getType() const override { return "NetworkTexture"; }
|
||||
|
||||
|
|
|
@ -344,7 +344,7 @@ class Resource : public QObject {
|
|||
public:
|
||||
|
||||
Resource(const QUrl& url);
|
||||
~Resource();
|
||||
virtual ~Resource();
|
||||
|
||||
virtual QString getType() const { return "Resource"; }
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
case PacketType::EntityPhysics:
|
||||
return VERSION_ENTITIES_HINGE_CONSTRAINT;
|
||||
return VERSION_ENTITIES_BULLET_DYNAMICS;
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
|
||||
case PacketType::AvatarIdentity:
|
||||
|
|
|
@ -208,6 +208,7 @@ const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66;
|
|||
const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
|
||||
const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68;
|
||||
const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69;
|
||||
const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70;
|
||||
|
||||
enum class EntityQueryPacketVersion: PacketVersion {
|
||||
JSONFilter = 18,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "PhysicsLogging.h"
|
||||
|
||||
const float SPRING_MAX_SPEED = 10.0f;
|
||||
const float MAX_SPRING_TIMESCALE = 600.0f; // 10 min is a long time
|
||||
|
||||
const uint16_t ObjectActionSpring::springVersion = 1;
|
||||
|
||||
|
@ -41,12 +42,65 @@ ObjectActionSpring::~ObjectActionSpring() {
|
|||
#endif
|
||||
}
|
||||
|
||||
SpatiallyNestablePointer ObjectActionSpring::getOther() {
|
||||
SpatiallyNestablePointer other;
|
||||
withWriteLock([&]{
|
||||
if (_otherID == QUuid()) {
|
||||
// no other
|
||||
return;
|
||||
}
|
||||
other = _other.lock();
|
||||
if (other && other->getID() == _otherID) {
|
||||
// other is already up-to-date
|
||||
return;
|
||||
}
|
||||
if (other) {
|
||||
// we have a pointer to other, but it's wrong
|
||||
other.reset();
|
||||
_other.reset();
|
||||
}
|
||||
// we have an other-id but no pointer to other cached
|
||||
QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();
|
||||
if (!parentFinder) {
|
||||
return;
|
||||
}
|
||||
EntityItemPointer ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return;
|
||||
}
|
||||
bool success;
|
||||
_other = parentFinder->find(_otherID, success, ownerEntity->getParentTree());
|
||||
if (success) {
|
||||
other = _other.lock();
|
||||
}
|
||||
});
|
||||
return other;
|
||||
}
|
||||
|
||||
bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity) {
|
||||
rotation = _desiredRotationalTarget;
|
||||
position = _desiredPositionalTarget;
|
||||
linearVelocity = glm::vec3();
|
||||
angularVelocity = glm::vec3();
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale) {
|
||||
SpatiallyNestablePointer other = getOther();
|
||||
withReadLock([&]{
|
||||
linearTimeScale = _linearTimeScale;
|
||||
angularTimeScale = _angularTimeScale;
|
||||
|
||||
if (!_otherID.isNull()) {
|
||||
if (other) {
|
||||
rotation = _desiredRotationalTarget * other->getRotation();
|
||||
position = other->getRotation() * _desiredPositionalTarget + other->getPosition();
|
||||
} else {
|
||||
// we should have an "other" but can't find it, so disable the spring.
|
||||
linearTimeScale = FLT_MAX;
|
||||
angularTimeScale = FLT_MAX;
|
||||
}
|
||||
} else {
|
||||
rotation = _desiredRotationalTarget;
|
||||
position = _desiredPositionalTarget;
|
||||
}
|
||||
linearVelocity = glm::vec3();
|
||||
angularVelocity = glm::vec3();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -61,8 +115,10 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
|
|||
glm::vec3 linearVelocity;
|
||||
glm::vec3 angularVelocity;
|
||||
|
||||
bool valid = false;
|
||||
int springCount = 0;
|
||||
bool linearValid = false;
|
||||
int linearSpringCount = 0;
|
||||
bool angularValid = false;
|
||||
int angularSpringCount = 0;
|
||||
|
||||
QList<EntityDynamicPointer> springDerivedActions;
|
||||
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING));
|
||||
|
@ -73,41 +129,55 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
|
|||
std::shared_ptr<ObjectActionSpring> springAction = std::static_pointer_cast<ObjectActionSpring>(action);
|
||||
glm::quat rotationForAction;
|
||||
glm::vec3 positionForAction;
|
||||
glm::vec3 linearVelocityForAction, angularVelocityForAction;
|
||||
bool success = springAction->getTarget(deltaTimeStep, rotationForAction,
|
||||
positionForAction, linearVelocityForAction,
|
||||
angularVelocityForAction);
|
||||
glm::vec3 linearVelocityForAction;
|
||||
glm::vec3 angularVelocityForAction;
|
||||
float linearTimeScale;
|
||||
float angularTimeScale;
|
||||
bool success = springAction->getTarget(deltaTimeStep,
|
||||
rotationForAction, positionForAction,
|
||||
linearVelocityForAction, angularVelocityForAction,
|
||||
linearTimeScale, angularTimeScale);
|
||||
if (success) {
|
||||
springCount ++;
|
||||
if (springAction.get() == this) {
|
||||
// only use the rotation for this action
|
||||
valid = true;
|
||||
rotation = rotationForAction;
|
||||
if (angularTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
angularValid = true;
|
||||
angularSpringCount++;
|
||||
angularVelocity += angularVelocityForAction;
|
||||
if (springAction.get() == this) {
|
||||
// only use the rotation for this action
|
||||
rotation = rotationForAction;
|
||||
}
|
||||
}
|
||||
|
||||
position += positionForAction;
|
||||
linearVelocity += linearVelocityForAction;
|
||||
angularVelocity += angularVelocityForAction;
|
||||
if (linearTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
linearValid = true;
|
||||
linearSpringCount++;
|
||||
position += positionForAction;
|
||||
linearVelocity += linearVelocityForAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valid && springCount > 0) {
|
||||
position /= springCount;
|
||||
linearVelocity /= springCount;
|
||||
angularVelocity /= springCount;
|
||||
|
||||
if ((angularValid && angularSpringCount > 0) || (linearValid && linearSpringCount > 0)) {
|
||||
withWriteLock([&]{
|
||||
_positionalTarget = position;
|
||||
_rotationalTarget = rotation;
|
||||
_linearVelocityTarget = linearVelocity;
|
||||
_angularVelocityTarget = angularVelocity;
|
||||
_positionalTargetSet = true;
|
||||
_rotationalTargetSet = true;
|
||||
_active = true;
|
||||
if (linearValid && linearSpringCount > 0) {
|
||||
position /= linearSpringCount;
|
||||
linearVelocity /= linearSpringCount;
|
||||
_positionalTarget = position;
|
||||
_linearVelocityTarget = linearVelocity;
|
||||
_positionalTargetSet = true;
|
||||
_active = true;
|
||||
}
|
||||
if (angularValid && angularSpringCount > 0) {
|
||||
angularVelocity /= angularSpringCount;
|
||||
_rotationalTarget = rotation;
|
||||
_angularVelocityTarget = angularVelocity;
|
||||
_rotationalTargetSet = true;
|
||||
_active = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return valid;
|
||||
return linearValid || angularValid;
|
||||
}
|
||||
|
||||
|
||||
|
@ -133,8 +203,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
return;
|
||||
}
|
||||
|
||||
const float MAX_TIMESCALE = 600.0f; // 10 min is a long time
|
||||
if (_linearTimeScale < MAX_TIMESCALE) {
|
||||
if (_linearTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
|
||||
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
|
||||
float offsetLength = offset.length();
|
||||
|
@ -150,7 +219,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
rigidBody->setLinearVelocity(targetVelocity);
|
||||
}
|
||||
|
||||
if (_angularTimeScale < MAX_TIMESCALE) {
|
||||
if (_angularTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
|
||||
|
||||
btQuaternion bodyRotation = rigidBody->getOrientation();
|
||||
|
@ -189,6 +258,8 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
float linearTimeScale;
|
||||
glm::quat rotationalTarget;
|
||||
float angularTimeScale;
|
||||
QUuid otherID;
|
||||
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
|
@ -218,11 +289,19 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
angularTimeScale = _angularTimeScale;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
otherID = QUuid(EntityDynamicInterface::extractStringArgument("spring action",
|
||||
arguments, "otherID", ok, false));
|
||||
if (!ok) {
|
||||
otherID = _otherID;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
positionalTarget != _desiredPositionalTarget ||
|
||||
linearTimeScale != _linearTimeScale ||
|
||||
rotationalTarget != _desiredRotationalTarget ||
|
||||
angularTimeScale != _angularTimeScale) {
|
||||
angularTimeScale != _angularTimeScale ||
|
||||
otherID != _otherID) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
|
@ -234,6 +313,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
_linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
|
||||
_desiredRotationalTarget = rotationalTarget;
|
||||
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
|
||||
_otherID = otherID;
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
|
@ -256,6 +336,8 @@ QVariantMap ObjectActionSpring::getArguments() {
|
|||
|
||||
arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget);
|
||||
arguments["angularTimeScale"] = _angularTimeScale;
|
||||
|
||||
arguments["otherID"] = _otherID;
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
@ -270,6 +352,7 @@ void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const {
|
|||
dataStream << _rotationalTargetSet;
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
dataStream << _otherID;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -302,6 +385,8 @@ void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, Q
|
|||
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _otherID;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ public:
|
|||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity);
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale);
|
||||
|
||||
protected:
|
||||
static const uint16_t springVersion;
|
||||
|
@ -46,6 +47,10 @@ protected:
|
|||
glm::vec3 _linearVelocityTarget;
|
||||
glm::vec3 _angularVelocityTarget;
|
||||
|
||||
EntityItemID _otherID;
|
||||
SpatiallyNestableWeakPointer _other;
|
||||
SpatiallyNestablePointer getOther();
|
||||
|
||||
virtual bool prepareForSpringUpdate(btScalar deltaTimeStep);
|
||||
|
||||
void serializeParameters(QDataStream& dataStream) const;
|
||||
|
|
240
libraries/physics/src/ObjectConstraintBallSocket.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
//
|
||||
// ObjectConstraintBallSocket.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-29
|
||||
// Copyright 2017 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 "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "ObjectConstraintBallSocket.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
|
||||
const uint16_t ObjectConstraintBallSocket::constraintVersion = 1;
|
||||
|
||||
|
||||
ObjectConstraintBallSocket::ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectConstraint(DYNAMIC_TYPE_BALL_SOCKET, id, ownerEntity),
|
||||
_pivotInA(glm::vec3(0.0f)),
|
||||
_pivotInB(glm::vec3(0.0f))
|
||||
{
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectConstraintBallSocket::ObjectConstraintBallSocket";
|
||||
#endif
|
||||
}
|
||||
|
||||
ObjectConstraintBallSocket::~ObjectConstraintBallSocket() {
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectConstraintBallSocket::~ObjectConstraintBallSocket";
|
||||
#endif
|
||||
}
|
||||
|
||||
QList<btRigidBody*> ObjectConstraintBallSocket::getRigidBodies() {
|
||||
QList<btRigidBody*> result;
|
||||
result += getRigidBody();
|
||||
QUuid otherEntityID;
|
||||
withReadLock([&]{
|
||||
otherEntityID = _otherEntityID;
|
||||
});
|
||||
if (!otherEntityID.isNull()) {
|
||||
result += getOtherRigidBody(otherEntityID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ObjectConstraintBallSocket::prepareForPhysicsSimulation() {
|
||||
}
|
||||
|
||||
void ObjectConstraintBallSocket::updateBallSocket() {
|
||||
btPoint2PointConstraint* constraint { nullptr };
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btPoint2PointConstraint*>(_constraint);
|
||||
});
|
||||
|
||||
if (!constraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
constraint->setPivotA(glmToBullet(_pivotInA));
|
||||
constraint->setPivotB(glmToBullet(_pivotInB));
|
||||
}
|
||||
|
||||
|
||||
btTypedConstraint* ObjectConstraintBallSocket::getConstraint() {
|
||||
btPoint2PointConstraint* constraint { nullptr };
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pivotInA;
|
||||
glm::vec3 pivotInB;
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btPoint2PointConstraint*>(_constraint);
|
||||
pivotInA = _pivotInA;
|
||||
otherEntityID = _otherEntityID;
|
||||
pivotInB = _pivotInB;
|
||||
});
|
||||
if (constraint) {
|
||||
return constraint;
|
||||
}
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!otherEntityID.isNull()) {
|
||||
// This constraint is between two entities... find the other rigid body.
|
||||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
constraint = new btPoint2PointConstraint(*rigidBodyA, *rigidBodyB, glmToBullet(pivotInA), glmToBullet(pivotInB));
|
||||
} else {
|
||||
// This constraint is between an entity and the world-frame.
|
||||
|
||||
constraint = new btPoint2PointConstraint(*rigidBodyA, glmToBullet(pivotInA));
|
||||
}
|
||||
|
||||
withWriteLock([&]{
|
||||
_constraint = constraint;
|
||||
});
|
||||
|
||||
// if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
|
||||
forceBodyNonStatic();
|
||||
activateBody();
|
||||
|
||||
updateBallSocket();
|
||||
|
||||
return constraint;
|
||||
}
|
||||
|
||||
|
||||
bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) {
|
||||
glm::vec3 pivotInA;
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pivotInB;
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
withReadLock([&]{
|
||||
bool ok = true;
|
||||
pivotInA = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "pivot", ok, false);
|
||||
if (!ok) {
|
||||
pivotInA = _pivotInA;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("ball-socket constraint",
|
||||
arguments, "otherEntityID", ok, false));
|
||||
if (!ok) {
|
||||
otherEntityID = _otherEntityID;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
pivotInB = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "otherPivot", ok, false);
|
||||
if (!ok) {
|
||||
pivotInB = _pivotInB;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
pivotInA != _pivotInA ||
|
||||
otherEntityID != _otherEntityID ||
|
||||
pivotInB != _pivotInB) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (needUpdate) {
|
||||
withWriteLock([&] {
|
||||
_pivotInA = pivotInA;
|
||||
_otherEntityID = otherEntityID;
|
||||
_pivotInB = pivotInB;
|
||||
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
}
|
||||
});
|
||||
|
||||
updateBallSocket();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectConstraintBallSocket::getArguments() {
|
||||
QVariantMap arguments = ObjectDynamic::getArguments();
|
||||
withReadLock([&] {
|
||||
if (_constraint) {
|
||||
arguments["pivot"] = glmToQMap(_pivotInA);
|
||||
arguments["otherEntityID"] = _otherEntityID;
|
||||
arguments["otherPivot"] = glmToQMap(_pivotInB);
|
||||
}
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
||||
QByteArray ObjectConstraintBallSocket::serialize() const {
|
||||
QByteArray serializedConstraintArguments;
|
||||
QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << DYNAMIC_TYPE_BALL_SOCKET;
|
||||
dataStream << getID();
|
||||
dataStream << ObjectConstraintBallSocket::constraintVersion;
|
||||
|
||||
withReadLock([&] {
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
|
||||
dataStream << _pivotInA;
|
||||
dataStream << _otherEntityID;
|
||||
dataStream << _pivotInB;
|
||||
});
|
||||
|
||||
return serializedConstraintArguments;
|
||||
}
|
||||
|
||||
void ObjectConstraintBallSocket::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
|
||||
QUuid id;
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
|
||||
uint16_t serializationVersion;
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectConstraintBallSocket::constraintVersion) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
quint64 serverExpires;
|
||||
dataStream >> serverExpires;
|
||||
_expires = serverTimeToLocalTime(serverExpires);
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _pivotInA;
|
||||
dataStream >> _otherEntityID;
|
||||
dataStream >> _pivotInB;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
46
libraries/physics/src/ObjectConstraintBallSocket.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// ObjectConstraintBallSocket.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-29
|
||||
// Copyright 2017 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_ObjectConstraintBallSocket_h
|
||||
#define hifi_ObjectConstraintBallSocket_h
|
||||
|
||||
#include "ObjectConstraint.h"
|
||||
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtBallSocketConstraint.html
|
||||
|
||||
class ObjectConstraintBallSocket : public ObjectConstraint {
|
||||
public:
|
||||
ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectConstraintBallSocket();
|
||||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
virtual QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
virtual QList<btRigidBody*> getRigidBodies() override;
|
||||
virtual btTypedConstraint* getConstraint() override;
|
||||
|
||||
protected:
|
||||
static const uint16_t constraintVersion;
|
||||
|
||||
void updateBallSocket();
|
||||
|
||||
glm::vec3 _pivotInA;
|
||||
|
||||
EntityItemID _otherEntityID;
|
||||
glm::vec3 _pivotInB;
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectConstraintBallSocket_h
|
367
libraries/physics/src/ObjectConstraintConeTwist.cpp
Normal file
|
@ -0,0 +1,367 @@
|
|||
//
|
||||
// ObjectConstraintConeTwist.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-29
|
||||
// Copyright 2017 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 "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "ObjectConstraintConeTwist.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
|
||||
const uint16_t ObjectConstraintConeTwist::constraintVersion = 1;
|
||||
|
||||
|
||||
ObjectConstraintConeTwist::ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectConstraint(DYNAMIC_TYPE_CONE_TWIST, id, ownerEntity),
|
||||
_pivotInA(glm::vec3(0.0f)),
|
||||
_axisInA(glm::vec3(0.0f))
|
||||
{
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectConstraintConeTwist::ObjectConstraintConeTwist";
|
||||
#endif
|
||||
}
|
||||
|
||||
ObjectConstraintConeTwist::~ObjectConstraintConeTwist() {
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectConstraintConeTwist::~ObjectConstraintConeTwist";
|
||||
#endif
|
||||
}
|
||||
|
||||
QList<btRigidBody*> ObjectConstraintConeTwist::getRigidBodies() {
|
||||
QList<btRigidBody*> result;
|
||||
result += getRigidBody();
|
||||
QUuid otherEntityID;
|
||||
withReadLock([&]{
|
||||
otherEntityID = _otherEntityID;
|
||||
});
|
||||
if (!otherEntityID.isNull()) {
|
||||
result += getOtherRigidBody(otherEntityID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ObjectConstraintConeTwist::prepareForPhysicsSimulation() {
|
||||
}
|
||||
|
||||
void ObjectConstraintConeTwist::updateConeTwist() {
|
||||
btConeTwistConstraint* constraint { nullptr };
|
||||
float swingSpan1;
|
||||
float swingSpan2;
|
||||
float twistSpan;
|
||||
float softness;
|
||||
float biasFactor;
|
||||
float relaxationFactor;
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btConeTwistConstraint*>(_constraint);
|
||||
swingSpan1 = _swingSpan1;
|
||||
swingSpan2 = _swingSpan2;
|
||||
twistSpan = _twistSpan;
|
||||
softness = _softness;
|
||||
biasFactor = _biasFactor;
|
||||
relaxationFactor = _relaxationFactor;
|
||||
});
|
||||
|
||||
if (!constraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
constraint->setLimit(swingSpan1,
|
||||
swingSpan2,
|
||||
twistSpan,
|
||||
softness,
|
||||
biasFactor,
|
||||
relaxationFactor);
|
||||
}
|
||||
|
||||
|
||||
btTypedConstraint* ObjectConstraintConeTwist::getConstraint() {
|
||||
btConeTwistConstraint* constraint { nullptr };
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pivotInA;
|
||||
glm::vec3 axisInA;
|
||||
glm::vec3 pivotInB;
|
||||
glm::vec3 axisInB;
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btConeTwistConstraint*>(_constraint);
|
||||
pivotInA = _pivotInA;
|
||||
axisInA = _axisInA;
|
||||
otherEntityID = _otherEntityID;
|
||||
pivotInB = _pivotInB;
|
||||
axisInB = _axisInB;
|
||||
});
|
||||
if (constraint) {
|
||||
return constraint;
|
||||
}
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!otherEntityID.isNull()) {
|
||||
// This coneTwist is between two entities... find the other rigid body.
|
||||
|
||||
glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
|
||||
glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB));
|
||||
|
||||
btTransform frameInA(glmToBullet(rotA), glmToBullet(pivotInA));
|
||||
btTransform frameInB(glmToBullet(rotB), glmToBullet(pivotInB));
|
||||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
constraint = new btConeTwistConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB);
|
||||
} else {
|
||||
// This coneTwist is between an entity and the world-frame.
|
||||
|
||||
glm::quat rot = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
|
||||
|
||||
btTransform frameInA(glmToBullet(rot), glmToBullet(pivotInA));
|
||||
|
||||
constraint = new btConeTwistConstraint(*rigidBodyA, frameInA);
|
||||
}
|
||||
|
||||
withWriteLock([&]{
|
||||
_constraint = constraint;
|
||||
});
|
||||
|
||||
// if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
|
||||
forceBodyNonStatic();
|
||||
activateBody();
|
||||
|
||||
updateConeTwist();
|
||||
|
||||
return constraint;
|
||||
}
|
||||
|
||||
|
||||
bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) {
|
||||
glm::vec3 pivotInA;
|
||||
glm::vec3 axisInA;
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pivotInB;
|
||||
glm::vec3 axisInB;
|
||||
float swingSpan1;
|
||||
float swingSpan2;
|
||||
float twistSpan;
|
||||
float softness;
|
||||
float biasFactor;
|
||||
float relaxationFactor;
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
withReadLock([&]{
|
||||
bool ok = true;
|
||||
pivotInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "pivot", ok, false);
|
||||
if (!ok) {
|
||||
pivotInA = _pivotInA;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
axisInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "axis", ok, false);
|
||||
if (!ok) {
|
||||
axisInA = _axisInA;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("coneTwist constraint",
|
||||
arguments, "otherEntityID", ok, false));
|
||||
if (!ok) {
|
||||
otherEntityID = _otherEntityID;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
pivotInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherPivot", ok, false);
|
||||
if (!ok) {
|
||||
pivotInB = _pivotInB;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
axisInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherAxis", ok, false);
|
||||
if (!ok) {
|
||||
axisInB = _axisInB;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
swingSpan1 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan1", ok, false);
|
||||
if (!ok) {
|
||||
swingSpan1 = _swingSpan1;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
swingSpan2 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan2", ok, false);
|
||||
if (!ok) {
|
||||
swingSpan2 = _swingSpan2;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
twistSpan = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "twistSpan", ok, false);
|
||||
if (!ok) {
|
||||
twistSpan = _twistSpan;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
softness = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "softness", ok, false);
|
||||
if (!ok) {
|
||||
softness = _softness;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
biasFactor = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "biasFactor", ok, false);
|
||||
if (!ok) {
|
||||
biasFactor = _biasFactor;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
relaxationFactor =
|
||||
EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "relaxationFactor", ok, false);
|
||||
if (!ok) {
|
||||
relaxationFactor = _relaxationFactor;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
pivotInA != _pivotInA ||
|
||||
axisInA != _axisInA ||
|
||||
otherEntityID != _otherEntityID ||
|
||||
pivotInB != _pivotInB ||
|
||||
axisInB != _axisInB ||
|
||||
swingSpan1 != _swingSpan1 ||
|
||||
swingSpan2 != _swingSpan2 ||
|
||||
twistSpan != _twistSpan ||
|
||||
softness != _softness ||
|
||||
biasFactor != _biasFactor ||
|
||||
relaxationFactor != _relaxationFactor) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (needUpdate) {
|
||||
withWriteLock([&] {
|
||||
_pivotInA = pivotInA;
|
||||
_axisInA = axisInA;
|
||||
_otherEntityID = otherEntityID;
|
||||
_pivotInB = pivotInB;
|
||||
_axisInB = axisInB;
|
||||
_swingSpan1 = swingSpan1;
|
||||
_swingSpan2 = swingSpan2;
|
||||
_twistSpan = twistSpan;
|
||||
_softness = softness;
|
||||
_biasFactor = biasFactor;
|
||||
_relaxationFactor = relaxationFactor;
|
||||
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
}
|
||||
});
|
||||
|
||||
updateConeTwist();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectConstraintConeTwist::getArguments() {
|
||||
QVariantMap arguments = ObjectDynamic::getArguments();
|
||||
withReadLock([&] {
|
||||
if (_constraint) {
|
||||
arguments["pivot"] = glmToQMap(_pivotInA);
|
||||
arguments["axis"] = glmToQMap(_axisInA);
|
||||
arguments["otherEntityID"] = _otherEntityID;
|
||||
arguments["otherPivot"] = glmToQMap(_pivotInB);
|
||||
arguments["otherAxis"] = glmToQMap(_axisInB);
|
||||
arguments["swingSpan1"] = _swingSpan1;
|
||||
arguments["swingSpan2"] = _swingSpan2;
|
||||
arguments["twistSpan"] = _twistSpan;
|
||||
arguments["softness"] = _softness;
|
||||
arguments["biasFactor"] = _biasFactor;
|
||||
arguments["relaxationFactor"] = _relaxationFactor;
|
||||
}
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
||||
QByteArray ObjectConstraintConeTwist::serialize() const {
|
||||
QByteArray serializedConstraintArguments;
|
||||
QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << DYNAMIC_TYPE_CONE_TWIST;
|
||||
dataStream << getID();
|
||||
dataStream << ObjectConstraintConeTwist::constraintVersion;
|
||||
|
||||
withReadLock([&] {
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
|
||||
dataStream << _pivotInA;
|
||||
dataStream << _axisInA;
|
||||
dataStream << _otherEntityID;
|
||||
dataStream << _pivotInB;
|
||||
dataStream << _axisInB;
|
||||
dataStream << _swingSpan1;
|
||||
dataStream << _swingSpan2;
|
||||
dataStream << _twistSpan;
|
||||
dataStream << _softness;
|
||||
dataStream << _biasFactor;
|
||||
dataStream << _relaxationFactor;
|
||||
});
|
||||
|
||||
return serializedConstraintArguments;
|
||||
}
|
||||
|
||||
void ObjectConstraintConeTwist::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
|
||||
QUuid id;
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
|
||||
uint16_t serializationVersion;
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectConstraintConeTwist::constraintVersion) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
quint64 serverExpires;
|
||||
dataStream >> serverExpires;
|
||||
_expires = serverTimeToLocalTime(serverExpires);
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _pivotInA;
|
||||
dataStream >> _axisInA;
|
||||
dataStream >> _otherEntityID;
|
||||
dataStream >> _pivotInB;
|
||||
dataStream >> _axisInB;
|
||||
dataStream >> _swingSpan1;
|
||||
dataStream >> _swingSpan2;
|
||||
dataStream >> _twistSpan;
|
||||
dataStream >> _softness;
|
||||
dataStream >> _biasFactor;
|
||||
dataStream >> _relaxationFactor;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
55
libraries/physics/src/ObjectConstraintConeTwist.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// ObjectConstraintConeTwist.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-23
|
||||
// Copyright 2017 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_ObjectConstraintConeTwist_h
|
||||
#define hifi_ObjectConstraintConeTwist_h
|
||||
|
||||
#include "ObjectConstraint.h"
|
||||
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtConeTwistConstraint.html
|
||||
|
||||
class ObjectConstraintConeTwist : public ObjectConstraint {
|
||||
public:
|
||||
ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectConstraintConeTwist();
|
||||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
virtual QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
virtual QList<btRigidBody*> getRigidBodies() override;
|
||||
virtual btTypedConstraint* getConstraint() override;
|
||||
|
||||
protected:
|
||||
static const uint16_t constraintVersion;
|
||||
|
||||
void updateConeTwist();
|
||||
|
||||
glm::vec3 _pivotInA;
|
||||
glm::vec3 _axisInA;
|
||||
|
||||
EntityItemID _otherEntityID;
|
||||
glm::vec3 _pivotInB;
|
||||
glm::vec3 _axisInB;
|
||||
|
||||
float _swingSpan1 { TWO_PI };
|
||||
float _swingSpan2 { TWO_PI };;
|
||||
float _twistSpan { TWO_PI };;
|
||||
float _softness { 1.0f };
|
||||
float _biasFactor {0.3f };
|
||||
float _relaxationFactor { 1.0f };
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectConstraintConeTwist_h
|
|
@ -48,23 +48,26 @@ QList<btRigidBody*> ObjectConstraintHinge::getRigidBodies() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void ObjectConstraintHinge::prepareForPhysicsSimulation() {
|
||||
}
|
||||
|
||||
void ObjectConstraintHinge::updateHinge() {
|
||||
btHingeConstraint* constraint { nullptr };
|
||||
glm::vec3 axisInA;
|
||||
float low;
|
||||
float high;
|
||||
float softness;
|
||||
float biasFactor;
|
||||
float relaxationFactor;
|
||||
float motorVelocity;
|
||||
|
||||
withReadLock([&]{
|
||||
axisInA = _axisInA;
|
||||
constraint = static_cast<btHingeConstraint*>(_constraint);
|
||||
low = _low;
|
||||
high = _high;
|
||||
softness = _softness;
|
||||
biasFactor = _biasFactor;
|
||||
relaxationFactor = _relaxationFactor;
|
||||
motorVelocity = _motorVelocity;
|
||||
softness = _softness;
|
||||
});
|
||||
|
||||
if (!constraint) {
|
||||
|
@ -72,12 +75,6 @@ void ObjectConstraintHinge::updateHinge() {
|
|||
}
|
||||
|
||||
constraint->setLimit(low, high, softness, biasFactor, relaxationFactor);
|
||||
if (motorVelocity != 0.0f) {
|
||||
constraint->setMotorTargetVelocity(motorVelocity);
|
||||
constraint->enableMotor(true);
|
||||
} else {
|
||||
constraint->enableMotor(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -150,7 +147,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
|
|||
float softness;
|
||||
float biasFactor;
|
||||
float relaxationFactor;
|
||||
float motorVelocity;
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
|
@ -217,13 +213,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
|
|||
relaxationFactor = _relaxationFactor;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
motorVelocity = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments,
|
||||
"motorVelocity", ok, false);
|
||||
if (!ok) {
|
||||
motorVelocity = _motorVelocity;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
pivotInA != _pivotInA ||
|
||||
axisInA != _axisInA ||
|
||||
|
@ -234,8 +223,7 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
|
|||
high != _high ||
|
||||
softness != _softness ||
|
||||
biasFactor != _biasFactor ||
|
||||
relaxationFactor != _relaxationFactor ||
|
||||
motorVelocity != _motorVelocity) {
|
||||
relaxationFactor != _relaxationFactor) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
|
@ -253,7 +241,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) {
|
|||
_softness = softness;
|
||||
_biasFactor = biasFactor;
|
||||
_relaxationFactor = relaxationFactor;
|
||||
_motorVelocity = motorVelocity;
|
||||
|
||||
_active = true;
|
||||
|
||||
|
@ -284,7 +271,6 @@ QVariantMap ObjectConstraintHinge::getArguments() {
|
|||
arguments["softness"] = _softness;
|
||||
arguments["biasFactor"] = _biasFactor;
|
||||
arguments["relaxationFactor"] = _relaxationFactor;
|
||||
arguments["motorVelocity"] = _motorVelocity;
|
||||
arguments["angle"] = static_cast<btHingeConstraint*>(_constraint)->getHingeAngle(); // [-PI,PI]
|
||||
}
|
||||
});
|
||||
|
@ -313,8 +299,6 @@ QByteArray ObjectConstraintHinge::serialize() const {
|
|||
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
|
||||
dataStream << _motorVelocity;
|
||||
});
|
||||
|
||||
return serializedConstraintArguments;
|
||||
|
@ -356,8 +340,6 @@ void ObjectConstraintHinge::deserialize(QByteArray serializedArguments) {
|
|||
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _motorVelocity;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ public:
|
|||
ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectConstraintHinge();
|
||||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
|
@ -42,12 +44,32 @@ protected:
|
|||
glm::vec3 _pivotInB;
|
||||
glm::vec3 _axisInB;
|
||||
|
||||
float _low { -2.0f * PI };
|
||||
float _high { 2.0f * PI };
|
||||
float _low { -TWO_PI };
|
||||
float _high { TWO_PI };
|
||||
|
||||
// https://gamedev.stackexchange.com/questions/71436/what-are-the-parameters-for-bthingeconstraintsetlimit
|
||||
//
|
||||
// softness: a negative measure of the friction that determines how much the hinge rotates for a given force. A high
|
||||
// softness would make the hinge rotate easily like it's oiled then.
|
||||
// biasFactor: an offset for the relaxed rotation of the hinge. It won't be right in the middle of the low and high angles
|
||||
// anymore. 1.0f is the neural value.
|
||||
// relaxationFactor: a measure of how much force is applied internally to bring the hinge in its central rotation.
|
||||
// This is right in the middle of the low and high angles. For example, consider a western swing door. After
|
||||
// walking through it will swing in both directions but at the end it stays right in the middle.
|
||||
|
||||
// http://javadoc.jmonkeyengine.org/com/jme3/bullet/joints/HingeJoint.html
|
||||
//
|
||||
// _softness - the factor at which the velocity error correction starts operating, i.e. a softness of 0.9 means that
|
||||
// the vel. corr starts at 90% of the limit range.
|
||||
// _biasFactor - the magnitude of the position correction. It tells you how strictly the position error (drift) is
|
||||
// corrected.
|
||||
// _relaxationFactor - the rate at which velocity errors are corrected. This can be seen as the strength of the
|
||||
// limits. A low value will make the the limits more spongy.
|
||||
|
||||
|
||||
float _softness { 0.9f };
|
||||
float _biasFactor { 0.3f };
|
||||
float _relaxationFactor { 1.0f };
|
||||
float _motorVelocity { 0.0f };
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectConstraintHinge_h
|
||||
|
|
326
libraries/physics/src/ObjectConstraintSlider.cpp
Normal file
|
@ -0,0 +1,326 @@
|
|||
//
|
||||
// ObjectConstraintSlider.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-23
|
||||
// Copyright 2017 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 "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "ObjectConstraintSlider.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
|
||||
const uint16_t ObjectConstraintSlider::constraintVersion = 1;
|
||||
|
||||
|
||||
ObjectConstraintSlider::ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectConstraint(DYNAMIC_TYPE_SLIDER, id, ownerEntity),
|
||||
_pointInA(glm::vec3(0.0f)),
|
||||
_axisInA(glm::vec3(0.0f))
|
||||
{
|
||||
}
|
||||
|
||||
ObjectConstraintSlider::~ObjectConstraintSlider() {
|
||||
}
|
||||
|
||||
QList<btRigidBody*> ObjectConstraintSlider::getRigidBodies() {
|
||||
QList<btRigidBody*> result;
|
||||
result += getRigidBody();
|
||||
QUuid otherEntityID;
|
||||
withReadLock([&]{
|
||||
otherEntityID = _otherEntityID;
|
||||
});
|
||||
if (!otherEntityID.isNull()) {
|
||||
result += getOtherRigidBody(otherEntityID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ObjectConstraintSlider::prepareForPhysicsSimulation() {
|
||||
}
|
||||
|
||||
void ObjectConstraintSlider::updateSlider() {
|
||||
btSliderConstraint* constraint { nullptr };
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btSliderConstraint*>(_constraint);
|
||||
});
|
||||
|
||||
if (!constraint) {
|
||||
return;
|
||||
}
|
||||
|
||||
// constraint->setFrames (const btTransform &frameA, const btTransform &frameB);
|
||||
constraint->setLowerLinLimit(_linearLow);
|
||||
constraint->setUpperLinLimit(_linearHigh);
|
||||
constraint->setLowerAngLimit(_angularLow);
|
||||
constraint->setUpperAngLimit(_angularHigh);
|
||||
|
||||
}
|
||||
|
||||
|
||||
btTypedConstraint* ObjectConstraintSlider::getConstraint() {
|
||||
btSliderConstraint* constraint { nullptr };
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pointInA;
|
||||
glm::vec3 axisInA;
|
||||
glm::vec3 pointInB;
|
||||
glm::vec3 axisInB;
|
||||
|
||||
withReadLock([&]{
|
||||
constraint = static_cast<btSliderConstraint*>(_constraint);
|
||||
pointInA = _pointInA;
|
||||
axisInA = _axisInA;
|
||||
otherEntityID = _otherEntityID;
|
||||
pointInB = _pointInB;
|
||||
axisInB = _axisInB;
|
||||
});
|
||||
if (constraint) {
|
||||
return constraint;
|
||||
}
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!otherEntityID.isNull()) {
|
||||
// This slider is between two entities... find the other rigid body.
|
||||
|
||||
glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
|
||||
glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB));
|
||||
|
||||
btTransform frameInA(glmToBullet(rotA), glmToBullet(pointInA));
|
||||
btTransform frameInB(glmToBullet(rotB), glmToBullet(pointInB));
|
||||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
constraint = new btSliderConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB, true);
|
||||
} else {
|
||||
// This slider is between an entity and the world-frame.
|
||||
|
||||
glm::quat rot = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA));
|
||||
|
||||
btTransform frameInA(glmToBullet(rot), glmToBullet(pointInA));
|
||||
|
||||
constraint = new btSliderConstraint(*rigidBodyA, frameInA, true);
|
||||
}
|
||||
|
||||
withWriteLock([&]{
|
||||
_constraint = constraint;
|
||||
});
|
||||
|
||||
// if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
|
||||
forceBodyNonStatic();
|
||||
activateBody();
|
||||
|
||||
updateSlider();
|
||||
|
||||
return constraint;
|
||||
}
|
||||
|
||||
|
||||
bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) {
|
||||
glm::vec3 pointInA;
|
||||
glm::vec3 axisInA;
|
||||
QUuid otherEntityID;
|
||||
glm::vec3 pointInB;
|
||||
glm::vec3 axisInB;
|
||||
float linearLow;
|
||||
float linearHigh;
|
||||
float angularLow;
|
||||
float angularHigh;
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
withReadLock([&]{
|
||||
bool ok = true;
|
||||
pointInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "point", ok, false);
|
||||
if (!ok) {
|
||||
pointInA = _pointInA;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
axisInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "axis", ok, false);
|
||||
if (!ok) {
|
||||
axisInA = _axisInA;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("slider constraint",
|
||||
arguments, "otherEntityID", ok, false));
|
||||
if (!ok) {
|
||||
otherEntityID = _otherEntityID;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
pointInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherPoint", ok, false);
|
||||
if (!ok) {
|
||||
pointInB = _pointInB;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
axisInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherAxis", ok, false);
|
||||
if (!ok) {
|
||||
axisInB = _axisInB;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
linearLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearLow", ok, false);
|
||||
if (!ok) {
|
||||
linearLow = _linearLow;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
linearHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearHigh", ok, false);
|
||||
if (!ok) {
|
||||
linearHigh = _linearHigh;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
angularLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularLow", ok, false);
|
||||
if (!ok) {
|
||||
angularLow = _angularLow;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
angularHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularHigh", ok, false);
|
||||
if (!ok) {
|
||||
angularHigh = _angularHigh;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
pointInA != _pointInA ||
|
||||
axisInA != _axisInA ||
|
||||
otherEntityID != _otherEntityID ||
|
||||
pointInB != _pointInB ||
|
||||
axisInB != _axisInB ||
|
||||
linearLow != _linearLow ||
|
||||
linearHigh != _linearHigh ||
|
||||
angularLow != _angularLow ||
|
||||
angularHigh != _angularHigh) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (needUpdate) {
|
||||
withWriteLock([&] {
|
||||
_pointInA = pointInA;
|
||||
_axisInA = axisInA;
|
||||
_otherEntityID = otherEntityID;
|
||||
_pointInB = pointInB;
|
||||
_axisInB = axisInB;
|
||||
_linearLow = linearLow;
|
||||
_linearHigh = linearHigh;
|
||||
_angularLow = angularLow;
|
||||
_angularHigh = angularHigh;
|
||||
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
}
|
||||
});
|
||||
|
||||
updateSlider();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectConstraintSlider::getArguments() {
|
||||
QVariantMap arguments = ObjectDynamic::getArguments();
|
||||
withReadLock([&] {
|
||||
if (_constraint) {
|
||||
arguments["point"] = glmToQMap(_pointInA);
|
||||
arguments["axis"] = glmToQMap(_axisInA);
|
||||
arguments["otherEntityID"] = _otherEntityID;
|
||||
arguments["otherPoint"] = glmToQMap(_pointInB);
|
||||
arguments["otherAxis"] = glmToQMap(_axisInB);
|
||||
arguments["linearLow"] = _linearLow;
|
||||
arguments["linearHigh"] = _linearHigh;
|
||||
arguments["angularLow"] = _angularLow;
|
||||
arguments["angularHigh"] = _angularHigh;
|
||||
arguments["linearPosition"] = static_cast<btSliderConstraint*>(_constraint)->getLinearPos();
|
||||
arguments["angularPosition"] = static_cast<btSliderConstraint*>(_constraint)->getAngularPos();
|
||||
}
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
||||
QByteArray ObjectConstraintSlider::serialize() const {
|
||||
QByteArray serializedConstraintArguments;
|
||||
QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << DYNAMIC_TYPE_SLIDER;
|
||||
dataStream << getID();
|
||||
dataStream << ObjectConstraintSlider::constraintVersion;
|
||||
|
||||
withReadLock([&] {
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
|
||||
dataStream << _pointInA;
|
||||
dataStream << _axisInA;
|
||||
dataStream << _otherEntityID;
|
||||
dataStream << _pointInB;
|
||||
dataStream << _axisInB;
|
||||
dataStream << _linearLow;
|
||||
dataStream << _linearHigh;
|
||||
dataStream << _angularLow;
|
||||
dataStream << _angularHigh;
|
||||
});
|
||||
|
||||
return serializedConstraintArguments;
|
||||
}
|
||||
|
||||
void ObjectConstraintSlider::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
|
||||
QUuid id;
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
|
||||
uint16_t serializationVersion;
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectConstraintSlider::constraintVersion) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
quint64 serverExpires;
|
||||
dataStream >> serverExpires;
|
||||
_expires = serverTimeToLocalTime(serverExpires);
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _pointInA;
|
||||
dataStream >> _axisInA;
|
||||
dataStream >> _otherEntityID;
|
||||
dataStream >> _pointInB;
|
||||
dataStream >> _axisInB;
|
||||
dataStream >> _linearLow;
|
||||
dataStream >> _linearHigh;
|
||||
dataStream >> _angularLow;
|
||||
dataStream >> _angularHigh;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
54
libraries/physics/src/ObjectConstraintSlider.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// ObjectConstraintSlider.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2017-4-23
|
||||
// Copyright 2017 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_ObjectConstraintSlider_h
|
||||
#define hifi_ObjectConstraintSlider_h
|
||||
|
||||
#include "ObjectConstraint.h"
|
||||
|
||||
// http://bulletphysics.org/Bullet/BulletFull/classbtSliderConstraint.html
|
||||
|
||||
class ObjectConstraintSlider : public ObjectConstraint {
|
||||
public:
|
||||
ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectConstraintSlider();
|
||||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
virtual QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
virtual QList<btRigidBody*> getRigidBodies() override;
|
||||
virtual btTypedConstraint* getConstraint() override;
|
||||
|
||||
protected:
|
||||
static const uint16_t constraintVersion;
|
||||
|
||||
void updateSlider();
|
||||
|
||||
glm::vec3 _pointInA;
|
||||
glm::vec3 _axisInA;
|
||||
|
||||
EntityItemID _otherEntityID;
|
||||
glm::vec3 _pointInB;
|
||||
glm::vec3 _axisInB;
|
||||
|
||||
float _linearLow { std::numeric_limits<float>::max() };
|
||||
float _linearHigh { std::numeric_limits<float>::min() };
|
||||
|
||||
float _angularLow { -TWO_PI };
|
||||
float _angularHigh { TWO_PI };
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectConstraintSlider_h
|
76
scripts/developer/tests/dynamics/dynamics-tests-interface.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// dynamics-tests-interface.js
|
||||
// scripts/developer/tests/dynamics/
|
||||
//
|
||||
// Created by Seth Alves 2017-4-30
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
|
||||
"use strict";
|
||||
/* globals $, EventBridge */
|
||||
|
||||
var parameters = {
|
||||
"lifetime":"integer"
|
||||
};
|
||||
|
||||
|
||||
function getQueryArgByName(name, url) {
|
||||
if (!url) {
|
||||
url = window.location.href;
|
||||
}
|
||||
name = name.replace(/[\[\]]/g, "\\$&");
|
||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
|
||||
function addCommandParameters(params) {
|
||||
// copy from html elements into an associative-array which will get passed (as JSON) through the EventBridge
|
||||
for (var parameterName in parameters) {
|
||||
if (parameters.hasOwnProperty(parameterName)) {
|
||||
var parameterType = parameters[parameterName];
|
||||
var strVal = $("#" + parameterName).val();
|
||||
if (parameterType == "integer") {
|
||||
params[parameterName] = parseInt(strVal);
|
||||
} else if (parameterType == "float") {
|
||||
params[parameterName] = parseFloat(strVal);
|
||||
} else {
|
||||
params[parameterName] = strVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
// hook all buttons to EventBridge
|
||||
$(":button").each(function(index) {
|
||||
$(this).click(function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify(addCommandParameters({ "dynamics-tests-command": this.id })));
|
||||
});
|
||||
});
|
||||
|
||||
// copy parameters from query-args into elements
|
||||
for (var parameterName in parameters) {
|
||||
if (parameters.hasOwnProperty(parameterName)) {
|
||||
var val = getQueryArgByName(parameterName);
|
||||
if (val) {
|
||||
var parameterType = parameters[parameterName];
|
||||
if (parameterType == "integer") {
|
||||
val = parseInt(val);
|
||||
} else if (parameterType == "float") {
|
||||
val = parseFloat(val);
|
||||
}
|
||||
$("#" + parameterName).val(val.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
37
scripts/developer/tests/dynamics/dynamics-tests.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Dynamics Tests</title>
|
||||
<meta charset="utf-8">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script src="dynamics-tests-interface.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
lifetime: <input id="lifetime" type="text" size=6>
|
||||
<hr>
|
||||
<input type="button" id="cone-twist-and-spring-lever-test" value="Cone-Twist and Spring-Action Lever"><br>
|
||||
A platform with a lever. The lever can be moved in a cone and rotated. A spring brings it back to its neutral position.
|
||||
<hr>
|
||||
<input type="button" id="door-vs-world-test" value="Hinge Between Swinging Door and World"><br>
|
||||
A grabbable door with a hinge between it and world-space.
|
||||
<hr>
|
||||
<input type="button" id="hinge-chain-test" value="Hinge Chain"><br>
|
||||
A chain of blocks connected by hinges.
|
||||
<hr>
|
||||
<input type="button" id="slider-vs-world-test" value="Slider vs World"><br>
|
||||
The block can only move up and down over a range of 1/2 meter.
|
||||
<hr>
|
||||
<input type="button" id="slider-chain-test" value="Slider Chain"><br>
|
||||
A chain of blocks connected by slider constraints.
|
||||
<hr>
|
||||
<input type="button" id="ball-socket-between-test" value="Ball-Socket Between Spheres Chain"><br>
|
||||
A chain of spheres connected by ball-and-socket joints between the spheres.
|
||||
<hr>
|
||||
<input type="button" id="ball-socket-coincident-test" value="Ball-Socket Coincident Spheres Chain"><br>
|
||||
A chain of spheres connected by ball-and-socket joints coincident-with the spheres.
|
||||
<hr>
|
||||
<input type="button" id="ragdoll-test" value="Ragdoll"><br>
|
||||
A self-righting ragdoll. The head is on a weak spring vs the body.
|
||||
|
||||
</body>
|
||||
</html>
|
759
scripts/developer/tests/dynamics/dynamicsTests.js
Normal file
|
@ -0,0 +1,759 @@
|
|||
//
|
||||
// dynamicsTests.js
|
||||
// scripts/developer/tests/dynamics/
|
||||
//
|
||||
// Created by Seth Alves 2017-4-30
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global Entities, Script, Tablet, MyAvatar, Vec3 */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var DYNAMICS_TESTS_URL = Script.resolvePath("dynamics-tests.html");
|
||||
var DEFAULT_LIFETIME = 120; // seconds
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
var button = tablet.addButton({
|
||||
icon: Script.resolvePath("dynamicsTests.svg"),
|
||||
text: "Dynamics",
|
||||
sortOrder: 15
|
||||
});
|
||||
|
||||
|
||||
|
||||
function coneTwistAndSpringLeverTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: -0.5, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var baseID = Entities.addEntity({
|
||||
name: "cone-twist test -- base",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: 0.5, y: 0.2, z: 0.5 },
|
||||
position: Vec3.sum(pos, { x: 0, y: 0, z:0 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
var leverID = Entities.addEntity({
|
||||
name: "cone-twist test -- lever",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 200 },
|
||||
dimensions: { x: 0.05, y: 1, z: 0.05 },
|
||||
position: Vec3.sum(pos, { x: 0, y: 0.7, z:0 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addEntity({
|
||||
name: "cone-twist test -- handle",
|
||||
type: "Box",
|
||||
color: { blue: 30, green: 100, red: 200 },
|
||||
dimensions: { x: 0.1, y: 0.08, z: 0.08 },
|
||||
position: Vec3.sum(pos, { x: 0, y: 0.7 + 0.5, z:0 }),
|
||||
dynamic: false,
|
||||
collisionless: true,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
parentID: leverID,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", baseID, {
|
||||
pivot: { x: 0, y: 0.2, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: leverID,
|
||||
otherPivot: { x: 0, y: -0.55, z: 0 },
|
||||
otherAxis: { x: 0, y: 1, z: 0 },
|
||||
swingSpan1: Math.PI / 4,
|
||||
swingSpan2: Math.PI / 4,
|
||||
twistSpan: Math.PI / 2,
|
||||
tag: "cone-twist test"
|
||||
});
|
||||
|
||||
Entities.addAction("spring", leverID, {
|
||||
targetRotation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
angularTimeScale: 0.2,
|
||||
tag: "cone-twist test spring"
|
||||
});
|
||||
|
||||
|
||||
Entities.editEntity(baseID, { gravity: { x: 0, y: -5, z: 0 } });
|
||||
}
|
||||
|
||||
function doorVSWorldTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var doorID = Entities.addEntity({
|
||||
name: "door test",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 20, red: 20 },
|
||||
dimensions: { x: 1.0, y: 2, z: 0.1 },
|
||||
position: pos,
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
lifetime: lifetime,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", doorID, {
|
||||
pivot: { x: -0.5, y: 0, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
low: 0,
|
||||
high: Math.PI,
|
||||
tag: "door hinge test"
|
||||
});
|
||||
}
|
||||
|
||||
function hingeChainTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var offset = 0.28;
|
||||
var prevEntityID = null;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var newID = Entities.addEntity({
|
||||
name: "hinge test " + i,
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 40 * i, red: 20 },
|
||||
dimensions: { x: 0.2, y: 0.2, z: 0.1 },
|
||||
position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
lifetime: lifetime,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
if (prevEntityID) {
|
||||
Entities.addAction("hinge", prevEntityID, {
|
||||
pivot: { x: 0, y: offset / 2, z: 0 },
|
||||
axis: { x: 1, y: 0, z: 0 },
|
||||
otherEntityID: newID,
|
||||
otherPivot: { x: 0, y: -offset / 2, z: 0 },
|
||||
otherAxis: { x: 1, y: 0, z: 0 },
|
||||
tag: "A/B hinge test " + i
|
||||
});
|
||||
}
|
||||
prevEntityID = newID;
|
||||
}
|
||||
}
|
||||
|
||||
function sliderVSWorldTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var sliderEntityID = Entities.addEntity({
|
||||
name: "slider test",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 20, red: 20 },
|
||||
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
|
||||
position: pos,
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("slider", sliderEntityID, {
|
||||
point: { x: -0.5, y: 0, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
linearLow: 0,
|
||||
linearHigh: 0.6,
|
||||
tag: "slider test"
|
||||
});
|
||||
}
|
||||
|
||||
function sliderChainTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var offset = 0.28;
|
||||
var prevEntityID = null;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var newID = Entities.addEntity({
|
||||
name: "hinge test " + i,
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 40 * i, red: 20 },
|
||||
dimensions: { x: 0.2, y: 0.1, z: 0.2 },
|
||||
position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
if (prevEntityID) {
|
||||
Entities.addAction("slider", prevEntityID, {
|
||||
point: { x: 0, y: 0, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: newID,
|
||||
otherPoint: { x: 0, y: -offset / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 1, z: 0 },
|
||||
linearLow: 0,
|
||||
linearHigh: 0.6,
|
||||
tag: "A/B slider test " + i
|
||||
});
|
||||
}
|
||||
prevEntityID = newID;
|
||||
}
|
||||
}
|
||||
|
||||
function ballSocketBetweenTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var offset = 0.2;
|
||||
var diameter = offset - 0.01;
|
||||
var prevEntityID = null;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var newID = Entities.addEntity({
|
||||
name: "ball and socket test " + i,
|
||||
type: "Sphere",
|
||||
color: { blue: 128, green: 40 * i, red: 20 },
|
||||
dimensions: { x: diameter, y: diameter, z: diameter },
|
||||
position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
lifetime: lifetime,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
if (prevEntityID) {
|
||||
Entities.addAction("ball-socket", prevEntityID, {
|
||||
pivot: { x: 0, y: offset / 2, z: 0 },
|
||||
otherEntityID: newID,
|
||||
otherPivot: { x: 0, y: -offset / 2, z: 0 },
|
||||
tag: "A/B ball-and-socket test " + i
|
||||
});
|
||||
}
|
||||
prevEntityID = newID;
|
||||
}
|
||||
}
|
||||
|
||||
function ballSocketCoincidentTest(params) {
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2}));
|
||||
var lifetime = params.lifetime;
|
||||
|
||||
var offset = 0.2;
|
||||
var diameter = offset - 0.01;
|
||||
var prevEntityID = null;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var newID = Entities.addEntity({
|
||||
name: "ball and socket test " + i,
|
||||
type: "Sphere",
|
||||
color: { blue: 128, green: 40 * i, red: 20 },
|
||||
dimensions: { x: diameter, y: diameter, z: diameter },
|
||||
position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
lifetime: lifetime,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
if (prevEntityID) {
|
||||
Entities.addAction("ball-socket", prevEntityID, {
|
||||
pivot: { x: 0, y: 0, z: 0 },
|
||||
otherEntityID: newID,
|
||||
otherPivot: { x: 0, y: offset, z: 0 },
|
||||
tag: "A/B ball-and-socket test " + i
|
||||
});
|
||||
}
|
||||
prevEntityID = newID;
|
||||
}
|
||||
}
|
||||
|
||||
function ragdollTest(params) {
|
||||
var scale = 1.6;
|
||||
var lifetime = params.lifetime;
|
||||
var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 1.0, z: -2}));
|
||||
|
||||
var neckLength = scale * 0.05;
|
||||
var shoulderGap = scale * 0.1;
|
||||
var elbowGap = scale * 0.06;
|
||||
var hipGap = scale * 0.07;
|
||||
var kneeGap = scale * 0.08;
|
||||
var ankleGap = scale * 0.06;
|
||||
var ankleMin = 0;
|
||||
var ankleMax = Math.PI / 4;
|
||||
|
||||
var headSize = scale * 0.2;
|
||||
|
||||
var bodyHeight = scale * 0.4;
|
||||
var bodyWidth = scale * 0.3;
|
||||
var bodyDepth = scale * 0.2;
|
||||
|
||||
var upperArmThickness = scale * 0.05;
|
||||
var upperArmLength = scale * 0.2;
|
||||
|
||||
var lowerArmThickness = scale * 0.05;
|
||||
var lowerArmLength = scale * 0.2;
|
||||
|
||||
var legLength = scale * 0.3;
|
||||
var legThickness = scale * 0.08;
|
||||
|
||||
var shinLength = scale * 0.2;
|
||||
var shinThickness = scale * 0.06;
|
||||
|
||||
var footLength = scale * 0.2;
|
||||
var footThickness = scale * 0.03;
|
||||
var footWidth = scale * 0.08;
|
||||
|
||||
|
||||
//
|
||||
// body
|
||||
//
|
||||
|
||||
var bodyID = Entities.addEntity({
|
||||
name: "ragdoll body",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: bodyDepth, y: bodyHeight, z: bodyWidth },
|
||||
position: Vec3.sum(pos, { x: 0, y: scale * 0.0, z:0 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
//
|
||||
// head
|
||||
//
|
||||
|
||||
var headID = Entities.addEntity({
|
||||
name: "ragdoll head",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: headSize, y: headSize, z: headSize },
|
||||
position: Vec3.sum(pos, { x: 0, y: bodyHeight / 2 + headSize / 2 + neckLength, z:0 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0.5, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("spring", headID, {
|
||||
targetRotation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
angularTimeScale: 2.0,
|
||||
otherID: bodyID,
|
||||
tag: "cone-twist test spring"
|
||||
});
|
||||
|
||||
|
||||
var noseID = Entities.addEntity({
|
||||
name: "ragdoll nose",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 100 },
|
||||
dimensions: { x: headSize / 5, y: headSize / 5, z: headSize / 5 },
|
||||
localPosition: { x: headSize / 2 + headSize / 10, y: 0, z: 0 },
|
||||
dynamic: false,
|
||||
collisionless: true,
|
||||
lifetime: lifetime,
|
||||
parentID: headID,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", headID, {
|
||||
pivot: { x: 0, y: -headSize / 2 - neckLength / 2, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: bodyID,
|
||||
otherPivot: { x: 0, y: bodyHeight / 2 + neckLength / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 1, z: 0 },
|
||||
swingSpan1: Math.PI / 4,
|
||||
swingSpan2: Math.PI / 4,
|
||||
twistSpan: Math.PI / 2,
|
||||
tag: "ragdoll neck joint"
|
||||
});
|
||||
|
||||
//
|
||||
// right upper arm
|
||||
//
|
||||
|
||||
var rightUpperArmID = Entities.addEntity({
|
||||
name: "ragdoll right arm",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: bodyHeight / 2 + upperArmThickness / 2,
|
||||
z: bodyWidth / 2 + shoulderGap + upperArmLength / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", bodyID, {
|
||||
pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: bodyWidth / 2 + shoulderGap / 2 },
|
||||
axis: { x: 0, y: 0, z: 1 },
|
||||
otherEntityID: rightUpperArmID,
|
||||
otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - shoulderGap / 2 },
|
||||
otherAxis: { x: 0, y: 0, z: 1 },
|
||||
swingSpan1: Math.PI / 2,
|
||||
swingSpan2: Math.PI / 2,
|
||||
twistSpan: 0,
|
||||
tag: "ragdoll right shoulder joint"
|
||||
});
|
||||
|
||||
//
|
||||
// left upper arm
|
||||
//
|
||||
|
||||
var leftUpperArmID = Entities.addEntity({
|
||||
name: "ragdoll left arm",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: bodyHeight / 2 + upperArmThickness / 2,
|
||||
z: -bodyWidth / 2 - shoulderGap - upperArmLength / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", bodyID, {
|
||||
pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: -bodyWidth / 2 - shoulderGap / 2 },
|
||||
axis: { x: 0, y: 0, z: -1 },
|
||||
otherEntityID: leftUpperArmID,
|
||||
otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + shoulderGap / 2 },
|
||||
otherAxis: { x: 0, y: 0, z: -1 },
|
||||
swingSpan1: Math.PI / 2,
|
||||
swingSpan2: Math.PI / 2,
|
||||
twistSpan: 0,
|
||||
tag: "ragdoll left shoulder joint"
|
||||
});
|
||||
|
||||
//
|
||||
// right lower arm
|
||||
//
|
||||
|
||||
var rightLowerArmID = Entities.addEntity({
|
||||
name: "ragdoll right lower arm",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: bodyHeight / 2 - upperArmThickness / 2,
|
||||
z: bodyWidth / 2 + shoulderGap + upperArmLength + elbowGap + lowerArmLength / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -1, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", rightLowerArmID, {
|
||||
pivot: { x: 0, y: 0, z: -lowerArmLength / 2 - elbowGap / 2 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: rightUpperArmID,
|
||||
otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + elbowGap / 2 },
|
||||
otherAxis: { x: 0, y: 1, z: 0 },
|
||||
low: Math.PI / -2,
|
||||
high: 0,
|
||||
tag: "ragdoll right elbow joint"
|
||||
});
|
||||
|
||||
//
|
||||
// left lower arm
|
||||
//
|
||||
|
||||
var leftLowerArmID = Entities.addEntity({
|
||||
name: "ragdoll left lower arm",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: bodyHeight / 2 - upperArmThickness / 2,
|
||||
z: -bodyWidth / 2 - shoulderGap - upperArmLength - elbowGap - lowerArmLength / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -1, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", leftLowerArmID, {
|
||||
pivot: { x: 0, y: 0, z: lowerArmLength / 2 + elbowGap / 2 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: leftUpperArmID,
|
||||
otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - elbowGap / 2 },
|
||||
otherAxis: { x: 0, y: 1, z: 0 },
|
||||
low: 0,
|
||||
high: Math.PI / 2,
|
||||
tag: "ragdoll left elbow joint"
|
||||
});
|
||||
|
||||
//
|
||||
// right leg
|
||||
//
|
||||
|
||||
var rightLegID = Entities.addEntity({
|
||||
name: "ragdoll right arm",
|
||||
type: "Box",
|
||||
color: { blue: 20, green: 200, red: 20 },
|
||||
dimensions: { x: legThickness, y: legLength, z: legThickness },
|
||||
position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: bodyWidth / 2 - legThickness / 2 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", rightLegID, {
|
||||
pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: bodyID,
|
||||
otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: bodyWidth / 2 - legThickness / 2 },
|
||||
otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }),
|
||||
swingSpan1: Math.PI / 4,
|
||||
swingSpan2: Math.PI / 4,
|
||||
twistSpan: 0,
|
||||
tag: "ragdoll right hip joint"
|
||||
});
|
||||
|
||||
//
|
||||
// left leg
|
||||
//
|
||||
|
||||
var leftLegID = Entities.addEntity({
|
||||
name: "ragdoll left arm",
|
||||
type: "Box",
|
||||
color: { blue: 20, green: 200, red: 20 },
|
||||
dimensions: { x: legThickness, y: legLength, z: legThickness },
|
||||
position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: -bodyWidth / 2 + legThickness / 2 }),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: 0, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("cone-twist", leftLegID, {
|
||||
pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 1, z: 0 },
|
||||
otherEntityID: bodyID,
|
||||
otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: -bodyWidth / 2 + legThickness / 2 },
|
||||
otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }),
|
||||
swingSpan1: Math.PI / 4,
|
||||
swingSpan2: Math.PI / 4,
|
||||
twistSpan: 0,
|
||||
tag: "ragdoll left hip joint"
|
||||
});
|
||||
|
||||
//
|
||||
// right shin
|
||||
//
|
||||
|
||||
var rightShinID = Entities.addEntity({
|
||||
name: "ragdoll right shin",
|
||||
type: "Box",
|
||||
color: { blue: 20, green: 200, red: 20 },
|
||||
dimensions: { x: shinThickness, y: shinLength, z: shinThickness },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2,
|
||||
z: bodyWidth / 2 - legThickness / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -2, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", rightShinID, {
|
||||
pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 0, z: 1 },
|
||||
otherEntityID: rightLegID,
|
||||
otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 0, z: 1 },
|
||||
low: 0,
|
||||
high: Math.PI / 2,
|
||||
tag: "ragdoll right knee joint"
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// left shin
|
||||
//
|
||||
|
||||
var leftShinID = Entities.addEntity({
|
||||
name: "ragdoll left shin",
|
||||
type: "Box",
|
||||
color: { blue: 20, green: 200, red: 20 },
|
||||
dimensions: { x: shinThickness, y: shinLength, z: shinThickness },
|
||||
position: Vec3.sum(pos, { x: 0,
|
||||
y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2,
|
||||
z: -bodyWidth / 2 + legThickness / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -2, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", leftShinID, {
|
||||
pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 0, z: 1 },
|
||||
otherEntityID: leftLegID,
|
||||
otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 0, z: 1 },
|
||||
low: 0,
|
||||
high: Math.PI / 2,
|
||||
tag: "ragdoll left knee joint"
|
||||
});
|
||||
|
||||
//
|
||||
// right foot
|
||||
//
|
||||
|
||||
var rightFootID = Entities.addEntity({
|
||||
name: "ragdoll right foot",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: footLength, y: footThickness, z: footWidth },
|
||||
position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2,
|
||||
y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2,
|
||||
z: bodyWidth / 2 - legThickness / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -5, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", rightFootID, {
|
||||
pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 0, z: 1 },
|
||||
otherEntityID: rightShinID,
|
||||
otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 0, z: 1 },
|
||||
low: ankleMin,
|
||||
high: ankleMax,
|
||||
tag: "ragdoll right ankle joint"
|
||||
});
|
||||
|
||||
//
|
||||
// left foot
|
||||
//
|
||||
|
||||
var leftFootID = Entities.addEntity({
|
||||
name: "ragdoll left foot",
|
||||
type: "Box",
|
||||
color: { blue: 128, green: 100, red: 20 },
|
||||
dimensions: { x: footLength, y: footThickness, z: footWidth },
|
||||
position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2,
|
||||
y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2,
|
||||
z: bodyWidth / 2 - legThickness / 2
|
||||
}),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
gravity: { x: 0, y: -5, z: 0 },
|
||||
lifetime: lifetime,
|
||||
userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }"
|
||||
});
|
||||
|
||||
Entities.addAction("hinge", leftFootID, {
|
||||
pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 },
|
||||
axis: { x: 0, y: 0, z: 1 },
|
||||
otherEntityID: leftShinID,
|
||||
otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 },
|
||||
otherAxis: { x: 0, y: 0, z: 1 },
|
||||
low: ankleMin,
|
||||
high: ankleMax,
|
||||
tag: "ragdoll left ankle joint"
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function onWebEventReceived(eventString) {
|
||||
print("received web event: " + JSON.stringify(eventString));
|
||||
if (typeof eventString === "string") {
|
||||
var event;
|
||||
try {
|
||||
event = JSON.parse(eventString);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event["dynamics-tests-command"]) {
|
||||
var commandToFunctionMap = {
|
||||
"cone-twist-and-spring-lever-test": coneTwistAndSpringLeverTest,
|
||||
"door-vs-world-test": doorVSWorldTest,
|
||||
"hinge-chain-test": hingeChainTest,
|
||||
"slider-vs-world-test": sliderVSWorldTest,
|
||||
"slider-chain-test": sliderChainTest,
|
||||
"ball-socket-between-test": ballSocketBetweenTest,
|
||||
"ball-socket-coincident-test": ballSocketCoincidentTest,
|
||||
"ragdoll-test": ragdollTest
|
||||
};
|
||||
|
||||
var cmd = event["dynamics-tests-command"];
|
||||
if (commandToFunctionMap.hasOwnProperty(cmd)) {
|
||||
var func = commandToFunctionMap[cmd];
|
||||
func(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var onDynamicsTestsScreen = false;
|
||||
var shouldActivateButton = false;
|
||||
|
||||
function onClicked() {
|
||||
if (onDynamicsTestsScreen) {
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
shouldActivateButton = true;
|
||||
tablet.gotoWebScreen(DYNAMICS_TESTS_URL +
|
||||
"?lifetime=" + DEFAULT_LIFETIME.toString()
|
||||
);
|
||||
onDynamicsTestsScreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onScreenChanged() {
|
||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||
button.editProperties({isActive: shouldActivateButton});
|
||||
shouldActivateButton = false;
|
||||
onDynamicsTestsScreen = shouldActivateButton;
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
button.clicked.disconnect(onClicked);
|
||||
tablet.removeButton(button);
|
||||
}
|
||||
|
||||
button.clicked.connect(onClicked);
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
}()); // END LOCAL_SCOPE
|
69
scripts/developer/tests/dynamics/dynamicsTests.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="dynamicsTests.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="-482.14286"
|
||||
inkscape:cy="520"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2815"
|
||||
inkscape:window-height="1776"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
sodipodi:type="spiral"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4136"
|
||||
sodipodi:cx="351.42856"
|
||||
sodipodi:cy="532.36218"
|
||||
sodipodi:expansion="1"
|
||||
sodipodi:revolution="3"
|
||||
sodipodi:radius="189.2628"
|
||||
sodipodi:argument="-18.34539"
|
||||
sodipodi:t0="0"
|
||||
d="m 351.42856,532.36218 c 8.30865,4.58408 -2.08005,13.46152 -7.61904,13.80953 -15.01033,0.94307 -22.49738,-16.46931 -20.00001,-29.04761 4.46719,-22.49963 30.09017,-32.38647 50.47618,-26.1905 29.91728,9.09285 42.49805,43.8703 32.38097,71.90475 -13.48446,37.36552 -57.7036,52.70019 -93.33331,38.57147 -44.83636,-17.77957 -62.94832,-71.56146 -44.76195,-114.76189 22.02613,-52.32152 85.43296,-73.22291 136.19046,-50.95243 59.81595,26.24498 83.51408,99.31294 57.14291,157.61902 -30.44656,67.31668 -113.1986,93.81634 -179.0476,63.3334 C 208.03536,622.01126 178.73083,529.55968 213.33329,456.17176 252.15203,373.8416 354.3141,341.72978 435.23803,380.45739 c 89.8409,42.99499 124.76183,154.87556 81.90485,243.3333"
|
||||
transform="matrix(1.6331701,0,0,2.2153757,-219.59811,-629.37371)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
82
scripts/modules/request.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
"use strict";
|
||||
|
||||
// request.js
|
||||
//
|
||||
// Created by Cisco Fresquet on 04/24/2017.
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
/* global module */
|
||||
// @module request
|
||||
//
|
||||
// This module contains the `request` module implementation
|
||||
|
||||
// ===========================================================================================
|
||||
module.exports = {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
request: function (options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
|
||||
var httpRequest = new XMLHttpRequest(), key;
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
response = { statusCode: httpRequest.status };
|
||||
}
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
options = { uri: options };
|
||||
}
|
||||
if (options.url) {
|
||||
options.uri = options.url;
|
||||
}
|
||||
if (!options.method) {
|
||||
options.method = 'GET';
|
||||
}
|
||||
if (options.body && (options.method === 'GET')) { // add query parameters
|
||||
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
|
||||
for (key in options.body) {
|
||||
if (options.body.hasOwnProperty(key)) {
|
||||
params.push(key + '=' + options.body[key]);
|
||||
}
|
||||
}
|
||||
options.uri += appender + params.join('&');
|
||||
delete options.body;
|
||||
}
|
||||
if (options.json) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers["Content-type"] = "application/json";
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
for (key in options.headers || {}) {
|
||||
if (options.headers.hasOwnProperty(key)) {
|
||||
httpRequest.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
}
|
||||
httpRequest.open(options.method, options.uri, true);
|
||||
httpRequest.send(options.body);
|
||||
}
|
||||
};
|
||||
|
||||
// ===========================================================================================
|
||||
// @function - debug logging
|
||||
function debug() {
|
||||
print('RequestModule | ' + [].slice.call(arguments).join(' '));
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
|
||||
Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset,
|
||||
setGrabCommunications, Menu, HMD, isInEditMode */
|
||||
setGrabCommunications, Menu, HMD, isInEditMode, AvatarList */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
@ -75,7 +75,6 @@ var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative numbe
|
|||
//
|
||||
// distant manipulation
|
||||
//
|
||||
var linearTimeScale = 0;
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
|
||||
|
@ -155,7 +154,6 @@ var INCHES_TO_METERS = 1.0 / 39.3701;
|
|||
|
||||
// these control how long an abandoned pointer line or action will hang around
|
||||
var ACTION_TTL = 15; // seconds
|
||||
var ACTION_TTL_ZERO = 0; // seconds
|
||||
var ACTION_TTL_REFRESH = 5;
|
||||
var PICKS_PER_SECOND_PER_HAND = 60;
|
||||
var MSECS_PER_SEC = 1000.0;
|
||||
|
@ -193,7 +191,6 @@ var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"];
|
|||
var holdEnabled = true;
|
||||
var nearGrabEnabled = true;
|
||||
var farGrabEnabled = true;
|
||||
var farToNearGrab = false;
|
||||
var myAvatarScalingEnabled = true;
|
||||
var objectScalingEnabled = true;
|
||||
var mostRecentSearchingHand = RIGHT_HAND;
|
||||
|
@ -300,14 +297,13 @@ function getFingerWorldLocation(hand) {
|
|||
// Object assign polyfill
|
||||
if (typeof Object.assign != 'function') {
|
||||
Object.assign = function(target, varArgs) {
|
||||
'use strict';
|
||||
if (target == null) {
|
||||
if (target === null) {
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
var to = Object(target);
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var nextSource = arguments[index];
|
||||
if (nextSource != null) {
|
||||
if (nextSource !== null) {
|
||||
for (var nextKey in nextSource) {
|
||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
|
@ -801,7 +797,7 @@ function calculateNearestStylusTarget(stylusTargets) {
|
|||
}
|
||||
|
||||
return nearestStylusTarget;
|
||||
};
|
||||
}
|
||||
|
||||
// EntityPropertiesCache is a helper class that contains a cache of entity properties.
|
||||
// the hope is to prevent excess calls to Entity.getEntityProperties()
|
||||
|
@ -1229,8 +1225,9 @@ function MyController(hand) {
|
|||
newState !== STATE_OVERLAY_LASER_TOUCHING)) {
|
||||
return;
|
||||
}
|
||||
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_DISTANCE_ROTATING)
|
||||
|| (newState === STATE_NEAR_GRABBING));
|
||||
setGrabCommunications((newState === STATE_DISTANCE_HOLDING) ||
|
||||
(newState === STATE_DISTANCE_ROTATING) ||
|
||||
(newState === STATE_NEAR_GRABBING));
|
||||
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
||||
var oldStateName = stateToName(this.state);
|
||||
var newStateName = stateToName(newState);
|
||||
|
@ -1428,7 +1425,7 @@ function MyController(hand) {
|
|||
if (PICK_WITH_HAND_RAY) {
|
||||
this.overlayLineOff();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) {
|
||||
if (this.otherGrabbingLine === null) {
|
||||
|
@ -1641,11 +1638,6 @@ function MyController(hand) {
|
|||
|
||||
var tipPosition = this.stylusTip.position;
|
||||
|
||||
var candidates = {
|
||||
entities: [],
|
||||
overlays: []
|
||||
};
|
||||
|
||||
// build list of stylus targets, near the stylusTip
|
||||
var stylusTargets = [];
|
||||
var candidateEntities = Entities.findEntities(tipPosition, WEB_DISPLAY_STYLUS_DISTANCE);
|
||||
|
@ -1972,9 +1964,10 @@ function MyController(hand) {
|
|||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||
|
||||
var otherHandControllerState = this.getOtherHandController().state;
|
||||
var okToEquipFromOtherHand = ((otherHandControllerState === STATE_NEAR_GRABBING
|
||||
|| otherHandControllerState === STATE_DISTANCE_HOLDING || otherHandControllerState === STATE_DISTANCE_ROTATING)
|
||||
&& this.getOtherHandController().grabbedThingID === hotspot.entityID);
|
||||
var okToEquipFromOtherHand = ((otherHandControllerState === STATE_NEAR_GRABBING ||
|
||||
otherHandControllerState === STATE_DISTANCE_HOLDING ||
|
||||
otherHandControllerState === STATE_DISTANCE_ROTATING) &&
|
||||
this.getOtherHandController().grabbedThingID === hotspot.entityID);
|
||||
var hasParent = true;
|
||||
if (props.parentID === NULL_UUID) {
|
||||
hasParent = false;
|
||||
|
@ -1999,7 +1992,7 @@ function MyController(hand) {
|
|||
return entityProps.cloneable;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
this.entityIsGrabbable = function(entityID) {
|
||||
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
|
@ -2720,7 +2713,8 @@ function MyController(hand) {
|
|||
|
||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||
|
||||
var candidateHotSpotEntities = Entities.findEntities(controllerLocation.position,MAX_FAR_TO_NEAR_EQUIP_HOTSPOT_RADIUS);
|
||||
var candidateHotSpotEntities =
|
||||
Entities.findEntities(controllerLocation.position,MAX_FAR_TO_NEAR_EQUIP_HOTSPOT_RADIUS);
|
||||
entityPropertiesCache.addEntities(candidateHotSpotEntities);
|
||||
|
||||
var potentialEquipHotspot = this.chooseBestEquipHotspotForFarToNearEquip(candidateHotSpotEntities);
|
||||
|
@ -2730,40 +2724,23 @@ function MyController(hand) {
|
|||
this.grabbedThingID = potentialEquipHotspot.entityID;
|
||||
this.grabbedIsOverlay = false;
|
||||
|
||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||
targetPosition: newTargetPosition,
|
||||
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
ttl: ACTION_TTL_ZERO
|
||||
});
|
||||
|
||||
if (success) {
|
||||
this.actionTimeout = now + (ACTION_TTL_ZERO * MSECS_PER_SEC);
|
||||
} else {
|
||||
print("continueDistanceHolding -- updateAction failed");
|
||||
}
|
||||
Entities.deleteAction(this.grabbedThingID, this.actionID);
|
||||
this.actionID = null;
|
||||
|
||||
this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
var rayPositionOnEntity = Vec3.subtract(grabbedProperties.position, this.offsetPosition);
|
||||
//Far to Near Grab: If object is draw by user inside FAR_TO_NEAR_GRAB_MAX_DISTANCE, grab it
|
||||
if (this.entityIsFarToNearGrabbable(rayPositionOnEntity, controllerLocation.position, FAR_TO_NEAR_GRAB_MAX_DISTANCE)) {
|
||||
if (this.entityIsFarToNearGrabbable(rayPositionOnEntity,
|
||||
controllerLocation.position,
|
||||
FAR_TO_NEAR_GRAB_MAX_DISTANCE)) {
|
||||
this.farToNearGrab = true;
|
||||
|
||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||
targetPosition: newTargetPosition,
|
||||
linearTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject),
|
||||
ttl: ACTION_TTL_ZERO // Overriding ACTION_TTL,Assign ACTION_TTL_ZERO so that the object is dropped down immediately after the trigger is released.
|
||||
});
|
||||
if (success) {
|
||||
this.actionTimeout = now + (ACTION_TTL_ZERO * MSECS_PER_SEC);
|
||||
} else {
|
||||
print("continueDistanceHolding -- updateAction failed");
|
||||
}
|
||||
Entities.deleteAction(this.grabbedThingID, this.actionID);
|
||||
this.actionID = null;
|
||||
|
||||
this.setState(STATE_NEAR_GRABBING , "near grab entity '" + this.grabbedThingID + "'");
|
||||
return;
|
||||
}
|
||||
|
@ -2844,7 +2821,7 @@ function MyController(hand) {
|
|||
COLORS_GRAB_DISTANCE_HOLD, this.grabbedThingID);
|
||||
|
||||
this.previousWorldControllerRotation = worldControllerRotation;
|
||||
}
|
||||
};
|
||||
|
||||
this.setupHoldAction = function() {
|
||||
this.actionID = Entities.addAction("hold", this.grabbedThingID, {
|
||||
|
@ -3043,15 +3020,14 @@ function MyController(hand) {
|
|||
var worldEntities = Entities.findEntities(MyAvatar.position, 50);
|
||||
var count = 0;
|
||||
worldEntities.forEach(function(item) {
|
||||
var item = Entities.getEntityProperties(item, ["name"]);
|
||||
if (item.name.indexOf('-clone-' + grabbedProperties.id) !== -1) {
|
||||
var itemWE = Entities.getEntityProperties(item, ["name"]);
|
||||
if (itemWE.name.indexOf('-clone-' + grabbedProperties.id) !== -1) {
|
||||
count++;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0;
|
||||
if (count >= limit && limit !== 0) {
|
||||
delete limit;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3067,7 +3043,7 @@ function MyController(hand) {
|
|||
delete cUserData.grabbableKey.cloneable;
|
||||
delete cUserData.grabbableKey.cloneDynamic;
|
||||
delete cUserData.grabbableKey.cloneLimit;
|
||||
delete cProperties.id
|
||||
delete cProperties.id;
|
||||
|
||||
cProperties.dynamic = dynamic;
|
||||
cProperties.locked = false;
|
||||
|
@ -3133,7 +3109,7 @@ function MyController(hand) {
|
|||
_this.currentAngularVelocity = ZERO_VEC;
|
||||
|
||||
_this.prevDropDetected = false;
|
||||
}
|
||||
};
|
||||
|
||||
if (isClone) {
|
||||
// 100 ms seems to be sufficient time to force the check even occur after the object has been initialized.
|
||||
|
@ -3149,7 +3125,6 @@ function MyController(hand) {
|
|||
var ttl = ACTION_TTL;
|
||||
|
||||
if (this.farToNearGrab) {
|
||||
ttl = ACTION_TTL_ZERO; // farToNearGrab - Assign ACTION_TTL_ZERO so that, the object is dropped down immediately after the trigger is released.
|
||||
if(!this.triggerClicked){
|
||||
this.farToNearGrab = false;
|
||||
}
|
||||
|
@ -3322,7 +3297,7 @@ function MyController(hand) {
|
|||
this.maybeScale(props);
|
||||
}
|
||||
|
||||
if (this.actionID && this.actionTimeout - now < ttl * MSECS_PER_SEC) {
|
||||
if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) {
|
||||
// if less than a 5 seconds left, refresh the actions ttl
|
||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||
|
@ -3761,10 +3736,12 @@ function MyController(hand) {
|
|||
var TABLET_MAX_TOUCH_DISTANCE = 0.01;
|
||||
|
||||
if (this.stylusTarget) {
|
||||
if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) {
|
||||
if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE &&
|
||||
this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) {
|
||||
var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds
|
||||
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||
distance2D(this.stylusTarget.position2D, this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
|
||||
distance2D(this.stylusTarget.position2D,
|
||||
this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
|
||||
sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget);
|
||||
this.deadspotExpired = true;
|
||||
}
|
||||
|
|
|
@ -104,97 +104,42 @@ input[type=button].naked:active {
|
|||
*/
|
||||
|
||||
/*
|
||||
// START styling of share bar
|
||||
// START styling of share overlay
|
||||
*/
|
||||
.shareControls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
line-height: 60px;
|
||||
height: 65px;
|
||||
line-height: 65px;
|
||||
width: calc(100% - 8px);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
.shareButtons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
}
|
||||
.blastToConnections {
|
||||
text-align: left;
|
||||
margin-right: 20px;
|
||||
height: 29px;
|
||||
}
|
||||
.shareWithEveryone {
|
||||
background: #DDDDDD url(../img/shareToFeed.png) no-repeat scroll center;
|
||||
border-width: 0px;
|
||||
text-align: left;
|
||||
margin-right: 8px;
|
||||
height: 29px;
|
||||
width: 30px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.facebookButton {
|
||||
background-image: url(../img/fb_logo.png);
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.twitterButton {
|
||||
background-image: url(../img/twitter_logo.png);
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.showShareButtonsButtonDiv {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-family: Raleway-SemiBold;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
width: 75px;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.showShareButtonsButtonDiv.active:hover {
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
background-size: 2px;
|
||||
}
|
||||
.showShareButtonsButtonDiv > label {
|
||||
text-shadow: 2px 2px 3px #000000;
|
||||
margin-bottom: -14px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.showShareButton {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border-width: 0;
|
||||
margin-left: 5px;
|
||||
outline: none;
|
||||
}
|
||||
.showShareButton.active {
|
||||
border-color: #00b4ef;
|
||||
border-width: 3px;
|
||||
background-color: white;
|
||||
}
|
||||
.showShareButton.active:hover {
|
||||
background-color: #afafaf;
|
||||
}
|
||||
.showShareButton.active:active {
|
||||
background-color: white;
|
||||
}
|
||||
.showShareButton.inactive {
|
||||
border-width: 0;
|
||||
background-color: white;
|
||||
}
|
||||
.showShareButton.inactive:hover {
|
||||
background-color: #afafaf;
|
||||
}
|
||||
.showShareButton.inactive:active {
|
||||
background-color: white;
|
||||
.showShareButtonsButtonDiv:hover > label {
|
||||
text-shadow: none;
|
||||
}
|
||||
.showShareButtonDots {
|
||||
display: block;
|
||||
|
@ -203,15 +148,81 @@ input[type=button].naked:active {
|
|||
font-family: HiFi-Glyphs;
|
||||
font-size: 60px;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 12px;
|
||||
color: #00b4ef;
|
||||
left: 6px;
|
||||
bottom: 32px;
|
||||
color: white;
|
||||
pointer-events: none;
|
||||
}
|
||||
.shareButtons {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
height: 40px;
|
||||
width: calc(100% - 60px);
|
||||
margin-bottom: 25px;
|
||||
margin-left: 0;
|
||||
}
|
||||
.shareButtons img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shareButton {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
}
|
||||
.shareButton.disabled {
|
||||
background-color: #000000;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.shareControlsHelp {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 73px;
|
||||
right: 0;
|
||||
font-family: Raleway-Regular;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
padding-left: 8px;
|
||||
color: white;
|
||||
}
|
||||
/*
|
||||
// END styling of share overlay
|
||||
*/
|
||||
|
||||
/*
|
||||
// START styling of confirmation message
|
||||
*/
|
||||
.confirmationMessageContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
text-align: center;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
.confirmationMessage {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
margin: 50px auto 0 auto;
|
||||
}
|
||||
.confirmationMessage > img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 10px 0 0 0;
|
||||
}
|
||||
/*
|
||||
// END styling of uploading message
|
||||
*/
|
||||
|
||||
/*
|
||||
// START styling of snapshot controls (bottom panel) and its contents
|
||||
*/
|
||||
|
|
20
scripts/system/html/img/blast_icon.svg
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M39.3,25.9c1.6,4.2,3.2,8.4,4.8,12.5c0.1,0.3,0.2,0.6,0,0.9c-0.3,0.3-0.6,0.3-1,0.2c-4-0.8-8.1-1.7-12.1-2.5
|
||||
c-0.5-0.1-1-0.1-1.5,0.1c-0.5,0.2-1,0.4-1.5,0.6c-0.8,0.3-0.9,0.7-0.4,1.4c2.1,2.7,4.2,5.5,6.3,8.2c0.1,0.2,0.2,0.3,0.3,0.5
|
||||
c0.2,0.4,0.1,0.7-0.3,1c-0.1,0.1-0.3,0.2-0.5,0.2c-1.5,0.6-3,1.1-4.4,1.7c-0.8,0.3-1,0.3-1.5-0.4c-2.3-3-4.6-5.9-6.9-8.9
|
||||
c-0.5-0.6-0.6-0.6-1.3-0.4c-0.3,0.1-0.6,0.2-0.8,0.3c-1,0.3-2-0.1-2.3-1.1c-1.2-3.2-2.5-6.5-3.7-9.7c-0.4-1.1,0-2,1.1-2.5
|
||||
c0.9-0.4,1.9-0.7,2.8-1.1c2.7-1,5.3-2,8-3c0.5-0.2,0.9-0.5,1.2-0.9c2.4-3.3,4.9-6.6,7.4-9.9c0.2-0.3,0.4-0.5,0.8-0.5
|
||||
c0.4,0,0.5,0.4,0.7,0.7C36.1,17.5,37.7,21.7,39.3,25.9z"/>
|
||||
<path class="st0" d="M38.2,15.6c-0.2,0-0.4-0.1-0.6-0.2c-0.6-0.3-0.7-1.1-0.4-1.6l3.7-6.1C41.3,7.1,42,7,42.6,7.3
|
||||
c0.6,0.3,0.7,1.1,0.4,1.6l-3.7,6.1C39,15.4,38.6,15.6,38.2,15.6z"/>
|
||||
<path class="st0" d="M53.1,36.7c-0.1,0-0.2,0-0.3,0l-7.4-1.6c-0.6-0.1-1-0.8-0.9-1.4c0.1-0.6,0.8-1,1.4-0.9l7.4,1.6
|
||||
c0.6,0.1,1,0.8,0.9,1.4C54.1,36.4,53.6,36.7,53.1,36.7z"/>
|
||||
<path class="st0" d="M42.5,24.8c-0.4,0-0.9-0.3-1.1-0.7c-0.3-0.6,0-1.3,0.6-1.6l7.5-3.5c0.6-0.3,1.3,0,1.6,0.6
|
||||
c0.3,0.6,0,1.3-0.6,1.6L43,24.7C42.8,24.8,42.7,24.8,42.5,24.8z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
13
scripts/system/html/img/fb_icon.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M22.3,32c1.9,0,3.8,0,5.8,0c0.4,0,0.4,0,0.4,0.4c0,6,0,12.1,0,18.1h7.8c0-6,0-12.1,0-18.1c0-0.4,0-0.4,0.4-0.4
|
||||
c1.9,0,3.8,0,5.8,0c0.3,0,0.3,0,0.4-0.3c0.2-1.2,0.3-2.5,0.5-3.7c0.1-1.2,0.3-2.3,0.4-3.5h-7.4c0-0.1,0-0.2,0-0.3c0-1.6,0-3.3,0-4.9
|
||||
c0-0.4,0.1-0.8,0.2-1.2c0.3-1.1,1-1.7,2.1-1.9c0.5-0.1,1.1-0.1,1.6-0.1c1.1,0,2.3,0,3.4,0c0.3,0,0.3-0.1,0.3-0.3c0-2,0-4.1,0-6.1
|
||||
c0-0.2-0.1-0.3-0.3-0.4c-2-0.2-4-0.3-6-0.2c-2.2,0.1-4.2,0.7-6,2.1c-1.5,1.3-2.4,2.9-2.8,4.8c-0.3,1-0.3,2.1-0.3,3.2
|
||||
c0,1.7,0,3.4,0,5v0.4h-0.3c-2,0-3.9,0-5.9,0c-0.3,0-0.3,0.1-0.3,0.3c0,2.3,0,4.6,0,6.9C22,32,22,32,22.3,32z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1,012 B |
Before Width: | Height: | Size: 1.2 KiB |
19
scripts/system/html/img/hifi_icon.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M12,52.3c6.5,0,13.2,0,20.2,0c6.5,0,13.4,0,20.6,0c3.5,0,5-1.6,5-5.6c0-10.2,0-20.5,0-30.7c0-4-1.3-5.5-5-5.5
|
||||
c-4.8,0-9.6,0-14.4,0l-5.9,0c-2,0-4,0-6,0c-4.8,0-9.7,0-14.5,0c-3.5,0-4.9,1.5-4.9,5.5c0,10.3,0,20.6,0,30.9
|
||||
C7.1,50.6,8.6,52.3,12,52.3z M41.9,43.3C41.9,43.3,41.9,43.3,41.9,43.3c0.1-0.1,0.1-0.1,0.1-0.2c1.4-1.5,2.8-3,4.3-4.6l0.8-0.9
|
||||
c0.6,0.6,1.1,1.2,1.7,1.8c1.8,1.9,3.7,3.9,5.4,6c0.2,0.3,0.3,1.2,0,2.7c-0.1,0.1-0.5,0.2-1.5,0.4c-4.4,0.9-7.7-0.6-10.2-4.5
|
||||
C42.4,43.8,42.2,43.5,41.9,43.3z M54.5,32.1c0,2.7,0,5.5,0,8.2L53.3,39c-1.4-1.5-2.8-3-4.2-4.4c-1.7-1.8-2.3-1.8-3.9,0
|
||||
c-1.4,1.5-2.8,3-4.2,4.5l-1.5,1.6l-3.4-3.6c-3.4-3.7-6.8-7.3-10.2-10.9c-1.1-1.2-1.8-1.8-2.4-1.8c-0.6,0-1.3,0.6-2.4,1.8
|
||||
c-2.6,2.8-5.4,5.5-8.1,8.4l-3,2.9v-4.4c0-1.6,0.2-3.2,0.2-4.8c0-4.1,0.1-8.4,0.1-12.5c0-0.7,0.1-1.1,0.3-1.3
|
||||
c0.2-0.2,0.6-0.3,1.2-0.3c13.7,0,27.3,0,41.2,0c0.5,0,1,0,1.2,0.3c0.1,0.2,0.2,0.5,0.2,0.9C54.5,20.9,54.5,26.6,54.5,32.1z
|
||||
M12.7,40.2c2.7-2.7,5.2-5.4,7.9-8.3c0.9-1,1.9-2.1,2.9-3.1L42,48.5c-0.2,0-0.4,0-0.6,0c-9.6,0-19.3,0-28.9,0c-0.8,0-1.5,0-1.7-0.2
|
||||
c-0.2-0.2-0.2-1-0.3-2l0-0.1C10.2,43.7,10.9,42,12.7,40.2z"/>
|
||||
<path class="st0" d="M37,23.7c0,2.1,1.4,3.8,3.3,3.8c1.9,0,3.4-1.6,3.4-3.7c0-2.1-1.4-3.8-3.3-3.8C38.5,19.9,37,21.6,37,23.7z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
scripts/system/html/img/loader.gif
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
12
scripts/system/html/img/twitter_icon.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M13.1,43.9c4.5,0,8.2-0.9,11.8-3.6c-3.7-0.7-6.2-2.3-7.6-5.5c0.9-0.2,1.7-0.3,2.9-0.6c-3.6-1.7-5.8-4-6.2-7.9
|
||||
c1.1,0.2,2,0.4,3.6,0.8c-3.3-3.5-4.5-6.9-2.5-11.1c4.8,4.9,10.2,8.2,16.6,8.5c0.7-2.3,1-4.6,2-6.4c2.2-3.9,7.4-5.2,11-2.6
|
||||
c2,1.4,3.6,1.2,5.5,0.4c0.4-0.2,0.7-0.2,1.7-0.5c-1.1,1.5-1.8,2.5-2.7,3.8c1.1-0.1,2-0.3,2.9-0.4c0,0.2,0.1,0.3,0,0.4
|
||||
c-2.6,3.4-2.9,4-3.8,8.9c-3.1,15.7-19.7,23.9-34,16.7C14.2,44.6,13.9,44.4,13.1,43.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 552 B |
|
@ -1,3 +1,5 @@
|
|||
/*jslint browser:true */
|
||||
/*jslint maxlen: 180*/
|
||||
"use strict";
|
||||
//
|
||||
// SnapshotReview.js
|
||||
|
@ -13,27 +15,46 @@
|
|||
var paths = [];
|
||||
var idCounter = 0;
|
||||
var imageCount = 0;
|
||||
var blastShareText = "Blast to my Connections",
|
||||
blastAlreadySharedText = "Already Blasted to Connections",
|
||||
hifiShareText = "Share to Snaps Feed",
|
||||
hifiAlreadySharedText = "Already Shared to Snaps Feed",
|
||||
facebookShareText = "Share to Facebook",
|
||||
twitterShareText = "Share to Twitter";
|
||||
function showSetupInstructions() {
|
||||
var snapshotImagesDiv = document.getElementById("snapshot-images");
|
||||
snapshotImagesDiv.className = "snapshotInstructions";
|
||||
snapshotImagesDiv.innerHTML = '<img class="centeredImage" src="./img/snapshotIcon.png" alt="Snapshot Instructions" width="64" height="64"/>' +
|
||||
'<br/>' +
|
||||
'<p>This app lets you take and share snaps and GIFs with your connections in High Fidelity.</p>' +
|
||||
"<h4>Setup Instructions</h4>" +
|
||||
"<p>Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:</p>" +
|
||||
'<br/>' +
|
||||
'<div style="text-align:center;">' +
|
||||
'<input class="blueButton" style="margin-left:auto;margin-right:auto;width:130px;" type="button" value="CHOOSE" onclick="chooseSnapshotLocation()" />' +
|
||||
'</div>';
|
||||
'<br/>' +
|
||||
'<p>Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.</p>' +
|
||||
"<h4>Setup Instructions</h4>" +
|
||||
"<p>Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:</p>" +
|
||||
'<br/>' +
|
||||
'<div style="text-align:center;">' +
|
||||
'<input class="blueButton" style="margin-left:auto;margin-right:auto;width:130px;" type="button" value="CHOOSE" onclick="chooseSnapshotLocation()" />' +
|
||||
'</div>';
|
||||
document.getElementById("snap-button").disabled = true;
|
||||
}
|
||||
function showSetupComplete() {
|
||||
var snapshotImagesDiv = document.getElementById("snapshot-images");
|
||||
snapshotImagesDiv.className = "snapshotInstructions";
|
||||
snapshotImagesDiv.innerHTML = '<img class="centeredImage" src="./img/snapshotIcon.png" alt="Snapshot Instructions" width="64" height="64"/>' +
|
||||
'<br/>' +
|
||||
"<h4>You're all set!</h4>" +
|
||||
'<p>Try taking a snapshot by pressing the red button below.</p>';
|
||||
'<br/>' +
|
||||
'<div style="text-align:center;font-weight:bold;">' +
|
||||
'<p>Snapshot location set.</p>' +
|
||||
'<p>Press the big red button to take a snap!</p>' +
|
||||
'</div>';
|
||||
}
|
||||
function showSnapshotInstructions() {
|
||||
var snapshotImagesDiv = document.getElementById("snapshot-images");
|
||||
snapshotImagesDiv.className = "snapshotInstructions";
|
||||
snapshotImagesDiv.innerHTML = '<img class="centeredImage" src="./img/snapshotIcon.png" alt="Snapshot Instructions" width="64" height="64"/>' +
|
||||
'<br/>' +
|
||||
'<p>Take and share snaps and GIFs with people in High Fidelity, Facebook, and Twitter.</p>' +
|
||||
'<br/>' +
|
||||
'<div style="text-align:center;font-weight:bold;">' +
|
||||
'<p>Press the big red button to take a snap!</p>' +
|
||||
'</div>';
|
||||
}
|
||||
function chooseSnapshotLocation() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
|
@ -52,13 +73,120 @@ function clearImages() {
|
|||
imageCount = 0;
|
||||
idCounter = 0;
|
||||
}
|
||||
function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled) {
|
||||
|
||||
function selectImageToShare(selectedID, isSelected) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
var imageContainer = document.getElementById(selectedID),
|
||||
image = document.getElementById(selectedID + 'img'),
|
||||
shareBar = document.getElementById(selectedID + "shareBar"),
|
||||
shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"),
|
||||
shareBarHelp = document.getElementById(selectedID + "shareBarHelp"),
|
||||
showShareButtonsButtonDiv = document.getElementById(selectedID + "showShareButtonsButtonDiv"),
|
||||
itr,
|
||||
containers = document.getElementsByClassName("shareControls");
|
||||
|
||||
if (isSelected) {
|
||||
showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, false); };
|
||||
showShareButtonsButtonDiv.classList.remove("inactive");
|
||||
showShareButtonsButtonDiv.classList.add("active");
|
||||
|
||||
image.onclick = function () { selectImageToShare(selectedID, false); };
|
||||
imageContainer.style.outline = "4px solid #00b4ef";
|
||||
imageContainer.style.outlineOffset = "-4px";
|
||||
|
||||
shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.45)";
|
||||
|
||||
shareButtonsDiv.style.visibility = "visible";
|
||||
shareBarHelp.style.visibility = "visible";
|
||||
|
||||
for (itr = 0; itr < containers.length; itr += 1) {
|
||||
var parentID = containers[itr].id.slice(0, 2);
|
||||
if (parentID !== selectedID) {
|
||||
selectImageToShare(parentID, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showShareButtonsButtonDiv.onclick = function () { selectImageToShare(selectedID, true); };
|
||||
showShareButtonsButtonDiv.classList.remove("active");
|
||||
showShareButtonsButtonDiv.classList.add("inactive");
|
||||
|
||||
image.onclick = function () { selectImageToShare(selectedID, true); };
|
||||
imageContainer.style.outline = "none";
|
||||
|
||||
shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)";
|
||||
|
||||
shareButtonsDiv.style.visibility = "hidden";
|
||||
shareBarHelp.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
function createShareBar(parentID, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) {
|
||||
var shareBar = document.createElement("div"),
|
||||
shareBarHelpID = parentID + "shareBarHelp",
|
||||
shareButtonsDivID = parentID + "shareButtonsDiv",
|
||||
showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv",
|
||||
showShareButtonsLabelID = parentID + "showShareButtonsLabel",
|
||||
blastToConnectionsButtonID = parentID + "blastToConnectionsButton",
|
||||
shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton",
|
||||
facebookButtonID = parentID + "facebookButton",
|
||||
twitterButtonID = parentID + "twitterButton";
|
||||
|
||||
shareBar.id = parentID + "shareBar";
|
||||
shareBar.className = "shareControls";
|
||||
var shareBarInnerHTML = '<div class="showShareButtonsButtonDiv inactive" id="' + showShareButtonsButtonDivID + '" onclick="selectImageToShare(' + parentID + ', true)">' +
|
||||
'<label id="' + showShareButtonsLabelID + '">SHARE</label>' +
|
||||
'<span class="showShareButtonDots">' +
|
||||
'' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="shareButtons" id="' + shareButtonsDivID + '" style="visibility:hidden">';
|
||||
if (canBlast) {
|
||||
shareBarInnerHTML += '<div class="shareButton blastToConnections' + (blastButtonDisabled ? ' disabled' : '') + '" id="' + blastToConnectionsButtonID + '" onmouseover="shareButtonHovered(\'blast\', ' + parentID + ')" onclick="' + (blastButtonDisabled ? '' : 'blastToConnections(' + parentID + ', ' + isGif + ')') + '"><img src="img/blast_icon.svg"></div>';
|
||||
}
|
||||
shareBarInnerHTML += '<div class="shareButton shareWithEveryone' + (hifiButtonDisabled ? ' disabled' : '') + '" id="' + shareWithEveryoneButtonID + '" onmouseover="shareButtonHovered(\'hifi\', ' + parentID + ')" onclick="' + (hifiButtonDisabled ? '' : 'shareWithEveryone(' + parentID + ', ' + isGif + ')') + '"><img src="img/hifi_icon.svg" style="width:35px;height:35px;margin:2px 0 0 2px;"></div>' +
|
||||
'<a class="shareButton facebookButton" id="' + facebookButtonID + '" onmouseover="shareButtonHovered(\'facebook\', ' + parentID + ')" onclick="shareButtonClicked(\'facebook\', ' + parentID + ')"><img src="img/fb_icon.svg"></a>' +
|
||||
'<a class="shareButton twitterButton" id="' + twitterButtonID + '" onmouseover="shareButtonHovered(\'twitter\', ' + parentID + ')" onclick="shareButtonClicked(\'twitter\', ' + parentID + ')"><img src="img/twitter_icon.svg"></a>' +
|
||||
'</div>';
|
||||
|
||||
shareBar.innerHTML = shareBarInnerHTML;
|
||||
|
||||
shareBar.innerHTML += '<div class="shareControlsHelp" id="' + shareBarHelpID + '" style="visibility:hidden;' + ((canBlast && blastButtonDisabled || !canBlast && hifiButtonDisabled) ? "background-color:#000;opacity:0.5;" : "") + '"></div>';
|
||||
|
||||
// Add onclick handler to parent DIV's img to toggle share buttons
|
||||
document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true); };
|
||||
|
||||
return shareBar;
|
||||
}
|
||||
function appendShareBar(divID, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast) {
|
||||
if (divID.id) {
|
||||
divID = divID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
document.getElementById(divID).appendChild(createShareBar(divID, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast));
|
||||
if (divID === "p0") {
|
||||
selectImageToShare(divID, true);
|
||||
if (canBlast) {
|
||||
shareButtonHovered('blast', divID);
|
||||
} else {
|
||||
shareButtonHovered('hifi', divID);
|
||||
}
|
||||
}
|
||||
}
|
||||
function shareForUrl(selectedID) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "shareSnapshotForUrl",
|
||||
data: paths[parseInt(selectedID.substring(1), 10)]
|
||||
}));
|
||||
}
|
||||
function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, blastButtonDisabled, hifiButtonDisabled, canBlast) {
|
||||
if (!image_data.localPath) {
|
||||
return;
|
||||
}
|
||||
var id = "p" + idCounter++;
|
||||
// imageContainer setup
|
||||
var imageContainer = document.createElement("DIV");
|
||||
var id = "p" + (idCounter++),
|
||||
imageContainer = document.createElement("DIV"),
|
||||
img = document.createElement("IMG"),
|
||||
isGif;
|
||||
imageContainer.id = id;
|
||||
imageContainer.style.width = "95%";
|
||||
imageContainer.style.height = "240px";
|
||||
|
@ -67,160 +195,264 @@ function addImage(image_data, isGifLoading, canShare, isShowingPreviousImages, b
|
|||
imageContainer.style.justifyContent = "center";
|
||||
imageContainer.style.alignItems = "center";
|
||||
imageContainer.style.position = "relative";
|
||||
// img setup
|
||||
var img = document.createElement("IMG");
|
||||
img.id = id + "img";
|
||||
if (imageCount > 1) {
|
||||
img.setAttribute("class", "multiple");
|
||||
}
|
||||
img.src = image_data.localPath;
|
||||
isGif = img.src.split('.').pop().toLowerCase() === "gif";
|
||||
imageContainer.appendChild(img);
|
||||
document.getElementById("snapshot-images").appendChild(imageContainer);
|
||||
paths.push(image_data.localPath);
|
||||
var isGif = img.src.split('.').pop().toLowerCase() === "gif";
|
||||
if (isGif) {
|
||||
imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
|
||||
}
|
||||
if (!isGifLoading && !isShowingPreviousImages && canShare) {
|
||||
appendShareBar(id, isGif, blastButtonDisabled, hifiButtonDisabled, true);
|
||||
shareForUrl(id);
|
||||
} else if (isShowingPreviousImages && canShare && image_data.story_id) {
|
||||
appendShareBar(id, image_data.story_id, isGif, blastButtonDisabled, hifiButtonDisabled)
|
||||
}
|
||||
if (isShowingPreviousImages && image_data.story_id) {
|
||||
appendShareBar(id, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
|
||||
updateShareInfo(id, image_data.story_id);
|
||||
}
|
||||
}
|
||||
function appendShareBar(divID, story_id, isGif, blastButtonDisabled, hifiButtonDisabled) {
|
||||
var story_url = "https://highfidelity.com/user_stories/" + story_id;
|
||||
var parentDiv = document.getElementById(divID);
|
||||
parentDiv.setAttribute('data-story-id', story_id);
|
||||
document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, blastButtonDisabled, hifiButtonDisabled));
|
||||
if (divID === "p0") {
|
||||
selectImageToShare(divID, true);
|
||||
function showConfirmationMessage(selectedID, destination) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
|
||||
var opacity = 2.0,
|
||||
fadeRate = 0.05,
|
||||
timeBetweenFadesMS = 50,
|
||||
confirmationMessageContainer = document.createElement("div"),
|
||||
confirmationMessage = document.createElement("div");
|
||||
confirmationMessageContainer.className = "confirmationMessageContainer";
|
||||
|
||||
confirmationMessage.className = "confirmationMessage";
|
||||
|
||||
var socialIcon = document.createElement("img");
|
||||
switch (destination) {
|
||||
case 'blast':
|
||||
socialIcon.src = "img/blast_icon.svg";
|
||||
confirmationMessage.appendChild(socialIcon);
|
||||
confirmationMessage.innerHTML += '<span>Blast Sent!</span>';
|
||||
confirmationMessage.style.backgroundColor = "#EA4C5F";
|
||||
break;
|
||||
case 'hifi':
|
||||
socialIcon.src = "img/hifi_icon.svg";
|
||||
confirmationMessage.appendChild(socialIcon);
|
||||
confirmationMessage.innerHTML += '<span>Snap Shared!</span>';
|
||||
confirmationMessage.style.backgroundColor = "#1FC6A6";
|
||||
break;
|
||||
}
|
||||
|
||||
confirmationMessageContainer.appendChild(confirmationMessage);
|
||||
document.getElementById(selectedID).appendChild(confirmationMessageContainer);
|
||||
|
||||
setInterval(function () {
|
||||
if (opacity <= fadeRate) {
|
||||
confirmationMessageContainer.remove();
|
||||
}
|
||||
opacity -= fadeRate;
|
||||
confirmationMessageContainer.style.opacity = opacity;
|
||||
}, timeBetweenFadesMS);
|
||||
}
|
||||
function showUploadingMessage(selectedID, destination) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
|
||||
var shareBarHelp = document.getElementById(selectedID + "shareBarHelp");
|
||||
|
||||
shareBarHelp.innerHTML = '<img style="display:inline;width:25px;height:25px;" src="./img/loader.gif"></img><span style="position:relative;margin-left:5px;bottom:7px;">Preparing to Share</span>';
|
||||
shareBarHelp.setAttribute("data-destination", destination);
|
||||
}
|
||||
function hideUploadingMessageAndShare(selectedID, storyID) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
|
||||
var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"),
|
||||
shareBarHelpDestination = shareBarHelp.getAttribute("data-destination");
|
||||
if (shareBarHelpDestination) {
|
||||
switch (shareBarHelpDestination) {
|
||||
case 'blast':
|
||||
blastToConnections(selectedID, selectedID === "p1");
|
||||
shareBarHelp.innerHTML = blastAlreadySharedText;
|
||||
break;
|
||||
case 'hifi':
|
||||
shareWithEveryone(selectedID, selectedID === "p1");
|
||||
shareBarHelp.innerHTML = hifiAlreadySharedText;
|
||||
break;
|
||||
case 'facebook':
|
||||
var facebookButton = document.getElementById(selectedID + "facebookButton");
|
||||
window.open(facebookButton.getAttribute("href"), "_blank");
|
||||
shareBarHelp.innerHTML = facebookShareText;
|
||||
break;
|
||||
case 'twitter':
|
||||
var twitterButton = document.getElementById(selectedID + "twitterButton");
|
||||
window.open(twitterButton.getAttribute("href"), "_blank");
|
||||
shareBarHelp.innerHTML = twitterShareText;
|
||||
break;
|
||||
}
|
||||
|
||||
shareBarHelp.setAttribute("data-destination", "");
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "removeFromStoryIDsToMaybeDelete",
|
||||
story_id: storyID
|
||||
}));
|
||||
}
|
||||
}
|
||||
function createShareBar(parentID, isGif, shareURL, blastButtonDisabled, hifiButtonDisabled) {
|
||||
var shareBar = document.createElement("div");
|
||||
shareBar.id = parentID + "shareBar";
|
||||
shareBar.className = "shareControls";
|
||||
var shareButtonsDivID = parentID + "shareButtonsDiv";
|
||||
var showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv";
|
||||
var showShareButtonsButtonID = parentID + "showShareButtonsButton";
|
||||
var showShareButtonsLabelID = parentID + "showShareButtonsLabel";
|
||||
var blastToConnectionsButtonID = parentID + "blastToConnectionsButton";
|
||||
var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton";
|
||||
var facebookButtonID = parentID + "facebookButton";
|
||||
var twitterButtonID = parentID + "twitterButton";
|
||||
shareBar.innerHTML += '' +
|
||||
'<div class="shareButtons" id="' + shareButtonsDivID + '" style="visibility:hidden">' +
|
||||
'<input type="button"' + (blastButtonDisabled ? ' disabled' : '') + ' class="blastToConnections blueButton" id="' + blastToConnectionsButtonID + '" value="BLAST TO MY CONNECTIONS" onclick="blastToConnections(' + parentID + ', ' + isGif + ')" />' +
|
||||
'<input type="button"' + (hifiButtonDisabled ? ' disabled' : '') + ' class="shareWithEveryone" id="' + shareWithEveryoneButtonID + '" onclick="shareWithEveryone(' + parentID + ', ' + isGif + ')" />' +
|
||||
'<a class="facebookButton" id="' + facebookButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL + '"></a>' +
|
||||
'<a class="twitterButton" id="' + twitterButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelity&hashtags=VR,HiFi"></a>' +
|
||||
'</div>' +
|
||||
'<div class="showShareButtonsButtonDiv" id="' + showShareButtonsButtonDivID + '">' +
|
||||
'<label id="' + showShareButtonsLabelID + '" for="' + showShareButtonsButtonID + '">SHARE</label>' +
|
||||
'<input type="button" class="showShareButton inactive" id="' + showShareButtonsButtonID + '" onclick="selectImageToShare(' + parentID + ', true)" />' +
|
||||
'<div class="showShareButtonDots">' +
|
||||
'' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
function updateShareInfo(containerID, storyID) {
|
||||
if (containerID.id) {
|
||||
containerID = containerID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
var shareBar = document.getElementById(containerID + "shareBar"),
|
||||
parentDiv = document.getElementById(containerID),
|
||||
shareURL = "https://highfidelity.com/user_stories/" + storyID,
|
||||
facebookButton = document.getElementById(containerID + "facebookButton"),
|
||||
twitterButton = document.getElementById(containerID + "twitterButton");
|
||||
|
||||
// Add onclick handler to parent DIV's img to toggle share buttons
|
||||
document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true) };
|
||||
parentDiv.setAttribute('data-story-id', storyID);
|
||||
|
||||
return shareBar;
|
||||
facebookButton.setAttribute("target", "_blank");
|
||||
facebookButton.setAttribute("href", 'https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL);
|
||||
|
||||
twitterButton.setAttribute("target", "_blank");
|
||||
twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelity&hashtags=VR,HiFi');
|
||||
|
||||
hideUploadingMessageAndShare(containerID, storyID);
|
||||
}
|
||||
function selectImageToShare(selectedID, isSelected) {
|
||||
function blastToConnections(selectedID, isGif) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
var imageContainer = document.getElementById(selectedID);
|
||||
var image = document.getElementById(selectedID + 'img');
|
||||
var shareBar = document.getElementById(selectedID + "shareBar");
|
||||
var shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv");
|
||||
var showShareButtonsButton = document.getElementById(selectedID + "showShareButtonsButton");
|
||||
|
||||
if (isSelected) {
|
||||
showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, false) };
|
||||
showShareButtonsButton.classList.remove("inactive");
|
||||
showShareButtonsButton.classList.add("active");
|
||||
var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton"),
|
||||
shareBar = document.getElementById(selectedID + "shareBar"),
|
||||
shareBarHelp = document.getElementById(selectedID + "shareBarHelp");
|
||||
blastToConnectionsButton.onclick = function () { };
|
||||
|
||||
image.onclick = function () { selectImageToShare(selectedID, false) };
|
||||
imageContainer.style.outline = "4px solid #00b4ef";
|
||||
imageContainer.style.outlineOffset = "-4px";
|
||||
var storyID = document.getElementById(selectedID).getAttribute("data-story-id");
|
||||
|
||||
shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
|
||||
|
||||
shareButtonsDiv.style.visibility = "visible";
|
||||
|
||||
var containers = document.getElementsByClassName("shareControls");
|
||||
var parentID;
|
||||
for (var i = 0; i < containers.length; ++i) {
|
||||
parentID = containers[i].id.slice(0, 2);
|
||||
if (parentID !== selectedID) {
|
||||
selectImageToShare(parentID, false);
|
||||
}
|
||||
}
|
||||
if (storyID) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "blastToConnections",
|
||||
story_id: storyID,
|
||||
isGif: isGif
|
||||
}));
|
||||
showConfirmationMessage(selectedID, 'blast');
|
||||
blastToConnectionsButton.classList.add("disabled");
|
||||
blastToConnectionsButton.style.backgroundColor = "#000000";
|
||||
blastToConnectionsButton.style.opacity = "0.5";
|
||||
shareBarHelp.style.backgroundColor = "#000000";
|
||||
shareBarHelp.style.opacity = "0.5";
|
||||
} else {
|
||||
showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) };
|
||||
showShareButtonsButton.classList.remove("active");
|
||||
showShareButtonsButton.classList.add("inactive");
|
||||
|
||||
image.onclick = function () { selectImageToShare(selectedID, true) };
|
||||
imageContainer.style.outline = "none";
|
||||
|
||||
shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)";
|
||||
|
||||
shareButtonsDiv.style.visibility = "hidden";
|
||||
showUploadingMessage(selectedID, 'blast');
|
||||
}
|
||||
}
|
||||
function shareForUrl(selectedID) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "shareSnapshotForUrl",
|
||||
data: paths[parseInt(selectedID.substring(1))]
|
||||
}));
|
||||
}
|
||||
function blastToConnections(selectedID, isGif) {
|
||||
selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
|
||||
document.getElementById(selectedID + "blastToConnectionsButton").disabled = true;
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "blastToConnections",
|
||||
story_id: document.getElementById(selectedID).getAttribute("data-story-id"),
|
||||
isGif: isGif
|
||||
}));
|
||||
}
|
||||
function shareWithEveryone(selectedID, isGif) {
|
||||
selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
|
||||
document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true;
|
||||
var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton"),
|
||||
shareBar = document.getElementById(selectedID + "shareBar"),
|
||||
shareBarHelp = document.getElementById(selectedID + "shareBarHelp");
|
||||
shareWithEveryoneButton.onclick = function () { };
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "shareSnapshotWithEveryone",
|
||||
story_id: document.getElementById(selectedID).getAttribute("data-story-id"),
|
||||
isGif: isGif
|
||||
}));
|
||||
var storyID = document.getElementById(selectedID).getAttribute("data-story-id");
|
||||
|
||||
if (storyID) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "shareSnapshotWithEveryone",
|
||||
story_id: storyID,
|
||||
isGif: isGif
|
||||
}));
|
||||
showConfirmationMessage(selectedID, 'hifi');
|
||||
shareWithEveryoneButton.classList.add("disabled");
|
||||
shareWithEveryoneButton.style.backgroundColor = "#000000";
|
||||
shareWithEveryoneButton.style.opacity = "0.5";
|
||||
shareBarHelp.style.backgroundColor = "#000000";
|
||||
shareBarHelp.style.opacity = "0.5";
|
||||
} else {
|
||||
showUploadingMessage(selectedID, 'hifi');
|
||||
}
|
||||
}
|
||||
function shareButtonClicked(selectedID) {
|
||||
selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "shareButtonClicked",
|
||||
story_id: document.getElementById(selectedID).getAttribute("data-story-id")
|
||||
}));
|
||||
}
|
||||
function cancelSharing(selectedID) {
|
||||
selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
var shareBar = document.getElementById(selectedID + "shareBar");
|
||||
function shareButtonHovered(destination, selectedID) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
var shareBarHelp = document.getElementById(selectedID + "shareBarHelp"),
|
||||
shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv").childNodes,
|
||||
itr;
|
||||
|
||||
shareBar.style.display = "inline";
|
||||
for (itr = 0; itr < shareButtonsDiv.length; itr += 1) {
|
||||
shareButtonsDiv[itr].style.backgroundColor = "rgba(0, 0, 0, 0)";
|
||||
}
|
||||
shareBarHelp.style.opacity = "1.0";
|
||||
|
||||
switch (destination) {
|
||||
case 'blast':
|
||||
var blastToConnectionsButton = document.getElementById(selectedID + "blastToConnectionsButton");
|
||||
if (!blastToConnectionsButton.classList.contains("disabled")) {
|
||||
shareBarHelp.style.backgroundColor = "#EA4C5F";
|
||||
shareBarHelp.style.opacity = "1.0";
|
||||
blastToConnectionsButton.style.backgroundColor = "#EA4C5F";
|
||||
blastToConnectionsButton.style.opacity = "1.0";
|
||||
shareBarHelp.innerHTML = blastShareText;
|
||||
} else {
|
||||
shareBarHelp.style.backgroundColor = "#000000";
|
||||
shareBarHelp.style.opacity = "0.5";
|
||||
blastToConnectionsButton.style.backgroundColor = "#000000";
|
||||
blastToConnectionsButton.style.opacity = "0.5";
|
||||
shareBarHelp.innerHTML = blastAlreadySharedText;
|
||||
}
|
||||
break;
|
||||
case 'hifi':
|
||||
var shareWithEveryoneButton = document.getElementById(selectedID + "shareWithEveryoneButton");
|
||||
if (!shareWithEveryoneButton.classList.contains("disabled")) {
|
||||
shareBarHelp.style.backgroundColor = "#1FC6A6";
|
||||
shareBarHelp.style.opacity = "1.0";
|
||||
shareWithEveryoneButton.style.backgroundColor = "#1FC6A6";
|
||||
shareWithEveryoneButton.style.opacity = "1.0";
|
||||
shareBarHelp.innerHTML = hifiShareText;
|
||||
} else {
|
||||
shareBarHelp.style.backgroundColor = "#000000";
|
||||
shareBarHelp.style.opacity = "0.5";
|
||||
shareWithEveryoneButton.style.backgroundColor = "#000000";
|
||||
shareWithEveryoneButton.style.opacity = "0.5";
|
||||
shareBarHelp.innerHTML = hifiAlreadySharedText;
|
||||
}
|
||||
break;
|
||||
case 'facebook':
|
||||
shareBarHelp.style.backgroundColor = "#3C58A0";
|
||||
shareBarHelp.innerHTML = facebookShareText;
|
||||
document.getElementById(selectedID + "facebookButton").style.backgroundColor = "#3C58A0";
|
||||
break;
|
||||
case 'twitter':
|
||||
shareBarHelp.style.backgroundColor = "#00B4EE";
|
||||
shareBarHelp.innerHTML = twitterShareText;
|
||||
document.getElementById(selectedID + "twitterButton").style.backgroundColor = "#00B4EE";
|
||||
break;
|
||||
}
|
||||
}
|
||||
function shareButtonClicked(destination, selectedID) {
|
||||
if (selectedID.id) {
|
||||
selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
|
||||
}
|
||||
var storyID = document.getElementById(selectedID).getAttribute("data-story-id");
|
||||
|
||||
if (!storyID) {
|
||||
showUploadingMessage(selectedID, destination);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCaptureSetting(setting) {
|
||||
var stillAndGif = document.getElementById('stillAndGif');
|
||||
var stillOnly = document.getElementById('stillOnly');
|
||||
var stillAndGif = document.getElementById('stillAndGif'),
|
||||
stillOnly = document.getElementById('stillOnly');
|
||||
|
||||
stillAndGif.checked = setting;
|
||||
stillOnly.checked = !setting;
|
||||
|
||||
|
@ -229,19 +461,20 @@ function handleCaptureSetting(setting) {
|
|||
type: "snapshot",
|
||||
action: "captureStillAndGif"
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
stillOnly.onclick = function () {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "captureStillOnly"
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
window.onload = function () {
|
||||
// Uncomment the line below to test functionality in a browser.
|
||||
// See definition of "testInBrowser()" to modify tests.
|
||||
//testInBrowser(false);
|
||||
//testInBrowser(3);
|
||||
openEventBridge(function () {
|
||||
// Set up a handler for receiving the data, and tell the .js we are ready to receive it.
|
||||
EventBridge.scriptEventReceived.connect(function (message) {
|
||||
|
@ -251,7 +484,9 @@ window.onload = function () {
|
|||
if (message.type !== "snapshot") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var messageOptions = message.options;
|
||||
|
||||
switch (message.action) {
|
||||
case 'showSetupInstructions':
|
||||
showSetupInstructions();
|
||||
|
@ -265,38 +500,41 @@ window.onload = function () {
|
|||
break;
|
||||
case 'showPreviousImages':
|
||||
clearImages();
|
||||
var messageOptions = message.options;
|
||||
imageCount = message.image_data.length;
|
||||
message.image_data.forEach(function (element, idx, array) {
|
||||
addImage(element, true, message.canShare, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled);
|
||||
});
|
||||
if (imageCount > 0) {
|
||||
message.image_data.forEach(function (element, idx) {
|
||||
addImage(element, true, message.canShare, true, message.image_data[idx].blastButtonDisabled, message.image_data[idx].hifiButtonDisabled, messageOptions.canBlast);
|
||||
});
|
||||
} else {
|
||||
showSnapshotInstructions();
|
||||
}
|
||||
break;
|
||||
case 'addImages':
|
||||
// The last element of the message contents list contains a bunch of options,
|
||||
// including whether or not we can share stuff
|
||||
// The other elements of the list contain image paths.
|
||||
var messageOptions = message.options;
|
||||
|
||||
if (messageOptions.containsGif) {
|
||||
if (messageOptions.processingGif) {
|
||||
imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
|
||||
message.image_data.push({ localPath: messageOptions.loadingGifPath });
|
||||
message.image_data.forEach(function (element, idx, array) {
|
||||
message.image_data.forEach(function (element, idx) {
|
||||
addImage(element, idx === 1, idx === 0 && messageOptions.canShare, false);
|
||||
});
|
||||
} else {
|
||||
var gifPath = message.image_data[0].localPath;
|
||||
var p1img = document.getElementById('p1img');
|
||||
var gifPath = message.image_data[0].localPath,
|
||||
p1img = document.getElementById('p1img');
|
||||
p1img.src = gifPath;
|
||||
|
||||
paths[1] = gifPath;
|
||||
if (messageOptions.canShare) {
|
||||
shareForUrl("p1");
|
||||
appendShareBar("p1", true, false, false, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imageCount = message.image_data.length;
|
||||
message.image_data.forEach(function (element, idx, array) {
|
||||
message.image_data.forEach(function (element) {
|
||||
addImage(element, false, messageOptions.canShare, false);
|
||||
});
|
||||
}
|
||||
|
@ -306,7 +544,7 @@ window.onload = function () {
|
|||
break;
|
||||
case 'snapshotUploadComplete':
|
||||
var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
|
||||
appendShareBar(isGif ? "p1" : "p0", message.story_id, isGif);
|
||||
updateShareInfo(isGif ? "p1" : "p0", message.story_id);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown message action received in SnapshotReview.js.");
|
||||
|
@ -333,13 +571,24 @@ function takeSnapshot() {
|
|||
}));
|
||||
}
|
||||
|
||||
function testInBrowser(isTestingSetupInstructions) {
|
||||
if (isTestingSetupInstructions) {
|
||||
function testInBrowser(test) {
|
||||
if (test === 0) {
|
||||
showSetupInstructions();
|
||||
} else {
|
||||
imageCount = 1;
|
||||
} else if (test === 1) {
|
||||
imageCount = 2;
|
||||
//addImage({ localPath: 'http://lorempixel.com/553/255' });
|
||||
addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.gif' }, false, true, true, false, false);
|
||||
addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-05-01_15-48-15.jpg' }, false, true, true, false, false);
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, true, false, false, true);
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, true, false, false, true);
|
||||
} else if (test === 2) {
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, true, false, false, true);
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, true, false, false, true);
|
||||
showConfirmationMessage("p0", 'blast');
|
||||
showConfirmationMessage("p1", 'hifi');
|
||||
} else if (test === 3) {
|
||||
imageCount = 2;
|
||||
//addImage({ localPath: 'http://lorempixel.com/553/255' });
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.jpg', story_id: 1338 }, false, true, true, false, false, true);
|
||||
addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/hifi-snap-by-zfox-on-2017-05-01_13-28-58.gif', story_id: 1337 }, false, true, true, false, false, true);
|
||||
showUploadingMessage("p0", 'hifi');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,11 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
|
||||
var LABEL = "makeUserConnection";
|
||||
var MAX_AVATAR_DISTANCE = 0.2; // m
|
||||
var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed
|
||||
|
@ -126,61 +129,6 @@
|
|||
function cleanId(guidWithCurlyBraces) {
|
||||
return guidWithCurlyBraces.slice(1, -1);
|
||||
}
|
||||
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
|
||||
var httpRequest = new XMLHttpRequest(), key;
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
response = {statusCode: httpRequest.status};
|
||||
}
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
options = {uri: options};
|
||||
}
|
||||
if (options.url) {
|
||||
options.uri = options.url;
|
||||
}
|
||||
if (!options.method) {
|
||||
options.method = 'GET';
|
||||
}
|
||||
if (options.body && (options.method === 'GET')) { // add query parameters
|
||||
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
|
||||
for (key in options.body) {
|
||||
if (options.body.hasOwnProperty(key)) {
|
||||
params.push(key + '=' + options.body[key]);
|
||||
}
|
||||
}
|
||||
options.uri += appender + params.join('&');
|
||||
delete options.body;
|
||||
}
|
||||
if (options.json) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers["Content-type"] = "application/json";
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
for (key in options.headers || {}) {
|
||||
if (options.headers.hasOwnProperty(key)) {
|
||||
httpRequest.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
}
|
||||
httpRequest.open(options.method, options.uri, true);
|
||||
httpRequest.send(options.body);
|
||||
}
|
||||
|
||||
function handToString(hand) {
|
||||
if (hand === Controller.Standard.RightHand) {
|
||||
|
@ -514,7 +462,7 @@
|
|||
endHandshakeAnimation();
|
||||
// No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us
|
||||
// in a weird state.
|
||||
request({uri: requestUrl, method: 'DELETE'}, debug);
|
||||
request({ uri: requestUrl, method: 'DELETE' }, debug);
|
||||
}
|
||||
|
||||
function updateTriggers(value, fromKeyboard, hand) {
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
|
||||
var populateNearbyUserList, color, textures, removeOverlays,
|
||||
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
|
||||
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
|
||||
|
@ -331,55 +333,6 @@ function updateUser(data) {
|
|||
//
|
||||
// These are prototype versions that will be changed when the back end changes.
|
||||
var METAVERSE_BASE = location.metaverseServerUrl;
|
||||
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
|
||||
var httpRequest = new XMLHttpRequest(), key;
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
options = {uri: options};
|
||||
}
|
||||
if (options.url) {
|
||||
options.uri = options.url;
|
||||
}
|
||||
if (!options.method) {
|
||||
options.method = 'GET';
|
||||
}
|
||||
if (options.body && (options.method === 'GET')) { // add query parameters
|
||||
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
|
||||
for (key in options.body) {
|
||||
params.push(key + '=' + options.body[key]);
|
||||
}
|
||||
options.uri += appender + params.join('&');
|
||||
delete options.body;
|
||||
}
|
||||
if (options.json) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers["Content-type"] = "application/json";
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
for (key in options.headers || {}) {
|
||||
httpRequest.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
httpRequest.open(options.method, options.uri, true);
|
||||
httpRequest.send(options.body);
|
||||
}
|
||||
|
||||
|
||||
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
|
||||
request({
|
||||
|
|
|
@ -182,7 +182,6 @@ function onMessage(message) {
|
|||
break;
|
||||
case 'blastToConnections':
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
|
||||
if (message.isGif) {
|
||||
Settings.setValue("previousAnimatedSnapBlastingDisabled", true);
|
||||
} else {
|
||||
|
@ -242,7 +241,6 @@ function onMessage(message) {
|
|||
break;
|
||||
case 'shareSnapshotWithEveryone':
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
|
||||
if (message.isGif) {
|
||||
Settings.setValue("previousAnimatedSnapHifiSharingDisabled", true);
|
||||
} else {
|
||||
|
@ -283,8 +281,7 @@ function onMessage(message) {
|
|||
snapshotToShareAfterLogin = { path: message.data, href: message.href || href };
|
||||
}
|
||||
break;
|
||||
case 'shareButtonClicked':
|
||||
print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].');
|
||||
case 'removeFromStoryIDsToMaybeDelete':
|
||||
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
|
||||
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
|
||||
break;
|
||||
|
@ -314,7 +311,8 @@ function onButtonClicked() {
|
|||
snapshotOptions = {
|
||||
containsGif: previousAnimatedSnapPath !== "",
|
||||
processingGif: false,
|
||||
shouldUpload: false
|
||||
shouldUpload: false,
|
||||
canBlast: location.domainId === Settings.getValue("previousSnapshotDomainID")
|
||||
}
|
||||
imageData = [];
|
||||
if (previousStillSnapPath !== "") {
|
||||
|
@ -395,7 +393,9 @@ function takeSnapshot() {
|
|||
resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked.
|
||||
reticleVisible = Reticle.visible;
|
||||
Reticle.visible = false;
|
||||
Reticle.allowMouseCapture = false;
|
||||
if (!HMD.active) {
|
||||
Reticle.allowMouseCapture = false;
|
||||
}
|
||||
|
||||
var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true);
|
||||
if (includeAnimated) {
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
//
|
||||
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
|
||||
var gotoQmlSource = "TabletAddressDialog.qml";
|
||||
var buttonName = "GOTO";
|
||||
var onGotoScreen = false;
|
||||
|
@ -30,54 +33,7 @@
|
|||
text: buttonName,
|
||||
sortOrder: 8
|
||||
});
|
||||
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
|
||||
var httpRequest = new XMLHttpRequest(), key;
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
if (typeof options === 'string') {
|
||||
options = {uri: options};
|
||||
}
|
||||
if (options.url) {
|
||||
options.uri = options.url;
|
||||
}
|
||||
if (!options.method) {
|
||||
options.method = 'GET';
|
||||
}
|
||||
if (options.body && (options.method === 'GET')) { // add query parameters
|
||||
var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
|
||||
for (key in options.body) {
|
||||
params.push(key + '=' + options.body[key]);
|
||||
}
|
||||
options.uri += appender + params.join('&');
|
||||
delete options.body;
|
||||
}
|
||||
if (options.json) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers["Content-type"] = "application/json";
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
for (key in options.headers || {}) {
|
||||
httpRequest.setRequestHeader(key, options.headers[key]);
|
||||
}
|
||||
httpRequest.open(options.method, options.uri, true);
|
||||
httpRequest.send(options.body);
|
||||
}
|
||||
|
||||
function fromQml(message) {
|
||||
var response = {id: message.id, jsonrpc: "2.0"};
|
||||
switch (message.method) {
|
||||
|
@ -99,6 +55,7 @@
|
|||
// No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway.
|
||||
});
|
||||
}
|
||||
|
||||
var hasEventBridge = false;
|
||||
function wireEventBridge(on) {
|
||||
if (on) {
|
||||
|
|