mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge pull request #8457 from hyperlogic/feature/handheld-marketplace
Handheld Marketplace Tablet
This commit is contained in:
commit
f971b4b041
25 changed files with 358 additions and 85 deletions
|
@ -55,9 +55,11 @@ WebEngineView {
|
|||
}
|
||||
|
||||
onNewViewRequested:{
|
||||
if (desktop) {
|
||||
var component = Qt.createComponent("../Browser.qml");
|
||||
var newWindow = component.createObject(desktop);
|
||||
request.openIn(newWindow.webView)
|
||||
request.openIn(newWindow.webView);
|
||||
}
|
||||
}
|
||||
|
||||
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
|
||||
|
|
|
@ -59,6 +59,8 @@ const float DISPLAYNAME_ALPHA = 1.0f;
|
|||
const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f;
|
||||
const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
|
||||
|
||||
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534;
|
||||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
|
||||
return ItemKey::Builder::opaqueShape();
|
||||
|
@ -851,15 +853,33 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
|
|||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
glm::quat rotation;
|
||||
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
|
||||
return Quaternions::Y_180 * rotation;
|
||||
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
bool success;
|
||||
Transform avatarTransform;
|
||||
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
|
||||
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
|
||||
return glmExtractRotation(invAvatarMat * sensorToWorldMatrix);
|
||||
} else {
|
||||
glm::quat rotation;
|
||||
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
|
||||
return Quaternions::Y_180 * rotation;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||
glm::vec3 translation;
|
||||
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
|
||||
return Quaternions::Y_180 * translation;
|
||||
if (index == SENSOR_TO_WORLD_MATRIX_INDEX) {
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
bool success;
|
||||
Transform avatarTransform;
|
||||
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
|
||||
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
|
||||
return extractTranslation(invAvatarMat * sensorToWorldMatrix);
|
||||
} else {
|
||||
glm::vec3 translation;
|
||||
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
|
||||
return Quaternions::Y_180 * translation;
|
||||
}
|
||||
}
|
||||
|
||||
int Avatar::getJointIndex(const QString& name) const {
|
||||
|
|
|
@ -107,7 +107,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
_hmdSensorOrientation(),
|
||||
_hmdSensorPosition(),
|
||||
_bodySensorMatrix(),
|
||||
_sensorToWorldMatrix(),
|
||||
_goToPending(false),
|
||||
_goToPosition(),
|
||||
_goToOrientation(),
|
||||
|
@ -511,13 +510,9 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
updateAvatarEntities();
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
|
||||
return _sensorToWorldMatrixCache.get();
|
||||
}
|
||||
|
||||
// As far as I know no HMD system supports a play area of a kilometer in radius.
|
||||
// As far as I know no HMD system supports a play area of a kilometer in radius.
|
||||
static const float MAX_HMD_ORIGIN_DISTANCE = 1000.0f;
|
||||
|
||||
// Pass a recent sample of the HMD to the avatar.
|
||||
// This can also update the avatar's position to follow the HMD
|
||||
// as it moves through the world.
|
||||
|
@ -526,7 +521,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
|
|||
_hmdSensorMatrix = hmdSensorMatrix;
|
||||
auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
|
||||
|
||||
if (newHmdSensorPosition != _hmdSensorPosition &&
|
||||
if (newHmdSensorPosition != _hmdSensorPosition &&
|
||||
glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
|
||||
qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
|
||||
// Ignore unreasonable HMD sensor data
|
||||
|
|
|
@ -79,8 +79,6 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
|
||||
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
||||
|
||||
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
|
||||
|
||||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
|
@ -110,9 +108,6 @@ public:
|
|||
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
|
||||
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
|
||||
|
||||
// thread safe
|
||||
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
|
||||
|
||||
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
||||
Q_INVOKABLE QVariant getOrientationVar() const;
|
||||
|
||||
|
@ -415,6 +410,10 @@ private:
|
|||
bool _useSnapTurn { true };
|
||||
bool _clearOverlayWhenMoving { true };
|
||||
|
||||
// working copy of sensorToWorldMatrix.
|
||||
// See AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
|
||||
glm::mat4 _sensorToWorldMatrix;
|
||||
|
||||
// cache of the current HMD sensor position and orientation
|
||||
// in sensor space.
|
||||
glm::mat4 _hmdSensorMatrix;
|
||||
|
@ -427,10 +426,6 @@ private:
|
|||
// in sensor space.
|
||||
glm::mat4 _bodySensorMatrix;
|
||||
|
||||
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
||||
glm::mat4 _sensorToWorldMatrix;
|
||||
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
|
||||
|
||||
struct FollowHelper {
|
||||
FollowHelper();
|
||||
|
||||
|
|
|
@ -53,15 +53,18 @@ namespace AvatarDataPacket {
|
|||
// NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure.
|
||||
|
||||
PACKED_BEGIN struct Header {
|
||||
float position[3]; // skeletal model's position
|
||||
float globalPosition[3]; // avatar's position
|
||||
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
|
||||
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
|
||||
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
||||
float audioLoudness; // current loundess of microphone
|
||||
float position[3]; // skeletal model's position
|
||||
float globalPosition[3]; // avatar's position
|
||||
uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to
|
||||
uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag.
|
||||
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
||||
float audioLoudness; // current loundess of microphone
|
||||
uint8_t sensorToWorldQuat[6]; // 6 byte compressed quaternion part of sensor to world matrix
|
||||
uint16_t sensorToWorldScale; // uniform scale of sensor to world matrix
|
||||
float sensorToWorldTrans[3]; // fourth column of sensor to world matrix
|
||||
uint8_t flags;
|
||||
} PACKED_END;
|
||||
const size_t HEADER_SIZE = 49;
|
||||
const size_t HEADER_SIZE = 69;
|
||||
|
||||
// only present if HAS_REFERENTIAL flag is set in header.flags
|
||||
PACKED_BEGIN struct ParentInfo {
|
||||
|
@ -93,6 +96,9 @@ namespace AvatarDataPacket {
|
|||
*/
|
||||
}
|
||||
|
||||
static const int TRANSLATION_COMPRESSION_RADIX = 12;
|
||||
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
|
||||
|
||||
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
|
||||
|
||||
AvatarData::AvatarData() :
|
||||
|
@ -210,6 +216,14 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
header->lookAtPosition[2] = _headData->_lookAtPosition.z;
|
||||
header->audioLoudness = _headData->_audioLoudness;
|
||||
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
packOrientationQuatToSixBytes(header->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix));
|
||||
glm::vec3 scale = extractScale(sensorToWorldMatrix);
|
||||
packFloatScalarToSignedTwoByteFixed((uint8_t*)&header->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||
header->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0];
|
||||
header->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1];
|
||||
header->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2];
|
||||
|
||||
setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState);
|
||||
// hand state
|
||||
bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG;
|
||||
|
@ -346,8 +360,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
*destinationBuffer++ = validity;
|
||||
}
|
||||
|
||||
const int TRANSLATION_COMPRESSION_RADIX = 12;
|
||||
|
||||
validityBit = 0;
|
||||
validity = *validityPosition++;
|
||||
for (int i = 0; i < _jointData.size(); i ++) {
|
||||
|
@ -500,6 +512,15 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
}
|
||||
_headData->_audioLoudness = audioLoudness;
|
||||
|
||||
glm::quat sensorToWorldQuat;
|
||||
unpackOrientationQuatFromSixBytes(header->sensorToWorldQuat, sensorToWorldQuat);
|
||||
float sensorToWorldScale;
|
||||
unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&header->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX);
|
||||
glm::vec3 sensorToWorldTrans(header->sensorToWorldTrans[0], header->sensorToWorldTrans[1], header->sensorToWorldTrans[2]);
|
||||
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
|
||||
|
||||
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
|
||||
|
||||
{ // bitFlags and face data
|
||||
uint8_t bitItems = header->flags;
|
||||
|
||||
|
@ -616,7 +637,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
// each joint translation component is stored in 6 bytes.
|
||||
const int COMPRESSED_TRANSLATION_SIZE = 6;
|
||||
PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE);
|
||||
const int TRANSLATION_COMPRESSION_RADIX = 12;
|
||||
|
||||
for (int i = 0; i < numJoints; i++) {
|
||||
JointData& data = _jointData[i];
|
||||
|
@ -1718,6 +1738,11 @@ AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
|
|||
return result;
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
glm::mat4 AvatarData::getSensorToWorldMatrix() const {
|
||||
return _sensorToWorldMatrixCache.get();
|
||||
}
|
||||
|
||||
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
|
|
|
@ -54,6 +54,7 @@ typedef unsigned long long quint64;
|
|||
#include <SpatiallyNestable.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <Packed.h>
|
||||
#include <ThreadSafeValueCache.h>
|
||||
|
||||
#include "AABox.h"
|
||||
#include "HeadData.h"
|
||||
|
@ -171,6 +172,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
|
||||
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
|
||||
|
||||
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
|
||||
|
||||
public:
|
||||
|
||||
static const QString FRAME_NAME;
|
||||
|
@ -351,6 +354,9 @@ public:
|
|||
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
|
||||
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
|
||||
|
||||
// thread safe
|
||||
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
|
||||
|
||||
public slots:
|
||||
void sendAvatarDataPacket();
|
||||
void sendIdentityPacket();
|
||||
|
@ -425,6 +431,9 @@ protected:
|
|||
bool _avatarEntityDataLocallyEdited { false };
|
||||
bool _avatarEntityDataChanged { false };
|
||||
|
||||
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
||||
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
|
||||
|
||||
private:
|
||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||
static QUrl _defaultFullAvatarModelUrl;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWindow>
|
||||
#include <QQmlContext>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
@ -26,7 +27,6 @@
|
|||
|
||||
#include "EntityTreeRenderer.h"
|
||||
|
||||
const float DPI = 30.47f;
|
||||
const float METERS_TO_INCHES = 39.3701f;
|
||||
static uint32_t _currentWebCount { 0 };
|
||||
// Don't allow more than 100 concurrent web views
|
||||
|
@ -34,6 +34,8 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 100;
|
|||
// If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer
|
||||
static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
|
||||
|
||||
static int MAX_WINDOW_SIZE = 4096;
|
||||
|
||||
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
|
@ -67,6 +69,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
_webSurface->load("WebView.qml");
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
|
@ -87,7 +90,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
QTouchEvent::TouchPoint point;
|
||||
point.setId(event.getID());
|
||||
point.setState(Qt::TouchPointReleased);
|
||||
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
|
||||
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
|
||||
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||
point.setPos(windowPoint);
|
||||
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||
|
@ -99,6 +102,19 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
return true;
|
||||
}
|
||||
|
||||
glm::vec2 RenderableWebEntityItem::getWindowSize() const {
|
||||
glm::vec2 dims = glm::vec2(getDimensions());
|
||||
dims *= METERS_TO_INCHES * _dpi;
|
||||
|
||||
// ensure no side is never larger then MAX_WINDOW_SIZE
|
||||
float max = (dims.x > dims.y) ? dims.x : dims.y;
|
||||
if (max > MAX_WINDOW_SIZE) {
|
||||
dims *= MAX_WINDOW_SIZE / max;
|
||||
}
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::render(RenderArgs* args) {
|
||||
checkFading();
|
||||
|
||||
|
@ -124,12 +140,13 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
glm::vec2 dims = glm::vec2(getDimensions());
|
||||
dims *= METERS_TO_INCHES * DPI;
|
||||
|
||||
glm::vec2 windowSize = getWindowSize();
|
||||
|
||||
// The offscreen surface is idempotent for resizes (bails early
|
||||
// if it's a no-op), so it's safe to just call resize every frame
|
||||
// without worrying about excessive overhead.
|
||||
_webSurface->resize(QSize(dims.x, dims.y));
|
||||
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
||||
|
||||
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Web);
|
||||
|
@ -185,7 +202,7 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
|
|||
return;
|
||||
}
|
||||
|
||||
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * DPI);
|
||||
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
|
||||
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||
|
||||
if (event.getType() == PointerEvent::Move) {
|
||||
|
|
|
@ -47,6 +47,7 @@ public:
|
|||
private:
|
||||
bool buildWebSurface(EntityTreeRenderer* renderer);
|
||||
void destroyWebSurface();
|
||||
glm::vec2 getWindowSize() const;
|
||||
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QMetaObject::Connection _connection;
|
||||
|
|
|
@ -335,6 +335,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape);
|
||||
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
|
||||
|
||||
changedProperties += _animation.getChangedProperties();
|
||||
changedProperties += _keyLight.getChangedProperties();
|
||||
|
@ -504,6 +505,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
// Web only
|
||||
if (_type == EntityTypes::Web) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi);
|
||||
}
|
||||
|
||||
// PolyVoxel only
|
||||
|
@ -726,6 +728,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -903,6 +907,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
|
||||
|
||||
// FIXME - these are not yet handled
|
||||
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
|
||||
|
||||
|
@ -1065,6 +1071,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
|
||||
if (properties.getType() == EntityTypes::Web) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Text) {
|
||||
|
@ -1364,6 +1371,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
|
||||
if (properties.getType() == EntityTypes::Web) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Text) {
|
||||
|
@ -1642,6 +1650,8 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_clientOnlyChanged = true;
|
||||
_owningAvatarIDChanged = true;
|
||||
|
||||
_dpiChanged = true;
|
||||
}
|
||||
|
||||
// The minimum bounding box for the entity.
|
||||
|
@ -1977,6 +1987,10 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
out += "ghostingAllowed";
|
||||
}
|
||||
|
||||
if (dpiChanged()) {
|
||||
out += "dpi";
|
||||
}
|
||||
|
||||
if (shapeChanged()) {
|
||||
out += "shape";
|
||||
}
|
||||
|
|
|
@ -215,6 +215,8 @@ public:
|
|||
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
|
||||
|
||||
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
|
||||
|
||||
static QString getBackgroundModeString(BackgroundMode mode);
|
||||
|
||||
|
||||
|
|
|
@ -73,4 +73,6 @@ const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
|
|||
|
||||
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
|
||||
|
||||
const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30;
|
||||
|
||||
#endif // hifi_EntityItemPropertiesDefaults_h
|
||||
|
|
|
@ -176,6 +176,7 @@ enum EntityPropertyList {
|
|||
PROP_OWNING_AVATAR_ID, // doesn't go over wire
|
||||
|
||||
PROP_SHAPE,
|
||||
PROP_DPI,
|
||||
|
||||
PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts
|
||||
PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GeometryUtil.h>
|
||||
|
@ -30,6 +31,7 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent
|
|||
|
||||
WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Web;
|
||||
_dpi = ENTITY_ITEM_DEFAULT_DPI;
|
||||
}
|
||||
|
||||
const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f;
|
||||
|
@ -42,6 +44,7 @@ void WebEntityItem::setDimensions(const glm::vec3& value) {
|
|||
EntityItemProperties WebEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI);
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -50,6 +53,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -74,6 +78,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
|
|||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl);
|
||||
READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -83,6 +88,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i
|
|||
EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SOURCE_URL;
|
||||
requestedProperties += PROP_DPI;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -96,6 +102,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst
|
|||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl);
|
||||
APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi);
|
||||
}
|
||||
|
||||
bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
|
@ -123,3 +130,11 @@ void WebEntityItem::setSourceUrl(const QString& value) {
|
|||
}
|
||||
|
||||
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
|
||||
|
||||
void WebEntityItem::setDPI(uint16_t value) {
|
||||
_dpi = value;
|
||||
}
|
||||
|
||||
uint16_t WebEntityItem::getDPI() const {
|
||||
return _dpi;
|
||||
}
|
||||
|
|
|
@ -54,8 +54,14 @@ public:
|
|||
virtual void setSourceUrl(const QString& value);
|
||||
const QString& getSourceUrl() const;
|
||||
|
||||
virtual bool wantsHandControllerPointerEvents() const override { return true; }
|
||||
|
||||
void setDPI(uint16_t value);
|
||||
uint16_t getDPI() const;
|
||||
|
||||
protected:
|
||||
QString _sourceUrl;
|
||||
uint16_t _dpi;
|
||||
};
|
||||
|
||||
#endif // hifi_WebEntityItem_h
|
||||
|
|
|
@ -166,7 +166,7 @@ private:
|
|||
QMyQuickRenderControl* _renderControl{ nullptr };
|
||||
FramebufferPtr _fbo;
|
||||
RenderbufferPtr _depthStencil;
|
||||
TextureRecycler _textures;
|
||||
TextureRecycler _textures { true };
|
||||
GLTextureEscrow _escrow;
|
||||
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
|
@ -399,6 +399,8 @@ void OffscreenQmlRenderThread::render() {
|
|||
glGetError();
|
||||
}
|
||||
|
||||
Context::Bound(oglplus::Texture::Target::_2D, *texture).GenerateMipmap();
|
||||
|
||||
// FIXME probably unecessary
|
||||
DefaultFramebuffer().Bind(Framebuffer::Target::Draw);
|
||||
_quickWindow->resetOpenGLState();
|
||||
|
|
|
@ -509,16 +509,28 @@ TexturePtr TextureRecycler::getNextTexture() {
|
|||
using namespace oglplus;
|
||||
if (_readyTextures.empty()) {
|
||||
TexturePtr newTexture(new Texture());
|
||||
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
|
||||
.MinFilter(TextureMinFilter::Linear)
|
||||
.MagFilter(TextureMagFilter::Linear)
|
||||
.WrapS(TextureWrap::ClampToEdge)
|
||||
.WrapT(TextureWrap::ClampToEdge)
|
||||
.Image2D(
|
||||
0, PixelDataInternalFormat::RGBA8,
|
||||
_size.x, _size.y,
|
||||
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr
|
||||
);
|
||||
|
||||
if (_useMipmaps) {
|
||||
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
|
||||
.MinFilter(TextureMinFilter::LinearMipmapLinear)
|
||||
.MagFilter(TextureMagFilter::Linear)
|
||||
.WrapS(TextureWrap::ClampToEdge)
|
||||
.WrapT(TextureWrap::ClampToEdge)
|
||||
.Anisotropy(8.0f)
|
||||
.LODBias(-0.2f)
|
||||
.Image2D(0, PixelDataInternalFormat::RGBA8,
|
||||
_size.x, _size.y,
|
||||
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
|
||||
} else {
|
||||
Context::Bound(oglplus::Texture::Target::_2D, *newTexture)
|
||||
.MinFilter(TextureMinFilter::Linear)
|
||||
.MagFilter(TextureMagFilter::Linear)
|
||||
.WrapS(TextureWrap::ClampToEdge)
|
||||
.WrapT(TextureWrap::ClampToEdge)
|
||||
.Image2D(0, PixelDataInternalFormat::RGBA8,
|
||||
_size.x, _size.y,
|
||||
0, PixelDataFormat::RGB, PixelDataType::UnsignedByte, nullptr);
|
||||
}
|
||||
GLuint texId = GetName(*newTexture);
|
||||
_allTextures[texId] = TexInfo{ newTexture, _size };
|
||||
_readyTextures.push(newTexture);
|
||||
|
|
|
@ -190,6 +190,7 @@ using BasicFramebufferWrapperPtr = std::shared_ptr<BasicFramebufferWrapper>;
|
|||
|
||||
class TextureRecycler {
|
||||
public:
|
||||
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
|
||||
void setSize(const uvec2& size);
|
||||
void clear();
|
||||
TexturePtr getNextTexture();
|
||||
|
@ -212,4 +213,5 @@ private:
|
|||
Map _allTextures;
|
||||
Queue _readyTextures;
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
bool _useMipmaps;
|
||||
};
|
||||
|
|
|
@ -47,12 +47,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS;
|
||||
return VERSION_WEB_ENTITIES_SUPPORT_DPI;
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AbsoluteSixByteRotations);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SensorToWorldMat);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
|
|
@ -186,12 +186,14 @@ const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
|
|||
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
|
||||
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
|
||||
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62;
|
||||
const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63;
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
TranslationSupport = 17,
|
||||
SoftAttachmentSupport,
|
||||
AvatarEntities,
|
||||
AbsoluteSixByteRotations
|
||||
AbsoluteSixByteRotations,
|
||||
SensorToWorldMat
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -234,6 +234,10 @@ CONTROLLER_STATE_MACHINE[STATE_ENTITY_TOUCHING] = {
|
|||
updateMethod: "entityTouching"
|
||||
};
|
||||
|
||||
function angleBetween(a, b) {
|
||||
return Math.acos(Vec3.dot(Vec3.normalize(a), Vec3.normalize(b)));
|
||||
}
|
||||
|
||||
function projectOntoEntityXYPlane(entityID, worldPos) {
|
||||
var props = entityPropertiesCache.getProps(entityID);
|
||||
var invRot = Quat.inverse(props.rotation);
|
||||
|
@ -1975,7 +1979,8 @@ function MyController(hand) {
|
|||
var handPosition = this.getHandPosition();
|
||||
// the center of the equipped object being far from the hand isn't enough to auto-unequip -- we also
|
||||
// need to fail the findEntities test.
|
||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||
var TEAR_AWAY_DISTANCE = 0.04;
|
||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS + TEAR_AWAY_DISTANCE);
|
||||
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
||||
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
||||
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
|
||||
|
@ -2128,6 +2133,11 @@ function MyController(hand) {
|
|||
|
||||
Entities.sendMousePressOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendClickDownOnEntity(this.grabbedEntity, pointerEvent);
|
||||
|
||||
this.touchingEnterTimer = 0;
|
||||
this.touchingEnterPointerEvent = pointerEvent;
|
||||
this.touchingEnterPointerEvent.button = "None";
|
||||
this.deadspotExpired = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2135,27 +2145,37 @@ function MyController(hand) {
|
|||
// test for intersection between controller laser and web entity plane.
|
||||
var intersectInfo = handLaserIntersectEntity(this.grabbedEntity, this.hand);
|
||||
if (intersectInfo) {
|
||||
var pointerEvent = {
|
||||
type: "Release",
|
||||
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
|
||||
pos3D: intersectInfo.point,
|
||||
normal: intersectInfo.normal,
|
||||
direction: intersectInfo.searchRay.direction,
|
||||
button: "Primary",
|
||||
isPrimaryButton: true,
|
||||
isSecondaryButton: false,
|
||||
isTertiaryButton: false
|
||||
};
|
||||
var pointerEvent;
|
||||
if (this.deadspotExpired) {
|
||||
pointerEvent = {
|
||||
type: "Release",
|
||||
id: this.hand + 1, // 0 is reserved for hardware mouse
|
||||
pos2D: projectOntoEntityXYPlane(this.grabbedEntity, intersectInfo.point),
|
||||
pos3D: intersectInfo.point,
|
||||
normal: intersectInfo.normal,
|
||||
direction: intersectInfo.searchRay.direction,
|
||||
button: "Primary",
|
||||
isPrimaryButton: false,
|
||||
isSecondaryButton: false,
|
||||
isTertiaryButton: false
|
||||
};
|
||||
} else {
|
||||
pointerEvent = this.touchingEnterPointerEvent;
|
||||
pointerEvent.button = "Primary";
|
||||
pointerEvent.isPrimaryButton = false;
|
||||
}
|
||||
|
||||
Entities.sendMouseReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
|
||||
this.focusedEntity = null;
|
||||
}
|
||||
this.focusedEntity = null;
|
||||
};
|
||||
|
||||
this.entityTouching = function() {
|
||||
this.entityTouching = function(dt) {
|
||||
|
||||
this.touchingEnterTimer += dt;
|
||||
|
||||
entityPropertiesCache.addEntity(this.grabbedEntity);
|
||||
|
||||
if (!this.triggerSmoothedGrab()) {
|
||||
|
@ -2184,8 +2204,14 @@ function MyController(hand) {
|
|||
isTertiaryButton: false
|
||||
};
|
||||
|
||||
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
|
||||
var POINTER_PRESS_TO_MOVE_DELAY = 0.15; // seconds
|
||||
var POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.05; // radians ~ 3 degrees
|
||||
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||
angleBetween(pointerEvent.direction, this.touchingEnterPointerEvent.direction) > POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE) {
|
||||
Entities.sendMouseMoveOnEntity(this.grabbedEntity, pointerEvent);
|
||||
Entities.sendHoldingClickOnEntity(this.grabbedEntity, pointerEvent);
|
||||
this.deadspotExpired = true;
|
||||
}
|
||||
|
||||
this.intersectionDistance = intersectInfo.distance;
|
||||
this.searchIndicatorOn(intersectInfo.searchRay);
|
||||
|
|
|
@ -121,7 +121,7 @@ function ignoreMouseActivity() {
|
|||
return true;
|
||||
}
|
||||
var pos = Reticle.position;
|
||||
if (pos.x == -1 && pos.y == -1) {
|
||||
if (!pos || (pos.x == -1 && pos.y == -1)) {
|
||||
return true;
|
||||
}
|
||||
// Only we know if we moved it, which is why this script has to replace depthReticle.js
|
||||
|
|
|
@ -525,6 +525,10 @@
|
|||
<label for="property-web-source-url">Source URL</label>
|
||||
<input type="text" id="property-web-source-url">
|
||||
</div>
|
||||
<div class="web-group web-section property dpi ">
|
||||
<label for="property-web-dpi">Resolution (DPI)</label>
|
||||
<input type="number" id="property-web-dpi">
|
||||
</div>
|
||||
<div class="section-header light-group light-section">
|
||||
<label>Light</label><span>M</span>
|
||||
</div>
|
||||
|
@ -557,4 +561,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -630,6 +630,7 @@ function loaded() {
|
|||
var elWebSections = document.querySelectorAll(".web-section");
|
||||
allSections.push(elWebSections);
|
||||
var elWebSourceURL = document.getElementById("property-web-source-url");
|
||||
var elWebDPI = document.getElementById("property-web-dpi");
|
||||
|
||||
var elDescription = document.getElementById("property-description");
|
||||
|
||||
|
@ -931,6 +932,7 @@ function loaded() {
|
|||
}
|
||||
|
||||
elWebSourceURL.value = properties.sourceUrl;
|
||||
elWebDPI.value = properties.dpi;
|
||||
} else if (properties.type == "Text") {
|
||||
for (var i = 0; i < elTextSections.length; i++) {
|
||||
elTextSections[i].style.display = 'table';
|
||||
|
@ -1228,6 +1230,7 @@ function loaded() {
|
|||
elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape'));
|
||||
|
||||
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
|
||||
elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi'));
|
||||
|
||||
elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL'));
|
||||
elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType'));
|
||||
|
|
93
scripts/system/libraries/WebTablet.js
Normal file
93
scripts/system/libraries/WebTablet.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// WebTablet.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 8/8/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var RAD_TO_DEG = 180 / Math.PI;
|
||||
var X_AXIS = {x: 1, y: 0, z: 0};
|
||||
var Y_AXIS = {x: 0, y: 1, z: 0};
|
||||
|
||||
var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx";
|
||||
|
||||
// returns object with two fields:
|
||||
// * position - position in front of the user
|
||||
// * rotation - rotation of entity so it faces the user.
|
||||
function calcSpawnInfo() {
|
||||
var front;
|
||||
var pitchBackRotation = Quat.angleAxis(20.0, X_AXIS);
|
||||
if (HMD.active) {
|
||||
front = Quat.getFront(HMD.orientation);
|
||||
var yawOnlyRotation = Quat.angleAxis(Math.atan2(front.x, front.z) * RAD_TO_DEG, Y_AXIS);
|
||||
return {
|
||||
position: Vec3.sum(Vec3.sum(HMD.position, Vec3.multiply(0.6, front)), Vec3.multiply(-0.5, Y_AXIS)),
|
||||
rotation: Quat.multiply(yawOnlyRotation, pitchBackRotation)
|
||||
};
|
||||
} else {
|
||||
front = Quat.getFront(MyAvatar.orientation);
|
||||
return {
|
||||
position: Vec3.sum(Vec3.sum(MyAvatar.position, Vec3.multiply(0.6, front)), {x: 0, y: 0.6, z: 0}),
|
||||
rotation: Quat.multiply(MyAvatar.orientation, pitchBackRotation)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ctor
|
||||
WebTablet = function (url) {
|
||||
|
||||
var ASPECT = 4.0 / 3.0;
|
||||
var WIDTH = 0.4;
|
||||
var HEIGHT = WIDTH * ASPECT;
|
||||
var DEPTH = 0.025;
|
||||
|
||||
var spawnInfo = calcSpawnInfo();
|
||||
|
||||
var tabletEntityPosition = spawnInfo.position;
|
||||
var tabletEntityRotation = spawnInfo.rotation;
|
||||
this.tabletEntityID = Entities.addEntity({
|
||||
name: "tablet",
|
||||
type: "Model",
|
||||
modelURL: TABLET_URL,
|
||||
position: tabletEntityPosition,
|
||||
rotation: tabletEntityRotation,
|
||||
userData: JSON.stringify({
|
||||
"grabbableKey": {"grabbable": true}
|
||||
}),
|
||||
dimensions: {x: WIDTH, y: HEIGHT, z: DEPTH},
|
||||
parentID: MyAvatar.sessionUUID,
|
||||
parentJointIndex: -2
|
||||
});
|
||||
|
||||
var WEB_ENTITY_REDUCTION_FACTOR = {x: 0.78, y: 0.85};
|
||||
var WEB_ENTITY_Z_OFFSET = -0.01;
|
||||
|
||||
var webEntityRotation = Quat.multiply(spawnInfo.rotation, Quat.angleAxis(180, Y_AXIS));
|
||||
var webEntityPosition = Vec3.sum(spawnInfo.position, Vec3.multiply(WEB_ENTITY_Z_OFFSET, Quat.getFront(webEntityRotation)));
|
||||
|
||||
this.webEntityID = Entities.addEntity({
|
||||
name: "web",
|
||||
type: "Web",
|
||||
sourceUrl: url,
|
||||
dimensions: {x: WIDTH * WEB_ENTITY_REDUCTION_FACTOR.x,
|
||||
y: HEIGHT * WEB_ENTITY_REDUCTION_FACTOR.y,
|
||||
z: 0.1},
|
||||
position: webEntityPosition,
|
||||
rotation: webEntityRotation,
|
||||
shapeType: "box",
|
||||
dpi: 45,
|
||||
parentID: this.tabletEntityID,
|
||||
parentJointIndex: -1
|
||||
});
|
||||
|
||||
this.state = "idle";
|
||||
};
|
||||
|
||||
WebTablet.prototype.destroy = function () {
|
||||
Entities.deleteEntity(this.webEntityID);
|
||||
Entities.deleteEntity(this.tabletEntityID);
|
||||
};
|
||||
|
|
@ -8,6 +8,9 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* global WebTablet */
|
||||
Script.include("../libraries/WebTablet.js");
|
||||
|
||||
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
|
||||
|
||||
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
|
||||
|
@ -22,26 +25,45 @@ var marketplaceWindow = new OverlayWebWindow({
|
|||
var toolHeight = 50;
|
||||
var toolWidth = 50;
|
||||
var TOOLBAR_MARGIN_Y = 0;
|
||||
var marketplaceVisible = false;
|
||||
var marketplaceWebTablet;
|
||||
|
||||
function shouldShowWebTablet() {
|
||||
var rightPose = Controller.getPoseValue(Controller.Standard.RightHand);
|
||||
var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
||||
var hasHydra = !!Controller.Hardware.Hydra;
|
||||
return HMD.active && (leftPose.valid || rightPose.valid || hasHydra);
|
||||
}
|
||||
|
||||
function showMarketplace(marketplaceID) {
|
||||
var url = MARKETPLACE_URL;
|
||||
if (marketplaceID) {
|
||||
url = url + "/items/" + marketplaceID;
|
||||
if (shouldShowWebTablet()) {
|
||||
marketplaceWebTablet = new WebTablet("https://metaverse.highfidelity.com/marketplace");
|
||||
} else {
|
||||
var url = MARKETPLACE_URL;
|
||||
if (marketplaceID) {
|
||||
url = url + "/items/" + marketplaceID;
|
||||
}
|
||||
marketplaceWindow.setURL(url);
|
||||
marketplaceWindow.setVisible(true);
|
||||
}
|
||||
marketplaceWindow.setURL(url);
|
||||
marketplaceWindow.setVisible(true);
|
||||
|
||||
marketplaceVisible = true;
|
||||
UserActivityLogger.openedMarketplace();
|
||||
}
|
||||
|
||||
function hideMarketplace() {
|
||||
marketplaceWindow.setVisible(false);
|
||||
marketplaceWindow.setURL("about:blank");
|
||||
if (marketplaceWindow.visible) {
|
||||
marketplaceWindow.setVisible(false);
|
||||
marketplaceWindow.setURL("about:blank");
|
||||
} else if (marketplaceWebTablet) {
|
||||
marketplaceWebTablet.destroy();
|
||||
marketplaceWebTablet = null;
|
||||
}
|
||||
marketplaceVisible = false;
|
||||
}
|
||||
|
||||
function toggleMarketplace() {
|
||||
if (marketplaceWindow.visible) {
|
||||
if (marketplaceVisible) {
|
||||
hideMarketplace();
|
||||
} else {
|
||||
showMarketplace();
|
||||
|
@ -59,19 +81,22 @@ var browseExamplesButton = toolBar.addButton({
|
|||
alpha: 0.9
|
||||
});
|
||||
|
||||
function onExamplesWindowVisibilityChanged() {
|
||||
function onMarketplaceWindowVisibilityChanged() {
|
||||
browseExamplesButton.writeProperty('buttonState', marketplaceWindow.visible ? 0 : 1);
|
||||
browseExamplesButton.writeProperty('defaultState', marketplaceWindow.visible ? 0 : 1);
|
||||
browseExamplesButton.writeProperty('hoverState', marketplaceWindow.visible ? 2 : 3);
|
||||
marketplaceVisible = marketplaceWindow.visible;
|
||||
}
|
||||
|
||||
function onClick() {
|
||||
toggleMarketplace();
|
||||
}
|
||||
|
||||
browseExamplesButton.clicked.connect(onClick);
|
||||
marketplaceWindow.visibleChanged.connect(onExamplesWindowVisibilityChanged);
|
||||
marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
toolBar.removeButton("marketplace");
|
||||
browseExamplesButton.clicked.disconnect(onClick);
|
||||
marketplaceWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged);
|
||||
});
|
||||
marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue