Merge branch 'master' into 21899

This commit is contained in:
David Rowe 2018-07-19 09:34:37 +12:00
commit 835fb927b7
30 changed files with 723 additions and 557 deletions

View file

@ -4,6 +4,7 @@ import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
@ -32,6 +33,7 @@ public class HomeFragment extends Fragment {
private OnHomeInteractionListener mListener;
private SwipeRefreshLayout mSwipeRefreshLayout;
public native String nativeGetLastLocation();
@ -57,6 +59,7 @@ public class HomeFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
searchNoResultsView = rootView.findViewById(R.id.searchNoResultsView);
mSwipeRefreshLayout = rootView.findViewById(R.id.swipeRefreshLayout);
mDomainsView = rootView.findViewById(R.id.rvDomains);
int numberOfColumns = 1;
@ -76,12 +79,14 @@ public class HomeFragment extends Fragment {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
mSwipeRefreshLayout.setRefreshing(false);
}
@Override
@ -104,7 +109,7 @@ public class HomeFragment extends Fragment {
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
mDomainAdapter.loadDomains(editable.toString(), false);
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
@ -130,6 +135,13 @@ public class HomeFragment extends Fragment {
mClearSearch.setOnClickListener(view -> onSearchClear(view));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mDomainAdapter.loadDomains(mSearchView.getText().toString(), true);
}
});
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
return rootView;

View file

@ -10,7 +10,7 @@ import io.highfidelity.hifiinterface.view.DomainAdapter;
public interface DomainProvider {
void retrieve(String filterText, DomainCallback domainCallback);
void retrieve(String filterText, DomainCallback domainCallback, boolean forceRefresh);
interface DomainCallback {
void retrieveOk(List<DomainAdapter.Domain> domain);

View file

@ -49,8 +49,8 @@ public class UserStoryDomainProvider implements DomainProvider {
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
public synchronized void retrieve(String filterText, DomainCallback domainCallback, boolean forceRefresh) {
if (!startedToGetFromAPI || forceRefresh) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
@ -72,6 +72,7 @@ public class UserStoryDomainProvider implements DomainProvider {
allStories.clear();
getUserStoryPage(1, allStories, null,
ex -> {
suggestions.clear();
allStories.forEach(userStory -> {
if (taggedStoriesIds.contains(userStory.id)) {
userStory.tagFound = true;

View file

@ -42,14 +42,14 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
mProtocol = protocol;
mLastLocation = lastLocation;
domainProvider = new UserStoryDomainProvider(mProtocol);
loadDomains("");
loadDomains("", true);
}
public void setListener(AdapterListener adapterListener) {
mAdapterListener = adapterListener;
}
public void loadDomains(String filterText) {
public void loadDomains(String filterText, boolean forceRefresh) {
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
@Override
public void retrieveOk(List<Domain> domain) {
@ -76,7 +76,7 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
Log.e("DOMAINS", message, e);
if (mAdapterListener != null) mAdapterListener.onError(e, message);
}
});
}, forceRefresh);
}
private void overrideDefaultThumbnails(List<Domain> domain) {

View file

@ -63,13 +63,18 @@
android:visibility="gone"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
app:layout_constraintTop_toBottomOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/contentHomeRoot"
app:layout_constraintStart_toStartOf="@id/contentHomeRoot"
app:layout_constraintEnd_toEndOf="@id/contentHomeRoot"
android:layout_width="0dp"
android:layout_height="0dp" />
android:layout_height="0dp">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -23,6 +23,7 @@
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QJsonDocument>
#include <QtCore/QSaveFile>
#include <QtCore/QString>
#include <QtGui/QImageReader>
#include <QtCore/QVector>
@ -1042,7 +1043,7 @@ bool AssetServer::loadMappingsFromFile() {
bool AssetServer::writeMappingsToFile() {
auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME);
QFile mapFile { mapFilePath };
QSaveFile mapFile { mapFilePath };
if (mapFile.open(QIODevice::WriteOnly)) {
QJsonObject root;
@ -1053,8 +1054,12 @@ bool AssetServer::writeMappingsToFile() {
QJsonDocument jsonDocument { root };
if (mapFile.write(jsonDocument.toJson()) != -1) {
qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath;
return true;
if (mapFile.commit()) {
qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath;
return true;
} else {
qCWarning(asset_server) << "Failed to commit JSON mappings to file at" << mapFilePath;
}
} else {
qCWarning(asset_server) << "Failed to write JSON mappings to file at" << mapFilePath;
}

View file

@ -1108,7 +1108,7 @@ Rectangle {
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
var data = optionalData || nearbyUserModelData, length = data.length;
for (var i = 0; i < length; i++) {
if (data[i].sessionId === sessionId) {
if (data[i].sessionId === sessionId.toString()) {
return i;
}
}
@ -1120,7 +1120,7 @@ Rectangle {
var data = message.params;
var index = -1;
iAmAdmin = Users.canKick;
index = findNearbySessionIndex('', data);
index = findNearbySessionIndex("", data);
if (index !== -1) {
myData = data[index];
data.splice(index, 1);
@ -1197,8 +1197,8 @@ Rectangle {
for (var userId in message.params) {
var audioLevel = message.params[userId][0];
var avgAudioLevel = message.params[userId][1];
// If the userId is 0, we're updating "myData".
if (userId == 0) {
// If the userId is "", we're updating "myData".
if (userId === "") {
myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;

View file

@ -731,7 +731,7 @@ Item {
}
HiFiGlyphs {
id: rezIcon;
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
text: root.isInstalled ? "" : (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
anchors.right: rezIconLabel.left;
anchors.rightMargin: 2;
anchors.verticalCenter: parent.verticalCenter;

View file

@ -14,6 +14,7 @@
#include <string>
#include <QScriptEngine>
#include <QtCore/QJsonDocument>
#include "AvatarLogging.h"
@ -668,3 +669,49 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
DependencyManager::get<NodeList>()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
}
}
QVariantMap AvatarManager::getPalData(const QList<QString> specificAvatarIdentifiers) {
QJsonArray palData;
auto avatarMap = getHashCopy();
AvatarHash::iterator itr = avatarMap.begin();
while (itr != avatarMap.end()) {
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(*itr);
QString currentSessionUUID = avatar->getSessionUUID().toString();
if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) {
QJsonObject thisAvatarPalData;
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (currentSessionUUID == myAvatar->getSessionUUID().toString()) {
currentSessionUUID = "";
}
thisAvatarPalData.insert("sessionUUID", currentSessionUUID);
thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName());
thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness());
thisAvatarPalData.insert("isReplicated", avatar->getIsReplicated());
glm::vec3 position = avatar->getWorldPosition();
QJsonObject jsonPosition;
jsonPosition.insert("x", position.x);
jsonPosition.insert("y", position.y);
jsonPosition.insert("z", position.z);
thisAvatarPalData.insert("position", jsonPosition);
float palOrbOffset = 0.2f;
int headIndex = avatar->getJointIndex("Head");
if (headIndex > 0) {
glm::vec3 jointTranslation = avatar->getAbsoluteJointTranslationInObjectFrame(headIndex);
palOrbOffset = jointTranslation.y / 2;
}
thisAvatarPalData.insert("palOrbOffset", palOrbOffset);
palData.append(thisAvatarPalData);
}
++itr;
}
QJsonObject doc;
doc.insert("data", palData);
return doc.toVariantMap();
}

View file

@ -157,6 +157,17 @@ public:
*/
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
/**jsdoc
* Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster
* than iterating over each avatar and obtaining data about them in JavaScript, as that method
* locks and unlocks each avatar's data structure potentially hundreds of times per update tick.
* @function AvatarManager.getPalData
* @param {string[]} specificAvatarIdentifiers - A list of specific Avatar Identifiers about
* which you want to get PAL data
* @returns {object}
*/
Q_INVOKABLE QVariantMap getPalData(const QList<QString> specificAvatarIdentifiers = QList<QString>());
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
int getIdentityRequestsSent() const { return _identityRequestsSent; }

View file

@ -262,6 +262,26 @@ void MyAvatar::setDominantHand(const QString& hand) {
}
}
void MyAvatar::requestDisableHandTouch() {
std::lock_guard<std::mutex> guard(_disableHandTouchMutex);
_disableHandTouchCount++;
emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
}
void MyAvatar::requestEnableHandTouch() {
std::lock_guard<std::mutex> guard(_disableHandTouchMutex);
_disableHandTouchCount = std::max(_disableHandTouchCount - 1, 0);
emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
}
void MyAvatar::disableHandTouchForID(const QUuid& entityID) {
emit disableHandTouchForIDChanged(entityID, true);
}
void MyAvatar::enableHandTouchForID(const QUuid& entityID) {
emit disableHandTouchForIDChanged(entityID, false);
}
void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) {
QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
engine->globalObject().setProperty("MyAvatar", value);
@ -3551,6 +3571,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Rotation);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
}
if (myAvatar.getCenterOfGravityModelEnabled()) {
if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
@ -3568,6 +3589,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
} else {
if (!isActive(Rotation) && getForceActivateRotation()) {
activate(Rotation);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
setForceActivateRotation(false);
}
if (!isActive(Horizontal) && getForceActivateHorizontal()) {

View file

@ -505,6 +505,28 @@ public:
* @returns {boolean}
*/
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
/**jsdoc
* Request to enable hand touch effect globally
* @function MyAvatar.requestEnableHandTouch
*/
Q_INVOKABLE void requestEnableHandTouch();
/**jsdoc
* Request to disable hand touch effect globally
* @function MyAvatar.requestDisableHandTouch
*/
Q_INVOKABLE void requestDisableHandTouch();
/**jsdoc
* Disables hand touch effect on a specific entity
* @function MyAvatar.disableHandTouchForID
* @param {Uuid} entityID - ID of the entity that will disable hand touch effect
*/
Q_INVOKABLE void disableHandTouchForID(const QUuid& entityID);
/**jsdoc
* Enables hand touch effect on a specific entity
* @function MyAvatar.enableHandTouchForID
* @param {Uuid} entityID - ID of the entity that will enable hand touch effect
*/
Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID);
bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); }
void setUseAdvancedMovementControls(bool useAdvancedMovementControls)
@ -883,6 +905,7 @@ public:
virtual void rebuildCollisionShape() override;
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; }
float getCurrentStandingHeight() const { return _currentStandingHeight; }
void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
@ -1391,6 +1414,23 @@ signals:
*/
void scaleChanged();
/**jsdoc
* Triggered when hand touch is globally enabled or disabled
* @function MyAvatar.shouldDisableHandTouchChanged
* @param {boolean} shouldDisable
* @returns {Signal}
*/
void shouldDisableHandTouchChanged(bool shouldDisable);
/**jsdoc
* Triggered when hand touch is enabled or disabled for an specific entity
* @function MyAvatar.disableHandTouchForIDChanged
* @param {Uuid} entityID - ID of the entity that will enable hand touch effect
* @param {boolean} disable
* @returns {Signal}
*/
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
private slots:
void leaveDomain();
@ -1627,6 +1667,7 @@ private:
// all poses are in sensor-frame
std::map<controller::Action, controller::Pose> _controllerPoseMap;
mutable std::mutex _controllerPoseMapMutex;
mutable std::mutex _disableHandTouchMutex;
bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { true };
@ -1667,6 +1708,7 @@ private:
bool _shouldLoadScripts { false };
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -381,7 +381,7 @@ public slots:
/**jsdoc
* Get the rotation of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmRotation
* @returns {Vec3} The rotation of the left palm in world coordinates.
* @returns {Quat} The rotation of the left palm in world coordinates.
* @example <caption>Report the rotation of your avatar's left palm.</caption>
* print(JSON.stringify(MyAvatar.getLeftPalmRotation()));
*/
@ -398,7 +398,7 @@ public slots:
/**jsdoc
* Get the rotation of the right palm in world coordinates.
* @function MyAvatar.getRightPalmRotation
* @returns {Vec3} The rotation of the right palm in world coordinates.
* @returns {Quat} The rotation of the right palm in world coordinates.
* @example <caption>Report the rotation of your avatar's right palm.</caption>
* print(JSON.stringify(MyAvatar.getRightPalmRotation()));
*/

View file

@ -363,12 +363,18 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity
return false;
}
void EntityRenderer::updateModelTransform() {
void EntityRenderer::updateModelTransformAndBound() {
bool success = false;
auto newModelTransform = _entity->getTransformToCenter(success);
if (success) {
_modelTransform = newModelTransform;
}
success = false;
auto bound = _entity->getAABox(success);
if (success) {
_bound = bound;
}
}
void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) {
@ -380,15 +386,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
}
_prevIsTransparent = transparent;
bool success = false;
auto bound = entity->getAABox(success);
if (success) {
_bound = bound;
}
auto newModelTransform = entity->getTransformToCenter(success);
if (success) {
_modelTransform = newModelTransform;
}
updateModelTransformAndBound();
_moving = entity->isMovingRelativeToParent();
_visible = entity->getVisible();

View file

@ -97,7 +97,7 @@ protected:
virtual void doRender(RenderArgs* args) = 0;
bool isFading() const { return _isFading; }
void updateModelTransform();
void updateModelTransformAndBound();
virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; }

View file

@ -126,7 +126,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () {
withWriteLock([&] {
updateModelTransform();
updateModelTransformAndBound();
_renderTransform = getModelTransform();
});
});

View file

@ -106,10 +106,8 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_position = entity->getWorldPosition();
_dimensions = entity->getScaledDimensions();
_orientation = entity->getWorldOrientation();
bool success = false;
auto newModelTransform = entity->getTransformToCenter(success);
_renderTransform = success ? newModelTransform : getModelTransform();
updateModelTransformAndBound();
_renderTransform = getModelTransform();
if (_shape == entity::Sphere) {
_renderTransform.postScale(SPHERE_ENTITY_SCALE);
}

View file

@ -70,7 +70,7 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () {
withWriteLock([&] {
_dimensions = entity->getScaledDimensions();
updateModelTransform();
updateModelTransformAndBound();
_renderTransform = getModelTransform();
});
});

View file

@ -206,7 +206,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
updateModelTransform();
updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});

View file

@ -21,14 +21,17 @@
#include <QtCore/qpair.h>
#include <QtCore/qlist.h>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <qfile.h>
#include <qfileinfo.h>
#include <shared/NsightHelpers.h>
#include <NetworkAccessManager.h>
#include <ResourceManager.h>
#include <PathUtils.h>
#include "FBXReader.h"
@ -786,13 +789,18 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) {
QVector<glm::vec3> raw_vertices;
QVector<glm::vec3> raw_normals;
addArrayOfType(indicesBuffer.blob,
bool success = addArrayOfType(indicesBuffer.blob,
indicesBufferview.byteOffset + indicesAccBoffset,
indicesBufferview.byteLength,
indicesAccessor.count,
part.triangleIndices,
indicesAccessor.type,
indicesAccessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url;
continue;
}
QList<QString> keys = primitive.attributes.values.keys();
foreach(auto &key, keys) {
@ -805,44 +813,60 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) {
int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0;
if (key == "POSITION") {
QVector<float> vertices;
addArrayOfType(buffer.blob,
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
bufferview.byteLength, vertices,
accessor.count, vertices,
accessor.type,
accessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url;
continue;
}
for (int n = 0; n < vertices.size(); n = n + 3) {
mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2]));
}
} else if (key == "NORMAL") {
QVector<float> normals;
addArrayOfType(buffer.blob,
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
bufferview.byteLength,
accessor.count,
normals,
accessor.type,
accessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url;
continue;
}
for (int n = 0; n < normals.size(); n = n + 3) {
mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2]));
}
} else if (key == "TEXCOORD_0") {
QVector<float> texcoords;
addArrayOfType(buffer.blob,
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
bufferview.byteLength,
accessor.count,
texcoords,
accessor.type,
accessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url;
continue;
}
for (int n = 0; n < texcoords.size(); n = n + 2) {
mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
}
} else if (key == "TEXCOORD_1") {
QVector<float> texcoords;
addArrayOfType(buffer.blob,
success = addArrayOfType(buffer.blob,
bufferview.byteOffset + accBoffset,
bufferview.byteLength,
accessor.count,
texcoords,
accessor.type,
accessor.componentType);
if (!success) {
qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url;
continue;
}
for (int n = 0; n < texcoords.size(); n = n + 2) {
mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1]));
}
@ -888,8 +912,16 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) {
FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping,
const QUrl& url, bool loadLightmaps, float lightmapLevel) {
_url = url;
// Normalize url for local files
QUrl normalizeUrl = DependencyManager::get<ResourceManager>()->normalizeURL(url);
if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) {
QString localFileName = PathUtils::expandToLocalDataAbsolutePath(normalizeUrl).toLocalFile();
_url = QUrl(QFileInfo(localFileName).absoluteFilePath());
}
parseGLTF(model);
//_file.dump();
FBXGeometry* geometryPtr = new FBXGeometry();
@ -904,6 +936,7 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping
bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) {
QUrl binaryUrl = _url.resolved(QUrl(url).fileName());
qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url;
bool success;
std::tie<bool, QByteArray>(success, outdata) = requestData(binaryUrl);
@ -1018,13 +1051,12 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia
fbxmat.opacityTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]);
fbxmat.albedoTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]);
fbxmat.useAlbedoMap = true;
fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]);
fbxmat.useMetallicMap = true;
}
if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) {
fbxmat.roughnessTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]);
fbxmat.useRoughnessMap = true;
fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]);
fbxmat.useMetallicMap = true;
}
if (material.pbrMetallicRoughness.defined["roughnessFactor"]) {
fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor);
@ -1043,7 +1075,7 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia
}
template<typename T, typename L>
bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int byteLength,
bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count,
QVector<L>& outarray, int accessorType) {
QDataStream blobstream(bin);
@ -1051,142 +1083,77 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int byteLength
blobstream.setVersion(QDataStream::Qt_5_9);
blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision);
int vsize = byteLength / sizeof(T);
qCDebug(modelformat) << "size1: " << vsize;
qCDebug(modelformat) << "size1: " << count;
int dataskipped = blobstream.skipRawData(byteOffset);
qCDebug(modelformat) << "dataskipped: " << dataskipped;
while (outarray.size() < vsize) {
T value1, value2, value3, value4,
value5, value6, value7, value8,
value9, value10, value11, value12,
value13, value14, value15, value16;
if (accessorType == GLTFAccessorType::SCALAR) {
blobstream >> value1;
outarray.push_back(value1);
} else if (accessorType == GLTFAccessorType::VEC2) {
blobstream >> value1;
blobstream >> value2;
outarray.push_back(value1);
outarray.push_back(value2);
} else if (accessorType == GLTFAccessorType::VEC3) {
blobstream >> value1;
blobstream >> value2;
blobstream >> value3;
outarray.push_back(value1);
outarray.push_back(value2);
outarray.push_back(value3);
} else if (accessorType == GLTFAccessorType::VEC4 || accessorType == GLTFAccessorType::MAT2) {
blobstream >> value1;
blobstream >> value2;
blobstream >> value3;
blobstream >> value4;
outarray.push_back(value1);
outarray.push_back(value2);
outarray.push_back(value3);
outarray.push_back(value4);
} else if (accessorType == GLTFAccessorType::MAT3) {
blobstream >> value1;
blobstream >> value2;
blobstream >> value3;
blobstream >> value4;
blobstream >> value5;
blobstream >> value6;
blobstream >> value7;
blobstream >> value8;
blobstream >> value9;
outarray.push_back(value1);
outarray.push_back(value2);
outarray.push_back(value3);
outarray.push_back(value4);
outarray.push_back(value5);
outarray.push_back(value6);
outarray.push_back(value7);
outarray.push_back(value8);
outarray.push_back(value9);
} else if (accessorType == GLTFAccessorType::MAT4) {
blobstream >> value1;
blobstream >> value2;
blobstream >> value3;
blobstream >> value4;
blobstream >> value5;
blobstream >> value6;
blobstream >> value7;
blobstream >> value8;
blobstream >> value9;
blobstream >> value10;
blobstream >> value11;
blobstream >> value12;
blobstream >> value13;
blobstream >> value14;
blobstream >> value15;
blobstream >> value16;
outarray.push_back(value1);
outarray.push_back(value2);
outarray.push_back(value3);
outarray.push_back(value4);
outarray.push_back(value5);
outarray.push_back(value6);
outarray.push_back(value7);
outarray.push_back(value8);
outarray.push_back(value9);
outarray.push_back(value10);
outarray.push_back(value11);
outarray.push_back(value12);
outarray.push_back(value13);
outarray.push_back(value14);
outarray.push_back(value15);
outarray.push_back(value16);
int bufferCount = 0;
switch (accessorType) {
case GLTFAccessorType::SCALAR:
bufferCount = 1;
break;
case GLTFAccessorType::VEC2:
bufferCount = 2;
break;
case GLTFAccessorType::VEC3:
bufferCount = 3;
break;
case GLTFAccessorType::VEC4:
bufferCount = 4;
break;
case GLTFAccessorType::MAT2:
bufferCount = 4;
break;
case GLTFAccessorType::MAT3:
bufferCount = 9;
break;
case GLTFAccessorType::MAT4:
bufferCount = 16;
break;
default:
qWarning(modelformat) << "Unknown accessorType: " << accessorType;
blobstream.unsetDevice();
return false;
}
for (int i = 0; i < count; i++) {
for (int j = 0; j < bufferCount; j++) {
if (!blobstream.atEnd()) {
T value;
blobstream >> value;
outarray.push_back(value);
} else {
blobstream.unsetDevice();
return false;
}
}
}
blobstream.unsetDevice();
return true;
}
template<typename T>
bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength,
bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count,
QVector<T>& outarray, int accessorType, int componentType) {
switch (componentType) {
case GLTFAccessorComponentType::BYTE: {}
case GLTFAccessorComponentType::UNSIGNED_BYTE: {
readArray<uchar>(bin, byteOffset, byteLength, outarray, accessorType);
break;
return readArray<uchar>(bin, byteOffset, count, outarray, accessorType);
}
case GLTFAccessorComponentType::SHORT: {
readArray<short>(bin, byteOffset, byteLength, outarray, accessorType);
break;
return readArray<short>(bin, byteOffset, count, outarray, accessorType);
}
case GLTFAccessorComponentType::UNSIGNED_INT: {
readArray<quint32>(bin, byteOffset, byteLength, outarray, accessorType);
break;
return readArray<uint>(bin, byteOffset, count, outarray, accessorType);
}
case GLTFAccessorComponentType::UNSIGNED_SHORT: {
readArray<ushort>(bin, byteOffset, byteLength, outarray, accessorType);
break;
return readArray<ushort>(bin, byteOffset, count, outarray, accessorType);
}
case GLTFAccessorComponentType::FLOAT: {
readArray<float>(bin, byteOffset, byteLength, outarray, accessorType);
break;
return readArray<float>(bin, byteOffset, count, outarray, accessorType);
}
}
return true;
return false;
}
void GLTFReader::retriangulate(const QVector<int>& inIndices, const QVector<glm::vec3>& in_vertices,

View file

@ -762,11 +762,11 @@ private:
bool readBinary(const QString& url, QByteArray& outdata);
template<typename T, typename L>
bool readArray(const QByteArray& bin, int byteOffset, int byteLength,
bool readArray(const QByteArray& bin, int byteOffset, int count,
QVector<L>& outarray, int accessorType);
template<typename T>
bool addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength,
bool addArrayOfType(const QByteArray& bin, int byteOffset, int count,
QVector<T>& outarray, int accessorType, int componentType);
void retriangulate(const QVector<int>& in_indices, const QVector<glm::vec3>& in_vertices,

View file

@ -11,7 +11,8 @@
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity,
distanceBetweenEntityLocalPositionAndBoundingBox
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -172,12 +173,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * TEAR_AWAY_CHECK_TIME) {
this.lastUnequipCheckTime = now;
if (props.parentID === MyAvatar.SELF_ID) {
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var handPosition = controllerData.controllerLocations[this.hand].position;
var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props);
var distance = Vec3.distance(props.position, handPosition);
if ((dist > TEAR_AWAY_DISTANCE) ||
(distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) {
var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale;
var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props);
if (distance > tearAwayDistance) {
this.autoUnequipCounter++;
} else {
this.autoUnequipCounter = 0;

View file

@ -14,64 +14,64 @@
/* global Script, Overlays, Controller, Vec3, MyAvatar, Entities
*/
(function() {
var MSECONDS_AFTER_LOAD = 2000;
(function () {
var handTouchEnabled = true;
var MSECONDS_AFTER_LOAD = 2000;
var updateFingerWithIndex = 0;
var untouchableEntities = [];
// Keys to access finger data
// Keys to access finger data
var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"];
// Additionally close the hands to achieve a grabbing effect
// Additionally close the hands to achieve a grabbing effect
var grabPercent = { left: 0, right: 0 };
var Palm = function() {
this.position = {x: 0, y: 0, z: 0};
this.perpendicular = {x: 0, y: 0, z: 0};
this.distance = 0;
this.fingers = {
pinky: {x: 0, y: 0, z: 0},
middle: {x: 0, y: 0, z: 0},
ring: {x: 0, y: 0, z: 0},
thumb: {x: 0, y: 0, z: 0},
pinky: {x: 0, y: 0, z: 0},
middle: {x: 0, y: 0, z: 0},
ring: {x: 0, y: 0, z: 0},
thumb: {x: 0, y: 0, z: 0},
index: {x: 0, y: 0, z: 0}
};
this.set = false;
};
var palmData = {
left: new Palm(),
right: new Palm()
};
var handJointNames = {left: "LeftHand", right: "RightHand"};
// Store which fingers are touching - if all false restate the default poses
// Store which fingers are touching - if all false restate the default poses
var isTouching = {
left: {
pinky: false,
middle: false,
ring: false,
thumb: false,
index: false
pinky: false,
middle: false,
ring: false,
thumb: false,
index: false
}, right: {
pinky: false,
middle: false,
ring: false,
thumb: false,
pinky: false,
middle: false,
ring: false,
thumb: false,
index: false
}
};
// frame count for transition to default pose
var countToDefault = {
left: 0,
right: 0
};
// joint data for open pose
var dataOpen = {
left: {
@ -128,7 +128,7 @@
]
}
};
// joint data for close pose
var dataClose = {
left: {
@ -185,78 +185,78 @@
]
}
};
// snapshot for the default pose
var dataDefault = {
left: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
set: false
},
right: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
set: false
}
};
// joint data for the current frame
var dataCurrent = {
left: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
},
right: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
}
};
// interpolated values on joint data to smooth movement
// interpolated values on joint data to smooth movement
var dataDelta = {
left: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
},
right: {
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}],
index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}]
}
};
// Acquire an updated value per hand every 5 frames when finger is touching (faster in)
var touchAnimationSteps = 5;
// Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out)
// Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out)
var defaultAnimationSteps = 10;
// Debugging info
var showSphere = false;
var showLines = false;
// This get setup on creation
var linesCreated = false;
var sphereCreated = false;
// Register object with API Debugger
// Register object with API Debugger
var varsToDebug = {
scriptLoaded: false,
toggleDebugSphere: function() {
@ -275,17 +275,17 @@
},
fingerPercent: {
left: {
pinky: 0.38,
middle: 0.38,
ring: 0.38,
thumb: 0.38,
pinky: 0.38,
middle: 0.38,
ring: 0.38,
thumb: 0.38,
index: 0.38
} ,
} ,
right: {
pinky: 0.38,
middle: 0.38,
ring: 0.38,
thumb: 0.38,
pinky: 0.38,
middle: 0.38,
ring: 0.38,
thumb: 0.38,
index: 0.38
}
},
@ -300,12 +300,11 @@
palmData: {
left: new Palm(),
right: new Palm()
},
},
offset: {x: 0, y: 0, z: 0},
avatarLoaded: false
};
// Add/Subtract the joint data - per finger joint
function addVals(val1, val2, sign) {
var val = [];
@ -321,7 +320,7 @@
}
return val;
}
// Multiply/Divide the joint data - per finger joint
function multiplyValsBy(val1, num) {
var val = [];
@ -334,7 +333,7 @@
}
return val;
}
// Calculate the finger lengths by adding its joint lengths
function getJointDistances(jointNamesArray) {
var result = {distances: [], totalDistance: 0};
@ -349,13 +348,12 @@
}
return result;
}
function dataRelativeToWorld(side, dataIn, dataOut) {
function dataRelativeToWorld(side, dataIn, dataOut) {
var handJoint = handJointNames[side];
var jointIndex = MyAvatar.getJointIndex(handJoint);
var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex);
dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex);
var localPerpendicular = side === "right" ? {x: 0.2, y: 0, z: 1} : {x: -0.2, y: 0, z: 1};
dataOut.perpendicular = Vec3.normalize(
@ -365,15 +363,14 @@
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
dataOut.fingers[finger] = MyAvatar.jointToWorldPoint(dataIn.fingers[finger], jointIndex);
}
}
}
function dataRelativeToHandJoint(side, dataIn, dataOut) {
function dataRelativeToHandJoint(side, dataIn, dataOut) {
var handJoint = handJointNames[side];
var jointIndex = MyAvatar.getJointIndex(handJoint);
var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex);
dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex);
dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex);
dataOut.distance = dataIn.distance;
@ -382,46 +379,44 @@
dataOut.fingers[finger] = MyAvatar.worldToJointPoint(dataIn.fingers[finger], jointIndex);
}
}
// Calculate touch field; Sphere at the center of the palm,
// Calculate touch field; Sphere at the center of the palm,
// perpendicular vector from the palm plane and origin of the the finger rays
function estimatePalmData(side) {
// Return data object
var data = new Palm();
var jointOffset = { x: 0, y: 0, z: 0 };
var data = new Palm();
var jointOffset = { x: 0, y: 0, z: 0 };
var upperSide = side[0].toUpperCase() + side.substring(1);
var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand");
// Store position of the hand joint
var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand);
var minusWorldPosHand = {x: -worldPosHand.x, y: -worldPosHand.y, z: -worldPosHand.z};
// Data for finger rays
var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined};
var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined};
var thumbLength = 0;
var weightCount = 0;
// Calculate palm center
var handJointWeight = 1;
var fingerJointWeight = 2;
var palmCenter = {x: 0, y: 0, z: 0};
palmCenter = Vec3.sum(worldPosHand, palmCenter);
weightCount += handJointWeight;
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 4; // Get 4 joint names with suffix numbers (0, 1, 2, 3)
var jointNames = getJointNames(side, finger, jointSuffixes);
var fingerLength = getJointDistances(jointNames).totalDistance;
var jointIndex = MyAvatar.getJointIndex(jointNames[0]);
positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex);
directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand));
@ -429,66 +424,63 @@
if (finger !== "thumb") {
// finger joints have double the weight than the hand joint
// This would better position the palm estimation
palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter);
palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter);
weightCount += fingerJointWeight;
} else {
thumbLength = fingerLength;
}
}
// perpendicular change direction depending on the side
data.perpendicular = (side === "right") ?
Vec3.normalize(Vec3.cross(directions.index, directions.pinky)):
data.perpendicular = (side === "right") ?
Vec3.normalize(Vec3.cross(directions.index, directions.pinky)):
Vec3.normalize(Vec3.cross(directions.pinky, directions.index));
data.position = Vec3.multiply(1.0/weightCount, palmCenter);
if (side === "right") {
varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand);
}
var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand
data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index);
data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index);
// move back thumb ray origin
var thumbBackMultiplier = 0.2;
data.fingers.thumb = Vec3.sum(
data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular));
// return getDataRelativeToHandJoint(side, data);
dataRelativeToHandJoint(side, data, palmData[side]);
palmData[side].set = true;
// return palmData[side];
}
// Register GlobalDebugger for API Debugger
Script.registerValue("GlobalDebugger", varsToDebug);
// store the rays for the fingers - only for debug purposes
var fingerRays = {
var fingerRays = {
left: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
index: undefined
},
},
right: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
index: undefined
}
};
// Create debug overlays - finger rays + palm rays + spheres
var palmRay, sphereHand;
function createDebugLines() {
for (var i = 0; i < fingerKeys.length; i++) {
fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", {
color: { red: 0, green: 0, blue: 255 },
@ -503,7 +495,7 @@
visible: showLines
});
}
palmRay = {
left: Overlays.addOverlay("line3d", {
color: { red: 255, green: 0, blue: 0 },
@ -520,9 +512,8 @@
};
linesCreated = true;
}
function createDebugSphere() {
sphereHand = {
right: Overlays.addOverlay("sphere", {
position: MyAvatar.position,
@ -536,10 +527,10 @@
scale: { x: 0.01, y: 0.01, z: 0.01 },
visible: showSphere
})
};
};
sphereCreated = true;
}
function acquireDefaultPose(side) {
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
@ -553,86 +544,87 @@
}
dataDefault[side].set = true;
}
var rayPicks = {
left: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
var rayPicks = {
left: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
index: undefined
},
right: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
right: {
pinky: undefined,
middle: undefined,
ring: undefined,
thumb: undefined,
index: undefined
}
};
var dataFailed = {
left: {
pinky: 0,
middle: 0,
ring: 0,
thumb: 0,
var dataFailed = {
left: {
pinky: 0,
middle: 0,
ring: 0,
thumb: 0,
index: 0
},
right: {
pinky: 0,
middle: 0,
ring: 0,
thumb: 0,
right: {
pinky: 0,
middle: 0,
ring: 0,
thumb: 0,
index: 0
}
};
function clearRayPicks(side) {
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
if (rayPicks[side][finger] !== undefined) {
RayPick.removeRayPick(rayPicks[side][finger]);
rayPicks[side][finger] = undefined;
}
}
}
}
function createRayPicks(side) {
var data = palmData[side];
clearRayPicks(side);
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var finger = fingerKeys[i];
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
var checkOffset = {
x: data.perpendicular.x * dist,
y: data.perpendicular.y * dist,
z: data.perpendicular.z * dist
var checkOffset = {
x: data.perpendicular.x * dist,
y: data.perpendicular.y * dist,
z: data.perpendicular.z * dist
};
var checkPoint = Vec3.sum(data.position, Vec3.multiply(2, checkOffset));
var sensorToWorldScale = MyAvatar.getSensorToWorldScale();
var origin = data.fingers[finger];
var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin));
origin = Vec3.multiply(1/sensorToWorldScale, origin);
rayPicks[side][finger] = RayPick.createRayPick(
{
"enabled": false,
{
"enabled": false,
"joint": handJointNames[side],
"posOffset": origin,
"dirOffset": direction,
"filter": RayPick.PICK_ENTITIES
}
);
RayPick.setPrecisionPicking(rayPicks[side][finger], true);
}
);
RayPick.setPrecisionPicking(rayPicks[side][finger], true);
}
}
function activateNextRay(side, index) {
var nextIndex = (index < fingerKeys.length-1) ? index + 1 : 0;
for (var i = 0; i < fingerKeys.length; i++) {
@ -641,46 +633,44 @@
RayPick.enableRayPick(rayPicks[side][finger]);
} else {
RayPick.disableRayPick(rayPicks[side][finger]);
}
}
}
}
function updateSphereHand(side) {
function updateSphereHand(side) {
var data = new Palm();
dataRelativeToWorld(side, palmData[side], data);
varsToDebug.palmData[side] = palmData[side];
var palmPoint = data.position;
var LOOKUP_DISTANCE_MULTIPLIER = 1.5;
var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance;
// Situate the debugging overlays
var checkOffset = {
x: data.perpendicular.x * dist,
y: data.perpendicular.y * dist,
z: data.perpendicular.z * dist
// Situate the debugging overlays
var checkOffset = {
x: data.perpendicular.x * dist,
y: data.perpendicular.y * dist,
z: data.perpendicular.z * dist
};
var spherePos = Vec3.sum(palmPoint, checkOffset);
var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset));
if (showLines) {
Overlays.editOverlay(palmRay[side], {
start: palmPoint,
end: checkPoint,
visible: showLines
});
});
for (var i = 0; i < fingerKeys.length; i++) {
Overlays.editOverlay(fingerRays[side][fingerKeys[i]], {
start: data.fingers[fingerKeys[i]],
end: checkPoint,
visible: showLines
});
}
}
}
if (showSphere) {
Overlays.editOverlay(sphereHand[side], {
position: spherePos,
@ -690,16 +680,16 @@
z: 2*dist
},
visible: showSphere
});
});
}
// Update the intersection of only one finger at a time
var finger = fingerKeys[updateFingerWithIndex];
var grabbables = Entities.findEntities(spherePos, dist);
var finger = fingerKeys[updateFingerWithIndex];
var nearbyEntities = Entities.findEntities(spherePos, dist);
// Filter the entities that are allowed to be touched
var touchableEntities = nearbyEntities.filter(function (id) {
return untouchableEntities.indexOf(id) == -1;
});
var intersection;
if (rayPicks[side][finger] !== undefined) {
intersection = RayPick.getPrevRayPickResult(rayPicks[side][finger]);
@ -708,11 +698,10 @@
var animationSteps = defaultAnimationSteps;
var newFingerData = dataDefault[side][finger];
var isAbleToGrab = false;
if (grabbables.length > 0) {
RayPick.setIncludeItems(rayPicks[side][finger], grabbables);
if (touchableEntities.length > 0) {
RayPick.setIncludeItems(rayPicks[side][finger], touchableEntities);
if (intersection === undefined) {
if (intersection === undefined) {
return;
}
@ -725,28 +714,27 @@
// Store if this finger is touching something
isTouching[side][finger] = isAbleToGrab;
if (isAbleToGrab) {
// update the open/close percentage for this finger
// update the open/close percentage for this finger
var FINGER_REACT_MULTIPLIER = 2.8;
percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist);
var THUMB_FACTOR = 0.2;
var FINGER_FACTOR = 0.05;
// Amount of grab coefficient added to the fingers - thumb is higher
var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR;
var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR;
percent += grabMultiplier * grabPercent[side];
// Calculate new interpolation data
var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1);
// Assign close/open ratio to finger to simulate touch
newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1);
newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1);
animationSteps = touchAnimationSteps;
}
}
varsToDebug.fingerPercent[side][finger] = percent;
}
}
if (!isAbleToGrab) {
dataFailed[side][finger] = dataFailed[side][finger] === 0 ? 1 : 2;
} else {
@ -755,13 +743,12 @@
// If it only fails once it will not update increments
if (dataFailed[side][finger] !== 1) {
// Calculate animation increments
dataDelta[side][finger] =
multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps);
dataDelta[side][finger] =
multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps);
}
}
// Recreate the finger joint names
function getJointNames(side, finger, count) {
var names = [];
for (var i = 1; i < count+1; i++) {
@ -772,30 +759,34 @@
}
// Capture the controller values
var leftTriggerPress = function (value) {
varsToDebug.triggerValues.leftTriggerValue = value;
// the value for the trigger increments the hand-close percentage
// the value for the trigger increments the hand-close percentage
grabPercent.left = value;
};
var leftTriggerClick = function (value) {
varsToDebug.triggerValues.leftTriggerClicked = value;
};
var rightTriggerPress = function (value) {
varsToDebug.triggerValues.rightTriggerValue = value;
// the value for the trigger increments the hand-close percentage
// the value for the trigger increments the hand-close percentage
grabPercent.right = value;
};
var rightTriggerClick = function (value) {
varsToDebug.triggerValues.rightTriggerClicked = value;
};
var leftSecondaryPress = function (value) {
varsToDebug.triggerValues.leftSecondaryValue = value;
};
var rightSecondaryPress = function (value) {
varsToDebug.triggerValues.rightSecondaryValue = value;
};
var MAPPING_NAME = "com.highfidelity.handTouch";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress);
@ -809,16 +800,17 @@
mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress);
Controller.enableMapping(MAPPING_NAME);
if (showLines && !linesCreated) {
createDebugLines();
linesCreated = true;
}
if (showSphere && !sphereCreated) {
createDebugSphere();
sphereCreated = true;
}
function getTouching(side) {
var animating = false;
for (var i = 0; i < fingerKeys.length; i++) {
@ -827,19 +819,70 @@
}
return animating; // return false only if none of the fingers are touching
}
function reEstimatePalmData() {
["right", "left"].forEach(function(side) {
estimatePalmData(side);
});
}
function recreateRayPicks() {
["right", "left"].forEach(function(side) {
createRayPicks(side);
});
}
function cleanUp() {
["right", "left"].forEach(function (side) {
if (linesCreated) {
Overlays.deleteOverlay(palmRay[side]);
}
if (sphereCreated) {
Overlays.deleteOverlay(sphereHand[side]);
}
clearRayPicks(side);
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints
var names = getJointNames(side, finger, jointSuffixes);
for (var j = 0; j < names.length; j++) {
var index = MyAvatar.getJointIndex(names[j]);
MyAvatar.clearJointData(index);
}
if (linesCreated) {
Overlays.deleteOverlay(fingerRays[side][finger]);
}
}
});
}
MyAvatar.shouldDisableHandTouchChanged.connect(function (shouldDisable) {
if (shouldDisable) {
if (handTouchEnabled) {
cleanUp();
}
} else {
if (!handTouchEnabled) {
reEstimatePalmData();
recreateRayPicks();
}
}
handTouchEnabled = !shouldDisable;
});
MyAvatar.disableHandTouchForIDChanged.connect(function (entityID, disable) {
var entityIndex = untouchableEntities.indexOf(entityID);
if (disable) {
if (entityIndex == -1) {
untouchableEntities.push(entityID);
}
} else {
if (entityIndex != -1) {
untouchableEntities.splice(entityIndex, 1);
}
}
});
MyAvatar.onLoadComplete.connect(function () {
// Sometimes the rig is not ready when this signal is trigger
console.log("avatar loaded");
@ -848,78 +891,55 @@
recreateRayPicks();
}, MSECONDS_AFTER_LOAD);
});
MyAvatar.sensorToWorldScaleChanged.connect(function() {
reEstimatePalmData();
});
Script.scriptEnding.connect(function () {
["right", "left"].forEach(function(side) {
if (linesCreated) {
Overlays.deleteOverlay(palmRay[side]);
}
if (sphereCreated) {
Overlays.deleteOverlay(sphereHand[side]);
}
clearRayPicks(side);
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints
var names = getJointNames(side, finger, jointSuffixes);
for (var j = 0; j < names.length; j++) {
var index = MyAvatar.getJointIndex(names[j]);
MyAvatar.clearJointData(index);
}
if (linesCreated) {
Overlays.deleteOverlay(fingerRays[side][finger]);
}
}
});
Script.scriptEnding.connect(function () {
cleanUp();
});
Script.update.connect(function() {
Script.update.connect(function () {
if (!handTouchEnabled) {
return;
}
// index of the finger that needs to be updated this frame
updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0;
["right", "left"].forEach(function(side) {
if (!palmData[side].set) {
reEstimatePalmData();
recreateRayPicks();
}
// recalculate the base data
updateSphereHand(side);
activateNextRay(side, updateFingerWithIndex);
// this vars manage the transition to default pose
var isHandTouching = getTouching(side);
countToDefault[side] = isHandTouching ? 0 : countToDefault[side] + 1;
for (var i = 0; i < fingerKeys.length; i++) {
var finger = fingerKeys[i];
var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints
var names = getJointNames(side, finger, jointSuffixes);
var names = getJointNames(side, finger, jointSuffixes);
// Add the animation increments
dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1);
dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1);
// update every finger joint
for (var j = 0; j < names.length; j++) {
var index = MyAvatar.getJointIndex(names[j]);
// if no finger is touching restate the default poses
if (isHandTouching || (dataDefault[side].set &&
if (isHandTouching || (dataDefault[side].set &&
countToDefault[side] < fingerKeys.length*touchAnimationSteps)) {
var quatRot = dataCurrent[side][finger][j];
MyAvatar.setJointRotation(index, quatRot);
MyAvatar.setJointRotation(index, quatRot);
} else {
MyAvatar.clearJointData(index);
}

View file

@ -63,6 +63,15 @@ var createToolsWindow = new CreateWindow(
false
);
/**
* @description Returns true in case we should use the tablet version of the CreateApp
* @returns boolean
*/
var shouldUseEditTabletApp = function() {
return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true));
};
var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager;
@ -88,11 +97,12 @@ var cameraManager = new CameraManager();
var grid = new Grid();
var gridTool = new GridTool({
horizontalGrid: grid,
createToolsWindow: createToolsWindow
createToolsWindow: createToolsWindow,
shouldUseEditTabletApp: shouldUseEditTabletApp
});
gridTool.setVisible(false);
var entityListTool = new EntityListTool();
var entityListTool = new EntityListTool(shouldUseEditTabletApp);
selectionManager.addEventListener(function () {
selectionDisplay.updateHandles();
@ -578,7 +588,8 @@ var toolBar = (function () {
});
createButton = activeButton;
tablet.screenChanged.connect(function (type, url) {
var isGoingToHomescreenOnDesktop = (!HMD.active && (url === 'hifi/tablet/TabletHome.qml' || url === ''));
var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() &&
(url === 'hifi/tablet/TabletHome.qml' || url === ''));
if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml") && !isGoingToHomescreenOnDesktop) {
that.setActive(false);
}
@ -605,7 +616,7 @@ var toolBar = (function () {
});
function createNewEntityDialogButtonCallback(entityType) {
return function() {
if (HMD.active) {
if (shouldUseEditTabletApp()) {
// tablet version of new-model dialog
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.pushOntoStack("hifi/tablet/New" + entityType + "Dialog.qml");
@ -837,7 +848,7 @@ var toolBar = (function () {
selectionDisplay.triggerMapping.disable();
tablet.landscape = false;
} else {
if (HMD.active) {
if (shouldUseEditTabletApp()) {
tablet.loadQMLSource("hifi/tablet/Edit.qml", true);
} else {
// make other apps inactive while in desktop mode
@ -1075,15 +1086,19 @@ function mouseReleaseEvent(event) {
}
}
function wasTabletClicked(event) {
function wasTabletOrEditHandleClicked(event) {
var rayPick = Camera.computePickRay(event.x, event.y);
var tabletIDs = getMainTabletIDs();
if (tabletIDs.length === 0) {
return false;
} else {
var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs());
return result.intersects;
var result = Overlays.findRayIntersection(rayPick, true);
if (result.intersects) {
var overlayID = result.overlayID;
var tabletIDs = getMainTabletIDs();
if (tabletIDs.indexOf(overlayID) >= 0) {
return true;
} else if (selectionDisplay.isEditHandle(overlayID)) {
return true;
}
}
return false;
}
function mouseClickEvent(event) {
@ -1091,8 +1106,8 @@ function mouseClickEvent(event) {
var result, properties, tabletClicked;
if (isActive && event.isLeftButton) {
result = findClickedEntity(event);
tabletClicked = wasTabletClicked(event);
if (tabletClicked) {
tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event);
if (tabletOrEditHandleClicked) {
return;
}
@ -1573,15 +1588,11 @@ function deleteSelectedEntities() {
Entities.deleteEntity(entityID);
}
}
if (savedProperties.length > 0) {
SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties);
entityListTool.webView.emitScriptEvent(JSON.stringify({
type: "deleted",
ids: deletedIDs
}));
entityListTool.deleteEntities(deletedIDs);
}
}
}
@ -1993,8 +2004,8 @@ var PropertiesTool = function (opts) {
that.setVisible = function (newVisible) {
visible = newVisible;
webView.setVisible(HMD.active && visible);
createToolsWindow.setVisible(!HMD.active && visible);
webView.setVisible(shouldUseEditTabletApp() && visible);
createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible);
};
that.setVisible(false);
@ -2420,7 +2431,7 @@ function selectParticleEntity(entityID) {
// Switch to particle explorer
var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } };
if (HMD.active) {
if (shouldUseEditTabletApp()) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.sendToQml(selectTabMethod);
} else {

View file

@ -59,6 +59,7 @@
highlightTargetEntity:true,
clearHighlightedEntities:true,
unhighlightTargetEntity:true
distanceBetweenEntityLocalPositionAndBoundingBox: true
*/
MSECS_PER_SEC = 1000.0;
@ -130,7 +131,9 @@ DISPATCHER_PROPERTIES = [
"type",
"href",
"cloneable",
"cloneDynamic"
"cloneDynamic",
"localPosition",
"localRotation"
];
// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step
@ -413,6 +416,25 @@ findHandChildEntities = function(hand) {
});
};
distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps) {
var localPoint = entityProps.localPosition;
var entityXform = new Xform(entityProps.rotation, entityProps.position);
var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions);
var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions);
var localMin = Vec3.subtract(entityXform.trans, minOffset);
var localMax = Vec3.sum(entityXform.trans, maxOffset);
var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z};
v.x = Math.max(v.x, localMin.x);
v.x = Math.min(v.x, localMax.x);
v.y = Math.max(v.y, localMin.y);
v.y = Math.min(v.y, localMax.y);
v.z = Math.max(v.z, localMin.z);
v.z = Math.min(v.z, localMax.z);
return Vec3.distance(v, localPoint);
};
distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) {
var entityXform = new Xform(entityProps.rotation, entityProps.position);
var localPoint = entityXform.inv().xformPoint(point);

View file

@ -11,7 +11,7 @@
/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages,
cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */
EntityListTool = function() {
EntityListTool = function(shouldUseEditTabletApp) {
var that = {};
var CreateWindow = Script.require('../modules/createWindow.js');
@ -55,8 +55,8 @@ EntityListTool = function() {
that.setVisible = function(newVisible) {
visible = newVisible;
webView.setVisible(HMD.active && visible);
entityListWindow.setVisible(!HMD.active && visible);
webView.setVisible(shouldUseEditTabletApp() && visible);
entityListWindow.setVisible(!shouldUseEditTabletApp() && visible);
};
that.setVisible(false);
@ -93,12 +93,18 @@ EntityListTool = function() {
};
that.removeEntities = function (deletedIDs, selectedIDs) {
var data = {
emitJSONScriptEvent({
type: 'removeEntities',
deletedIDs: deletedIDs,
selectedIDs: selectedIDs
};
webView.emitScriptEvent(JSON.stringify(data));
});
};
that.deleteEntities = function (deletedIDs) {
emitJSONScriptEvent({
type: "deleted",
ids: deletedIDs
});
};
function valueIfDefined(value) {

View file

@ -658,6 +658,7 @@ SelectionDisplay = (function() {
selectionBox,
iconSelectionBox
];
var maximumHandleInAllOverlays = handleCloner;
overlayNames[handleTranslateXCone] = "handleTranslateXCone";
overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder";
@ -781,6 +782,12 @@ SelectionDisplay = (function() {
return Math.abs(position.x) <= box.dimensions.x / 2 && Math.abs(position.y) <= box.dimensions.y / 2
&& Math.abs(position.z) <= box.dimensions.z / 2;
}
that.isEditHandle = function(overlayID) {
var overlayIndex = allOverlays.indexOf(overlayID);
var maxHandleIndex = allOverlays.indexOf(maximumHandleInAllOverlays);
return overlayIndex >= 0 && overlayIndex <= maxHandleIndex;
};
// FUNCTION: MOUSE PRESS EVENT
that.mousePressEvent = function (event) {

View file

@ -1,6 +1,6 @@
var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html');
Grid = function(opts) {
Grid = function() {
var that = {};
var gridColor = { red: 0, green: 0, blue: 0 };
var gridAlpha = 0.6;
@ -247,6 +247,7 @@ GridTool = function(opts) {
var horizontalGrid = opts.horizontalGrid;
var verticalGrid = opts.verticalGrid;
var createToolsWindow = opts.createToolsWindow;
var shouldUseEditTabletApp = opts.shouldUseEditTabletApp;
var listeners = [];
var webView = null;
@ -299,7 +300,7 @@ GridTool = function(opts) {
};
that.setVisible = function(visible) {
webView.setVisible(HMD.active && visible);
webView.setVisible(shouldUseEditTabletApp() && visible);
};
return that;

View file

@ -19,7 +19,7 @@
var populateNearbyUserList, color, textures, removeOverlays,
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged,
tablet, CHANNEL, getConnectionData, findableByChanged,
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
@ -447,21 +447,24 @@ function populateNearbyUserList(selectData, oldAudioData) {
verticalAngleNormal = filter && Quat.getRight(orientation),
horizontalAngleNormal = filter && Quat.getUp(orientation);
avatarsOfInterest = {};
avatars.forEach(function (id) {
var avatar = AvatarList.getAvatar(id);
var name = avatar.sessionDisplayName;
var avatarData = AvatarList.getPalData().data;
avatarData.forEach(function (currentAvatarData) {
var id = currentAvatarData.sessionUUID;
var name = currentAvatarData.sessionDisplayName;
if (!name) {
// Either we got a data packet but no identity yet, or something is really messed up. In any case,
// we won't be able to do anything with this user, so don't include them.
// In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded,
// we could be losing and gaining people randomly.
print('No avatar identity data for', id);
print('No avatar identity data for', currentAvatarData.sessionUUID);
return;
}
if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) {
if (id && myPosition && (Vec3.distance(currentAvatarData.position, myPosition) > filter.distance)) {
return;
}
var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition));
var normal = id && filter && Vec3.normalize(Vec3.subtract(currentAvatarData.position, myPosition));
var horizontal = normal && angleBetweenVectorsInPlane(normal, forward, horizontalAngleNormal);
var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal);
if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) {
@ -480,11 +483,11 @@ function populateNearbyUserList(selectData, oldAudioData) {
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
ignore: !!id && Users.getIgnoreStatus(id), // ditto
isPresent: true,
isReplicated: avatar.isReplicated
isReplicated: currentAvatarData.isReplicated
};
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
Users.requestUsernameFromID(id);
if (id) {
if (id !== "") {
addAvatarNode(id); // No overlay for ourselves
avatarsOfInterest[id] = true;
} else {
@ -515,30 +518,63 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
updateUser(data);
}
function updateAudioLevel(avatarData) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
// of updating (the latter for efficiency too).
var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = avatarData.sessionUUID === "" ? myData : ExtendedOverlay.get(avatarData.sessionUUID);
if (data) {
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatarData.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
// decay avgAudioLevel
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// now scale for the gain. Also, asked to boost the low end, so one simple way is
// to take sqrt of the value. Lets try that, see how it feels.
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[avatarData.sessionUUID] || 0.75)));
}
var param = {};
var level = [audioLevel, avgAudioLevel];
var userId = avatarData.sessionUUID;
param[userId] = level;
sendToQml({ method: 'updateAudioLevel', params: param });
}
var pingPong = true;
function updateOverlays() {
var eye = Camera.position;
AvatarList.getAvatarIdentifiers().forEach(function (id) {
if (!id || !avatarsOfInterest[id]) {
var avatarData = AvatarList.getPalData().data;
avatarData.forEach(function (currentAvatarData) {
if (currentAvatarData.sessionUUID === "" || !avatarsOfInterest[currentAvatarData.sessionUUID]) {
return; // don't update ourself, or avatars we're not interested in
}
var avatar = AvatarList.getAvatar(id);
if (!avatar) {
return; // will be deleted below if there had been an overlay.
}
var overlay = ExtendedOverlay.get(id);
updateAudioLevel(currentAvatarData);
var overlay = ExtendedOverlay.get(currentAvatarData.sessionUUID);
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
print('Adding non-PAL avatar node', id);
overlay = addAvatarNode(id);
print('Adding non-PAL avatar node', currentAvatarData.sessionUUID);
overlay = addAvatarNode(currentAvatarData.sessionUUID);
}
var target = avatar.position;
var target = currentAvatarData.position;
var distance = Vec3.distance(target, eye);
var offset = 0.2;
var offset = currentAvatarData.palOrbOffset;
var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
if (headIndex > 0) {
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
}
// move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
@ -548,7 +584,7 @@ function updateOverlays() {
overlay.ping = pingPong;
overlay.editOverlay({
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
color: color(ExtendedOverlay.isSelected(currentAvatarData.sessionUUID), overlay.hovering, overlay.audioLevel),
position: target,
dimensions: 0.032 * distance
});
@ -704,6 +740,13 @@ function wireEventBridge(on) {
hasEventBridge = false;
}
}
}
var UPDATE_INTERVAL_MS = 100;
function createUpdateInterval() {
return Script.setInterval(function () {
updateOverlays();
}, UPDATE_INTERVAL_MS);
}
function onTabletScreenChanged(type, url) {
@ -719,10 +762,8 @@ function onTabletScreenChanged(type, url) {
ContextOverlay.enabled = false;
Users.requestsDomainListData = true;
audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS);
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
Script.update.connect(updateOverlays);
updateInterval = createUpdateInterval();
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
Users.usernameFromIDReply.connect(usernameFromIDReply);
@ -778,50 +819,6 @@ function scaleAudio(val) {
return audioLevel;
}
function getAudioLevel(id) {
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
// of updating (the latter for efficiency too).
var avatar = AvatarList.getAvatar(id);
var audioLevel = 0.0;
var avgAudioLevel = 0.0;
var data = id ? ExtendedOverlay.get(id) : myData;
if (data) {
// we will do exponential moving average by taking some the last loudness and averaging
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
// add 1 to insure we don't go log() and hit -infinity. Math.log is
// natural log, so to get log base 2, just divide by ln(2).
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
// decay avgAudioLevel
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
data.avgAudioLevel = avgAudioLevel;
data.audioLevel = audioLevel;
// now scale for the gain. Also, asked to boost the low end, so one simple way is
// to take sqrt of the value. Lets try that, see how it feels.
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75)));
}
return [audioLevel, avgAudioLevel];
}
function createAudioInterval(interval) {
// we will update the audioLevels periodically
// TODO: tune for efficiency - expecially with large numbers of avatars
return Script.setInterval(function () {
var param = {};
AvatarList.getAvatarIdentifiers().forEach(function (id) {
var level = getAudioLevel(id),
userId = id || 0; // qml didn't like an object with null/empty string for a key, so...
param[userId] = level;
});
sendToQml({method: 'updateAudioLevel', params: param});
}, interval);
}
function avatarDisconnected(nodeID) {
// remove from the pal list
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
@ -874,11 +871,11 @@ startup();
var isWired = false;
var audioTimer;
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
function off() {
if (isWired) {
Script.update.disconnect(updateOverlays);
if (updateInterval) {
Script.clearInterval(updateInterval);
}
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
@ -889,10 +886,6 @@ function off() {
Users.requestsDomainListData = false;
isWired = false;
if (audioTimer) {
Script.clearInterval(audioTimer);
}
}
removeOverlays();

View file

@ -31,8 +31,8 @@ BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) {
void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type) {
// if the URL doesn't have a scheme, assume it is a local file
if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") {
inputUrl.setScheme("file");
if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp" && inputUrl.scheme() != "file") {
inputUrl = QUrl::fromLocalFile(inputUrl.toString());
}
qDebug() << "Baking file type: " << type;