mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 15:13:41 +02:00
Merge branch 'master' into M19690
# Conflicts: # libraries/entities/src/EntityItemProperties.cpp
This commit is contained in:
commit
b36b981b55
145 changed files with 5230 additions and 1843 deletions
|
@ -344,8 +344,6 @@ void Agent::scriptRequestFinished() {
|
|||
void Agent::executeScript() {
|
||||
_scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload);
|
||||
|
||||
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine);
|
||||
|
||||
// setup an Avatar for the script to use
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
|
||||
|
|
|
@ -522,11 +522,8 @@ void EntityServer::startDynamicDomainVerification() {
|
|||
qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID;
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; NOT deleting entity" << entityID
|
||||
<< "More info:" << jsonObject;
|
||||
tree->withWriteLock([&] {
|
||||
tree->deleteEntity(entityID, true);
|
||||
});
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
|
|
|
@ -480,7 +480,7 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
popup.showSpecifyAvatarUrl(function() {
|
||||
popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() {
|
||||
var url = popup.inputText.text;
|
||||
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
|
||||
}, function(link) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import QtQuick 2.5
|
|||
MessageBox {
|
||||
id: popup
|
||||
|
||||
function showSpecifyAvatarUrl(callback, linkCallback) {
|
||||
function showSpecifyAvatarUrl(url, callback, linkCallback) {
|
||||
popup.onButton2Clicked = callback;
|
||||
popup.titleText = 'Specify Avatar URL'
|
||||
popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.<br>' +
|
||||
|
@ -12,6 +12,8 @@ MessageBox {
|
|||
'</a>'
|
||||
popup.inputText.visible = true;
|
||||
popup.inputText.placeholderText = 'Enter Avatar Url';
|
||||
popup.inputText.text = url;
|
||||
popup.inputText.selectAll();
|
||||
popup.button1text = 'CANCEL';
|
||||
popup.button2text = 'CONFIRM';
|
||||
|
||||
|
|
|
@ -335,7 +335,8 @@ Item {
|
|||
upgradeUrl: root.upgradeUrl,
|
||||
itemHref: root.itemHref,
|
||||
itemType: root.itemType,
|
||||
isInstalled: root.isInstalled
|
||||
isInstalled: root.isInstalled,
|
||||
wornEntityID: root.wornEntityID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -707,6 +707,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
} else if (msg.method === "updateItemClicked") {
|
||||
// These three cases are very similar to the conditionals below, under
|
||||
// "if msg.method === "giftAsset". They differ in their popup's wording
|
||||
// and the actions to take when continuing.
|
||||
// I could see an argument for DRYing up this code, but I think the
|
||||
// actions are different enough now and potentially moving forward such that I'm
|
||||
// OK with "somewhat repeating myself".
|
||||
if (msg.itemType === "app" && msg.isInstalled) {
|
||||
lightboxPopup.titleText = "Uninstall App";
|
||||
lightboxPopup.bodyText = "The app that you are trying to update is installed.<br><br>" +
|
||||
|
@ -721,6 +727,35 @@ Rectangle {
|
|||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.itemType === "wearable" && msg.wornEntityID !== '') {
|
||||
lightboxPopup.titleText = "Remove Wearable";
|
||||
lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to update.<br><br>" +
|
||||
"If you proceed, this wearable will be removed.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
Entities.deleteEntity(msg.wornEntityID);
|
||||
purchasesModel.setProperty(index, 'wornEntityID', '');
|
||||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) {
|
||||
lightboxPopup.titleText = "Change Avatar to Default";
|
||||
lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to update.<br><br>" +
|
||||
"If you proceed, your avatar will be changed to the default avatar.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
MyAvatar.useFullAvatarURL('');
|
||||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
|
|
|
@ -4844,12 +4844,6 @@ void Application::loadSettings() {
|
|||
|
||||
isFirstPerson = (qApp->isHMDMode());
|
||||
|
||||
// Flying should be disabled by default in HMD mode on first run, and it
|
||||
// should be enabled by default in desktop mode.
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
myAvatar->setFlyingEnabled(!isFirstPerson);
|
||||
|
||||
} else {
|
||||
// if this is not the first run, the camera will be initialized differently depending on user settings
|
||||
|
||||
|
@ -5297,7 +5291,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
|
|||
_keyboardFocusHighlight->setPulseMin(0.5);
|
||||
_keyboardFocusHighlight->setPulseMax(1.0);
|
||||
_keyboardFocusHighlight->setColorPulse(1.0);
|
||||
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
|
||||
_keyboardFocusHighlight->setIgnorePickIntersection(true);
|
||||
_keyboardFocusHighlight->setDrawInFront(false);
|
||||
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
|
||||
}
|
||||
|
@ -6201,6 +6195,9 @@ PickRay Application::computePickRay(float x, float y) const {
|
|||
getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction);
|
||||
} else {
|
||||
pickPoint /= getCanvasSize();
|
||||
if (_myCamera.getMode() == CameraMode::CAMERA_MODE_MIRROR) {
|
||||
pickPoint.x = 1.0f - pickPoint.x;
|
||||
}
|
||||
QMutexLocker viewLocker(&_viewMutex);
|
||||
_viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction);
|
||||
}
|
||||
|
@ -6536,9 +6533,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
entityScriptingInterface->setPacketSender(&_entityEditSender);
|
||||
entityScriptingInterface->setEntityTree(getEntities()->getTree());
|
||||
|
||||
// give the script engine to the RecordingScriptingInterface for its callbacks
|
||||
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(scriptEngine);
|
||||
|
||||
if (property(hifi::properties::TEST).isValid()) {
|
||||
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
|
||||
}
|
||||
|
|
|
@ -618,6 +618,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
result.intersects = true;
|
||||
result.avatarID = avatar->getID();
|
||||
result.distance = distance;
|
||||
result.face = face;
|
||||
result.surfaceNormal = surfaceNormal;
|
||||
result.extraInfo = extraInfo;
|
||||
}
|
||||
}
|
||||
|
@ -629,6 +631,79 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
return result;
|
||||
}
|
||||
|
||||
ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick,
|
||||
const QVector<EntityItemID>& avatarsToInclude,
|
||||
const QVector<EntityItemID>& avatarsToDiscard) {
|
||||
ParabolaToAvatarIntersectionResult result;
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findParabolaIntersectionVector",
|
||||
Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result),
|
||||
Q_ARG(const PickParabola&, pick),
|
||||
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
|
||||
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
|
||||
return result;
|
||||
}
|
||||
|
||||
auto avatarHashCopy = getHashCopy();
|
||||
for (auto avatarData : avatarHashCopy) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
|
||||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float parabolicDistance;
|
||||
BoxFace face;
|
||||
glm::vec3 surfaceNormal;
|
||||
|
||||
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
|
||||
|
||||
// It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to
|
||||
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
|
||||
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
|
||||
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
|
||||
|
||||
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
|
||||
|
||||
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
|
||||
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
|
||||
// if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) {
|
||||
// // parabola doesn't intersect avatar's bounding-box
|
||||
// continue;
|
||||
// }
|
||||
|
||||
glm::vec3 start;
|
||||
glm::vec3 end;
|
||||
float radius;
|
||||
avatar->getCapsule(start, end, radius);
|
||||
bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance);
|
||||
if (!intersects) {
|
||||
// ray doesn't intersect avatar's capsule
|
||||
continue;
|
||||
}
|
||||
|
||||
QVariantMap extraInfo;
|
||||
intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration,
|
||||
parabolicDistance, face, surfaceNormal, extraInfo, true);
|
||||
|
||||
if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) {
|
||||
result.intersects = true;
|
||||
result.avatarID = avatar->getID();
|
||||
result.parabolicDistance = parabolicDistance;
|
||||
result.face = face;
|
||||
result.surfaceNormal = surfaceNormal;
|
||||
result.extraInfo = extraInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.intersects) {
|
||||
result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance;
|
||||
result.distance = glm::distance(pick.origin, result.intersection);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// HACK
|
||||
float AvatarManager::getAvatarSortCoefficient(const QString& name) {
|
||||
if (name == "size") {
|
||||
|
|
|
@ -142,6 +142,10 @@ public:
|
|||
const QVector<EntityItemID>& avatarsToInclude,
|
||||
const QVector<EntityItemID>& avatarsToDiscard);
|
||||
|
||||
Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick,
|
||||
const QVector<EntityItemID>& avatarsToInclude,
|
||||
const QVector<EntityItemID>& avatarsToDiscard);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarManager.getAvatarSortCoefficient
|
||||
* @param {string} name
|
||||
|
|
|
@ -577,9 +577,11 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
|
|||
|
||||
void MyAvatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
||||
|
||||
animateScaleChanges(deltaTime);
|
||||
|
||||
setFlyingEnabled(getFlyingEnabled());
|
||||
|
||||
if (_cauterizationNeedsUpdate) {
|
||||
_cauterizationNeedsUpdate = false;
|
||||
|
||||
|
@ -724,16 +726,18 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
properties.setQueryAACubeDirty();
|
||||
properties.setLastEdited(now);
|
||||
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
||||
entity->getID(), properties);
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
|
||||
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
|
||||
if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
|
||||
EntityItemPointer entityDescendant = std::dynamic_pointer_cast<EntityItem>(descendant);
|
||||
if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
|
||||
EntityItemProperties descendantProperties;
|
||||
descendantProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
descendantProperties.setLastEdited(now);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
||||
entityDescendant->getID(), descendantProperties);
|
||||
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
|
||||
}
|
||||
});
|
||||
|
@ -1131,6 +1135,8 @@ void MyAvatar::saveData() {
|
|||
settings.setValue("collisionSoundURL", _collisionSoundURL);
|
||||
settings.setValue("useSnapTurn", _useSnapTurn);
|
||||
settings.setValue("userHeight", getUserHeight());
|
||||
settings.setValue("flyingDesktop", getFlyingDesktopPref());
|
||||
settings.setValue("flyingHMD", getFlyingHMDPref());
|
||||
settings.setValue("enabledFlying", getFlyingEnabled());
|
||||
|
||||
settings.endGroup();
|
||||
|
@ -1281,8 +1287,13 @@ void MyAvatar::loadData() {
|
|||
settings.remove("avatarEntityData");
|
||||
}
|
||||
setAvatarEntityDataChanged(true);
|
||||
|
||||
// Flying preferences must be loaded before calling setFlyingEnabled()
|
||||
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
|
||||
setFlyingEnabled(firstRunVal.get() ? getFlyingEnabled() : settings.value("enabledFlying").toBool());
|
||||
setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool());
|
||||
setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
|
||||
setFlyingEnabled(getFlyingEnabled());
|
||||
|
||||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
|
||||
|
@ -2825,6 +2836,12 @@ void MyAvatar::setFlyingEnabled(bool enabled) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
setFlyingHMDPref(enabled);
|
||||
} else {
|
||||
setFlyingDesktopPref(enabled);
|
||||
}
|
||||
|
||||
_enableFlying = enabled;
|
||||
}
|
||||
|
||||
|
@ -2840,7 +2857,33 @@ bool MyAvatar::isInAir() {
|
|||
|
||||
bool MyAvatar::getFlyingEnabled() {
|
||||
// May return true even if client is not allowed to fly in the zone.
|
||||
return _enableFlying;
|
||||
return (qApp->isHMDMode() ? getFlyingHMDPref() : getFlyingDesktopPref());
|
||||
}
|
||||
|
||||
void MyAvatar::setFlyingDesktopPref(bool enabled) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setFlyingDesktopPref", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
_flyingPrefDesktop = enabled;
|
||||
}
|
||||
|
||||
bool MyAvatar::getFlyingDesktopPref() {
|
||||
return _flyingPrefDesktop;
|
||||
}
|
||||
|
||||
void MyAvatar::setFlyingHMDPref(bool enabled) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setFlyingHMDPref", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
_flyingPrefHMD = enabled;
|
||||
}
|
||||
|
||||
bool MyAvatar::getFlyingHMDPref() {
|
||||
return _flyingPrefHMD;
|
||||
}
|
||||
|
||||
// Public interface for targetscale
|
||||
|
@ -3042,7 +3085,7 @@ static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float b
|
|||
}
|
||||
|
||||
// computeCounterBalance returns the center of gravity in Avatar space
|
||||
glm::vec3 MyAvatar::computeCounterBalance() const {
|
||||
glm::vec3 MyAvatar::computeCounterBalance() {
|
||||
struct JointMass {
|
||||
QString name;
|
||||
float weight;
|
||||
|
@ -3060,7 +3103,8 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
|
|||
JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f));
|
||||
glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS;
|
||||
glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 tposeHips = DEFAULT_AVATAR_HIPS_POS;
|
||||
glm::vec3 tposeRightFoot = DEFAULT_AVATAR_RIGHTFOOT_POS;
|
||||
|
||||
if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) {
|
||||
cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name));
|
||||
|
@ -3079,6 +3123,9 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
|
|||
if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) {
|
||||
tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips"));
|
||||
}
|
||||
if (_skeletonModel->getRig().indexOfJoint("RightFoot") != -1) {
|
||||
tposeRightFoot = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot"));
|
||||
}
|
||||
|
||||
// find the current center of gravity position based on head and hand moments
|
||||
glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position);
|
||||
|
@ -3099,9 +3146,12 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
|
|||
glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead;
|
||||
|
||||
// find the height of the hips
|
||||
const float UPPER_LEG_FRACTION = 0.3333f;
|
||||
glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z));
|
||||
float headMinusHipXz = glm::length(xzDiff);
|
||||
float headHipDefault = glm::length(tposeHead - tposeHips);
|
||||
float hipFootDefault = tposeHips.y - tposeRightFoot.y;
|
||||
float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault);
|
||||
float hipHeight = 0.0f;
|
||||
if (headHipDefault > headMinusHipXz) {
|
||||
hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz));
|
||||
|
@ -3113,6 +3163,10 @@ glm::vec3 MyAvatar::computeCounterBalance() const {
|
|||
if (counterBalancedCg.y > (tposeHips.y + 0.05f)) {
|
||||
// if the height is higher than default hips, clamp to default hips
|
||||
counterBalancedCg.y = tposeHips.y + 0.05f;
|
||||
} else if (counterBalancedCg.y < sitSquatThreshold) {
|
||||
//do a height reset
|
||||
setResetMode(true);
|
||||
_follow.activate(FollowHelper::Vertical);
|
||||
}
|
||||
return counterBalancedCg;
|
||||
}
|
||||
|
@ -3162,7 +3216,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma
|
|||
// this function finds the hips position using a center of gravity model that
|
||||
// balances the head and hands with the hips over the base of support
|
||||
// returns the rotation (-z forward) and position of the Avatar in Sensor space
|
||||
glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
|
||||
glm::mat4 MyAvatar::deriveBodyUsingCgModel() {
|
||||
glm::mat4 sensorToWorldMat = getSensorToWorldMatrix();
|
||||
glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat);
|
||||
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
|
@ -3180,7 +3234,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
|
|||
}
|
||||
|
||||
// get the new center of gravity
|
||||
const glm::vec3 cgHipsPosition = computeCounterBalance();
|
||||
glm::vec3 cgHipsPosition = computeCounterBalance();
|
||||
|
||||
// find the new hips rotation using the new head-hips axis as the up axis
|
||||
glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition);
|
||||
|
|
|
@ -613,6 +613,8 @@ public:
|
|||
|
||||
const MyHead* getMyHead() const;
|
||||
|
||||
Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); };
|
||||
|
||||
/**jsdoc
|
||||
* Get the current position of the avatar's "Head" joint.
|
||||
* @function MyAvatar.getHeadPosition
|
||||
|
@ -953,6 +955,30 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE bool getFlyingEnabled();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setFlyingDesktopPref
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
Q_INVOKABLE void setFlyingDesktopPref(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getFlyingDesktopPref
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool getFlyingDesktopPref();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setFlyingDesktopPref
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
Q_INVOKABLE void setFlyingHMDPref(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getFlyingDesktopPref
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool getFlyingHMDPref();
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getAvatarScale
|
||||
|
@ -1017,12 +1043,12 @@ public:
|
|||
// results are in sensor frame (-z forward)
|
||||
glm::mat4 deriveBodyFromHMDSensor() const;
|
||||
|
||||
glm::vec3 computeCounterBalance() const;
|
||||
glm::vec3 computeCounterBalance();
|
||||
|
||||
// derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous
|
||||
// location of the base of support of the avatar.
|
||||
// results are in sensor frame (-z foward)
|
||||
glm::mat4 deriveBodyUsingCgModel() const;
|
||||
glm::mat4 deriveBodyUsingCgModel();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.isUp
|
||||
|
@ -1503,6 +1529,8 @@ private:
|
|||
std::bitset<MAX_DRIVE_KEYS> _disabledDriveKeys;
|
||||
|
||||
bool _enableFlying { false };
|
||||
bool _flyingPrefDesktop { true };
|
||||
bool _flyingPrefHMD { false };
|
||||
bool _wasPushing { false };
|
||||
bool _isPushing { false };
|
||||
bool _isBeingPushed { false };
|
||||
|
|
|
@ -48,7 +48,7 @@ void OtherAvatar::createOrb() {
|
|||
_otherAvatarOrbMeshPlaceholder->setPulseMin(0.5);
|
||||
_otherAvatarOrbMeshPlaceholder->setPulseMax(1.0);
|
||||
_otherAvatarOrbMeshPlaceholder->setColorPulse(1.0);
|
||||
_otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true);
|
||||
_otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true);
|
||||
_otherAvatarOrbMeshPlaceholder->setDrawInFront(false);
|
||||
_otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder);
|
||||
// Position focus
|
||||
|
|
43
interface/src/raypick/JointParabolaPick.cpp
Normal file
43
interface/src/raypick/JointParabolaPick.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "JointParabolaPick.h"
|
||||
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
|
||||
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) :
|
||||
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
|
||||
_jointName(jointName),
|
||||
_posOffset(posOffset),
|
||||
_dirOffset(dirOffset)
|
||||
{
|
||||
}
|
||||
|
||||
PickParabola JointParabolaPick::getMathematicalPick() const {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
|
||||
bool useAvatarHead = _jointName == "Avatar";
|
||||
const int INVALID_JOINT = -1;
|
||||
if (jointIndex != INVALID_JOINT || useAvatarHead) {
|
||||
glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex);
|
||||
glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex);
|
||||
glm::vec3 avatarPos = myAvatar->getWorldPosition();
|
||||
glm::quat avatarRot = myAvatar->getWorldOrientation();
|
||||
|
||||
glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos);
|
||||
glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot;
|
||||
|
||||
// Apply offset
|
||||
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
|
||||
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
|
||||
|
||||
return PickParabola(pos, getSpeed() * dir, getAcceleration());
|
||||
}
|
||||
|
||||
return PickParabola();
|
||||
}
|
32
interface/src/raypick/JointParabolaPick.h
Normal file
32
interface/src/raypick/JointParabolaPick.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_JointParabolaPick_h
|
||||
#define hifi_JointParabolaPick_h
|
||||
|
||||
#include "ParabolaPick.h"
|
||||
|
||||
class JointParabolaPick : public ParabolaPick {
|
||||
|
||||
public:
|
||||
JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
|
||||
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
|
||||
PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickParabola getMathematicalPick() const override;
|
||||
|
||||
bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); }
|
||||
bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); }
|
||||
|
||||
private:
|
||||
std::string _jointName;
|
||||
glm::vec3 _posOffset;
|
||||
glm::vec3 _dirOffset;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_JointParabolaPick_h
|
|
@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const {
|
|||
|
||||
// Apply offset
|
||||
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
|
||||
glm::vec3 dir = rot * glm::normalize(_dirOffset);
|
||||
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
|
||||
|
||||
return PickRay(pos, dir);
|
||||
}
|
||||
|
|
|
@ -14,315 +14,114 @@
|
|||
#include "avatar/AvatarManager.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <PickManager.h>
|
||||
#include "PickScriptingInterface.h"
|
||||
#include "RayPick.h"
|
||||
|
||||
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
|
||||
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
|
||||
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
|
||||
_triggers(triggers),
|
||||
_renderStates(renderStates),
|
||||
_defaultRenderStates(defaultRenderStates),
|
||||
_faceAvatar(faceAvatar),
|
||||
_centerEndY(centerEndY),
|
||||
_lockEnd(lockEnd),
|
||||
_distanceScaleEnd(distanceScaleEnd),
|
||||
_scaleWithAvatar(scaleWithAvatar)
|
||||
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd,
|
||||
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
|
||||
PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime,
|
||||
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
|
||||
{
|
||||
for (auto& state : _renderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
disableRenderState(state.second);
|
||||
}
|
||||
}
|
||||
for (auto& state : _defaultRenderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
disableRenderState(state.second.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaserPointer::~LaserPointer() {
|
||||
for (auto& renderState : _renderStates) {
|
||||
renderState.second.deleteOverlays();
|
||||
}
|
||||
for (auto& renderState : _defaultRenderStates) {
|
||||
renderState.second.second.deleteOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
void LaserPointer::setRenderState(const std::string& state) {
|
||||
withWriteLock([&] {
|
||||
if (!_currentRenderState.empty() && state != _currentRenderState) {
|
||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
||||
disableRenderState(_renderStates[_currentRenderState]);
|
||||
}
|
||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
}
|
||||
}
|
||||
_currentRenderState = state;
|
||||
});
|
||||
}
|
||||
|
||||
void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
|
||||
withWriteLock([&] {
|
||||
updateRenderStateOverlay(_renderStates[state].getStartID(), startProps);
|
||||
updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps);
|
||||
updateRenderStateOverlay(_renderStates[state].getEndID(), endProps);
|
||||
QVariant endDim = endProps.toMap()["dimensions"];
|
||||
if (endDim.isValid()) {
|
||||
_renderStates[state].setEndDim(vec3FromVariant(endDim));
|
||||
}
|
||||
void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
|
||||
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
|
||||
if (renderState) {
|
||||
updateRenderStateOverlay(renderState->getPathID(), pathProps);
|
||||
QVariant lineWidth = pathProps.toMap()["lineWidth"];
|
||||
if (lineWidth.isValid()) {
|
||||
_renderStates[state].setLineWidth(lineWidth.toFloat());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) {
|
||||
PickResultPointer visualPickResult = pickResult;
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(visualPickResult);
|
||||
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
|
||||
|
||||
if (type != IntersectionType::HUD) {
|
||||
glm::vec3 endVec;
|
||||
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
|
||||
if (!_lockEndObject.id.isNull()) {
|
||||
glm::vec3 pos;
|
||||
glm::quat rot;
|
||||
glm::vec3 dim;
|
||||
glm::vec3 registrationPoint;
|
||||
if (_lockEndObject.isOverlay) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
|
||||
registrationPoint = glm::vec3(0.5f);
|
||||
} else {
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
|
||||
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
||||
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
||||
pos = extractTranslation(finalPosAndRotMat);
|
||||
rot = glmExtractRotation(finalPosAndRotMat);
|
||||
dim = props.getDimensions();
|
||||
registrationPoint = props.getRegistrationPoint();
|
||||
}
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
|
||||
glm::vec3 direction = endVec - pickRay.origin;
|
||||
float distance = glm::distance(pickRay.origin, endVec);
|
||||
glm::vec3 normalizedDirection = glm::normalize(direction);
|
||||
|
||||
rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
|
||||
rayPickResult->objectID = _lockEndObject.id;
|
||||
rayPickResult->intersection = endVec;
|
||||
rayPickResult->distance = distance;
|
||||
rayPickResult->surfaceNormal = -normalizedDirection;
|
||||
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
|
||||
} else if (type != IntersectionType::NONE && _lockEnd) {
|
||||
if (type == IntersectionType::ENTITY) {
|
||||
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(rayPickResult->objectID)[3];
|
||||
} else if (type == IntersectionType::OVERLAY) {
|
||||
endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value);
|
||||
} else if (type == IntersectionType::AVATAR) {
|
||||
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(rayPickResult->objectID)->getPosition();
|
||||
}
|
||||
glm::vec3 direction = endVec - pickRay.origin;
|
||||
float distance = glm::distance(pickRay.origin, endVec);
|
||||
glm::vec3 normalizedDirection = glm::normalize(direction);
|
||||
rayPickResult->intersection = endVec;
|
||||
rayPickResult->distance = distance;
|
||||
rayPickResult->surfaceNormal = -normalizedDirection;
|
||||
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
|
||||
renderState->setLineWidth(lineWidth.toFloat());
|
||||
}
|
||||
}
|
||||
return visualPickResult;
|
||||
}
|
||||
|
||||
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
|
||||
if (!id.isNull() && props.isValid()) {
|
||||
QVariantMap propMap = props.toMap();
|
||||
propMap.remove("visible");
|
||||
qApp->getOverlays().editOverlay(id, propMap);
|
||||
glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
if (distance > 0.0f) {
|
||||
PickRay pick = PickRay(rayPickResult->pickVariant);
|
||||
return pick.origin + distance * pick.direction;
|
||||
} else {
|
||||
return rayPickResult->intersection;
|
||||
}
|
||||
}
|
||||
|
||||
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) {
|
||||
if (!renderState.getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("position", vec3toVariant(pickRay.origin));
|
||||
startProps.insert("visible", true);
|
||||
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
|
||||
}
|
||||
glm::vec3 endVec = pickRay.origin + pickRay.direction * distance;
|
||||
|
||||
QVariant end = vec3toVariant(endVec);
|
||||
if (!renderState.getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
pathProps.insert("start", vec3toVariant(pickRay.origin));
|
||||
pathProps.insert("end", end);
|
||||
pathProps.insert("visible", true);
|
||||
pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays());
|
||||
if (_scaleWithAvatar) {
|
||||
pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
|
||||
}
|
||||
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
|
||||
}
|
||||
if (!renderState.getEndID().isNull()) {
|
||||
QVariantMap endProps;
|
||||
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
|
||||
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
|
||||
if (_distanceScaleEnd) {
|
||||
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec);
|
||||
endProps.insert("dimensions", vec3toVariant(dim));
|
||||
}
|
||||
if (_centerEndY) {
|
||||
endProps.insert("position", end);
|
||||
} else {
|
||||
glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP;
|
||||
endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y)));
|
||||
}
|
||||
if (_faceAvatar) {
|
||||
endProps.insert("rotation", quatToVariant(faceAvatarRotation));
|
||||
}
|
||||
endProps.insert("visible", true);
|
||||
endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
|
||||
}
|
||||
glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
void LaserPointer::disableRenderState(const RenderState& renderState) {
|
||||
if (!renderState.getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("visible", false);
|
||||
startProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
|
||||
}
|
||||
if (!renderState.getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
pathProps.insert("visible", false);
|
||||
pathProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
|
||||
}
|
||||
if (!renderState.getEndID().isNull()) {
|
||||
QVariantMap endProps;
|
||||
endProps.insert("visible", false);
|
||||
endProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
|
||||
}
|
||||
IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
return (rayPickResult ? rayPickResult->type : IntersectionType::NONE);
|
||||
}
|
||||
|
||||
void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
|
||||
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
|
||||
|
||||
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
|
||||
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
||||
(type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
|
||||
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay();
|
||||
QUuid uid = rayPickResult->objectID;
|
||||
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
|
||||
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
disableRenderState(_renderStates[_currentRenderState]);
|
||||
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
|
||||
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay);
|
||||
} else if (!_currentRenderState.empty()) {
|
||||
disableRenderState(_renderStates[_currentRenderState]);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
}
|
||||
QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
return (rayPickResult ? rayPickResult->objectID : QUuid());
|
||||
}
|
||||
|
||||
Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) {
|
||||
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
|
||||
if (!rayPickResult) {
|
||||
return PickedObject();
|
||||
}
|
||||
return PickedObject(rayPickResult->objectID, rayPickResult->type);
|
||||
}
|
||||
|
||||
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
|
||||
std::unordered_set<std::string> toReturn;
|
||||
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
|
||||
|
||||
void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
|
||||
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
if (rayPickResult) {
|
||||
for (const PointerTrigger& trigger : _triggers) {
|
||||
std::string button = trigger.getButton();
|
||||
TriggerState& state = _states[button];
|
||||
// TODO: right now, LaserPointers don't support axes, only on/off buttons
|
||||
if (trigger.getEndpoint()->peek() >= 1.0f) {
|
||||
toReturn.insert(button);
|
||||
|
||||
if (_previousButtons.find(button) == _previousButtons.end()) {
|
||||
// start triggering for buttons that were just pressed
|
||||
state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type);
|
||||
state.intersection = rayPickResult->intersection;
|
||||
state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection);
|
||||
state.triggerStartTime = usecTimestampNow();
|
||||
state.surfaceNormal = rayPickResult->surfaceNormal;
|
||||
state.deadspotExpired = false;
|
||||
state.wasTriggering = true;
|
||||
state.triggering = true;
|
||||
_latestState = state;
|
||||
}
|
||||
} else {
|
||||
// stop triggering for buttons that aren't pressed
|
||||
state.wasTriggering = state.triggering;
|
||||
state.triggering = false;
|
||||
_latestState = state;
|
||||
}
|
||||
}
|
||||
_previousButtons = toReturn;
|
||||
rayPickResult->type = type;
|
||||
rayPickResult->objectID = id;
|
||||
rayPickResult->intersection = intersection;
|
||||
rayPickResult->distance = distance;
|
||||
rayPickResult->surfaceNormal = surfaceNormal;
|
||||
rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void LaserPointer::setLength(float length) {
|
||||
withWriteLock([&] {
|
||||
_laserLength = length;
|
||||
});
|
||||
}
|
||||
|
||||
void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
|
||||
withWriteLock([&] {
|
||||
_lockEndObject.id = objectID;
|
||||
_lockEndObject.isOverlay = isOverlay;
|
||||
_lockEndObject.offsetMat = offsetMat;
|
||||
});
|
||||
}
|
||||
|
||||
RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
|
||||
_startID(startID), _pathID(pathID), _endID(endID)
|
||||
LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
|
||||
StartEndRenderState(startID, endID), _pathID(pathID)
|
||||
{
|
||||
if (!_startID.isNull()) {
|
||||
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
|
||||
}
|
||||
if (!_pathID.isNull()) {
|
||||
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
|
||||
_lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat();
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
|
||||
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderState::deleteOverlays() {
|
||||
if (!_startID.isNull()) {
|
||||
qApp->getOverlays().deleteOverlay(_startID);
|
||||
}
|
||||
void LaserPointer::RenderState::cleanup() {
|
||||
StartEndRenderState::cleanup();
|
||||
if (!_pathID.isNull()) {
|
||||
qApp->getOverlays().deleteOverlay(_pathID);
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
qApp->getOverlays().deleteOverlay(_endID);
|
||||
}
|
||||
|
||||
void LaserPointer::RenderState::disable() {
|
||||
StartEndRenderState::disable();
|
||||
if (!getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
pathProps.insert("visible", false);
|
||||
pathProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getPathID(), pathProps);
|
||||
}
|
||||
}
|
||||
|
||||
RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
||||
void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
|
||||
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
|
||||
QVariant endVariant = vec3toVariant(end);
|
||||
if (!getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
pathProps.insert("start", vec3toVariant(origin));
|
||||
pathProps.insert("end", endVariant);
|
||||
pathProps.insert("visible", true);
|
||||
pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays());
|
||||
if (scaleWithAvatar) {
|
||||
pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
|
||||
}
|
||||
qApp->getOverlays().editOverlay(getPathID(), pathProps);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StartEndRenderState> LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
||||
QUuid startID;
|
||||
if (propMap["start"].isValid()) {
|
||||
QVariantMap startMap = propMap["start"].toMap();
|
||||
|
@ -335,7 +134,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
|||
QUuid pathID;
|
||||
if (propMap["path"].isValid()) {
|
||||
QVariantMap pathMap = propMap["path"].toMap();
|
||||
// right now paths must be line3ds
|
||||
// laser paths must be line3ds
|
||||
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
|
||||
pathMap.remove("visible");
|
||||
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
|
||||
|
@ -351,7 +150,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
|||
}
|
||||
}
|
||||
|
||||
return RenderState(startID, pathID, endID);
|
||||
return std::make_shared<RenderState>(startID, pathID, endID);
|
||||
}
|
||||
|
||||
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
|
||||
|
@ -391,24 +190,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
|
|||
|
||||
glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
|
||||
case OVERLAY:
|
||||
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
|
||||
default:
|
||||
return glm::vec3(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
|
||||
case OVERLAY:
|
||||
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
|
||||
case HUD:
|
||||
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
|
||||
default:
|
||||
return glm::vec2(NAN);
|
||||
case ENTITY:
|
||||
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
|
||||
case OVERLAY:
|
||||
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
|
||||
default:
|
||||
return glm::vec3(NAN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,117 +11,54 @@
|
|||
#ifndef hifi_LaserPointer_h
|
||||
#define hifi_LaserPointer_h
|
||||
|
||||
#include <QString>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ui/overlays/Overlay.h"
|
||||
|
||||
#include <Pointer.h>
|
||||
#include <Pick.h>
|
||||
|
||||
struct LockEndObject {
|
||||
QUuid id { QUuid() };
|
||||
bool isOverlay { false };
|
||||
glm::mat4 offsetMat { glm::mat4() };
|
||||
};
|
||||
|
||||
class RenderState {
|
||||
#include "PathPointer.h"
|
||||
|
||||
class LaserPointer : public PathPointer {
|
||||
using Parent = PathPointer;
|
||||
public:
|
||||
RenderState() {}
|
||||
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
|
||||
class RenderState : public StartEndRenderState {
|
||||
public:
|
||||
RenderState() {}
|
||||
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
|
||||
|
||||
const OverlayID& getStartID() const { return _startID; }
|
||||
const OverlayID& getPathID() const { return _pathID; }
|
||||
const OverlayID& getEndID() const { return _endID; }
|
||||
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
|
||||
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
|
||||
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
|
||||
const OverlayID& getPathID() const { return _pathID; }
|
||||
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
|
||||
|
||||
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
|
||||
const glm::vec3& getEndDim() const { return _endDim; }
|
||||
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
|
||||
const float& getLineWidth() const { return _lineWidth; }
|
||||
|
||||
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
|
||||
const float& getLineWidth() const { return _lineWidth; }
|
||||
void cleanup() override;
|
||||
void disable() override;
|
||||
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
|
||||
|
||||
void deleteOverlays();
|
||||
private:
|
||||
OverlayID _pathID;
|
||||
bool _pathIgnoreRays;
|
||||
|
||||
private:
|
||||
OverlayID _startID;
|
||||
OverlayID _pathID;
|
||||
OverlayID _endID;
|
||||
bool _startIgnoreRays;
|
||||
bool _pathIgnoreRays;
|
||||
bool _endIgnoreRays;
|
||||
|
||||
glm::vec3 _endDim;
|
||||
float _lineWidth;
|
||||
};
|
||||
|
||||
class LaserPointer : public Pointer {
|
||||
using Parent = Pointer;
|
||||
public:
|
||||
typedef std::unordered_map<std::string, RenderState> RenderStateMap;
|
||||
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
|
||||
|
||||
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
|
||||
bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
|
||||
~LaserPointer();
|
||||
|
||||
void setRenderState(const std::string& state) override;
|
||||
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
|
||||
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
|
||||
|
||||
void setLength(float length) override;
|
||||
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
|
||||
|
||||
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
|
||||
|
||||
static RenderState buildRenderState(const QVariantMap& propMap);
|
||||
|
||||
protected:
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
|
||||
|
||||
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
|
||||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
|
||||
|
||||
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
|
||||
private:
|
||||
PointerTriggers _triggers;
|
||||
float _laserLength { 0.0f };
|
||||
std::string _currentRenderState { "" };
|
||||
RenderStateMap _renderStates;
|
||||
DefaultRenderStateMap _defaultRenderStates;
|
||||
bool _faceAvatar;
|
||||
bool _centerEndY;
|
||||
bool _lockEnd;
|
||||
bool _distanceScaleEnd;
|
||||
bool _scaleWithAvatar;
|
||||
LockEndObject _lockEndObject;
|
||||
|
||||
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
|
||||
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay);
|
||||
void disableRenderState(const RenderState& renderState);
|
||||
|
||||
struct TriggerState {
|
||||
PickedObject triggeredObject;
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
glm::vec2 triggerPos2D { NAN };
|
||||
quint64 triggerStartTime { 0 };
|
||||
bool deadspotExpired { true };
|
||||
bool triggering { false };
|
||||
bool wasTriggering { false };
|
||||
float _lineWidth;
|
||||
};
|
||||
|
||||
Pointer::Buttons _previousButtons;
|
||||
std::unordered_map<std::string, TriggerState> _states;
|
||||
TriggerState _latestState;
|
||||
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
|
||||
|
||||
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
|
||||
|
||||
protected:
|
||||
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
|
||||
|
||||
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
|
||||
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
|
||||
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
|
||||
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
|
||||
QUuid getPickedObjectID(const PickResultPointer& pickResult) const override;
|
||||
void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
|
||||
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override;
|
||||
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
|
||||
|
||||
private:
|
||||
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
|
||||
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
|
||||
|
||||
};
|
||||
|
||||
|
|
28
interface/src/raypick/MouseParabolaPick.cpp
Normal file
28
interface/src/raypick/MouseParabolaPick.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "MouseParabolaPick.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "display-plugins/CompositorHelper.h"
|
||||
|
||||
MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
|
||||
bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled)
|
||||
{
|
||||
}
|
||||
|
||||
PickParabola MouseParabolaPick::getMathematicalPick() const {
|
||||
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
|
||||
if (position.isValid()) {
|
||||
QVariantMap posMap = position.toMap();
|
||||
PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat());
|
||||
return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration());
|
||||
}
|
||||
|
||||
return PickParabola();
|
||||
}
|
24
interface/src/raypick/MouseParabolaPick.h
Normal file
24
interface/src/raypick/MouseParabolaPick.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_MouseParabolaPick_h
|
||||
#define hifi_MouseParabolaPick_h
|
||||
|
||||
#include "ParabolaPick.h"
|
||||
|
||||
class MouseParabolaPick : public ParabolaPick {
|
||||
|
||||
public:
|
||||
MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
|
||||
const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickParabola getMathematicalPick() const override;
|
||||
|
||||
bool isMouse() const override { return true; }
|
||||
};
|
||||
|
||||
#endif // hifi_MouseParabolaPick_h
|
70
interface/src/raypick/ParabolaPick.cpp
Normal file
70
interface/src/raypick/ParabolaPick.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "ParabolaPick.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "EntityScriptingInterface.h"
|
||||
#include "ui/overlays/Overlays.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "DependencyManager.h"
|
||||
|
||||
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
ParabolaToEntityIntersectionResult entityRes =
|
||||
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
}
|
||||
}
|
||||
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
||||
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
ParabolaToOverlayIntersectionResult overlayRes =
|
||||
qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (overlayRes.intersects) {
|
||||
return std::make_shared<ParabolaPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||
}
|
||||
}
|
||||
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
||||
PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findParabolaIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (avatarRes.intersects) {
|
||||
return std::make_shared<ParabolaPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
|
||||
}
|
||||
}
|
||||
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
||||
PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
float parabolicDistance;
|
||||
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance);
|
||||
return std::make_shared<ParabolaPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick);
|
||||
}
|
||||
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
||||
float ParabolaPick::getSpeed() const {
|
||||
return (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed);
|
||||
}
|
||||
|
||||
glm::vec3 ParabolaPick::getAcceleration() const {
|
||||
float scale = (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : 1.0f);
|
||||
if (_rotateAccelerationWithAvatar) {
|
||||
return scale * (DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * _accelerationAxis);
|
||||
}
|
||||
return scale * _accelerationAxis;
|
||||
}
|
97
interface/src/raypick/ParabolaPick.h
Normal file
97
interface/src/raypick/ParabolaPick.h
Normal file
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_ParabolaPick_h
|
||||
#define hifi_ParabolaPick_h
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <Pick.h>
|
||||
|
||||
class EntityItemID;
|
||||
class OverlayID;
|
||||
|
||||
class ParabolaPickResult : public PickResult {
|
||||
public:
|
||||
ParabolaPickResult() {}
|
||||
ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola,
|
||||
const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
|
||||
PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) {
|
||||
}
|
||||
|
||||
ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) {
|
||||
type = parabolaPickResult.type;
|
||||
intersects = parabolaPickResult.intersects;
|
||||
objectID = parabolaPickResult.objectID;
|
||||
distance = parabolaPickResult.distance;
|
||||
parabolicDistance = parabolaPickResult.parabolicDistance;
|
||||
intersection = parabolaPickResult.intersection;
|
||||
surfaceNormal = parabolaPickResult.surfaceNormal;
|
||||
extraInfo = parabolaPickResult.extraInfo;
|
||||
}
|
||||
|
||||
QVariantMap extraInfo;
|
||||
QUuid objectID;
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
IntersectionType type { NONE };
|
||||
float distance { FLT_MAX };
|
||||
float parabolicDistance { FLT_MAX };
|
||||
bool intersects { false };
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
toReturn["type"] = type;
|
||||
toReturn["intersects"] = intersects;
|
||||
toReturn["objectID"] = objectID;
|
||||
toReturn["distance"] = distance;
|
||||
toReturn["parabolicDistance"] = parabolicDistance;
|
||||
toReturn["intersection"] = vec3toVariant(intersection);
|
||||
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
|
||||
toReturn["parabola"] = PickResult::toVariantMap();
|
||||
toReturn["extraInfo"] = extraInfo;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
bool doesIntersect() const override { return intersects; }
|
||||
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; }
|
||||
|
||||
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
|
||||
auto newParabolaRes = std::static_pointer_cast<ParabolaPickResult>(newRes);
|
||||
if (newParabolaRes->parabolicDistance < parabolicDistance) {
|
||||
return std::make_shared<ParabolaPickResult>(*newParabolaRes);
|
||||
} else {
|
||||
return std::make_shared<ParabolaPickResult>(*this);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class ParabolaPick : public Pick<PickParabola> {
|
||||
|
||||
public:
|
||||
ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar),
|
||||
_scaleWithAvatar(scaleWithAvatar) {}
|
||||
|
||||
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared<ParabolaPickResult>(pickVariant); }
|
||||
PickResultPointer getEntityIntersection(const PickParabola& pick) override;
|
||||
PickResultPointer getOverlayIntersection(const PickParabola& pick) override;
|
||||
PickResultPointer getAvatarIntersection(const PickParabola& pick) override;
|
||||
PickResultPointer getHUDIntersection(const PickParabola& pick) override;
|
||||
|
||||
protected:
|
||||
float _speed;
|
||||
glm::vec3 _accelerationAxis;
|
||||
bool _rotateAccelerationWithAvatar;
|
||||
bool _scaleWithAvatar;
|
||||
|
||||
float getSpeed() const;
|
||||
glm::vec3 getAcceleration() const;
|
||||
};
|
||||
|
||||
#endif // hifi_ParabolaPick_h
|
406
interface/src/raypick/ParabolaPointer.cpp
Normal file
406
interface/src/raypick/ParabolaPointer.cpp
Normal file
|
@ -0,0 +1,406 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/17/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "ParabolaPointer.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include <StencilMaskPass.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "ParabolaPick.h"
|
||||
|
||||
#include "render-utils/parabola_vert.h"
|
||||
#include "render-utils/parabola_frag.h"
|
||||
|
||||
const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f };
|
||||
const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f };
|
||||
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
|
||||
|
||||
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr };
|
||||
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr };
|
||||
|
||||
ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
|
||||
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd,
|
||||
bool scaleWithAvatar, bool enabled) :
|
||||
PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength,
|
||||
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
|
||||
{
|
||||
}
|
||||
|
||||
void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
|
||||
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
|
||||
if (renderState) {
|
||||
QVariantMap pathMap = pathProps.toMap();
|
||||
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
|
||||
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
|
||||
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
|
||||
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
|
||||
bool enabled = false;
|
||||
if (!pathMap.isEmpty()) {
|
||||
enabled = true;
|
||||
if (pathMap["color"].isValid()) {
|
||||
bool valid;
|
||||
color = toGlm(xColorFromVariant(pathMap["color"], valid));
|
||||
}
|
||||
if (pathMap["alpha"].isValid()) {
|
||||
alpha = pathMap["alpha"].toFloat();
|
||||
}
|
||||
if (pathMap["width"].isValid()) {
|
||||
width = pathMap["width"].toFloat();
|
||||
renderState->setPathWidth(width);
|
||||
}
|
||||
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
|
||||
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
|
||||
}
|
||||
}
|
||||
renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
if (distance > 0.0f) {
|
||||
PickParabola pick = PickParabola(parabolaPickResult->pickVariant);
|
||||
return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance;
|
||||
} else {
|
||||
return parabolaPickResult->intersection;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f));
|
||||
}
|
||||
|
||||
IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE);
|
||||
}
|
||||
|
||||
QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
return (parabolaPickResult ? parabolaPickResult->objectID : QUuid());
|
||||
}
|
||||
|
||||
void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
|
||||
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
if (parabolaPickResult) {
|
||||
parabolaPickResult->type = type;
|
||||
parabolaPickResult->objectID = id;
|
||||
parabolaPickResult->intersection = intersection;
|
||||
parabolaPickResult->distance = distance;
|
||||
parabolaPickResult->surfaceNormal = surfaceNormal;
|
||||
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
|
||||
parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin -
|
||||
0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance);
|
||||
}
|
||||
}
|
||||
|
||||
ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
|
||||
bool isVisibleInSecondaryCamera, bool pathEnabled) :
|
||||
StartEndRenderState(startID, endID)
|
||||
{
|
||||
render::Transaction transaction;
|
||||
auto scene = qApp->getMain3DScene();
|
||||
_pathID = scene->allocateID();
|
||||
_pathWidth = pathWidth;
|
||||
if (render::Item::isValidID(_pathID)) {
|
||||
auto renderItem = std::make_shared<ParabolaRenderItem>(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled);
|
||||
// TODO: update bounds properly
|
||||
renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f);
|
||||
transaction.resetItem(_pathID, std::make_shared<ParabolaRenderItem::Payload>(renderItem));
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::cleanup() {
|
||||
StartEndRenderState::cleanup();
|
||||
if (render::Item::isValidID(_pathID)) {
|
||||
render::Transaction transaction;
|
||||
auto scene = qApp->getMain3DScene();
|
||||
transaction.removeItem(_pathID);
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::disable() {
|
||||
StartEndRenderState::disable();
|
||||
if (render::Item::isValidID(_pathID)) {
|
||||
render::Transaction transaction;
|
||||
auto scene = qApp->getMain3DScene();
|
||||
transaction.updateItem<ParabolaRenderItem>(_pathID, [](ParabolaRenderItem& item) {
|
||||
item.setVisible(false);
|
||||
});
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) {
|
||||
if (render::Item::isValidID(_pathID)) {
|
||||
render::Transaction transaction;
|
||||
auto scene = qApp->getMain3DScene();
|
||||
transaction.updateItem<ParabolaRenderItem>(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) {
|
||||
item.setColor(color);
|
||||
item.setAlpha(alpha);
|
||||
item.setWidth(width);
|
||||
item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera);
|
||||
item.setEnabled(enabled);
|
||||
item.updateKey();
|
||||
});
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
|
||||
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
if (parabolaPickResult && render::Item::isValidID(_pathID)) {
|
||||
render::Transaction transaction;
|
||||
auto scene = qApp->getMain3DScene();
|
||||
|
||||
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
|
||||
glm::vec3 velocity = parabola.velocity;
|
||||
glm::vec3 acceleration = parabola.acceleration;
|
||||
float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance;
|
||||
float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : getPathWidth();
|
||||
transaction.updateItem<ParabolaRenderItem>(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) {
|
||||
item.setVisible(true);
|
||||
item.setOrigin(origin);
|
||||
item.setVelocity(velocity);
|
||||
item.setAcceleration(acceleration);
|
||||
item.setParabolicDistance(parabolicDistance);
|
||||
item.setWidth(width);
|
||||
});
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<StartEndRenderState> ParabolaPointer::buildRenderState(const QVariantMap& propMap) {
|
||||
QUuid startID;
|
||||
if (propMap["start"].isValid()) {
|
||||
QVariantMap startMap = propMap["start"].toMap();
|
||||
if (startMap["type"].isValid()) {
|
||||
startMap.remove("visible");
|
||||
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
|
||||
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
|
||||
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
|
||||
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
|
||||
bool enabled = false;
|
||||
if (propMap["path"].isValid()) {
|
||||
enabled = true;
|
||||
QVariantMap pathMap = propMap["path"].toMap();
|
||||
if (pathMap["color"].isValid()) {
|
||||
bool valid;
|
||||
color = toGlm(xColorFromVariant(pathMap["color"], valid));
|
||||
}
|
||||
|
||||
if (pathMap["alpha"].isValid()) {
|
||||
alpha = pathMap["alpha"].toFloat();
|
||||
}
|
||||
|
||||
if (pathMap["width"].isValid()) {
|
||||
width = pathMap["width"].toFloat();
|
||||
}
|
||||
|
||||
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
|
||||
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
|
||||
}
|
||||
}
|
||||
|
||||
QUuid endID;
|
||||
if (propMap["end"].isValid()) {
|
||||
QVariantMap endMap = propMap["end"].toMap();
|
||||
if (endMap["type"].isValid()) {
|
||||
endMap.remove("visible");
|
||||
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<RenderState>(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled);
|
||||
}
|
||||
|
||||
PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
|
||||
QUuid pickedID;
|
||||
glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration;
|
||||
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
|
||||
if (parabolaPickResult) {
|
||||
intersection = parabolaPickResult->intersection;
|
||||
surfaceNormal = parabolaPickResult->surfaceNormal;
|
||||
const QVariantMap& parabola = parabolaPickResult->pickVariant;
|
||||
origin = vec3FromVariant(parabola["origin"]);
|
||||
velocity = vec3FromVariant(parabola["velocity"]);
|
||||
acceleration = vec3FromVariant(parabola["acceleration"]);
|
||||
pickedID = parabolaPickResult->objectID;
|
||||
}
|
||||
|
||||
if (pickedID != target.objectID) {
|
||||
intersection = findIntersection(target, origin, velocity, acceleration);
|
||||
}
|
||||
glm::vec2 pos2D = findPos2D(target, intersection);
|
||||
|
||||
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
|
||||
TriggerState& state = hover ? _latestState : _states[button];
|
||||
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
|
||||
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
|
||||
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
|
||||
pos2D = state.triggerPos2D;
|
||||
intersection = state.intersection;
|
||||
surfaceNormal = state.surfaceNormal;
|
||||
}
|
||||
if (!withinDeadspot) {
|
||||
state.deadspotExpired = true;
|
||||
}
|
||||
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, velocity);
|
||||
}
|
||||
|
||||
glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) {
|
||||
// TODO: implement
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
//return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration);
|
||||
case OVERLAY:
|
||||
//return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration);
|
||||
default:
|
||||
return glm::vec3(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
|
||||
bool isVisibleInSecondaryCamera, bool enabled) :
|
||||
_isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled)
|
||||
{
|
||||
_uniformBuffer->resize(sizeof(ParabolaData));
|
||||
setColor(color);
|
||||
setAlpha(alpha);
|
||||
setWidth(width);
|
||||
updateKey();
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) {
|
||||
if (visible && _enabled) {
|
||||
_key = render::ItemKey::Builder(_key).withVisible();
|
||||
} else {
|
||||
_key = render::ItemKey::Builder(_key).withInvisible();
|
||||
}
|
||||
_visible = visible;
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() {
|
||||
// FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain,
|
||||
// it seems to block all shadows. I think this is a bug with shadows.
|
||||
//auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape();
|
||||
auto builder = render::ItemKey::Builder::transparentShape();
|
||||
|
||||
if (_enabled && _visible) {
|
||||
builder.withVisible();
|
||||
} else {
|
||||
builder.withInvisible();
|
||||
}
|
||||
|
||||
if (_isVisibleInSecondaryCamera) {
|
||||
builder.withTagBits(render::hifi::TAG_ALL_VIEWS);
|
||||
} else {
|
||||
builder.withTagBits(render::hifi::TAG_MAIN_VIEW);
|
||||
}
|
||||
|
||||
_key = builder.build();
|
||||
}
|
||||
|
||||
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
|
||||
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
|
||||
auto vs = parabola_vert::getShader();
|
||||
auto ps = parabola_frag::getShader();
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0));
|
||||
gpu::Shader::makeProgram(*program, slotBindings);
|
||||
|
||||
{
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
PrepareStencil::testMaskDrawShape(*state);
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
_parabolaPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
||||
{
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
PrepareStencil::testMask(*state);
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
_transparentParabolaPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
}
|
||||
return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline);
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) {
|
||||
if (!_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *(args->_batch);
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_origin);
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
batch.setPipeline(getParabolaPipeline());
|
||||
|
||||
const int MAX_SECTIONS = 100;
|
||||
if (glm::length2(_parabolaData.acceleration) < EPSILON) {
|
||||
_parabolaData.numSections = 1;
|
||||
} else {
|
||||
_parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS);
|
||||
}
|
||||
updateUniformBuffer();
|
||||
batch.setUniformBuffer(0, _uniformBuffer);
|
||||
|
||||
// We draw 2 * n + 2 vertices for a triangle strip
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0);
|
||||
}
|
||||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
|
||||
return payload->getKey();
|
||||
}
|
||||
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
|
||||
if (payload) {
|
||||
return payload->getBound();
|
||||
}
|
||||
return Item::Bound();
|
||||
}
|
||||
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) {
|
||||
if (payload) {
|
||||
payload->render(args);
|
||||
}
|
||||
}
|
||||
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
|
||||
return ShapeKey::Builder::ownPipeline();
|
||||
}
|
||||
}
|
125
interface/src/raypick/ParabolaPointer.h
Normal file
125
interface/src/raypick/ParabolaPointer.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/17/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_ParabolaPointer_h
|
||||
#define hifi_ParabolaPointer_h
|
||||
|
||||
#include "PathPointer.h"
|
||||
|
||||
class ParabolaPointer : public PathPointer {
|
||||
using Parent = PathPointer;
|
||||
public:
|
||||
class RenderState : public StartEndRenderState {
|
||||
public:
|
||||
class ParabolaRenderItem {
|
||||
public:
|
||||
using Payload = render::Payload<ParabolaRenderItem>;
|
||||
using Pointer = Payload::DataPointer;
|
||||
|
||||
ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
|
||||
bool isVisibleInSecondaryCamera, bool enabled);
|
||||
~ParabolaRenderItem() {}
|
||||
|
||||
static gpu::PipelinePointer _parabolaPipeline;
|
||||
static gpu::PipelinePointer _transparentParabolaPipeline;
|
||||
const gpu::PipelinePointer getParabolaPipeline();
|
||||
|
||||
void render(RenderArgs* args);
|
||||
render::Item::Bound& editBound() { return _bound; }
|
||||
const render::Item::Bound& getBound() { return _bound; }
|
||||
render::ItemKey getKey() const { return _key; }
|
||||
|
||||
void setVisible(bool visible);
|
||||
void updateKey();
|
||||
void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); }
|
||||
|
||||
void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); }
|
||||
void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; }
|
||||
void setWidth(const float& width) { _parabolaData.width = width; }
|
||||
void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; }
|
||||
void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; }
|
||||
void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; }
|
||||
void setOrigin(const glm::vec3& origin) { _origin = origin; }
|
||||
void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; }
|
||||
void setEnabled(const bool& enabled) { _enabled = enabled; }
|
||||
|
||||
static const glm::vec4 DEFAULT_PARABOLA_COLOR;
|
||||
static const float DEFAULT_PARABOLA_WIDTH;
|
||||
static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
|
||||
|
||||
private:
|
||||
render::Item::Bound _bound;
|
||||
render::ItemKey _key;
|
||||
|
||||
glm::vec3 _origin { 0.0f };
|
||||
bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA };
|
||||
bool _visible { false };
|
||||
bool _enabled { false };
|
||||
|
||||
struct ParabolaData {
|
||||
glm::vec3 velocity { 0.0f };
|
||||
float parabolicDistance { 0.0f };
|
||||
vec3 acceleration { 0.0f };
|
||||
float width { DEFAULT_PARABOLA_WIDTH };
|
||||
vec4 color { vec4(DEFAULT_PARABOLA_COLOR)};
|
||||
int numSections { 0 };
|
||||
ivec3 spare;
|
||||
};
|
||||
|
||||
ParabolaData _parabolaData;
|
||||
gpu::BufferPointer _uniformBuffer { std::make_shared<gpu::Buffer>() };
|
||||
};
|
||||
|
||||
RenderState() {}
|
||||
RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
|
||||
bool isVisibleInSecondaryCamera, bool pathEnabled);
|
||||
|
||||
void setPathWidth(float width) { _pathWidth = width; }
|
||||
float getPathWidth() const { return _pathWidth; }
|
||||
|
||||
void cleanup() override;
|
||||
void disable() override;
|
||||
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
|
||||
|
||||
void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled);
|
||||
|
||||
private:
|
||||
int _pathID;
|
||||
float _pathWidth;
|
||||
};
|
||||
|
||||
ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
|
||||
|
||||
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
|
||||
|
||||
protected:
|
||||
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
|
||||
|
||||
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
|
||||
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
|
||||
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
|
||||
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
|
||||
QUuid getPickedObjectID(const PickResultPointer& pickResult) const override;
|
||||
void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
|
||||
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override;
|
||||
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
|
||||
|
||||
private:
|
||||
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration);
|
||||
};
|
||||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
|
||||
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
|
||||
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args);
|
||||
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
|
||||
}
|
||||
|
||||
#endif // hifi_ParabolaPointer_h
|
353
interface/src/raypick/PathPointer.cpp
Normal file
353
interface/src/raypick/PathPointer.cpp
Normal file
|
@ -0,0 +1,353 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/17/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "PathPointer.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <PickManager.h>
|
||||
#include "PickScriptingInterface.h"
|
||||
#include "RayPick.h"
|
||||
|
||||
PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
|
||||
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd,
|
||||
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
|
||||
Pointer(DependencyManager::get<PickScriptingInterface>()->createPick(type, rayProps), enabled, hover),
|
||||
_renderStates(renderStates),
|
||||
_defaultRenderStates(defaultRenderStates),
|
||||
_triggers(triggers),
|
||||
_faceAvatar(faceAvatar),
|
||||
_followNormal(followNormal),
|
||||
_followNormalStrength(followNormalStrength),
|
||||
_centerEndY(centerEndY),
|
||||
_lockEnd(lockEnd),
|
||||
_distanceScaleEnd(distanceScaleEnd),
|
||||
_scaleWithAvatar(scaleWithAvatar)
|
||||
{
|
||||
for (auto& state : _renderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
state.second->disable();
|
||||
}
|
||||
}
|
||||
for (auto& state : _defaultRenderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
state.second.second->disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathPointer::~PathPointer() {
|
||||
for (auto& renderState : _renderStates) {
|
||||
renderState.second->cleanup();
|
||||
}
|
||||
for (auto& renderState : _defaultRenderStates) {
|
||||
renderState.second.second->cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
void PathPointer::setRenderState(const std::string& state) {
|
||||
withWriteLock([&] {
|
||||
if (!_currentRenderState.empty() && state != _currentRenderState) {
|
||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
||||
_renderStates[_currentRenderState]->disable();
|
||||
}
|
||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
_defaultRenderStates[_currentRenderState].second->disable();
|
||||
}
|
||||
}
|
||||
_currentRenderState = state;
|
||||
});
|
||||
}
|
||||
|
||||
void PathPointer::setLength(float length) {
|
||||
withWriteLock([&] {
|
||||
_pathLength = length;
|
||||
});
|
||||
}
|
||||
|
||||
void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
|
||||
withWriteLock([&] {
|
||||
_lockEndObject.id = objectID;
|
||||
_lockEndObject.isOverlay = isOverlay;
|
||||
_lockEndObject.offsetMat = offsetMat;
|
||||
});
|
||||
}
|
||||
|
||||
PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) {
|
||||
PickResultPointer visualPickResult = pickResult;
|
||||
glm::vec3 origin = getPickOrigin(pickResult);
|
||||
IntersectionType type = getPickedObjectType(pickResult);
|
||||
QUuid id;
|
||||
glm::vec3 intersection;
|
||||
float distance;
|
||||
glm::vec3 surfaceNormal;
|
||||
|
||||
if (type != IntersectionType::HUD) {
|
||||
glm::vec3 endVec;
|
||||
if (!_lockEndObject.id.isNull()) {
|
||||
glm::vec3 pos;
|
||||
glm::quat rot;
|
||||
glm::vec3 dim;
|
||||
glm::vec3 registrationPoint;
|
||||
if (_lockEndObject.isOverlay) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
|
||||
registrationPoint = glm::vec3(0.5f);
|
||||
} else {
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
|
||||
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
||||
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
||||
pos = extractTranslation(finalPosAndRotMat);
|
||||
rot = glmExtractRotation(finalPosAndRotMat);
|
||||
dim = props.getDimensions();
|
||||
registrationPoint = props.getRegistrationPoint();
|
||||
}
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
|
||||
glm::vec3 direction = endVec - origin;
|
||||
distance = glm::distance(origin, endVec);
|
||||
glm::vec3 normalizedDirection = glm::normalize(direction);
|
||||
|
||||
type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
|
||||
id = _lockEndObject.id;
|
||||
intersection = endVec;
|
||||
surfaceNormal = -normalizedDirection;
|
||||
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
|
||||
} else if (type != IntersectionType::NONE && _lockEnd) {
|
||||
id = getPickedObjectID(pickResult);
|
||||
if (type == IntersectionType::ENTITY) {
|
||||
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(id)[3];
|
||||
} else if (type == IntersectionType::OVERLAY) {
|
||||
endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value);
|
||||
} else if (type == IntersectionType::AVATAR) {
|
||||
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(id)->getPosition();
|
||||
}
|
||||
glm::vec3 direction = endVec - origin;
|
||||
distance = glm::distance(origin, endVec);
|
||||
glm::vec3 normalizedDirection = glm::normalize(direction);
|
||||
intersection = endVec;
|
||||
surfaceNormal = -normalizedDirection;
|
||||
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
|
||||
}
|
||||
}
|
||||
return visualPickResult;
|
||||
}
|
||||
|
||||
void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
|
||||
IntersectionType type = getPickedObjectType(pickResult);
|
||||
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
||||
(type != IntersectionType::NONE || _pathLength > 0.0f)) {
|
||||
glm::vec3 origin = getPickOrigin(pickResult);
|
||||
glm::vec3 end = getPickEnd(pickResult, _pathLength);
|
||||
glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);
|
||||
_renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar,
|
||||
_followNormal, _followNormalStrength, _pathLength, pickResult);
|
||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
_defaultRenderStates[_currentRenderState].second->disable();
|
||||
}
|
||||
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
||||
_renderStates[_currentRenderState]->disable();
|
||||
}
|
||||
glm::vec3 origin = getPickOrigin(pickResult);
|
||||
glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first);
|
||||
_defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY,
|
||||
_faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult);
|
||||
} else if (!_currentRenderState.empty()) {
|
||||
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
|
||||
_renderStates[_currentRenderState]->disable();
|
||||
}
|
||||
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
|
||||
_defaultRenderStates[_currentRenderState].second->disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
|
||||
withWriteLock([&] {
|
||||
updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps);
|
||||
updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps);
|
||||
QVariant startDim = startProps.toMap()["dimensions"];
|
||||
if (startDim.isValid()) {
|
||||
_renderStates[state]->setStartDim(vec3FromVariant(startDim));
|
||||
}
|
||||
QVariant endDim = endProps.toMap()["dimensions"];
|
||||
if (endDim.isValid()) {
|
||||
_renderStates[state]->setEndDim(vec3FromVariant(endDim));
|
||||
}
|
||||
QVariant rotation = endProps.toMap()["rotation"];
|
||||
if (rotation.isValid()) {
|
||||
_renderStates[state]->setEndRot(quatFromVariant(rotation));
|
||||
}
|
||||
|
||||
editRenderStatePath(state, pathProps);
|
||||
});
|
||||
}
|
||||
|
||||
void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
|
||||
if (!id.isNull() && props.isValid()) {
|
||||
QVariantMap propMap = props.toMap();
|
||||
propMap.remove("visible");
|
||||
qApp->getOverlays().editOverlay(id, propMap);
|
||||
}
|
||||
}
|
||||
|
||||
Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) {
|
||||
return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
|
||||
}
|
||||
|
||||
Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) {
|
||||
std::unordered_set<std::string> toReturn;
|
||||
|
||||
for (const PointerTrigger& trigger : _triggers) {
|
||||
std::string button = trigger.getButton();
|
||||
TriggerState& state = _states[button];
|
||||
// TODO: right now, LaserPointers don't support axes, only on/off buttons
|
||||
if (trigger.getEndpoint()->peek() >= 1.0f) {
|
||||
toReturn.insert(button);
|
||||
|
||||
if (_previousButtons.find(button) == _previousButtons.end()) {
|
||||
// start triggering for buttons that were just pressed
|
||||
state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
|
||||
state.intersection = getPickEnd(pickResult);
|
||||
state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection);
|
||||
state.triggerStartTime = usecTimestampNow();
|
||||
state.surfaceNormal = getPickedObjectNormal(pickResult);
|
||||
state.deadspotExpired = false;
|
||||
state.wasTriggering = true;
|
||||
state.triggering = true;
|
||||
_latestState = state;
|
||||
}
|
||||
} else {
|
||||
// stop triggering for buttons that aren't pressed
|
||||
state.wasTriggering = state.triggering;
|
||||
state.triggering = false;
|
||||
_latestState = state;
|
||||
}
|
||||
}
|
||||
_previousButtons = toReturn;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) :
|
||||
_startID(startID), _endID(endID) {
|
||||
if (!_startID.isNull()) {
|
||||
_startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value);
|
||||
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
|
||||
_endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value);
|
||||
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
|
||||
}
|
||||
}
|
||||
|
||||
void StartEndRenderState::cleanup() {
|
||||
if (!_startID.isNull()) {
|
||||
qApp->getOverlays().deleteOverlay(_startID);
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
qApp->getOverlays().deleteOverlay(_endID);
|
||||
}
|
||||
}
|
||||
|
||||
void StartEndRenderState::disable() {
|
||||
if (!getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("visible", false);
|
||||
startProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getStartID(), startProps);
|
||||
}
|
||||
if (!getEndID().isNull()) {
|
||||
QVariantMap endProps;
|
||||
endProps.insert("visible", false);
|
||||
endProps.insert("ignoreRayIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||
}
|
||||
}
|
||||
|
||||
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
|
||||
if (!getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("position", vec3toVariant(origin));
|
||||
startProps.insert("visible", true);
|
||||
if (scaleWithAvatar) {
|
||||
startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale()));
|
||||
}
|
||||
startProps.insert("ignoreRayIntersection", doesStartIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(getStartID(), startProps);
|
||||
}
|
||||
|
||||
if (!getEndID().isNull()) {
|
||||
QVariantMap endProps;
|
||||
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value);
|
||||
if (distanceScaleEnd) {
|
||||
dim = getEndDim() * glm::distance(origin, end);
|
||||
endProps.insert("dimensions", vec3toVariant(dim));
|
||||
} else if (scaleWithAvatar) {
|
||||
dim = getEndDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
endProps.insert("dimensions", vec3toVariant(dim));
|
||||
}
|
||||
|
||||
glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal);
|
||||
normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0));
|
||||
glm::vec3 avatarUp = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
|
||||
glm::quat rotation = glm::rotation(Vectors::UP, avatarUp);
|
||||
glm::vec3 position = end;
|
||||
if (!centerEndY) {
|
||||
if (followNormal) {
|
||||
position = end + 0.5f * dim.y * surfaceNormal;
|
||||
} else {
|
||||
position = end + 0.5f * dim.y * avatarUp;
|
||||
}
|
||||
}
|
||||
if (faceAvatar) {
|
||||
glm::quat orientation = followNormal ? normalQuat : DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation();
|
||||
glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition(), surfaceNormal);
|
||||
glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld;
|
||||
glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel);
|
||||
glm::quat lookAtFlatWorld = orientation * lookAtFlatModel;
|
||||
rotation = lookAtFlatWorld;
|
||||
} else if (followNormal) {
|
||||
rotation = normalQuat;
|
||||
}
|
||||
if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) {
|
||||
if (!_avgEndRotInitialized) {
|
||||
_avgEndRot = rotation;
|
||||
_avgEndRotInitialized = true;
|
||||
} else {
|
||||
rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength);
|
||||
if (!centerEndY) {
|
||||
position = end + 0.5f * dim.y * (rotation * Vectors::UP);
|
||||
}
|
||||
_avgEndRot = rotation;
|
||||
}
|
||||
}
|
||||
endProps.insert("position", vec3toVariant(position));
|
||||
endProps.insert("rotation", quatToVariant(rotation));
|
||||
endProps.insert("visible", true);
|
||||
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
|
||||
switch (pickedObject.type) {
|
||||
case ENTITY:
|
||||
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
|
||||
case OVERLAY:
|
||||
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
|
||||
case HUD:
|
||||
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
|
||||
default:
|
||||
return glm::vec2(NAN);
|
||||
}
|
||||
}
|
135
interface/src/raypick/PathPointer.h
Normal file
135
interface/src/raypick/PathPointer.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/17/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_PathPointer_h
|
||||
#define hifi_PathPointer_h
|
||||
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ui/overlays/Overlay.h"
|
||||
|
||||
#include <Pointer.h>
|
||||
#include <Pick.h>
|
||||
|
||||
struct LockEndObject {
|
||||
QUuid id { QUuid() };
|
||||
bool isOverlay { false };
|
||||
glm::mat4 offsetMat { glm::mat4() };
|
||||
};
|
||||
|
||||
class StartEndRenderState {
|
||||
public:
|
||||
StartEndRenderState() {}
|
||||
StartEndRenderState(const OverlayID& startID, const OverlayID& endID);
|
||||
|
||||
const OverlayID& getStartID() const { return _startID; }
|
||||
const OverlayID& getEndID() const { return _endID; }
|
||||
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
|
||||
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
|
||||
|
||||
void setStartDim(const glm::vec3& startDim) { _startDim = startDim; }
|
||||
const glm::vec3& getStartDim() const { return _startDim; }
|
||||
|
||||
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
|
||||
const glm::vec3& getEndDim() const { return _endDim; }
|
||||
|
||||
void setEndRot(const glm::quat& endRot) { _endRot = endRot; }
|
||||
const glm::quat& getEndRot() const { return _endRot; }
|
||||
|
||||
virtual void cleanup();
|
||||
virtual void disable();
|
||||
virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
|
||||
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
|
||||
|
||||
protected:
|
||||
OverlayID _startID;
|
||||
OverlayID _endID;
|
||||
bool _startIgnoreRays;
|
||||
bool _endIgnoreRays;
|
||||
|
||||
glm::vec3 _startDim;
|
||||
glm::vec3 _endDim;
|
||||
glm::quat _endRot;
|
||||
|
||||
glm::quat _avgEndRot;
|
||||
bool _avgEndRotInitialized { false };
|
||||
};
|
||||
|
||||
typedef std::unordered_map<std::string, std::shared_ptr<StartEndRenderState>> RenderStateMap;
|
||||
typedef std::unordered_map<std::string, std::pair<float, std::shared_ptr<StartEndRenderState>>> DefaultRenderStateMap;
|
||||
|
||||
class PathPointer : public Pointer {
|
||||
using Parent = Pointer;
|
||||
public:
|
||||
PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
|
||||
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd,
|
||||
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
|
||||
virtual ~PathPointer();
|
||||
|
||||
void setRenderState(const std::string& state) override;
|
||||
// You cannot use editRenderState to change the type of any part of the pointer. You can only edit the properties of the existing overlays.
|
||||
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
|
||||
|
||||
void setLength(float length) override;
|
||||
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
|
||||
|
||||
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
|
||||
|
||||
protected:
|
||||
RenderStateMap _renderStates;
|
||||
DefaultRenderStateMap _defaultRenderStates;
|
||||
std::string _currentRenderState { "" };
|
||||
PointerTriggers _triggers;
|
||||
float _pathLength { 0.0f };
|
||||
bool _faceAvatar;
|
||||
bool _followNormal;
|
||||
float _followNormalStrength;
|
||||
bool _centerEndY;
|
||||
bool _lockEnd;
|
||||
bool _distanceScaleEnd;
|
||||
bool _scaleWithAvatar;
|
||||
LockEndObject _lockEndObject;
|
||||
|
||||
struct TriggerState {
|
||||
PickedObject triggeredObject;
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
glm::vec2 triggerPos2D { NAN };
|
||||
quint64 triggerStartTime { 0 };
|
||||
bool deadspotExpired { true };
|
||||
bool triggering { false };
|
||||
bool wasTriggering { false };
|
||||
};
|
||||
|
||||
Pointer::Buttons _previousButtons;
|
||||
std::unordered_map<std::string, TriggerState> _states;
|
||||
TriggerState _latestState;
|
||||
|
||||
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
|
||||
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
|
||||
virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0;
|
||||
|
||||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
|
||||
|
||||
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
|
||||
virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const = 0;
|
||||
virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0;
|
||||
virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0;
|
||||
virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0;
|
||||
virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 0;
|
||||
virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
|
||||
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0;
|
||||
|
||||
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
|
||||
};
|
||||
|
||||
#endif // hifi_PathPointer_h
|
|
@ -17,6 +17,9 @@
|
|||
#include "JointRayPick.h"
|
||||
#include "MouseRayPick.h"
|
||||
#include "StylusPick.h"
|
||||
#include "StaticParabolaPick.h"
|
||||
#include "JointParabolaPick.h"
|
||||
#include "MouseParabolaPick.h"
|
||||
|
||||
#include <ScriptEngine.h>
|
||||
|
||||
|
@ -26,6 +29,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
|
|||
return createRayPick(properties);
|
||||
case PickQuery::PickType::Stylus:
|
||||
return createStylusPick(properties);
|
||||
case PickQuery::PickType::Parabola:
|
||||
return createParabolaPick(properties);
|
||||
default:
|
||||
return PickManager::INVALID_PICK_ID;
|
||||
}
|
||||
|
@ -134,6 +139,101 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
|
|||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled));
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick.
|
||||
* @typedef {object} Picks.ParabolaPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD.
|
||||
* If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it
|
||||
* exists on your current avatar.
|
||||
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment.
|
||||
* @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment.
|
||||
* @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola.
|
||||
* @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction.
|
||||
* @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis.
|
||||
* @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
||||
bool enabled = false;
|
||||
if (propMap["enabled"].isValid()) {
|
||||
enabled = propMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
float maxDistance = 0.0f;
|
||||
if (propMap["maxDistance"].isValid()) {
|
||||
maxDistance = propMap["maxDistance"].toFloat();
|
||||
}
|
||||
|
||||
float speed = 1.0f;
|
||||
if (propMap["speed"].isValid()) {
|
||||
speed = propMap["speed"].toFloat();
|
||||
}
|
||||
|
||||
glm::vec3 accelerationAxis = -Vectors::UP;
|
||||
if (propMap["accelerationAxis"].isValid()) {
|
||||
accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]);
|
||||
}
|
||||
|
||||
bool rotateAccelerationWithAvatar = true;
|
||||
if (propMap["rotateAccelerationWithAvatar"].isValid()) {
|
||||
rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool();
|
||||
}
|
||||
|
||||
bool scaleWithAvatar = false;
|
||||
if (propMap["scaleWithAvatar"].isValid()) {
|
||||
scaleWithAvatar = propMap["scaleWithAvatar"].toBool();
|
||||
}
|
||||
|
||||
if (propMap["joint"].isValid()) {
|
||||
std::string jointName = propMap["joint"].toString().toStdString();
|
||||
|
||||
if (jointName != "Mouse") {
|
||||
// x = upward, y = forward, z = lateral
|
||||
glm::vec3 posOffset = Vectors::ZERO;
|
||||
if (propMap["posOffset"].isValid()) {
|
||||
posOffset = vec3FromVariant(propMap["posOffset"]);
|
||||
}
|
||||
|
||||
glm::vec3 dirOffset = Vectors::UP;
|
||||
if (propMap["dirOffset"].isValid()) {
|
||||
dirOffset = vec3FromVariant(propMap["dirOffset"]);
|
||||
}
|
||||
|
||||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<JointParabolaPick>(jointName, posOffset, dirOffset,
|
||||
speed, accelerationAxis, rotateAccelerationWithAvatar,
|
||||
scaleWithAvatar, filter, maxDistance, enabled));
|
||||
|
||||
} else {
|
||||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<MouseParabolaPick>(speed, accelerationAxis, rotateAccelerationWithAvatar,
|
||||
scaleWithAvatar, filter, maxDistance, enabled));
|
||||
}
|
||||
} else if (propMap["position"].isValid()) {
|
||||
glm::vec3 position = vec3FromVariant(propMap["position"]);
|
||||
|
||||
glm::vec3 direction = -Vectors::FRONT;
|
||||
if (propMap["direction"].isValid()) {
|
||||
direction = vec3FromVariant(propMap["direction"]);
|
||||
}
|
||||
|
||||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<StaticParabolaPick>(position, direction, speed, accelerationAxis,
|
||||
rotateAccelerationWithAvatar, scaleWithAvatar,
|
||||
filter, maxDistance, enabled));
|
||||
}
|
||||
|
||||
return PickManager::INVALID_PICK_ID;
|
||||
}
|
||||
|
||||
void PickScriptingInterface::enablePick(unsigned int uid) {
|
||||
DependencyManager::get<PickManager>()->enablePick(uid);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ class PickScriptingInterface : public QObject, public Dependency {
|
|||
public:
|
||||
unsigned int createRayPick(const QVariant& properties);
|
||||
unsigned int createStylusPick(const QVariant& properties);
|
||||
unsigned int createParabolaPick(const QVariant& properties);
|
||||
|
||||
void registerMetaTypes(QScriptEngine* engine);
|
||||
|
||||
|
@ -71,7 +72,7 @@ public:
|
|||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
|
||||
* @function Picks.createPick
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
||||
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
||||
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
|
||||
*/
|
||||
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
|
||||
|
@ -125,6 +126,21 @@ public:
|
|||
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a Parabola Pick.
|
||||
*
|
||||
* @typedef {object} ParabolaPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola.
|
||||
* @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
|
||||
* @function Picks.getPrevPickResult
|
||||
|
@ -162,7 +178,7 @@ public:
|
|||
* Check if a Pick is associated with the left hand.
|
||||
* @function Picks.isLeftHand
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
|
||||
*/
|
||||
Q_INVOKABLE bool isLeftHand(unsigned int uid);
|
||||
|
||||
|
@ -170,7 +186,7 @@ public:
|
|||
* Check if a Pick is associated with the right hand.
|
||||
* @function Picks.isRightHand
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
|
||||
*/
|
||||
Q_INVOKABLE bool isRightHand(unsigned int uid);
|
||||
|
||||
|
@ -178,7 +194,7 @@ public:
|
|||
* Check if a Pick is associated with the system mouse.
|
||||
* @function Picks.isMouse
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise.
|
||||
* @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise.
|
||||
*/
|
||||
Q_INVOKABLE bool isMouse(unsigned int uid);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "Application.h"
|
||||
#include "LaserPointer.h"
|
||||
#include "StylusPointer.h"
|
||||
#include "ParabolaPointer.h"
|
||||
|
||||
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
|
||||
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
|
||||
|
@ -37,6 +38,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
|
|||
return createLaserPointer(properties);
|
||||
case PickQuery::PickType::Stylus:
|
||||
return createStylus(properties);
|
||||
case PickQuery::PickType::Parabola:
|
||||
return createParabolaPointer(properties);
|
||||
default:
|
||||
return PointerEvent::INVALID_POINTER_ID;
|
||||
}
|
||||
|
@ -84,27 +87,22 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
|
|||
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
|
||||
* An overlay to represent the end of the Ray Pointer, if desired.
|
||||
*/
|
||||
/**jsdoc
|
||||
* A trigger mechanism for Ray Pointers.
|
||||
*
|
||||
* @typedef {object} Pointers.Trigger
|
||||
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
|
||||
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
|
||||
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
|
||||
*/
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
|
||||
* @typedef {object} Pointers.LaserPointerProperties
|
||||
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
|
||||
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
|
||||
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
|
||||
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
|
||||
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
|
||||
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
|
||||
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
|
||||
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
|
||||
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
|
||||
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
|
||||
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
|
||||
* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
|
||||
* the normal will follow exactly.
|
||||
* @property {boolean} [enabled=false]
|
||||
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
|
||||
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
|
||||
* @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between.
|
||||
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
|
||||
* @property {boolean} [hover=false] If this Pointer should generate hover events.
|
||||
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
|
||||
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
|
||||
*/
|
||||
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
|
||||
QVariantMap propertyMap = properties.toMap();
|
||||
|
@ -134,12 +132,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
|
|||
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
|
||||
}
|
||||
|
||||
bool followNormal = false;
|
||||
if (propertyMap["followNormal"].isValid()) {
|
||||
followNormal = propertyMap["followNormal"].toBool();
|
||||
}
|
||||
float followNormalStrength = 0.0f;
|
||||
if (propertyMap["followNormalStrength"].isValid()) {
|
||||
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
if (propertyMap["enabled"].isValid()) {
|
||||
enabled = propertyMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
LaserPointer::RenderStateMap renderStates;
|
||||
RenderStateMap renderStates;
|
||||
if (propertyMap["renderStates"].isValid()) {
|
||||
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
|
||||
for (const QVariant& renderStateVariant : renderStateVariants) {
|
||||
|
@ -153,7 +160,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
|
|||
}
|
||||
}
|
||||
|
||||
LaserPointer::DefaultRenderStateMap defaultRenderStates;
|
||||
DefaultRenderStateMap defaultRenderStates;
|
||||
if (propertyMap["defaultRenderStates"].isValid()) {
|
||||
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
|
||||
for (const QVariant& renderStateVariant : renderStateVariants) {
|
||||
|
@ -162,7 +169,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
|
|||
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
|
||||
std::string name = renderStateMap["name"].toString().toStdString();
|
||||
float distance = renderStateMap["distance"].toFloat();
|
||||
defaultRenderStates[name] = std::pair<float, RenderState>(distance, LaserPointer::buildRenderState(renderStateMap));
|
||||
defaultRenderStates[name] = std::pair<float, std::shared_ptr<StartEndRenderState>>(distance, LaserPointer::buildRenderState(renderStateMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +199,151 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
|
|||
}
|
||||
|
||||
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<LaserPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
|
||||
faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled));
|
||||
faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd,
|
||||
distanceScaleEnd, scaleWithAvatar, enabled));
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* The rendering properties of the parabolic path
|
||||
*
|
||||
* @typedef {object} Pointers.ParabolaProperties
|
||||
* @property {Color} color The color of the parabola.
|
||||
* @property {number} alpha The alpha of the parabola.
|
||||
* @property {number} width The width of the parabola, in meters.
|
||||
*/
|
||||
/**jsdoc
|
||||
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState},
|
||||
* but with an additional distance field.
|
||||
*
|
||||
* @typedef {object} Pointers.DefaultParabolaPointerRenderState
|
||||
* @augments Pointers.ParabolaPointerRenderState
|
||||
* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined.
|
||||
*/
|
||||
/**jsdoc
|
||||
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something.
|
||||
*
|
||||
* @typedef {object} Pointers.ParabolaPointerRenderState
|
||||
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
|
||||
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
|
||||
* An overlay to represent the beginning of the Parabola Pointer, if desired.
|
||||
* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer.
|
||||
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
|
||||
* An overlay to represent the end of the Parabola Pointer, if desired.
|
||||
*/
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
|
||||
* @typedef {object} Pointers.LaserPointerProperties
|
||||
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
|
||||
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
|
||||
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
|
||||
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
|
||||
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
|
||||
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
|
||||
* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
|
||||
* the normal will follow exactly.
|
||||
* @property {boolean} [enabled=false]
|
||||
* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between.
|
||||
* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
|
||||
* @property {boolean} [hover=false] If this Pointer should generate hover events.
|
||||
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
|
||||
*/
|
||||
unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const {
|
||||
QVariantMap propertyMap = properties.toMap();
|
||||
|
||||
bool faceAvatar = false;
|
||||
if (propertyMap["faceAvatar"].isValid()) {
|
||||
faceAvatar = propertyMap["faceAvatar"].toBool();
|
||||
}
|
||||
|
||||
bool centerEndY = true;
|
||||
if (propertyMap["centerEndY"].isValid()) {
|
||||
centerEndY = propertyMap["centerEndY"].toBool();
|
||||
}
|
||||
|
||||
bool lockEnd = false;
|
||||
if (propertyMap["lockEnd"].isValid()) {
|
||||
lockEnd = propertyMap["lockEnd"].toBool();
|
||||
}
|
||||
|
||||
bool distanceScaleEnd = false;
|
||||
if (propertyMap["distanceScaleEnd"].isValid()) {
|
||||
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
|
||||
}
|
||||
|
||||
bool scaleWithAvatar = false;
|
||||
if (propertyMap["scaleWithAvatar"].isValid()) {
|
||||
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
|
||||
}
|
||||
|
||||
bool followNormal = false;
|
||||
if (propertyMap["followNormal"].isValid()) {
|
||||
followNormal = propertyMap["followNormal"].toBool();
|
||||
}
|
||||
float followNormalStrength = 0.0f;
|
||||
if (propertyMap["followNormalStrength"].isValid()) {
|
||||
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
if (propertyMap["enabled"].isValid()) {
|
||||
enabled = propertyMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
RenderStateMap renderStates;
|
||||
if (propertyMap["renderStates"].isValid()) {
|
||||
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
|
||||
for (const QVariant& renderStateVariant : renderStateVariants) {
|
||||
if (renderStateVariant.isValid()) {
|
||||
QVariantMap renderStateMap = renderStateVariant.toMap();
|
||||
if (renderStateMap["name"].isValid()) {
|
||||
std::string name = renderStateMap["name"].toString().toStdString();
|
||||
renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DefaultRenderStateMap defaultRenderStates;
|
||||
if (propertyMap["defaultRenderStates"].isValid()) {
|
||||
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
|
||||
for (const QVariant& renderStateVariant : renderStateVariants) {
|
||||
if (renderStateVariant.isValid()) {
|
||||
QVariantMap renderStateMap = renderStateVariant.toMap();
|
||||
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
|
||||
std::string name = renderStateMap["name"].toString().toStdString();
|
||||
float distance = renderStateMap["distance"].toFloat();
|
||||
defaultRenderStates[name] = std::pair<float, std::shared_ptr<StartEndRenderState>>(distance, ParabolaPointer::buildRenderState(renderStateMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hover = false;
|
||||
if (propertyMap["hover"].isValid()) {
|
||||
hover = propertyMap["hover"].toBool();
|
||||
}
|
||||
|
||||
PointerTriggers triggers;
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
if (propertyMap["triggers"].isValid()) {
|
||||
QList<QVariant> triggerVariants = propertyMap["triggers"].toList();
|
||||
for (const QVariant& triggerVariant : triggerVariants) {
|
||||
if (triggerVariant.isValid()) {
|
||||
QVariantMap triggerMap = triggerVariant.toMap();
|
||||
if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) {
|
||||
controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt()));
|
||||
if (endpoint) {
|
||||
std::string button = triggerMap["button"].toString().toStdString();
|
||||
triggers.emplace_back(endpoint, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<ParabolaPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
|
||||
faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd,
|
||||
scaleWithAvatar, enabled));
|
||||
}
|
||||
|
||||
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {
|
||||
|
|
|
@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency {
|
|||
public:
|
||||
unsigned int createLaserPointer(const QVariant& properties) const;
|
||||
unsigned int createStylus(const QVariant& properties) const;
|
||||
unsigned int createParabolaPointer(const QVariant& properties) const;
|
||||
|
||||
/**jsdoc
|
||||
* A trigger mechanism for Ray and Parabola Pointers.
|
||||
*
|
||||
* @typedef {object} Pointers.Trigger
|
||||
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
|
||||
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
|
||||
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
|
||||
*/
|
||||
/**jsdoc
|
||||
* Adds a new Pointer
|
||||
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
|
||||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
|
||||
* @function Pointers.createPointer
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
|
||||
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
|
||||
* this Pointer will use to do its picking.
|
||||
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
|
||||
*
|
||||
|
|
|
@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
|
||||
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (avatarRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public:
|
|||
RayPickResult() {}
|
||||
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
|
||||
PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) {
|
||||
}
|
||||
|
||||
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
|
||||
|
@ -32,13 +32,13 @@ public:
|
|||
extraInfo = rayPickResult.extraInfo;
|
||||
}
|
||||
|
||||
IntersectionType type { NONE };
|
||||
bool intersects { false };
|
||||
QVariantMap extraInfo;
|
||||
QUuid objectID;
|
||||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
QVariantMap extraInfo;
|
||||
IntersectionType type { NONE };
|
||||
float distance { FLT_MAX };
|
||||
bool intersects { false };
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
|
|
19
interface/src/raypick/StaticParabolaPick.cpp
Normal file
19
interface/src/raypick/StaticParabolaPick.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "StaticParabolaPick.h"
|
||||
|
||||
StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis,
|
||||
bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
|
||||
_position(position), _velocity(direction)
|
||||
{
|
||||
}
|
||||
|
||||
PickParabola StaticParabolaPick::getMathematicalPick() const {
|
||||
return PickParabola(_position, getSpeed() * _velocity, getAcceleration());
|
||||
}
|
26
interface/src/raypick/StaticParabolaPick.h
Normal file
26
interface/src/raypick/StaticParabolaPick.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Created by Sam Gondelman 7/2/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_StaticParabolaPick_h
|
||||
#define hifi_StaticParabolaPick_h
|
||||
|
||||
#include "ParabolaPick.h"
|
||||
|
||||
class StaticParabolaPick : public ParabolaPick {
|
||||
|
||||
public:
|
||||
StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
|
||||
bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickParabola getMathematicalPick() const override;
|
||||
|
||||
private:
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _velocity;
|
||||
};
|
||||
|
||||
#endif // hifi_StaticParabolaPick_h
|
|
@ -56,6 +56,7 @@ Audio::Audio() : _devices(_contextIsHMD) {
|
|||
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
|
||||
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
|
||||
enableNoiseReduction(enableNoiseReductionSetting.get());
|
||||
onContextChanged();
|
||||
}
|
||||
|
||||
bool Audio::startRecording(const QString& filepath) {
|
||||
|
|
|
@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p
|
|||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const {
|
||||
glm::vec3 result;
|
||||
qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance);
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const {
|
||||
return qApp->getApplicationCompositor().overlayFromSphereSurface(position);
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
|
||||
|
||||
glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const;
|
||||
|
||||
/**jsdoc
|
||||
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
|
||||
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.
|
||||
|
|
|
@ -313,8 +313,8 @@ void setupPreferences() {
|
|||
getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
|
||||
auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
|
||||
auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); };
|
||||
auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); };
|
||||
preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter));
|
||||
}
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ Base3DOverlay::Base3DOverlay() :
|
|||
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
|
||||
_isSolid(DEFAULT_IS_SOLID),
|
||||
_isDashedLine(DEFAULT_IS_DASHED_LINE),
|
||||
_ignoreRayIntersection(false),
|
||||
_ignorePickIntersection(false),
|
||||
_drawInFront(false),
|
||||
_drawHUDLayer(false)
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
|
|||
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
|
||||
_isSolid(base3DOverlay->_isSolid),
|
||||
_isDashedLine(base3DOverlay->_isDashedLine),
|
||||
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
|
||||
_ignorePickIntersection(base3DOverlay->_ignorePickIntersection),
|
||||
_drawInFront(base3DOverlay->_drawInFront),
|
||||
_drawHUDLayer(base3DOverlay->_drawHUDLayer),
|
||||
_isGrabbable(base3DOverlay->_isGrabbable),
|
||||
|
@ -183,8 +183,10 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
if (properties["dashed"].isValid()) {
|
||||
setIsDashedLine(properties["dashed"].toBool());
|
||||
}
|
||||
if (properties["ignoreRayIntersection"].isValid()) {
|
||||
setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool());
|
||||
if (properties["ignorePickIntersection"].isValid()) {
|
||||
setIgnorePickIntersection(properties["ignorePickIntersection"].toBool());
|
||||
} else if (properties["ignoreRayIntersection"].isValid()) {
|
||||
setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool());
|
||||
}
|
||||
|
||||
if (properties["parentID"].isValid()) {
|
||||
|
@ -224,8 +226,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
@ -260,8 +261,8 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "isDashedLine" || property == "dashed") {
|
||||
return _isDashedLine;
|
||||
}
|
||||
if (property == "ignoreRayIntersection") {
|
||||
return _ignoreRayIntersection;
|
||||
if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") {
|
||||
return _ignorePickIntersection;
|
||||
}
|
||||
if (property == "drawInFront") {
|
||||
return _drawInFront;
|
||||
|
@ -282,11 +283,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
return Overlay::getProperty(property);
|
||||
}
|
||||
|
||||
bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Base3DOverlay::locationChanged(bool tellPhysics) {
|
||||
SpatiallyNestable::locationChanged(tellPhysics);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
bool getIsSolid() const { return _isSolid; }
|
||||
bool getIsDashedLine() const { return _isDashedLine; }
|
||||
bool getIsSolidLine() const { return !_isDashedLine; }
|
||||
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
|
||||
bool getIgnorePickIntersection() const { return _ignorePickIntersection; }
|
||||
bool getDrawInFront() const { return _drawInFront; }
|
||||
bool getDrawHUDLayer() const { return _drawHUDLayer; }
|
||||
bool getIsGrabbable() const { return _isGrabbable; }
|
||||
|
@ -53,7 +53,7 @@ public:
|
|||
|
||||
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
|
||||
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
|
||||
void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; }
|
||||
virtual void setDrawInFront(bool value) { _drawInFront = value; }
|
||||
virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; }
|
||||
void setIsGrabbable(bool value) { _isGrabbable = value; }
|
||||
|
@ -69,13 +69,21 @@ public:
|
|||
virtual QVariant getProperty(const QString& property) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false);
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; }
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking);
|
||||
}
|
||||
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; }
|
||||
|
||||
virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) {
|
||||
return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking);
|
||||
}
|
||||
|
||||
virtual SpatialParentTree* getParentTree() const override;
|
||||
|
||||
protected:
|
||||
|
@ -91,7 +99,7 @@ protected:
|
|||
|
||||
bool _isSolid;
|
||||
bool _isDashedLine;
|
||||
bool _ignoreRayIntersection;
|
||||
bool _ignorePickIntersection;
|
||||
bool _drawInFront;
|
||||
bool _drawHUDLayer;
|
||||
bool _isGrabbable { false };
|
||||
|
|
|
@ -397,8 +397,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
@ -520,22 +519,66 @@ QVariant Circle3DOverlay::getProperty(const QString& property) {
|
|||
|
||||
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
|
||||
// Scale the dimensions by the diameter
|
||||
glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions();
|
||||
bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
|
||||
if (intersects) {
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) {
|
||||
glm::vec3 hitPosition = origin + (distance * direction);
|
||||
glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition());
|
||||
localHitPosition.x /= getDimensions().x;
|
||||
localHitPosition.y /= getDimensions().y;
|
||||
float distanceToHit = glm::length(localHitPosition);
|
||||
|
||||
intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius();
|
||||
if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) {
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (glm::dot(forward, direction) > 0.0f) {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
} else {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return intersects;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
// Scale the dimensions by the diameter
|
||||
glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions();
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
glm::quat inverseRot = glm::inverse(rotation);
|
||||
glm::vec3 localOrigin = inverseRot * (origin - position);
|
||||
glm::vec3 localVelocity = inverseRot * velocity;
|
||||
glm::vec3 localAcceleration = inverseRot * acceleration;
|
||||
|
||||
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
|
||||
glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance;
|
||||
localHitPosition.x /= getDimensions().x;
|
||||
localHitPosition.y /= getDimensions().y;
|
||||
float distanceToHit = glm::length(localHitPosition);
|
||||
|
||||
if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) {
|
||||
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (localIntersectionVelocityZ > 0.0f) {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
} else {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Circle3DOverlay* Circle3DOverlay::createClone() const {
|
||||
|
|
|
@ -54,8 +54,10 @@ public:
|
|||
void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; }
|
||||
void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
virtual Circle3DOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
|
|||
_contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN);
|
||||
_contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX);
|
||||
_contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE);
|
||||
_contextOverlay->setIgnoreRayIntersection(false);
|
||||
_contextOverlay->setIgnorePickIntersection(false);
|
||||
_contextOverlay->setDrawInFront(true);
|
||||
_contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png");
|
||||
_contextOverlay->setIsFacingAvatar(true);
|
||||
|
|
|
@ -160,8 +160,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -145,8 +145,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -35,7 +35,10 @@ public:
|
|||
virtual Grid3DOverlay* createClone() const override;
|
||||
|
||||
// Grids are UI tools, and may not be intersected (pickable)
|
||||
virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; }
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face,
|
||||
glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; }
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; }
|
||||
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
|
|
|
@ -216,8 +216,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
@ -260,10 +259,7 @@ void Image3DOverlay::setURL(const QString& url) {
|
|||
bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
if (_texture && _texture->isLoaded()) {
|
||||
// Make sure position and rotation is updated.
|
||||
Transform transform = getTransform();
|
||||
|
||||
// Don't call applyTransformTo() or setTransform() here because this code runs too frequently.
|
||||
|
||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||
bool isNull = _fromImage.isNull();
|
||||
|
@ -271,12 +267,55 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec
|
|||
float height = isNull ? _texture->getHeight() : _fromImage.height();
|
||||
float maxSize = glm::max(width, height);
|
||||
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
|
||||
glm::quat rotation = transform.getRotation();
|
||||
|
||||
// FIXME - face and surfaceNormal not being set
|
||||
return findRayRectangleIntersection(origin, direction,
|
||||
transform.getRotation(),
|
||||
transform.getTranslation(),
|
||||
dimensions, distance);
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) {
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (glm::dot(forward, direction) > 0.0f) {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
} else {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
if (_texture && _texture->isLoaded()) {
|
||||
Transform transform = getTransform();
|
||||
|
||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||
bool isNull = _fromImage.isNull();
|
||||
float width = isNull ? _texture->getWidth() : _fromImage.width();
|
||||
float height = isNull ? _texture->getHeight() : _fromImage.height();
|
||||
float maxSize = glm::max(width, height);
|
||||
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
|
||||
glm::quat rotation = transform.getRotation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
glm::quat inverseRot = glm::inverse(rotation);
|
||||
glm::vec3 localOrigin = inverseRot * (origin - position);
|
||||
glm::vec3 localVelocity = inverseRot * velocity;
|
||||
glm::vec3 localAcceleration = inverseRot * acceleration;
|
||||
|
||||
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) {
|
||||
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (localIntersectionVelocityZ > 0.0f) {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
} else {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -42,8 +42,10 @@ public:
|
|||
QVariant getProperty(const QString& property) override;
|
||||
bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; }
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
virtual Image3DOverlay* createClone() const override;
|
||||
|
||||
|
|
|
@ -288,8 +288,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -27,10 +27,6 @@ ModelOverlay::ModelOverlay()
|
|||
{
|
||||
_model->setLoadingPriority(_loadPriority);
|
||||
_isLoaded = false;
|
||||
|
||||
// Don't show overlay until textures have loaded
|
||||
_visible = false;
|
||||
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
_model->setVisibleInScene(false, scene);
|
||||
}
|
||||
|
@ -136,11 +132,13 @@ void ModelOverlay::update(float deltatime) {
|
|||
}
|
||||
scene->enqueueTransaction(transaction);
|
||||
|
||||
if (_texturesDirty && !_modelTextures.isEmpty()) {
|
||||
_texturesDirty = false;
|
||||
_model->setTextures(_modelTextures);
|
||||
}
|
||||
|
||||
if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) {
|
||||
_texturesLoaded = true;
|
||||
if (!_modelTextures.isEmpty()) {
|
||||
_model->setTextures(_modelTextures);
|
||||
}
|
||||
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
_model->updateRenderItems();
|
||||
|
@ -242,6 +240,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
|
|||
_texturesLoaded = false;
|
||||
QVariantMap textureMap = texturesValue.toMap();
|
||||
_modelTextures = textureMap;
|
||||
_texturesDirty = true;
|
||||
}
|
||||
|
||||
auto groupCulledValue = properties["isGroupCulled"];
|
||||
|
@ -382,8 +381,7 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} isGroupCulled=false - If <code>true</code>, the mesh parts of the model are LOD culled as a group.
|
||||
|
@ -519,17 +517,26 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
|
||||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
|
||||
QVariantMap extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
QVariantMap extraInfo;
|
||||
return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) {
|
||||
return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
ModelOverlay* ModelOverlay::createClone() const {
|
||||
return new ModelOverlay(this);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,11 @@ public:
|
|||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
|
||||
|
||||
virtual ModelOverlay* createClone() const override;
|
||||
|
||||
|
@ -94,6 +98,7 @@ private:
|
|||
ModelPointer _model;
|
||||
QVariantMap _modelTextures;
|
||||
bool _texturesLoaded { false };
|
||||
bool _texturesDirty { false };
|
||||
|
||||
render::ItemIDs _subRenderItemIDs;
|
||||
|
||||
|
|
|
@ -546,7 +546,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
continue;
|
||||
}
|
||||
|
||||
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) {
|
||||
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) {
|
||||
float thisDistance;
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
|
@ -573,76 +573,86 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
return result;
|
||||
}
|
||||
|
||||
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
|
||||
auto obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
obj.setProperty("overlayID", OverlayIDtoScriptValue(engine, value.overlayID));
|
||||
obj.setProperty("distance", value.distance);
|
||||
ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
|
||||
const QVector<OverlayID>& overlaysToInclude,
|
||||
const QVector<OverlayID>& overlaysToDiscard,
|
||||
bool visibleOnly, bool collidableOnly) {
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
bool bestIsFront = false;
|
||||
|
||||
QString faceName = "";
|
||||
// handle BoxFace
|
||||
switch (value.face) {
|
||||
case MIN_X_FACE:
|
||||
faceName = "MIN_X_FACE";
|
||||
break;
|
||||
case MAX_X_FACE:
|
||||
faceName = "MAX_X_FACE";
|
||||
break;
|
||||
case MIN_Y_FACE:
|
||||
faceName = "MIN_Y_FACE";
|
||||
break;
|
||||
case MAX_Y_FACE:
|
||||
faceName = "MAX_Y_FACE";
|
||||
break;
|
||||
case MIN_Z_FACE:
|
||||
faceName = "MIN_Z_FACE";
|
||||
break;
|
||||
case MAX_Z_FACE:
|
||||
faceName = "MAX_Z_FACE";
|
||||
break;
|
||||
default:
|
||||
case UNKNOWN_FACE:
|
||||
faceName = "UNKNOWN_FACE";
|
||||
break;
|
||||
QMutexLocker locker(&_mutex);
|
||||
ParabolaToOverlayIntersectionResult result;
|
||||
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
OverlayID thisID = i.key();
|
||||
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
|
||||
|
||||
if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) ||
|
||||
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) {
|
||||
float thisDistance;
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
QVariantMap thisExtraInfo;
|
||||
if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance,
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) {
|
||||
bool isDrawInFront = thisOverlay->getDrawInFront();
|
||||
if ((bestIsFront && isDrawInFront && thisDistance < bestDistance)
|
||||
|| (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) {
|
||||
|
||||
bestIsFront = isDrawInFront;
|
||||
bestDistance = thisDistance;
|
||||
result.intersects = true;
|
||||
result.parabolicDistance = thisDistance;
|
||||
result.face = thisFace;
|
||||
result.surfaceNormal = thisSurfaceNormal;
|
||||
result.overlayID = thisID;
|
||||
result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance;
|
||||
result.distance = glm::distance(result.intersection, parabola.origin);
|
||||
result.extraInfo = thisExtraInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
obj.setProperty("face", faceName);
|
||||
auto intersection = vec3toScriptValue(engine, value.intersection);
|
||||
return result;
|
||||
}
|
||||
|
||||
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID);
|
||||
obj.setProperty("overlayID", overlayIDValue);
|
||||
obj.setProperty("distance", value.distance);
|
||||
obj.setProperty("face", boxFaceToString(value.face));
|
||||
|
||||
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) {
|
||||
QVariantMap object = objectVar.toVariant().toMap();
|
||||
value.intersects = object["intersects"].toBool();
|
||||
value.overlayID = OverlayID(QUuid(object["overlayID"].toString()));
|
||||
value.distance = object["distance"].toFloat();
|
||||
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) {
|
||||
value.intersects = object.property("intersects").toVariant().toBool();
|
||||
QScriptValue overlayIDValue = object.property("overlayID");
|
||||
quuidFromScriptValue(overlayIDValue, value.overlayID);
|
||||
value.distance = object.property("distance").toVariant().toFloat();
|
||||
value.face = boxFaceFromString(object.property("face").toVariant().toString());
|
||||
|
||||
QString faceName = object["face"].toString();
|
||||
if (faceName == "MIN_X_FACE") {
|
||||
value.face = MIN_X_FACE;
|
||||
} else if (faceName == "MAX_X_FACE") {
|
||||
value.face = MAX_X_FACE;
|
||||
} else if (faceName == "MIN_Y_FACE") {
|
||||
value.face = MIN_Y_FACE;
|
||||
} else if (faceName == "MAX_Y_FACE") {
|
||||
value.face = MAX_Y_FACE;
|
||||
} else if (faceName == "MIN_Z_FACE") {
|
||||
value.face = MIN_Z_FACE;
|
||||
} else if (faceName == "MAX_Z_FACE") {
|
||||
value.face = MAX_Z_FACE;
|
||||
} else {
|
||||
value.face = UNKNOWN_FACE;
|
||||
};
|
||||
auto intersection = object["intersection"];
|
||||
QScriptValue intersection = object.property("intersection");
|
||||
if (intersection.isValid()) {
|
||||
bool valid;
|
||||
auto newIntersection = vec3FromVariant(intersection, valid);
|
||||
if (valid) {
|
||||
value.intersection = newIntersection;
|
||||
}
|
||||
vec3FromScriptValue(intersection, value.intersection);
|
||||
}
|
||||
value.extraInfo = object["extraInfo"].toMap();
|
||||
QScriptValue surfaceNormal = object.property("surfaceNormal");
|
||||
if (surfaceNormal.isValid()) {
|
||||
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
bool Overlays::isLoaded(OverlayID id) {
|
||||
|
@ -1046,7 +1056,8 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
|
|||
i.next();
|
||||
OverlayID thisID = i.key();
|
||||
auto overlay = std::dynamic_pointer_cast<Volume3DOverlay>(i.value());
|
||||
if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) {
|
||||
// FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong
|
||||
if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) {
|
||||
// get AABox in frame of overlay
|
||||
glm::vec3 dimensions = overlay->getDimensions();
|
||||
glm::vec3 low = dimensions * -0.5f;
|
||||
|
|
|
@ -59,19 +59,28 @@ class RayToOverlayIntersectionResult {
|
|||
public:
|
||||
bool intersects { false };
|
||||
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
|
||||
float distance { 0 };
|
||||
float distance { 0.0f };
|
||||
BoxFace face { UNKNOWN_FACE };
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE(RayToOverlayIntersectionResult);
|
||||
|
||||
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value);
|
||||
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value);
|
||||
|
||||
class ParabolaToOverlayIntersectionResult {
|
||||
public:
|
||||
bool intersects { false };
|
||||
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
|
||||
float distance { 0.0f };
|
||||
float parabolicDistance { 0.0f };
|
||||
BoxFace face { UNKNOWN_FACE };
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
* The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to
|
||||
* yourself and that aren't persisted to the domain. They are used for UI.
|
||||
|
@ -110,6 +119,11 @@ public:
|
|||
const QVector<OverlayID>& overlaysToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
|
||||
const QVector<OverlayID>& overlaysToInclude,
|
||||
const QVector<OverlayID>& overlaysToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
bool mousePressEvent(QMouseEvent* event);
|
||||
bool mouseDoublePressEvent(QMouseEvent* event);
|
||||
bool mouseReleaseEvent(QMouseEvent* event);
|
||||
|
|
|
@ -72,8 +72,48 @@ QVariant Planar3DOverlay::getProperty(const QString& property) {
|
|||
|
||||
bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
// FIXME - face and surfaceNormal not being returned
|
||||
return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance);
|
||||
glm::vec2 xyDimensions = getDimensions();
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (glm::dot(forward, direction) > 0.0f) {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
} else {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
glm::vec2 xyDimensions = getDimensions();
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
glm::quat inverseRot = glm::inverse(rotation);
|
||||
glm::vec3 localOrigin = inverseRot * (origin - position);
|
||||
glm::vec3 localVelocity = inverseRot * velocity;
|
||||
glm::vec3 localAcceleration = inverseRot * acceleration;
|
||||
|
||||
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
|
||||
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (localIntersectionVelocityZ > 0.0f) {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
} else {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform Planar3DOverlay::evalRenderTransform() {
|
||||
|
|
|
@ -32,6 +32,8 @@ public:
|
|||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
protected:
|
||||
glm::vec2 _dimensions;
|
||||
|
|
|
@ -140,8 +140,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -160,8 +160,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -60,8 +60,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -229,8 +229,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
|
|
@ -88,7 +88,34 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
|
|||
|
||||
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
|
||||
// and testing intersection there.
|
||||
return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
|
||||
bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
|
||||
|
||||
if (hit) {
|
||||
surfaceNormal = transform.getRotation() * surfaceNormal;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
// extents is the entity relative, scaled, centered extents of the entity
|
||||
glm::mat4 worldToEntityMatrix;
|
||||
Transform transform = getTransform();
|
||||
transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable
|
||||
transform.getInverseMatrix(worldToEntityMatrix);
|
||||
|
||||
glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
|
||||
glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
|
||||
|
||||
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
|
||||
// and testing intersection there.
|
||||
bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal);
|
||||
|
||||
if (hit) {
|
||||
surfaceNormal = transform.getRotation() * surfaceNormal;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
Transform Volume3DOverlay::evalRenderTransform() {
|
||||
|
|
|
@ -32,6 +32,8 @@ public:
|
|||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
protected:
|
||||
// Centered local bounding box
|
||||
|
|
|
@ -543,8 +543,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
|
||||
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
|
@ -627,20 +626,6 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
|
||||
glm::vec2 dimensions = getDimensions();
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) {
|
||||
surfaceNormal = rotation * Vectors::UNIT_Z;
|
||||
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Web3DOverlay* Web3DOverlay::createClone() const {
|
||||
return new Web3DOverlay(this);
|
||||
}
|
||||
|
|
|
@ -52,9 +52,6 @@ public:
|
|||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
|
||||
|
||||
virtual Web3DOverlay* createClone() const override;
|
||||
|
||||
enum InputMode {
|
||||
|
|
|
@ -1247,11 +1247,9 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
|
|||
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
|
||||
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
|
||||
|
||||
const bool ENABLE_POLE_VECTORS = false;
|
||||
const bool ENABLE_POLE_VECTORS = true;
|
||||
const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f;
|
||||
|
||||
int hipsIndex = indexOfJoint("Hips");
|
||||
|
||||
if (leftHandEnabled) {
|
||||
|
||||
glm::vec3 handPosition = leftHandPose.trans();
|
||||
|
@ -1270,22 +1268,33 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
|
|||
int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand");
|
||||
int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm");
|
||||
int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm");
|
||||
if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
|
||||
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true);
|
||||
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
|
||||
int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm");
|
||||
if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) {
|
||||
glm::vec3 poleVector;
|
||||
bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector);
|
||||
if (usePoleVector) {
|
||||
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevLeftHandPoleVectorValid) {
|
||||
_prevLeftHandPoleVectorValid = true;
|
||||
_prevLeftHandPoleVector = sensorPoleVector;
|
||||
if (_smoothPoleVectors) {
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevLeftHandPoleVectorValid) {
|
||||
_prevLeftHandPoleVectorValid = true;
|
||||
_prevLeftHandPoleVector = sensorPoleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector;
|
||||
} else {
|
||||
_prevLeftHandPoleVector = sensorPoleVector;
|
||||
}
|
||||
_animVars.set("leftHandPoleVectorEnabled", true);
|
||||
_animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X);
|
||||
_animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector));
|
||||
} else {
|
||||
_prevLeftHandPoleVectorValid = false;
|
||||
_animVars.set("leftHandPoleVectorEnabled", false);
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector;
|
||||
|
||||
_animVars.set("leftHandPoleVectorEnabled", true);
|
||||
_animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X);
|
||||
_animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector));
|
||||
} else {
|
||||
_prevLeftHandPoleVectorValid = false;
|
||||
_animVars.set("leftHandPoleVectorEnabled", false);
|
||||
|
@ -1297,7 +1306,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
|
|||
_animVars.unset("leftHandPosition");
|
||||
_animVars.unset("leftHandRotation");
|
||||
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
|
||||
|
||||
}
|
||||
|
||||
if (rightHandEnabled) {
|
||||
|
@ -1318,22 +1326,34 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
|
|||
int handJointIndex = _animSkeleton->nameToJointIndex("RightHand");
|
||||
int armJointIndex = _animSkeleton->nameToJointIndex("RightArm");
|
||||
int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm");
|
||||
if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
|
||||
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false);
|
||||
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
|
||||
int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm");
|
||||
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevRightHandPoleVectorValid) {
|
||||
_prevRightHandPoleVectorValid = true;
|
||||
_prevRightHandPoleVector = sensorPoleVector;
|
||||
if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) {
|
||||
glm::vec3 poleVector;
|
||||
bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector);
|
||||
if (usePoleVector) {
|
||||
glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
|
||||
|
||||
if (_smoothPoleVectors) {
|
||||
// smooth toward desired pole vector from previous pole vector... to reduce jitter
|
||||
if (!_prevRightHandPoleVectorValid) {
|
||||
_prevRightHandPoleVectorValid = true;
|
||||
_prevRightHandPoleVector = sensorPoleVector;
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector;
|
||||
} else {
|
||||
_prevRightHandPoleVector = sensorPoleVector;
|
||||
}
|
||||
|
||||
_animVars.set("rightHandPoleVectorEnabled", true);
|
||||
_animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X);
|
||||
_animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector));
|
||||
} else {
|
||||
_prevRightHandPoleVectorValid = false;
|
||||
_animVars.set("rightHandPoleVectorEnabled", false);
|
||||
}
|
||||
glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector);
|
||||
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
|
||||
_prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector;
|
||||
|
||||
_animVars.set("rightHandPoleVectorEnabled", true);
|
||||
_animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X);
|
||||
_animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector));
|
||||
} else {
|
||||
_prevRightHandPoleVectorValid = false;
|
||||
_animVars.set("rightHandPoleVectorEnabled", false);
|
||||
|
@ -1472,39 +1492,73 @@ static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha)
|
|||
return glm::normalize(glm::lerp(q1, temp, alpha));
|
||||
}
|
||||
|
||||
glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const {
|
||||
AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex];
|
||||
bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const {
|
||||
// The resulting Pole Vector is calculated as the sum of a three vectors.
|
||||
// The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector.
|
||||
// The second vector is always perpendicular to previous vector and is part of the plane that contains a point located on the horizontal line,
|
||||
// pointing forward and with height aprox to the avatar head. The position of the horizontal point will be determined by the hands Y component.
|
||||
// The third vector apply a weighted correction to the resulting pole vector to avoid interpenetration and force a more natural pose.
|
||||
|
||||
AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex];
|
||||
AnimPose handPose = _externalPoseSet._absolutePoses[handIndex];
|
||||
AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex];
|
||||
AnimPose armPose = _externalPoseSet._absolutePoses[armIndex];
|
||||
AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex];
|
||||
|
||||
// ray from hand to arm.
|
||||
glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans());
|
||||
glm::vec3 armToHand = handPose.trans() - armPose.trans();
|
||||
glm::vec3 armToElbow = elbowPose.trans() - armPose.trans();
|
||||
glm::vec3 elbowToHand = handPose.trans() - elbowPose.trans();
|
||||
|
||||
float sign = isLeft ? 1.0f : -1.0f;
|
||||
glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans();
|
||||
glm::vec3 backCenter = armPose.trans() + 0.5f * backVector;
|
||||
|
||||
const float OVER_BACK_HEAD_PERCENTAGE = 0.2f;
|
||||
|
||||
// form a plane normal to the hips x-axis.
|
||||
glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X;
|
||||
glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y;
|
||||
glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0);
|
||||
glm::vec3 frontVector = glm::normalize(glm::cross(backVector, glm::vec3(0, 1, 0)));
|
||||
// Make sure is pointing forward
|
||||
frontVector = frontVector.z < 0 ? -frontVector : frontVector;
|
||||
|
||||
// project d onto this plane
|
||||
glm::vec3 dProj = d - glm::dot(d, n) * n;
|
||||
float horizontalModule = glm::dot(armToHand, glm::vec3(0, -1, 0));
|
||||
glm::vec3 headForward = headCenter + horizontalModule * frontVector;
|
||||
|
||||
// give dProj a bit of offset away from the body.
|
||||
float avatarScale = extractUniformScale(_modelOffset);
|
||||
const float LATERAL_OFFSET = 1.0f * avatarScale;
|
||||
const float VERTICAL_OFFSET = -0.333f * avatarScale;
|
||||
glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET;
|
||||
glm::vec3 armToHead = headForward - armPose.trans();
|
||||
|
||||
float armToHandDistance = glm::length(armToHand);
|
||||
float armToElbowDistance = glm::length(armToElbow);
|
||||
float elbowToHandDistance = glm::length(elbowToHand);
|
||||
float armTotalDistance = armToElbowDistance + elbowToHandDistance;
|
||||
|
||||
// rotate dProj by 90 degrees to get the poleVector.
|
||||
glm::vec3 poleVector = glm::angleAxis(PI / 2.0f, n) * dProjWithOffset;
|
||||
glm::vec3 armToHandDir = armToHand / armToHandDistance;
|
||||
glm::vec3 armToHeadPlaneNormal = glm::cross(armToHead, armToHandDir);
|
||||
|
||||
// blend the wrist oreintation into the pole vector to reduce the painfully bent wrist problem.
|
||||
glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot());
|
||||
const float WRIST_POLE_ADJUST_FACTOR = 0.5f;
|
||||
glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR);
|
||||
// How much the hand is reaching for the opposite side
|
||||
float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector));
|
||||
|
||||
// Don't use pole vector when the hands are behind
|
||||
if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return glm::normalize(poleAdjust * poleVector);
|
||||
// The strenght of the resulting pole determined by the arm flex.
|
||||
float armFlexCoeficient = armToHandDistance / armTotalDistance;
|
||||
glm::vec3 attenuationVector = armFlexCoeficient * armToHandDir;
|
||||
// Pole vector is perpendicular to the shoulder-hand direction and located on the plane that contains the head-forward line
|
||||
glm::vec3 fullPoleVector = glm::normalize(glm::cross(armToHeadPlaneNormal, armToHandDir));
|
||||
|
||||
// Push elbow forward when hand reaches opposite side
|
||||
glm::vec3 correctionVector = glm::vec3(0, 0, 0);
|
||||
|
||||
const float FORWARD_TRIGGER_PERCENTAGE = 0.2f;
|
||||
const float FORWARD_CORRECTOR_WEIGHT = 3.0f;
|
||||
|
||||
float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance;
|
||||
|
||||
if (oppositeProjection > -elbowForwardTrigger) {
|
||||
float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection);
|
||||
correctionVector = forwardAmount * frontVector;
|
||||
}
|
||||
poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector);
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const {
|
||||
|
|
|
@ -217,7 +217,7 @@ public:
|
|||
|
||||
// input assumed to be in rig space
|
||||
void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const;
|
||||
|
||||
void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; };
|
||||
signals:
|
||||
void onLoadComplete();
|
||||
|
||||
|
@ -240,11 +240,12 @@ protected:
|
|||
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
|
||||
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;
|
||||
|
||||
glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const;
|
||||
bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const;
|
||||
glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const;
|
||||
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
|
||||
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const;
|
||||
|
||||
|
||||
AnimPose _modelOffset; // model to rig space
|
||||
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)
|
||||
AnimPose _invGeometryOffset;
|
||||
|
@ -368,11 +369,13 @@ protected:
|
|||
glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space
|
||||
bool _prevLeftFootPoleVectorValid { false };
|
||||
|
||||
glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z }; // sensor space
|
||||
bool _prevRightHandPoleVectorValid { false };
|
||||
glm::vec3 _prevRightHandPoleVector{ -Vectors::UNIT_Z }; // sensor space
|
||||
bool _prevRightHandPoleVectorValid{ false };
|
||||
|
||||
glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; // sensor space
|
||||
bool _prevLeftHandPoleVectorValid { false };
|
||||
glm::vec3 _prevLeftHandPoleVector{ -Vectors::UNIT_Z }; // sensor space
|
||||
bool _prevLeftHandPoleVectorValid{ false };
|
||||
|
||||
bool _smoothPoleVectors { false };
|
||||
|
||||
int _rigId;
|
||||
};
|
||||
|
|
|
@ -1568,6 +1568,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
|
|||
}
|
||||
|
||||
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
|
||||
// FIXME: this doesn't take into account Avatar rotation
|
||||
ShapeInfo shapeInfo;
|
||||
computeShapeInfo(shapeInfo);
|
||||
glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
|
||||
|
|
|
@ -2555,15 +2555,18 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const {
|
|||
return _controllerRightHandMatrixCache.get();
|
||||
}
|
||||
|
||||
|
||||
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID);
|
||||
obj.setProperty("avatarID", avatarIDValue);
|
||||
obj.setProperty("distance", value.distance);
|
||||
obj.setProperty("face", boxFaceToString(value.face));
|
||||
|
||||
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
@ -2573,10 +2576,16 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
QScriptValue avatarIDValue = object.property("avatarID");
|
||||
quuidFromScriptValue(avatarIDValue, value.avatarID);
|
||||
value.distance = object.property("distance").toVariant().toFloat();
|
||||
value.face = boxFaceFromString(object.property("face").toVariant().toString());
|
||||
|
||||
QScriptValue intersection = object.property("intersection");
|
||||
if (intersection.isValid()) {
|
||||
vec3FromScriptValue(intersection, value.intersection);
|
||||
}
|
||||
QScriptValue surfaceNormal = object.property("surfaceNormal");
|
||||
if (surfaceNormal.isValid()) {
|
||||
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
|
||||
}
|
||||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
|
|
|
@ -1524,19 +1524,30 @@ void registerAvatarTypes(QScriptEngine* engine);
|
|||
|
||||
class RayToAvatarIntersectionResult {
|
||||
public:
|
||||
RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
|
||||
bool intersects;
|
||||
bool intersects { false };
|
||||
QUuid avatarID;
|
||||
float distance;
|
||||
float distance { 0.0f };
|
||||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)
|
||||
|
||||
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results);
|
||||
void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results);
|
||||
|
||||
class ParabolaToAvatarIntersectionResult {
|
||||
public:
|
||||
bool intersects { false };
|
||||
QUuid avatarID;
|
||||
float distance { 0.0f };
|
||||
float parabolicDistance { 0.0f };
|
||||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AvatarEntityMap)
|
||||
|
||||
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value);
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include <CursorManager.h>
|
||||
#include <gl/GLWidget.h>
|
||||
|
||||
#include "GeometryUtil.h"
|
||||
|
||||
// Used to animate the magnification windows
|
||||
|
||||
//static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
|
||||
|
@ -357,9 +359,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
|
|||
glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction));
|
||||
|
||||
const float UI_RADIUS = 1.0f;
|
||||
float instersectionDistance;
|
||||
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) {
|
||||
result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance);
|
||||
float intersectionDistance;
|
||||
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) {
|
||||
result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance);
|
||||
#ifdef WANT_DEBUG
|
||||
DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
|
||||
#endif
|
||||
|
@ -372,6 +374,23 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
|
|||
return false;
|
||||
}
|
||||
|
||||
bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const {
|
||||
glm::mat4 uiToWorld = getUiTransform();
|
||||
glm::mat4 worldToUi = glm::inverse(uiToWorld);
|
||||
glm::vec3 localOrigin = transformPoint(worldToUi, origin);
|
||||
glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity));
|
||||
glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration));
|
||||
|
||||
const float UI_RADIUS = 1.0f;
|
||||
float intersectionDistance;
|
||||
if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) {
|
||||
result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance;
|
||||
parabolicDistance = intersectionDistance;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const {
|
||||
glm::vec2 result = sphericalPos;
|
||||
result.x *= -1.0f;
|
||||
|
|
|
@ -52,6 +52,7 @@ public:
|
|||
void setRenderingWidget(QWidget* widget) { _renderingWidget = widget; }
|
||||
|
||||
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
|
||||
bool calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const;
|
||||
|
||||
bool isHMD() const;
|
||||
bool fakeEventActive() const { return _fakeMouseEvent; }
|
||||
|
|
|
@ -278,7 +278,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag
|
|||
return properties;
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
|
||||
bool RenderableModelEntityItem::supportsDetailedIntersection() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -294,6 +294,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face,
|
||||
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
auto model = getModel();
|
||||
if (!model || !isModelLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance,
|
||||
face, surfaceNormal, extraInfo, precisionPicking, false);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::getCollisionGeometryResource() {
|
||||
QUrl hullURL(getCompoundShapeURL());
|
||||
QUrlQuery queryArgs(hullURL);
|
||||
|
@ -699,14 +711,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
|||
adjustShapeInfoByRegistration(shapeInfo);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) {
|
||||
const void* key = static_cast<const void*>(shape);
|
||||
if (_collisionMeshKey != key) {
|
||||
_collisionMeshKey = key;
|
||||
emit requestCollisionGeometryUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) {
|
||||
if (jointMap.size() > 0) {
|
||||
_jointMap = jointMap;
|
||||
|
@ -1278,10 +1282,6 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
|
|||
return false;
|
||||
}
|
||||
|
||||
void ModelEntityRenderer::setCollisionMeshKey(const void*key) {
|
||||
_collisionMeshKey = key;
|
||||
}
|
||||
|
||||
void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
||||
DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__);
|
||||
if (_hasModel != entity->hasModel()) {
|
||||
|
|
|
@ -66,11 +66,15 @@ public:
|
|||
void doInitialModelSimulation();
|
||||
void updateModelBounds();
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override;
|
||||
virtual bool supportsDetailedIntersection() const override;
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setShapeType(ShapeType type) override;
|
||||
virtual void setCompoundShapeURL(const QString& url) override;
|
||||
|
@ -78,8 +82,6 @@ public:
|
|||
virtual bool isReadyToComputeShape() const override;
|
||||
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
|
||||
|
||||
void setCollisionShape(const btCollisionShape* shape) override;
|
||||
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
void stopModelOverrideIfNoParent();
|
||||
|
||||
|
@ -112,10 +114,6 @@ public:
|
|||
virtual QStringList getJointNames() const override;
|
||||
|
||||
bool getMeshes(MeshProxyList& result) override; // deprecated
|
||||
const void* getCollisionMeshKey() const { return _collisionMeshKey; }
|
||||
|
||||
signals:
|
||||
void requestCollisionGeometryUpdate();
|
||||
|
||||
private:
|
||||
bool needsUpdateModelBounds() const;
|
||||
|
@ -130,7 +128,6 @@ private:
|
|||
QVariantMap _originalTextures;
|
||||
bool _dimensionsInitialized { true };
|
||||
bool _needsJointSimulation { false };
|
||||
const void* _collisionMeshKey { nullptr };
|
||||
};
|
||||
|
||||
namespace render { namespace entities {
|
||||
|
@ -161,7 +158,6 @@ protected:
|
|||
virtual bool needsRenderUpdate() const override;
|
||||
virtual void doRender(RenderArgs* args) override;
|
||||
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
|
||||
void setCollisionMeshKey(const void* key);
|
||||
|
||||
render::hifi::Tag getTagMask() const override;
|
||||
|
||||
|
|
|
@ -101,6 +101,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
|
|||
_timeUntilNextEmit = 0;
|
||||
withWriteLock([&]{
|
||||
_particleProperties = newParticleProperties;
|
||||
if (!_prevEmitterShouldTrailInitialized) {
|
||||
_prevEmitterShouldTrailInitialized = true;
|
||||
_prevEmitterShouldTrail = _particleProperties.emission.shouldTrail;
|
||||
}
|
||||
});
|
||||
}
|
||||
_emitting = entity->getIsEmitting();
|
||||
|
@ -144,7 +148,12 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn
|
|||
particleUniforms.color.middle = _particleProperties.getColorMiddle();
|
||||
particleUniforms.color.finish = _particleProperties.getColorFinish();
|
||||
particleUniforms.color.spread = _particleProperties.getColorSpread();
|
||||
particleUniforms.spin.start = _particleProperties.spin.range.start;
|
||||
particleUniforms.spin.middle = _particleProperties.spin.gradient.target;
|
||||
particleUniforms.spin.finish = _particleProperties.spin.range.finish;
|
||||
particleUniforms.spin.spread = _particleProperties.spin.gradient.spread;
|
||||
particleUniforms.lifespan = _particleProperties.lifespan;
|
||||
particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0;
|
||||
});
|
||||
// Update particle uniforms
|
||||
memcpy(&_uniformBuffer.edit<ParticleUniforms>(), &particleUniforms, sizeof(ParticleUniforms));
|
||||
|
@ -176,7 +185,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
|
|||
const auto& azimuthFinish = particleProperties.azimuth.finish;
|
||||
const auto& emitDimensions = particleProperties.emission.dimensions;
|
||||
const auto& emitAcceleration = particleProperties.emission.acceleration.target;
|
||||
auto emitOrientation = particleProperties.emission.orientation;
|
||||
auto emitOrientation = baseTransform.getRotation() * particleProperties.emission.orientation;
|
||||
const auto& emitRadiusStart = glm::max(particleProperties.radiusStart, EPSILON); // Avoid math complications at center
|
||||
const auto& emitSpeed = particleProperties.emission.speed.target;
|
||||
const auto& speedSpread = particleProperties.emission.speed.spread;
|
||||
|
@ -185,10 +194,9 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
|
|||
|
||||
particle.seed = randFloatInRange(-1.0f, 1.0f);
|
||||
particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND);
|
||||
if (particleProperties.emission.shouldTrail) {
|
||||
particle.position = baseTransform.getTranslation();
|
||||
emitOrientation = baseTransform.getRotation() * emitOrientation;
|
||||
}
|
||||
|
||||
particle.relativePosition = glm::vec3(0.0f);
|
||||
particle.basePosition = baseTransform.getTranslation();
|
||||
|
||||
// Position, velocity, and acceleration
|
||||
if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) {
|
||||
|
@ -237,7 +245,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
|
|||
radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f,
|
||||
radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f
|
||||
));
|
||||
particle.position += emitOrientation * emitPosition;
|
||||
particle.relativePosition += emitOrientation * emitPosition;
|
||||
}
|
||||
|
||||
particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection);
|
||||
|
@ -262,8 +270,8 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
|||
particleProperties = _particleProperties;
|
||||
});
|
||||
|
||||
const auto& modelTransform = getModelTransform();
|
||||
if (_emitting && particleProperties.emitting()) {
|
||||
const auto& modelTransform = getModelTransform();
|
||||
uint64_t emitInterval = particleProperties.emitIntervalUsecs();
|
||||
if (emitInterval > 0 && interval >= _timeUntilNextEmit) {
|
||||
auto timeRemaining = interval;
|
||||
|
@ -288,15 +296,23 @@ void ParticleEffectEntityRenderer::stepSimulation() {
|
|||
const float deltaTime = (float)interval / (float)USECS_PER_SECOND;
|
||||
// update the particles
|
||||
for (auto& particle : _cpuParticles) {
|
||||
if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) {
|
||||
if (_prevEmitterShouldTrail) {
|
||||
particle.relativePosition = particle.relativePosition + particle.basePosition - modelTransform.getTranslation();
|
||||
}
|
||||
particle.basePosition = modelTransform.getTranslation();
|
||||
}
|
||||
particle.integrate(deltaTime);
|
||||
}
|
||||
_prevEmitterShouldTrail = particleProperties.emission.shouldTrail;
|
||||
|
||||
// Build particle primitives
|
||||
static GpuParticles gpuParticles;
|
||||
gpuParticles.clear();
|
||||
gpuParticles.reserve(_cpuParticles.size()); // Reserve space
|
||||
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [](const CpuParticle& particle) {
|
||||
return GpuParticle(particle.position, glm::vec2(particle.lifetime, particle.seed));
|
||||
std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) {
|
||||
glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation());
|
||||
return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed));
|
||||
});
|
||||
|
||||
// Update particle buffer
|
||||
|
@ -324,15 +340,11 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
|
||||
Transform transform;
|
||||
// In trail mode, the particles are created in world space.
|
||||
// so we only set a transform if they're not in trail mode
|
||||
if (!_particleProperties.emission.shouldTrail) {
|
||||
|
||||
withReadLock([&] {
|
||||
transform = _renderTransform;
|
||||
});
|
||||
transform.setScale(vec3(1));
|
||||
}
|
||||
// The particles are in world space, so the transform is unused, except for the rotation, which we use
|
||||
// if the particles are marked rotateWithEntity
|
||||
withReadLock([&] {
|
||||
transform.setRotation(_renderTransform.getRotation());
|
||||
});
|
||||
batch.setModelTransform(transform);
|
||||
batch.setUniformBuffer(PARTICLE_UNIFORM_SLOT, _uniformBuffer);
|
||||
batch.setInputFormat(_vertexFormat);
|
||||
|
@ -341,5 +353,3 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) {
|
|||
auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle);
|
||||
batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -49,13 +49,14 @@ private:
|
|||
float seed { 0.0f };
|
||||
uint64_t expiration { 0 };
|
||||
float lifetime { 0.0f };
|
||||
glm::vec3 position;
|
||||
glm::vec3 basePosition;
|
||||
glm::vec3 relativePosition;
|
||||
glm::vec3 velocity;
|
||||
glm::vec3 acceleration;
|
||||
|
||||
void integrate(float deltaTime) {
|
||||
glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * acceleration;
|
||||
position += velocity * deltaTime + atSquared;
|
||||
relativePosition += velocity * deltaTime + atSquared;
|
||||
velocity += acceleration * deltaTime;
|
||||
lifetime += deltaTime;
|
||||
}
|
||||
|
@ -74,15 +75,18 @@ private:
|
|||
struct ParticleUniforms {
|
||||
InterpolationData<float> radius;
|
||||
InterpolationData<glm::vec4> color; // rgba
|
||||
InterpolationData<float> spin;
|
||||
float lifespan;
|
||||
glm::vec3 spare;
|
||||
int rotateWithEntity;
|
||||
glm::vec2 spare;
|
||||
};
|
||||
|
||||
|
||||
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties);
|
||||
void stepSimulation();
|
||||
|
||||
particle::Properties _particleProperties;
|
||||
bool _prevEmitterShouldTrail;
|
||||
bool _prevEmitterShouldTrailInitialized { false };
|
||||
CpuParticles _cpuParticles;
|
||||
bool _emitting { false };
|
||||
uint64_t _timeUntilNextEmit { 0 };
|
||||
|
|
|
@ -567,8 +567,7 @@ public:
|
|||
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const
|
||||
{
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// TODO -- correctly pick against marching-cube generated meshes
|
||||
if (!precisionPicking) {
|
||||
// just intersect with bounding box
|
||||
|
@ -605,7 +604,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
|||
voxelBox += result3 + Vectors::HALF;
|
||||
|
||||
float voxelDistance;
|
||||
|
||||
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel),
|
||||
voxelDistance, face, surfaceNormal);
|
||||
|
||||
|
@ -615,6 +613,87 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
|
|||
return hit;
|
||||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element,
|
||||
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// TODO -- correctly pick against marching-cube generated meshes
|
||||
if (!precisionPicking) {
|
||||
// just intersect with bounding box
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::mat4 wtvMatrix = worldToVoxelMatrix();
|
||||
glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f);
|
||||
glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f);
|
||||
glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f);
|
||||
|
||||
// find the first intersection with the voxel bounding box (slightly enlarged so we can catch voxels that touch the sides)
|
||||
bool success;
|
||||
glm::vec3 center = getCenterPosition(success);
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
const float FIRST_BOX_HALF_SCALE = 0.51f;
|
||||
AABox voxelBox1(wtvMatrix * vec4(center - FIRST_BOX_HALF_SCALE * dimensions, 1.0f),
|
||||
wtvMatrix * vec4(2.0f * FIRST_BOX_HALF_SCALE * dimensions, 0.0f));
|
||||
bool hit1;
|
||||
float parabolicDistance1;
|
||||
// If we're starting inside the box, our first point is originInVoxel
|
||||
if (voxelBox1.contains(originInVoxel)) {
|
||||
parabolicDistance1 = 0.0f;
|
||||
hit1 = true;
|
||||
} else {
|
||||
BoxFace face1;
|
||||
glm::vec3 surfaceNormal1;
|
||||
hit1 = voxelBox1.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel),
|
||||
parabolicDistance1, face1, surfaceNormal1);
|
||||
}
|
||||
|
||||
if (hit1) {
|
||||
// find the second intersection, which should be with the inside of the box (use a slightly large box again)
|
||||
const float SECOND_BOX_HALF_SCALE = 0.52f;
|
||||
AABox voxelBox2(wtvMatrix * vec4(center - SECOND_BOX_HALF_SCALE * dimensions, 1.0f),
|
||||
wtvMatrix * vec4(2.0f * SECOND_BOX_HALF_SCALE * dimensions, 0.0f));
|
||||
glm::vec4 originInVoxel2 = originInVoxel + velocityInVoxel * parabolicDistance1 + 0.5f * accelerationInVoxel * parabolicDistance1 * parabolicDistance1;
|
||||
glm::vec4 velocityInVoxel2 = velocityInVoxel + accelerationInVoxel * parabolicDistance1;
|
||||
glm::vec4 accelerationInVoxel2 = accelerationInVoxel;
|
||||
float parabolicDistance2;
|
||||
BoxFace face2;
|
||||
glm::vec3 surfaceNormal2;
|
||||
// this should always be true
|
||||
if (voxelBox2.findParabolaIntersection(glm::vec3(originInVoxel2), glm::vec3(velocityInVoxel2), glm::vec3(accelerationInVoxel2),
|
||||
parabolicDistance2, face2, surfaceNormal2)) {
|
||||
const int MAX_SECTIONS = 15;
|
||||
PolyVox::RaycastResult raycastResult = PolyVox::RaycastResults::Completed;
|
||||
glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glm::vec4 segmentStartVoxel = originInVoxel2;
|
||||
for (int i = 0; i < MAX_SECTIONS; i++) {
|
||||
float t = parabolicDistance2 * ((float)(i + 1)) / ((float)MAX_SECTIONS);
|
||||
glm::vec4 segmentEndVoxel = originInVoxel2 + velocityInVoxel2 * t + 0.5f * accelerationInVoxel2 * t * t;
|
||||
raycastResult = doRayCast(segmentStartVoxel, segmentEndVoxel, result);
|
||||
if (raycastResult != PolyVox::RaycastResults::Completed) {
|
||||
// We hit something!
|
||||
break;
|
||||
}
|
||||
segmentStartVoxel = segmentEndVoxel;
|
||||
}
|
||||
|
||||
if (raycastResult == PolyVox::RaycastResults::Completed) {
|
||||
// the parabola completed its path -- nothing was hit.
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 result3 = glm::vec3(result);
|
||||
|
||||
AABox voxelBox;
|
||||
voxelBox += result3 - Vectors::HALF;
|
||||
voxelBox += result3 + Vectors::HALF;
|
||||
|
||||
return voxelBox.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel),
|
||||
parabolicDistance, face, surfaceNormal);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel,
|
||||
glm::vec4 farInVoxel,
|
||||
|
|
|
@ -51,11 +51,15 @@ public:
|
|||
|
||||
int getOnCount() const override { return _onCount; }
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration,
|
||||
OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
virtual void setVoxelData(const QByteArray& voxelData) override;
|
||||
virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override;
|
||||
|
|
|
@ -178,10 +178,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
|||
|
||||
|
||||
withWriteLock([&] {
|
||||
if (_contentType == ContentType::NoContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This work must be done on the main thread
|
||||
// If we couldn't create a new web surface, exit
|
||||
if (!hasWebSurface() && !buildWebSurface(entity)) {
|
||||
|
@ -315,7 +311,13 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
|
|||
});
|
||||
} else if (_contentType == ContentType::QmlContent) {
|
||||
_webSurface->load(_lastSourceUrl);
|
||||
} else if (_contentType == ContentType::NoContent) {
|
||||
// Show empty white panel
|
||||
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
|
||||
item->setProperty(URL_PROPERTY, "");
|
||||
});
|
||||
}
|
||||
|
||||
_fadeStartTime = usecTimestampNow();
|
||||
_webSurface->resume();
|
||||
|
||||
|
|
|
@ -27,11 +27,20 @@ struct Colors {
|
|||
vec4 finish;
|
||||
vec4 spread;
|
||||
};
|
||||
struct Spin {
|
||||
float start;
|
||||
float middle;
|
||||
float finish;
|
||||
float spread;
|
||||
};
|
||||
|
||||
struct ParticleUniforms {
|
||||
Radii radius;
|
||||
Colors color;
|
||||
vec4 lifespan; // x is lifespan, 3 spare floats
|
||||
Spin spin;
|
||||
float lifespan;
|
||||
int rotateWithEntity;
|
||||
vec2 spare;
|
||||
};
|
||||
|
||||
layout(std140) uniform particleBuffer {
|
||||
|
@ -44,15 +53,6 @@ layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed
|
|||
out vec4 varColor;
|
||||
out vec2 varTexcoord;
|
||||
|
||||
const int NUM_VERTICES_PER_PARTICLE = 4;
|
||||
// This ordering ensures that un-rotated particles render upright in the viewer.
|
||||
const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE](
|
||||
vec4(-1.0, 1.0, 0.0, 0.0),
|
||||
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||
vec4(1.0, 1.0, 0.0, 0.0),
|
||||
vec4(1.0, -1.0, 0.0, 0.0)
|
||||
);
|
||||
|
||||
float bezierInterpolate(float y1, float y2, float y3, float u) {
|
||||
// https://en.wikipedia.org/wiki/Bezier_curve
|
||||
return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3;
|
||||
|
@ -103,6 +103,15 @@ vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) {
|
|||
interpolate3Points(y1.w, y2.w, y3.w, u));
|
||||
}
|
||||
|
||||
const int NUM_VERTICES_PER_PARTICLE = 4;
|
||||
const vec2 TEX_COORDS[NUM_VERTICES_PER_PARTICLE] = vec2[NUM_VERTICES_PER_PARTICLE](
|
||||
vec2(-1.0, 0.0),
|
||||
vec2(-1.0, 1.0),
|
||||
vec2(0.0, 0.0),
|
||||
vec2(0.0, 1.0)
|
||||
);
|
||||
|
||||
|
||||
void main(void) {
|
||||
TransformCamera cam = getTransformCamera();
|
||||
TransformObject obj = getTransformObject();
|
||||
|
@ -113,28 +122,54 @@ void main(void) {
|
|||
int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE;
|
||||
|
||||
// Particle properties
|
||||
float age = inColor.x / particle.lifespan.x;
|
||||
float age = inColor.x / particle.lifespan;
|
||||
float seed = inColor.y;
|
||||
|
||||
// Pass the texcoord and the z texcoord is representing the texture icon
|
||||
// Offset for corrected vertex ordering.
|
||||
varTexcoord = vec2((UNIT_QUAD[twoTriID].xy -1.0) * vec2(0.5, -0.5));
|
||||
// Pass the texcoord
|
||||
varTexcoord = TEX_COORDS[twoTriID].xy;
|
||||
varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age);
|
||||
vec3 colorSpread = 2.0 * vec3(hifi_hash(seed), hifi_hash(seed * 2.0), hifi_hash(seed * 3.0)) - 1.0;
|
||||
varColor.rgb = clamp(varColor.rgb + colorSpread * particle.color.spread.rgb, vec3(0), vec3(1));
|
||||
float alphaSpread = 2.0 * hifi_hash(seed * 4.0) - 1.0;
|
||||
varColor.a = clamp(varColor.a + alphaSpread * particle.color.spread.a, 0.0, 1.0);
|
||||
|
||||
float spin = interpolate3Points(particle.spin.start, particle.spin.middle, particle.spin.finish, age);
|
||||
float spinSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0;
|
||||
spin = spin + spinSpread * particle.spin.spread;
|
||||
|
||||
// anchor point in eye space
|
||||
float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age);
|
||||
float radiusSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0;
|
||||
float radiusSpread = 2.0 * hifi_hash(seed * 6.0) - 1.0;
|
||||
radius = max(radius + radiusSpread * particle.radius.spread, 0.0);
|
||||
vec4 quadPos = radius * UNIT_QUAD[twoTriID];
|
||||
|
||||
vec4 anchorPoint;
|
||||
vec4 _inPosition = vec4(inPosition, 1.0);
|
||||
<$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$>
|
||||
// inPosition is in world space
|
||||
vec4 anchorPoint = cam._view * vec4(inPosition, 1.0);
|
||||
|
||||
vec4 eyePos = anchorPoint + quadPos;
|
||||
mat3 view3 = mat3(cam._view);
|
||||
vec3 UP = vec3(0, 1, 0);
|
||||
vec3 modelUpWorld;
|
||||
<$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$>
|
||||
vec3 upWorld = mix(UP, normalize(modelUpWorld), particle.rotateWithEntity);
|
||||
vec3 upEye = normalize(view3 * upWorld);
|
||||
vec3 FORWARD = vec3(0, 0, -1);
|
||||
vec3 particleRight = normalize(cross(FORWARD, upEye));
|
||||
vec3 particleUp = cross(particleRight, FORWARD); // don't need to normalize
|
||||
// This ordering ensures that un-rotated particles render upright in the viewer.
|
||||
vec3 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec3[NUM_VERTICES_PER_PARTICLE](
|
||||
normalize(-particleRight + particleUp),
|
||||
normalize(-particleRight - particleUp),
|
||||
normalize(particleRight + particleUp),
|
||||
normalize(particleRight - particleUp)
|
||||
);
|
||||
float c = cos(spin);
|
||||
float s = sin(spin);
|
||||
mat4 rotation = mat4(
|
||||
c, -s, 0, 0,
|
||||
s, c, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0);
|
||||
vec4 eyePos = anchorPoint + rotation * quadPos;
|
||||
<$transformEyeToClipPos(cam, eyePos, gl_Position)$>
|
||||
}
|
||||
|
|
|
@ -84,9 +84,15 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID,
|
||||
const EntityItemProperties& properties) {
|
||||
if (properties.getClientOnly() && properties.getOwningAvatarID() == _myAvatar->getID()) {
|
||||
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
|
||||
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
|
||||
if (properties.getClientOnly()) {
|
||||
if (!_myAvatar) {
|
||||
qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit with no myAvatar";
|
||||
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
|
||||
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
|
||||
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
|
||||
} else {
|
||||
qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit for another avatar";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -159,11 +159,15 @@ public:
|
|||
|
||||
virtual void debugDump() const;
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const { return false; }
|
||||
virtual bool supportsDetailedIntersection() const { return false; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
|
||||
|
||||
// attributes applicable to all entity types
|
||||
EntityTypes::EntityType getType() const { return _type; }
|
||||
|
@ -378,8 +382,6 @@ public:
|
|||
/// return preferred shape type (actual physical shape may differ)
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
|
||||
|
||||
virtual void setCollisionShape(const btCollisionShape* shape) {}
|
||||
|
||||
void setPosition(const glm::vec3& value);
|
||||
virtual void setParentID(const QUuid& parentID) override;
|
||||
virtual void setShapeType(ShapeType type) { /* do nothing */ }
|
||||
|
|
|
@ -369,6 +369,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData);
|
||||
CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart);
|
||||
CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity);
|
||||
|
||||
// Certifiable Properties
|
||||
CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName);
|
||||
|
@ -910,6 +915,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If <code>alpha == 0.5</code>
|
||||
* and <code>alphaSpread == 0.25</code>, each particle will have an alpha in the range <code>0.25</code> –
|
||||
* <code>0.75</code>.
|
||||
* @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range <code>-2*PI</code> – <code>2*PI</code>.
|
||||
* @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range <code>-2*PI</code> – <code>2*PI</code>.
|
||||
* If <code>NaN</code>, the <code>particleSpin</code> value is used.
|
||||
* @property {number} spinFinish=NaN - The spin of each particle at the end of its life. In the range <code>-2*PI</code> – <code>2*PI</code>.
|
||||
* If <code>NaN</code>, the <code>particleSpin</code> value is used.
|
||||
* @property {number} spinSpread=0 - The spread in spin that each particle is given. In the range <code>0</code> – <code>2*PI</code>. If <code>particleSpin == PI</code>
|
||||
* and <code>spinSpread == PI/2</code>, each particle will have a spin in the range <code>PI/2</code> – <code>3*PI/2</code>.
|
||||
* @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when <code>particleSpin == 0</code>, the particles will point
|
||||
* up in the world. If true, they will point towards the entity's up vector, based on its orientation.
|
||||
*
|
||||
* @property {ShapeType} shapeType="none" - <em>Currently not used.</em> <em>Read-only.</em>
|
||||
*
|
||||
|
@ -1293,6 +1307,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_FINISH, spinFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity);
|
||||
}
|
||||
|
||||
// Models only
|
||||
|
@ -1585,6 +1604,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity);
|
||||
|
||||
// Certifiable Properties
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName);
|
||||
|
@ -1753,6 +1777,11 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
|||
COPY_PROPERTY_IF_CHANGED(radiusSpread);
|
||||
COPY_PROPERTY_IF_CHANGED(radiusStart);
|
||||
COPY_PROPERTY_IF_CHANGED(radiusFinish);
|
||||
COPY_PROPERTY_IF_CHANGED(particleSpin);
|
||||
COPY_PROPERTY_IF_CHANGED(spinSpread);
|
||||
COPY_PROPERTY_IF_CHANGED(spinStart);
|
||||
COPY_PROPERTY_IF_CHANGED(spinFinish);
|
||||
COPY_PROPERTY_IF_CHANGED(rotateWithEntity);
|
||||
|
||||
// Certifiable Properties
|
||||
COPY_PROPERTY_IF_CHANGED(itemName);
|
||||
|
@ -1966,6 +1995,12 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
|
||||
ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float);
|
||||
|
||||
// Certifiable Properties
|
||||
ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString);
|
||||
|
@ -2294,6 +2329,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, properties.getSpinFinish());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, properties.getRotateWithEntity())
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Zone) {
|
||||
|
@ -2669,6 +2709,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_FINISH, float, setSpinFinish);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Zone) {
|
||||
|
@ -2938,7 +2983,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_shapeTypeChanged = true;
|
||||
|
||||
_isEmittingChanged = true;
|
||||
_emitterShouldTrail = true;
|
||||
_emitterShouldTrailChanged = true;
|
||||
_maxParticlesChanged = true;
|
||||
_lifespanChanged = true;
|
||||
_emitRateChanged = true;
|
||||
|
@ -2963,6 +3008,11 @@ void EntityItemProperties::markAllChanged() {
|
|||
_colorFinishChanged = true;
|
||||
_alphaStartChanged = true;
|
||||
_alphaFinishChanged = true;
|
||||
_particleSpinChanged = true;
|
||||
_spinStartChanged = true;
|
||||
_spinFinishChanged = true;
|
||||
_spinSpreadChanged = true;
|
||||
_rotateWithEntityChanged = true;
|
||||
|
||||
_materialURLChanged = true;
|
||||
_materialMappingModeChanged = true;
|
||||
|
@ -3311,6 +3361,21 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (radiusFinishChanged()) {
|
||||
out += "radiusFinish";
|
||||
}
|
||||
if (particleSpinChanged()) {
|
||||
out += "particleSpin";
|
||||
}
|
||||
if (spinSpreadChanged()) {
|
||||
out += "spinSpread";
|
||||
}
|
||||
if (spinStartChanged()) {
|
||||
out += "spinStart";
|
||||
}
|
||||
if (spinFinishChanged()) {
|
||||
out += "spinFinish";
|
||||
}
|
||||
if (rotateWithEntityChanged()) {
|
||||
out += "rotateWithEntity";
|
||||
}
|
||||
if (materialURLChanged()) {
|
||||
out += "materialURL";
|
||||
}
|
||||
|
|
|
@ -235,6 +235,12 @@ public:
|
|||
|
||||
DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA);
|
||||
|
||||
DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN);
|
||||
DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD);
|
||||
DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START);
|
||||
DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH);
|
||||
DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY);
|
||||
|
||||
// Certifiable Properties - related to Proof of Purchase certificates
|
||||
DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME);
|
||||
DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION);
|
||||
|
|
|
@ -251,6 +251,12 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally
|
||||
|
||||
PROP_PARTICLE_SPIN,
|
||||
PROP_SPIN_START,
|
||||
PROP_SPIN_FINISH,
|
||||
PROP_SPIN_SPREAD,
|
||||
PROP_PARTICLE_ROTATE_WITH_ENTITY,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -871,6 +871,30 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
|
|||
return result;
|
||||
}
|
||||
|
||||
ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
|
||||
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly);
|
||||
}
|
||||
|
||||
ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola,
|
||||
Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
|
||||
|
||||
|
||||
ParabolaToEntityIntersectionResult result;
|
||||
if (_entityTree) {
|
||||
OctreeElementPointer element;
|
||||
result.entityID = _entityTree->findParabolaIntersection(parabola,
|
||||
entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal,
|
||||
result.extraInfo, lockType, &result.accurate);
|
||||
result.intersects = !result.entityID.isNull();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
return client->reloadServerScript(entityID);
|
||||
|
@ -1025,75 +1049,17 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const {
|
|||
return ZoneEntityItem::getDrawZoneBoundaries();
|
||||
}
|
||||
|
||||
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
||||
intersects(false),
|
||||
accurate(true), // assume it's accurate
|
||||
entityID(),
|
||||
distance(0),
|
||||
face()
|
||||
{
|
||||
}
|
||||
|
||||
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
obj.setProperty("accurate", value.accurate);
|
||||
QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID);
|
||||
obj.setProperty("entityID", entityItemValue);
|
||||
|
||||
obj.setProperty("distance", value.distance);
|
||||
|
||||
QString faceName = "";
|
||||
// handle BoxFace
|
||||
/**jsdoc
|
||||
* <p>A <code>BoxFace</code> specifies the face of an axis-aligned (AA) box.
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Value</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>"MIN_X_FACE"</code></td><td>The minimum x-axis face.</td></tr>
|
||||
* <tr><td><code>"MAX_X_FACE"</code></td><td>The maximum x-axis face.</td></tr>
|
||||
* <tr><td><code>"MIN_Y_FACE"</code></td><td>The minimum y-axis face.</td></tr>
|
||||
* <tr><td><code>"MAX_Y_FACE"</code></td><td>The maximum y-axis face.</td></tr>
|
||||
* <tr><td><code>"MIN_Z_FACE"</code></td><td>The minimum z-axis face.</td></tr>
|
||||
* <tr><td><code>"MAX_Z_FACE"</code></td><td>The maximum z-axis face.</td></tr>
|
||||
* <tr><td><code>"UNKNOWN_FACE"</code></td><td>Unknown value.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* @typedef {string} BoxFace
|
||||
*/
|
||||
// FIXME: Move enum to string function to BoxBase.cpp.
|
||||
switch (value.face) {
|
||||
case MIN_X_FACE:
|
||||
faceName = "MIN_X_FACE";
|
||||
break;
|
||||
case MAX_X_FACE:
|
||||
faceName = "MAX_X_FACE";
|
||||
break;
|
||||
case MIN_Y_FACE:
|
||||
faceName = "MIN_Y_FACE";
|
||||
break;
|
||||
case MAX_Y_FACE:
|
||||
faceName = "MAX_Y_FACE";
|
||||
break;
|
||||
case MIN_Z_FACE:
|
||||
faceName = "MIN_Z_FACE";
|
||||
break;
|
||||
case MAX_Z_FACE:
|
||||
faceName = "MAX_Z_FACE";
|
||||
break;
|
||||
case UNKNOWN_FACE:
|
||||
faceName = "UNKNOWN_FACE";
|
||||
break;
|
||||
}
|
||||
obj.setProperty("face", faceName);
|
||||
obj.setProperty("face", boxFaceToString(value.face));
|
||||
|
||||
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
|
||||
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
|
||||
obj.setProperty("surfaceNormal", surfaceNormal);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
|
@ -1101,29 +1067,13 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
|
|||
}
|
||||
|
||||
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
value.intersects = object.property("intersects").toVariant().toBool();
|
||||
value.accurate = object.property("accurate").toVariant().toBool();
|
||||
QScriptValue entityIDValue = object.property("entityID");
|
||||
// EntityItemIDfromScriptValue(entityIDValue, value.entityID);
|
||||
quuidFromScriptValue(entityIDValue, value.entityID);
|
||||
value.distance = object.property("distance").toVariant().toFloat();
|
||||
value.face = boxFaceFromString(object.property("face").toVariant().toString());
|
||||
|
||||
QString faceName = object.property("face").toVariant().toString();
|
||||
if (faceName == "MIN_X_FACE") {
|
||||
value.face = MIN_X_FACE;
|
||||
} else if (faceName == "MAX_X_FACE") {
|
||||
value.face = MAX_X_FACE;
|
||||
} else if (faceName == "MIN_Y_FACE") {
|
||||
value.face = MIN_Y_FACE;
|
||||
} else if (faceName == "MAX_Y_FACE") {
|
||||
value.face = MAX_Y_FACE;
|
||||
} else if (faceName == "MIN_Z_FACE") {
|
||||
value.face = MIN_Z_FACE;
|
||||
} else {
|
||||
value.face = MAX_Z_FACE;
|
||||
};
|
||||
QScriptValue intersection = object.property("intersection");
|
||||
if (intersection.isValid()) {
|
||||
vec3FromScriptValue(intersection, value.intersection);
|
||||
|
|
|
@ -71,22 +71,31 @@ private:
|
|||
// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock.
|
||||
class RayToEntityIntersectionResult {
|
||||
public:
|
||||
RayToEntityIntersectionResult();
|
||||
bool intersects;
|
||||
bool accurate;
|
||||
bool intersects { false };
|
||||
bool accurate { true };
|
||||
QUuid entityID;
|
||||
float distance;
|
||||
float distance { 0.0f };
|
||||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
|
||||
|
||||
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results);
|
||||
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results);
|
||||
|
||||
class ParabolaToEntityIntersectionResult {
|
||||
public:
|
||||
bool intersects { false };
|
||||
bool accurate { true };
|
||||
QUuid entityID;
|
||||
float distance { 0.0f };
|
||||
float parabolicDistance { 0.0f };
|
||||
BoxFace face;
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 surfaceNormal;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
* The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible
|
||||
|
@ -131,6 +140,12 @@ public:
|
|||
|
||||
void resetActivityTracking();
|
||||
ActivityTracking getActivityTracking() const { return _activityTracking; }
|
||||
|
||||
// TODO: expose to script?
|
||||
ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
|
||||
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1895,6 +1910,11 @@ private:
|
|||
bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
/// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode
|
||||
ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType,
|
||||
bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
|
||||
bool visibleOnly = false, bool collidableOnly = false);
|
||||
|
||||
EntityTreePointer _entityTree;
|
||||
|
||||
std::recursive_mutex _entitiesScriptEngineLock;
|
||||
|
|
|
@ -63,6 +63,27 @@ public:
|
|||
EntityItemID entityID;
|
||||
};
|
||||
|
||||
class ParabolaArgs {
|
||||
public:
|
||||
// Inputs
|
||||
glm::vec3 origin;
|
||||
glm::vec3 velocity;
|
||||
glm::vec3 acceleration;
|
||||
const QVector<EntityItemID>& entityIdsToInclude;
|
||||
const QVector<EntityItemID>& entityIdsToDiscard;
|
||||
bool visibleOnly;
|
||||
bool collidableOnly;
|
||||
bool precisionPicking;
|
||||
|
||||
// Outputs
|
||||
OctreeElementPointer& element;
|
||||
float& parabolicDistance;
|
||||
BoxFace& face;
|
||||
glm::vec3& surfaceNormal;
|
||||
QVariantMap& extraInfo;
|
||||
EntityItemID entityID;
|
||||
};
|
||||
|
||||
|
||||
EntityTree::EntityTree(bool shouldReaverage) :
|
||||
Octree(shouldReaverage)
|
||||
|
@ -820,8 +841,7 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm:
|
|||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType, bool* accurateResult) {
|
||||
RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard,
|
||||
visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||
visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||
distance = FLT_MAX;
|
||||
|
||||
bool requireLock = lockType == Octree::Lock;
|
||||
|
@ -836,6 +856,47 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm:
|
|||
return args.entityID;
|
||||
}
|
||||
|
||||
bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) {
|
||||
ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData);
|
||||
bool keepSearching = true;
|
||||
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching,
|
||||
args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude,
|
||||
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
|
||||
if (!entityID.isNull()) {
|
||||
args->entityID = entityID;
|
||||
}
|
||||
return keepSearching;
|
||||
}
|
||||
|
||||
EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType, bool* accurateResult) {
|
||||
ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard,
|
||||
visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() };
|
||||
parabolicDistance = FLT_MAX;
|
||||
distance = FLT_MAX;
|
||||
|
||||
bool requireLock = lockType == Octree::Lock;
|
||||
bool lockResult = withReadLock([&] {
|
||||
recurseTreeWithOperation(findParabolaIntersectionOp, &args);
|
||||
}, requireLock);
|
||||
|
||||
if (accurateResult) {
|
||||
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
|
||||
}
|
||||
|
||||
if (!args.entityID.isNull()) {
|
||||
intersection = parabola.origin + parabola.velocity * parabolicDistance + 0.5f * parabola.acceleration * parabolicDistance * parabolicDistance;
|
||||
distance = glm::distance(intersection, parabola.origin);
|
||||
}
|
||||
|
||||
return args.entityID;
|
||||
}
|
||||
|
||||
|
||||
EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) {
|
||||
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };
|
||||
|
|
|
@ -95,7 +95,14 @@ public:
|
|||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& node, float& distance,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
virtual EntityItemID findParabolaIntersection(const PickParabola& parabola,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, bool precisionPicking,
|
||||
OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
|
||||
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
|
|||
}
|
||||
|
||||
// by default, we only allow intersections with leaves with content
|
||||
if (!canRayIntersect()) {
|
||||
if (!canPickIntersect()) {
|
||||
return result; // we don't intersect with non-leaves, and we keep searching
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
|
|||
QVariantMap localExtraInfo;
|
||||
float distanceToElementDetails = distance;
|
||||
EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails,
|
||||
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
|
||||
localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
|
||||
localExtraInfo, precisionPicking);
|
||||
if (!entityID.isNull() && distanceToElementDetails < distance) {
|
||||
distance = distanceToElementDetails;
|
||||
|
@ -232,7 +232,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
|
|||
localFace, localSurfaceNormal)) {
|
||||
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) {
|
||||
// now ask the entity if we actually intersect
|
||||
if (entity->supportsDetailedRayIntersection()) {
|
||||
if (entity->supportsDetailedIntersection()) {
|
||||
QVariantMap localExtraInfo;
|
||||
if (entity->findDetailedRayIntersection(origin, direction, element, localDistance,
|
||||
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
|
||||
|
@ -250,7 +250,8 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
|
|||
if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) {
|
||||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f));
|
||||
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f));
|
||||
extraInfo = QVariantMap();
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
|
@ -287,6 +288,144 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
|
|||
return result;
|
||||
}
|
||||
|
||||
EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
EntityItemID result;
|
||||
float distanceToElementCube = std::numeric_limits<float>::max();
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
|
||||
// if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance
|
||||
// we can stop searching!
|
||||
bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal);
|
||||
if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) {
|
||||
keepSearching = false; // no point in continuing to search
|
||||
return result; // we did not intersect
|
||||
}
|
||||
|
||||
// by default, we only allow intersections with leaves with content
|
||||
if (!canPickIntersect()) {
|
||||
return result; // we don't intersect with non-leaves, and we keep searching
|
||||
}
|
||||
|
||||
// if the distance to the element cube is not less than the current best distance, then it's not possible
|
||||
// for any details inside the cube to be closer so we don't need to consider them.
|
||||
QVariantMap localExtraInfo;
|
||||
float distanceToElementDetails = parabolicDistance;
|
||||
// We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check
|
||||
glm::vec3 vectorOnPlane = velocity;
|
||||
if (glm::dot(glm::normalize(velocity), glm::normalize(acceleration)) > 1.0f - EPSILON) {
|
||||
// Handle the degenerate case where velocity is parallel to acceleration
|
||||
// We pick t = 1 and calculate a second point on the plane
|
||||
vectorOnPlane = velocity + 0.5f * acceleration;
|
||||
}
|
||||
// Get the normal of the plane, the cross product of two vectors on the plane
|
||||
glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration));
|
||||
EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails,
|
||||
localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
|
||||
localExtraInfo, precisionPicking);
|
||||
if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) {
|
||||
parabolicDistance = distanceToElementDetails;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
extraInfo = localExtraInfo;
|
||||
result = entityID;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard,
|
||||
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) {
|
||||
|
||||
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
|
||||
int entityNumber = 0;
|
||||
EntityItemID entityID;
|
||||
forEachEntity([&](EntityItemPointer entity) {
|
||||
// use simple line-sphere for broadphase check
|
||||
// (this is faster and more likely to cull results than the filter check below so we do it first)
|
||||
bool success;
|
||||
AABox entityBox = entity->getAABox(success);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane
|
||||
// defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic,
|
||||
// the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection
|
||||
// below
|
||||
if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check RayPick filter settings
|
||||
if ((visibleOnly && !entity->isVisible())
|
||||
|| (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|
||||
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|
||||
|| (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// extents is the entity relative, scaled, centered extents of the entity
|
||||
glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation());
|
||||
glm::mat4 translation = glm::translate(entity->getWorldPosition());
|
||||
glm::mat4 entityToWorldMatrix = translation * rotation;
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
|
||||
glm::vec3 dimensions = entity->getRaycastDimensions();
|
||||
glm::vec3 registrationPoint = entity->getRegistrationPoint();
|
||||
glm::vec3 corner = -(dimensions * registrationPoint);
|
||||
|
||||
AABox entityFrameBox(corner, dimensions);
|
||||
|
||||
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
|
||||
glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
|
||||
|
||||
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
|
||||
// and testing intersection there.
|
||||
float localDistance;
|
||||
BoxFace localFace;
|
||||
glm::vec3 localSurfaceNormal;
|
||||
if (entityFrameBox.findParabolaIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, localDistance,
|
||||
localFace, localSurfaceNormal)) {
|
||||
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < parabolicDistance) {
|
||||
// now ask the entity if we actually intersect
|
||||
if (entity->supportsDetailedIntersection()) {
|
||||
QVariantMap localExtraInfo;
|
||||
if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance,
|
||||
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
|
||||
if (localDistance < parabolicDistance) {
|
||||
parabolicDistance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
extraInfo = localExtraInfo;
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if the entity type doesn't support a detailed intersection, then just return the non-AABox results
|
||||
// Never intersect with particle entities
|
||||
if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) {
|
||||
parabolicDistance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f));
|
||||
extraInfo = QVariantMap();
|
||||
entityID = entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entityNumber++;
|
||||
});
|
||||
return entityID;
|
||||
}
|
||||
|
||||
EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const {
|
||||
EntityItemPointer closestEntity = NULL;
|
||||
float closestEntityDistance = FLT_MAX;
|
||||
|
|
|
@ -134,9 +134,9 @@ public:
|
|||
virtual bool isRendered() const override { return getShouldRender(); }
|
||||
virtual bool deleteApproved() const override { return !hasEntities(); }
|
||||
|
||||
virtual bool canRayIntersect() const override { return hasEntities(); }
|
||||
virtual bool canPickIntersect() const override { return hasEntities(); }
|
||||
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& node, float& distance,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
||||
|
@ -148,6 +148,16 @@ public:
|
|||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const override;
|
||||
|
||||
virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking = false);
|
||||
virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
|
||||
QVariantMap& extraInfo, bool precisionPicking);
|
||||
|
||||
template <typename F>
|
||||
void forEachEntity(F f) const {
|
||||
|
|
|
@ -309,3 +309,14 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
return _lightsArePickable;
|
||||
}
|
||||
|
||||
bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state
|
||||
// this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could
|
||||
// be on the clipboard and someone might be trying to use the parabola intersection API there. Anyway... if you ever try to
|
||||
// do parabola intersection testing off of trees other than the main tree of the main entity renderer, then we'll need to
|
||||
// fix this mechanism.
|
||||
return _lightsArePickable;
|
||||
}
|
||||
|
|
|
@ -84,11 +84,15 @@ public:
|
|||
bool lightPropertiesChanged() const { return _lightPropertiesChanged; }
|
||||
void resetLightPropertiesChanged();
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
private:
|
||||
// properties of a light
|
||||
|
|
|
@ -58,12 +58,17 @@ class LineEntityItem : public EntityItem {
|
|||
virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
|
||||
|
||||
// never have a ray intersection pick a LineEntityItem.
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo,
|
||||
bool precisionPicking) const override { return false; }
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo,
|
||||
bool precisionPicking) const override { return false; }
|
||||
bool pointsChanged() const { return _pointsChanged; }
|
||||
void resetPointsChanged();
|
||||
virtual void debugDump() const override;
|
||||
|
|
|
@ -91,6 +91,8 @@ bool operator==(const Properties& a, const Properties& b) {
|
|||
(a.color == b.color) &&
|
||||
(a.alpha == b.alpha) &&
|
||||
(a.radius == b.radius) &&
|
||||
(a.spin == b.spin) &&
|
||||
(a.rotateWithEntity == b.rotateWithEntity) &&
|
||||
(a.radiusStart == b.radiusStart) &&
|
||||
(a.lifespan == b.lifespan) &&
|
||||
(a.maxParticles == b.maxParticles) &&
|
||||
|
@ -130,7 +132,11 @@ bool Properties::valid() const {
|
|||
(radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS));
|
||||
(radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN));
|
||||
}
|
||||
|
||||
bool Properties::emitting() const {
|
||||
|
@ -332,6 +338,43 @@ void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) {
|
|||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setParticleSpin(float particleSpin) {
|
||||
particleSpin = glm::clamp(particleSpin, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN);
|
||||
if (particleSpin != _particleProperties.spin.gradient.target) {
|
||||
withWriteLock([&] {
|
||||
_particleProperties.spin.gradient.target = particleSpin;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setSpinStart(float spinStart) {
|
||||
spinStart =
|
||||
glm::isnan(spinStart) ? spinStart : glm::clamp(spinStart, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN);
|
||||
if (spinStart != _particleProperties.spin.range.start) {
|
||||
withWriteLock([&] {
|
||||
_particleProperties.spin.range.start = spinStart;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setSpinFinish(float spinFinish) {
|
||||
spinFinish =
|
||||
glm::isnan(spinFinish) ? spinFinish : glm::clamp(spinFinish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN);
|
||||
if (spinFinish != _particleProperties.spin.range.finish) {
|
||||
withWriteLock([&] {
|
||||
_particleProperties.spin.range.finish = spinFinish;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setSpinSpread(float spinSpread) {
|
||||
spinSpread = glm::clamp(spinSpread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN);
|
||||
if (spinSpread != _particleProperties.spin.gradient.spread) {
|
||||
withWriteLock([&] {
|
||||
_particleProperties.spin.gradient.spread = spinSpread;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::computeAndUpdateDimensions() {
|
||||
particle::Properties particleProperties;
|
||||
|
@ -398,6 +441,11 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinFinish, getSpinFinish);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotateWithEntity, getRotateWithEntity);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
@ -435,6 +483,11 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotateWithEntity, setRotateWithEntity);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -515,6 +568,12 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
|
||||
READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail);
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin);
|
||||
READ_ENTITY_PROPERTY(PROP_SPIN_SPREAD, float, setSpinSpread);
|
||||
READ_ENTITY_PROPERTY(PROP_SPIN_START, float, setSpinStart);
|
||||
READ_ENTITY_PROPERTY(PROP_SPIN_FINISH, float, setSpinFinish);
|
||||
READ_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -551,6 +610,11 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea
|
|||
requestedProperties += PROP_AZIMUTH_START;
|
||||
requestedProperties += PROP_AZIMUTH_FINISH;
|
||||
requestedProperties += PROP_EMITTER_SHOULD_TRAIL;
|
||||
requestedProperties += PROP_PARTICLE_SPIN;
|
||||
requestedProperties += PROP_SPIN_SPREAD;
|
||||
requestedProperties += PROP_SPIN_START;
|
||||
requestedProperties += PROP_SPIN_FINISH;
|
||||
requestedProperties += PROP_PARTICLE_ROTATE_WITH_ENTITY;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
@ -594,6 +658,11 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData,
|
|||
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart());
|
||||
APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart());
|
||||
APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, getSpinFinish());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, getRotateWithEntity());
|
||||
}
|
||||
|
||||
|
||||
|
@ -665,6 +734,12 @@ void ParticleEffectEntityItem::setEmitterShouldTrail(bool emitterShouldTrail) {
|
|||
});
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::setRotateWithEntity(bool rotateWithEntity) {
|
||||
withWriteLock([&] {
|
||||
_particleProperties.rotateWithEntity = rotateWithEntity;
|
||||
});
|
||||
}
|
||||
|
||||
particle::Properties ParticleEffectEntityItem::getParticleProperties() const {
|
||||
particle::Properties result;
|
||||
withReadLock([&] {
|
||||
|
@ -689,6 +764,12 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const {
|
|||
if (glm::isnan(result.radius.range.finish)) {
|
||||
result.radius.range.finish = getParticleRadius();
|
||||
}
|
||||
if (glm::isnan(result.spin.range.start)) {
|
||||
result.spin.range.start = getParticleSpin();
|
||||
}
|
||||
if (glm::isnan(result.spin.range.finish)) {
|
||||
result.spin.range.finish = getParticleSpin();
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.valid()) {
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace particle {
|
|||
static const float MINIMUM_EMIT_RATE = 0.0f;
|
||||
static const float MAXIMUM_EMIT_RATE = 100000.0f;
|
||||
static const float DEFAULT_EMIT_SPEED = 5.0f;
|
||||
static const float MINIMUM_EMIT_SPEED = 0.0f;
|
||||
static const float MINIMUM_EMIT_SPEED = -1000.0f;
|
||||
static const float MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3
|
||||
static const float DEFAULT_SPEED_SPREAD = 1.0f;
|
||||
static const glm::quat DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical
|
||||
|
@ -69,8 +69,15 @@ namespace particle {
|
|||
static const float DEFAULT_RADIUS_SPREAD = 0.0f;
|
||||
static const float DEFAULT_RADIUS_START = UNINITIALIZED;
|
||||
static const float DEFAULT_RADIUS_FINISH = UNINITIALIZED;
|
||||
static const float DEFAULT_PARTICLE_SPIN = 0.0f;
|
||||
static const float DEFAULT_SPIN_START = UNINITIALIZED;
|
||||
static const float DEFAULT_SPIN_FINISH = UNINITIALIZED;
|
||||
static const float DEFAULT_SPIN_SPREAD = 0.0f;
|
||||
static const float MINIMUM_PARTICLE_SPIN = -2.0f * SCRIPT_MAXIMUM_PI;
|
||||
static const float MAXIMUM_PARTICLE_SPIN = 2.0f * SCRIPT_MAXIMUM_PI;
|
||||
static const QString DEFAULT_TEXTURES = "";
|
||||
static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false;
|
||||
static const bool DEFAULT_ROTATE_WITH_ENTITY = false;
|
||||
|
||||
template <typename T>
|
||||
struct Range {
|
||||
|
@ -151,6 +158,8 @@ namespace particle {
|
|||
RangeGradient<float> alpha { DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD };
|
||||
float radiusStart { DEFAULT_EMIT_RADIUS_START };
|
||||
RangeGradient<float> radius { DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD };
|
||||
RangeGradient<float> spin { DEFAULT_PARTICLE_SPIN, DEFAULT_SPIN_START, DEFAULT_SPIN_FINISH, DEFAULT_SPIN_SPREAD };
|
||||
bool rotateWithEntity { DEFAULT_ROTATE_WITH_ENTITY };
|
||||
float lifespan { DEFAULT_LIFESPAN };
|
||||
uint32_t maxParticles { DEFAULT_MAX_PARTICLES };
|
||||
EmitProperties emission;
|
||||
|
@ -168,6 +177,8 @@ namespace particle {
|
|||
Properties& operator =(const Properties& other) {
|
||||
color = other.color;
|
||||
alpha = other.alpha;
|
||||
spin = other.spin;
|
||||
rotateWithEntity = other.rotateWithEntity;
|
||||
radius = other.radius;
|
||||
lifespan = other.lifespan;
|
||||
maxParticles = other.maxParticles;
|
||||
|
@ -306,6 +317,21 @@ public:
|
|||
void setRadiusSpread(float radiusSpread);
|
||||
float getRadiusSpread() const { return _particleProperties.radius.gradient.spread; }
|
||||
|
||||
void setParticleSpin(float particleSpin);
|
||||
float getParticleSpin() const { return _particleProperties.spin.gradient.target; }
|
||||
|
||||
void setSpinStart(float spinStart);
|
||||
float getSpinStart() const { return _particleProperties.spin.range.start; }
|
||||
|
||||
void setSpinFinish(float spinFinish);
|
||||
float getSpinFinish() const { return _particleProperties.spin.range.finish; }
|
||||
|
||||
void setSpinSpread(float spinSpread);
|
||||
float getSpinSpread() const { return _particleProperties.spin.gradient.spread; }
|
||||
|
||||
void setRotateWithEntity(bool rotateWithEntity);
|
||||
bool getRotateWithEntity() const { return _particleProperties.rotateWithEntity; }
|
||||
|
||||
void computeAndUpdateDimensions();
|
||||
|
||||
void setTextures(const QString& textures);
|
||||
|
@ -314,7 +340,7 @@ public:
|
|||
bool getEmitterShouldTrail() const { return _particleProperties.emission.shouldTrail; }
|
||||
void setEmitterShouldTrail(bool emitterShouldTrail);
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override { return false; }
|
||||
virtual bool supportsDetailedIntersection() const override { return false; }
|
||||
|
||||
particle::Properties getParticleProperties() const;
|
||||
|
||||
|
|
|
@ -89,11 +89,15 @@ class PolyLineEntityItem : public EntityItem {
|
|||
|
||||
|
||||
// never have a ray intersection pick a PolyLineEntityItem.
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
// disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain
|
||||
virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious!
|
||||
|
|
|
@ -42,11 +42,15 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
bool& somethingChanged) override;
|
||||
|
||||
// never have a ray intersection pick a PolyVoxEntityItem.
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
|
||||
|
||||
virtual void debugDump() const override;
|
||||
|
||||
|
|
|
@ -250,7 +250,7 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
|||
}
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::supportsDetailedRayIntersection() const {
|
||||
bool ShapeEntityItem::supportsDetailedIntersection() const {
|
||||
return _shape == entity::Sphere;
|
||||
}
|
||||
|
||||
|
@ -273,6 +273,7 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
|
||||
distance = glm::distance(origin, hitAt);
|
||||
bool success;
|
||||
// FIXME: this is only correct for uniformly scaled spheres
|
||||
surfaceNormal = glm::normalize(hitAt - getCenterPosition(success));
|
||||
if (!success) {
|
||||
return false;
|
||||
|
@ -282,6 +283,30 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
// determine the parabola in the frame of the entity transformed from a unit sphere
|
||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
|
||||
glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
|
||||
|
||||
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
||||
if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) {
|
||||
bool success;
|
||||
// FIXME: this is only correct for uniformly scaled spheres
|
||||
surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
|
|
|
@ -90,11 +90,15 @@ public:
|
|||
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool supportsDetailedRayIntersection() const override;
|
||||
bool supportsDetailedIntersection() const override;
|
||||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
void debugDump() const override;
|
||||
|
||||
|
|
|
@ -127,17 +127,55 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
|||
}
|
||||
|
||||
bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition() + rotation *
|
||||
(dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
|
||||
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
|
||||
|
||||
// FIXME - should set face and surfaceNormal
|
||||
return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance);
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (glm::dot(forward, direction) > 0.0f) {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
} else {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
|
||||
|
||||
glm::quat inverseRot = glm::inverse(rotation);
|
||||
glm::vec3 localOrigin = inverseRot * (origin - position);
|
||||
glm::vec3 localVelocity = inverseRot * velocity;
|
||||
glm::vec3 localAcceleration = inverseRot * acceleration;
|
||||
|
||||
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
|
||||
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (localIntersectionVelocityZ > 0.0f) {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
} else {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextEntityItem::setText(const QString& value) {
|
||||
|
|
|
@ -45,11 +45,15 @@ public:
|
|||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) override;
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool supportsDetailedIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
||||
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const override;
|
||||
|
||||
static const QString DEFAULT_TEXT;
|
||||
void setText(const QString& value);
|
||||
|
|
|
@ -113,8 +113,44 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
|
|||
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
|
||||
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
|
||||
surfaceNormal = rotation * Vectors::UNIT_Z;
|
||||
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (glm::dot(forward, direction) > 0.0f) {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
} else {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
||||
OctreeElementPointer& element, float& parabolicDistance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QVariantMap& extraInfo, bool precisionPicking) const {
|
||||
glm::vec3 dimensions = getScaledDimensions();
|
||||
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
|
||||
|
||||
glm::quat inverseRot = glm::inverse(rotation);
|
||||
glm::vec3 localOrigin = inverseRot * (origin - position);
|
||||
glm::vec3 localVelocity = inverseRot * velocity;
|
||||
glm::vec3 localAcceleration = inverseRot * acceleration;
|
||||
|
||||
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
|
||||
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
|
||||
glm::vec3 forward = rotation * Vectors::FRONT;
|
||||
if (localIntersectionVelocityZ > 0.0f) {
|
||||
face = MIN_Z_FACE;
|
||||
surfaceNormal = forward;
|
||||
} else {
|
||||
face = MAX_Z_FACE;
|
||||
surfaceNormal = -forward;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue