mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 17:17:58 +02:00
commit
de7738e644
7 changed files with 303 additions and 127 deletions
|
@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
cycleCamera();
|
cycleCamera();
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
||||||
if (!offscreenUi->navigationFocused()) {
|
if (!offscreenUi->navigationFocused()) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
|
||||||
if (!offscreenUi->navigationFocused()) {
|
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
|
||||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||||
|
@ -1240,7 +1233,16 @@ QString Application::getUserAgent() {
|
||||||
return userAgent;
|
return userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::toggleMenuUnderReticle() const {
|
||||||
|
// In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly
|
||||||
|
// different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both
|
||||||
|
// on the menu and off.
|
||||||
|
// Even in 2D, it is arguable whether the user would want the menu to be to the side.
|
||||||
|
const float X_LEFT_SHIFT = 50.0;
|
||||||
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||||
|
offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));
|
||||||
|
}
|
||||||
|
|
||||||
void Application::checkChangeCursor() {
|
void Application::checkChangeCursor() {
|
||||||
QMutexLocker locker(&_changeCursorLock);
|
QMutexLocker locker(&_changeCursorLock);
|
||||||
|
@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
|
|
||||||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
toggleMenuUnderReticle();
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_keysPressed.remove(event->key());
|
_keysPressed.remove(event->key());
|
||||||
|
|
|
@ -408,6 +408,7 @@ private:
|
||||||
static void dragEnterEvent(QDragEnterEvent* event);
|
static void dragEnterEvent(QDragEnterEvent* event);
|
||||||
|
|
||||||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||||
|
void toggleMenuUnderReticle() const;
|
||||||
|
|
||||||
MainWindow* _window;
|
MainWindow* _window;
|
||||||
QElapsedTimer& _sessionRunTimer;
|
QElapsedTimer& _sessionRunTimer;
|
||||||
|
|
|
@ -118,10 +118,12 @@ void OverlayConductor::update(float dt) {
|
||||||
bool isDriving = updateAvatarHasDriveInput();
|
bool isDriving = updateAvatarHasDriveInput();
|
||||||
bool drivingChanged = prevDriving != isDriving;
|
bool drivingChanged = prevDriving != isDriving;
|
||||||
bool isAtRest = updateAvatarIsAtRest();
|
bool isAtRest = updateAvatarIsAtRest();
|
||||||
|
bool shouldRecenter = false;
|
||||||
|
|
||||||
if (_flags & SuppressedByDrive) {
|
if (_flags & SuppressedByDrive) {
|
||||||
if (!isDriving) {
|
if (!isDriving) {
|
||||||
_flags &= ~SuppressedByDrive;
|
_flags &= ~SuppressedByDrive;
|
||||||
|
shouldRecenter = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
||||||
|
@ -132,6 +134,7 @@ void OverlayConductor::update(float dt) {
|
||||||
if (_flags & SuppressedByHead) {
|
if (_flags & SuppressedByHead) {
|
||||||
if (isAtRest) {
|
if (isAtRest) {
|
||||||
_flags &= ~SuppressedByHead;
|
_flags &= ~SuppressedByHead;
|
||||||
|
shouldRecenter = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_hmdMode && headOutsideOverlay()) {
|
if (_hmdMode && headOutsideOverlay()) {
|
||||||
|
@ -143,8 +146,8 @@ void OverlayConductor::update(float dt) {
|
||||||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||||
if (targetVisible != currentVisible) {
|
if (targetVisible != currentVisible) {
|
||||||
offscreenUi->setPinned(!targetVisible);
|
offscreenUi->setPinned(!targetVisible);
|
||||||
if (targetVisible && _hmdMode) {
|
}
|
||||||
centerUI();
|
if (shouldRecenter && !_flags) {
|
||||||
}
|
centerUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() {
|
||||||
Q_ASSERT(invokeResult);
|
Q_ASSERT(invokeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenUi::toggleMenu(const QPoint& screenPosition) {
|
void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition
|
||||||
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
||||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition));
|
||||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,9 @@ var THUMB_ON_VALUE = 0.5;
|
||||||
var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move.
|
var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move.
|
||||||
|
|
||||||
var PICK_WITH_HAND_RAY = true;
|
var PICK_WITH_HAND_RAY = true;
|
||||||
|
|
||||||
|
var DRAW_GRAB_BOXES = false;
|
||||||
|
var DRAW_HAND_SPHERES = false;
|
||||||
var DROP_WITHOUT_SHAKE = false;
|
var DROP_WITHOUT_SHAKE = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -67,16 +70,22 @@ var LINE_ENTITY_DIMENSIONS = {
|
||||||
|
|
||||||
var LINE_LENGTH = 500;
|
var LINE_LENGTH = 500;
|
||||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||||
|
|
||||||
//
|
//
|
||||||
// near grabbing
|
// near grabbing
|
||||||
//
|
//
|
||||||
|
|
||||||
var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected
|
var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping.
|
||||||
|
|
||||||
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
|
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
|
||||||
var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected
|
|
||||||
|
var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing.
|
||||||
|
var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand
|
||||||
|
|
||||||
|
var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing.
|
||||||
|
|
||||||
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
|
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
|
||||||
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
|
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
|
||||||
var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size
|
|
||||||
var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
|
var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -253,7 +262,8 @@ function restore2DMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// constructor
|
// EntityPropertiesCache is a helper class that contains a cache of entity properties.
|
||||||
|
// the hope is to prevent excess calls to Entity.getEntityProperties()
|
||||||
function EntityPropertiesCache() {
|
function EntityPropertiesCache() {
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
}
|
}
|
||||||
|
@ -264,34 +274,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) {
|
||||||
var entities = Entities.findEntities(position, radius);
|
var entities = Entities.findEntities(position, radius);
|
||||||
var _this = this;
|
var _this = this;
|
||||||
entities.forEach(function (x) {
|
entities.forEach(function (x) {
|
||||||
_this.addEntity(x);
|
_this.updateEntity(x);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.addEntity = function(entityID) {
|
EntityPropertiesCache.prototype.updateEntity = function(entityID) {
|
||||||
var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES);
|
var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES);
|
||||||
var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA);
|
|
||||||
var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
// convert props.userData from a string to an object.
|
||||||
var wearableProps = getEntityCustomData("wearable", entityID, {});
|
var userData = {};
|
||||||
this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps };
|
if (props.userData) {
|
||||||
|
try {
|
||||||
|
userData = JSON.parse(props.userData);
|
||||||
|
} catch(err) {
|
||||||
|
print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.userData = userData;
|
||||||
|
|
||||||
|
this.cache[entityID] = props;
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getEntities = function() {
|
EntityPropertiesCache.prototype.getEntities = function() {
|
||||||
return Object.keys(this.cache);
|
return Object.keys(this.cache);
|
||||||
}
|
}
|
||||||
EntityPropertiesCache.prototype.getProps = function(entityID) {
|
EntityPropertiesCache.prototype.getProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var obj = this.cache[entityID]
|
||||||
return obj ? obj.props : undefined;
|
return obj ? obj : undefined;
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) {
|
EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.grabbableProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getGrabProps = function(entityID) {
|
EntityPropertiesCache.prototype.getGrabProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.grabProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.grabKey ? props.userData.grabKey : {};
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getWearableProps = function(entityID) {
|
EntityPropertiesCache.prototype.getWearableProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.wearableProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.wearable ? props.userData.wearable : {};
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function MyController(hand) {
|
function MyController(hand) {
|
||||||
|
@ -322,7 +353,6 @@ function MyController(hand) {
|
||||||
//for visualizations
|
//for visualizations
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
this.particleBeamObject = null;
|
this.particleBeamObject = null;
|
||||||
this.grabSphere = null;
|
|
||||||
|
|
||||||
//for lights
|
//for lights
|
||||||
this.spotlight = null;
|
this.spotlight = null;
|
||||||
|
@ -383,7 +413,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState = function(newState, reason) {
|
this.setState = function(newState, reason) {
|
||||||
this.grabSphereOff();
|
|
||||||
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
||||||
var oldStateName = stateToName(this.state);
|
var oldStateName = stateToName(this.state);
|
||||||
var newStateName = stateToName(newState);
|
var newStateName = stateToName(newState);
|
||||||
|
@ -490,39 +520,6 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.grabSphereOn = function() {
|
|
||||||
var color = {red: 0, green: 255, blue: 0};
|
|
||||||
if (this.grabSphere === null) {
|
|
||||||
var sphereProperties = {
|
|
||||||
position: this.getHandPosition(),
|
|
||||||
size: GRAB_RADIUS*2,
|
|
||||||
color: color,
|
|
||||||
alpha: 0.1,
|
|
||||||
solid: true,
|
|
||||||
ignoreRayIntersection: true,
|
|
||||||
drawInFront: true, // Even when burried inside of something, show it.
|
|
||||||
visible: true
|
|
||||||
}
|
|
||||||
this.grabSphere = Overlays.addOverlay("sphere", sphereProperties);
|
|
||||||
} else {
|
|
||||||
Overlays.editOverlay(this.grabSphere, {
|
|
||||||
position: this.getHandPosition(),
|
|
||||||
size: GRAB_RADIUS*2,
|
|
||||||
color: color,
|
|
||||||
alpha: 0.1,
|
|
||||||
solid: true,
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.grabSphereOff = function() {
|
|
||||||
if (this.grabSphere !== null) {
|
|
||||||
Overlays.deleteOverlay(this.grabSphere);
|
|
||||||
this.grabSphere = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.overlayLineOn = function(closePoint, farPoint, color) {
|
this.overlayLineOn = function(closePoint, farPoint, color) {
|
||||||
if (this.overlayLine === null) {
|
if (this.overlayLine === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
|
@ -872,43 +869,147 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.searchEnter = function() {
|
this.createHotspots = function () {
|
||||||
this.equipHotspotOverlays = [];
|
var props, overlay;
|
||||||
|
|
||||||
|
var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 };
|
||||||
|
var HAND_EQUIP_SPHERE_ALPHA = 0.7;
|
||||||
|
var HAND_EQUIP_SPHERE_RADIUS = 0.01;
|
||||||
|
|
||||||
|
var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 };
|
||||||
|
var HAND_GRAB_SPHERE_ALPHA = 0.3;
|
||||||
|
var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS;
|
||||||
|
|
||||||
|
var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 };
|
||||||
|
var EQUIP_SPHERE_ALPHA = 0.3;
|
||||||
|
|
||||||
|
var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 };
|
||||||
|
var GRAB_BOX_ALPHA = 0.1;
|
||||||
|
|
||||||
|
this.hotspotOverlays = [];
|
||||||
|
|
||||||
|
if (DRAW_HAND_SPHERES) {
|
||||||
|
// add tiny green sphere around the palm.
|
||||||
|
var handPosition = this.getHandPosition();
|
||||||
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
|
position: handPosition,
|
||||||
|
size: HAND_EQUIP_SPHERE_RADIUS * 2,
|
||||||
|
color: HAND_EQUIP_SPHERE_COLOR,
|
||||||
|
alpha: HAND_EQUIP_SPHERE_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hotspotOverlays.push({
|
||||||
|
entityID: undefined,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "hand"
|
||||||
|
});
|
||||||
|
|
||||||
|
// add larger blue sphere around the palm.
|
||||||
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
|
position: handPosition,
|
||||||
|
size: HAND_GRAB_SPHERE_RADIUS * 2,
|
||||||
|
color: HAND_GRAB_SPHERE_COLOR,
|
||||||
|
alpha: HAND_GRAB_SPHERE_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hotspotOverlays.push({
|
||||||
|
entityID: undefined,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "hand"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// find entities near the avatar that might be equipable.
|
// find entities near the avatar that might be equipable.
|
||||||
var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
this.entityPropertyCache.clear();
|
||||||
var i, length = entities.length;
|
this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
||||||
for (i = 0; i < length; i++) {
|
|
||||||
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
|
var _this = this;
|
||||||
// does this entity have an attach point?
|
this.entityPropertyCache.getEntities().forEach(function (entityID) {
|
||||||
var wearableData = getEntityCustomData("wearable", entities[i], undefined);
|
if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) {
|
||||||
if (wearableData && wearableData.joints) {
|
props = _this.entityPropertyCache.getProps(entityID);
|
||||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
|
||||||
if (wearableData.joints[handJointName]) {
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
// draw the hotspot
|
rotation: props.rotation,
|
||||||
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
|
position: props.position,
|
||||||
position: grabProps.position,
|
size: EQUIP_RADIUS * 2,
|
||||||
size: 0.2,
|
color: EQUIP_SPHERE_COLOR,
|
||||||
color: { red: 90, green: 255, blue: 90 },
|
alpha: EQUIP_SPHERE_ALPHA,
|
||||||
alpha: 0.7,
|
solid: true,
|
||||||
solid: true,
|
visible: true,
|
||||||
visible: true,
|
ignoreRayIntersection: true,
|
||||||
ignoreRayIntersection: false,
|
drawInFront: false
|
||||||
drawInFront: false
|
});
|
||||||
}));
|
|
||||||
}
|
_this.hotspotOverlays.push({
|
||||||
|
entityID: entityID,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "equip"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) {
|
||||||
|
props = _this.entityPropertyCache.getProps(entityID);
|
||||||
|
|
||||||
|
overlay = Overlays.addOverlay("cube", {
|
||||||
|
rotation: props.rotation,
|
||||||
|
position: props.position,
|
||||||
|
size: props.dimensions, //{x: props.dimensions.x, y: props.dimensions.y, z: props.dimensions.z},
|
||||||
|
color: GRAB_BOX_COLOR,
|
||||||
|
alpha: GRAB_BOX_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
_this.hotspotOverlays.push({
|
||||||
|
entityID: entityID,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "near"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateHotspots = function() {
|
||||||
|
var _this = this;
|
||||||
|
var props;
|
||||||
|
this.hotspotOverlays.forEach(function (overlayInfo) {
|
||||||
|
if (overlayInfo.type === "hand") {
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() });
|
||||||
|
} else if (overlayInfo.type === "equip") {
|
||||||
|
_this.entityPropertyCache.updateEntity(overlayInfo.entityID);
|
||||||
|
props = _this.entityPropertyCache.getProps(overlayInfo.entityID);
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation });
|
||||||
|
} else if (overlayInfo.type === "near") {
|
||||||
|
_this.entityPropertyCache.updateEntity(overlayInfo.entityID);
|
||||||
|
props = _this.entityPropertyCache.getProps(overlayInfo.entityID);
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.destroyHotspots = function() {
|
||||||
|
this.hotspotOverlays.forEach(function (overlayInfo) {
|
||||||
|
Overlays.deleteOverlay(overlayInfo.overlay);
|
||||||
|
});
|
||||||
|
this.hotspotOverlays = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
this.searchEnter = function() {
|
||||||
|
this.createHotspots();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.searchExit = function() {
|
this.searchExit = function() {
|
||||||
|
this.destroyHotspots();
|
||||||
// delete all equip hotspots
|
|
||||||
var i, l = this.equipHotspotOverlays.length;
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
Overlays.deleteOverlay(this.equipHotspotOverlays[i]);
|
|
||||||
}
|
|
||||||
this.equipHotspotOverlays = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -975,7 +1076,13 @@ function MyController(hand) {
|
||||||
return grabbableProps && grabbableProps.wantsTrigger;
|
return grabbableProps && grabbableProps.wantsTrigger;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entityIsEquippable = function (entityID, handPosition) {
|
this.entityIsEquippableWithoutDistanceCheck = function (entityID) {
|
||||||
|
var props = this.entityPropertyCache.getProps(entityID);
|
||||||
|
var handPosition = props.position;
|
||||||
|
return this.entityIsEquippableWithDistanceCheck(entityID, handPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition) {
|
||||||
var props = this.entityPropertyCache.getProps(entityID);
|
var props = this.entityPropertyCache.getProps(entityID);
|
||||||
var distance = Vec3.distance(props.position, handPosition);
|
var distance = Vec3.distance(props.position, handPosition);
|
||||||
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
||||||
|
@ -989,9 +1096,9 @@ function MyController(hand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distance > NEAR_PICK_MAX_DISTANCE) {
|
if (distance > EQUIP_RADIUS) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print("equip is skipping '" + props.name + "': too far away.");
|
print("equip is skipping '" + props.name + "': too far away, " + distance + " meters");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1104,7 +1211,7 @@ function MyController(hand) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entityIsNearGrabbable = function(entityID, handPosition) {
|
this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) {
|
||||||
|
|
||||||
if (!this.entityIsGrabbable(entityID)) {
|
if (!this.entityIsGrabbable(entityID)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1114,7 +1221,7 @@ function MyController(hand) {
|
||||||
var distance = Vec3.distance(props.position, handPosition);
|
var distance = Vec3.distance(props.position, handPosition);
|
||||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||||
|
|
||||||
if (distance > NEAR_PICK_MAX_DISTANCE) {
|
if (distance > maxDistance) {
|
||||||
// too far away, don't grab
|
// too far away, don't grab
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print(" grab is skipping '" + props.name + "': too far away.");
|
print(" grab is skipping '" + props.name + "': too far away.");
|
||||||
|
@ -1129,6 +1236,8 @@ function MyController(hand) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var name;
|
var name;
|
||||||
|
|
||||||
|
this.updateHotspots();
|
||||||
|
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
this.isInitialGrab = false;
|
this.isInitialGrab = false;
|
||||||
this.shouldResetParentOnRelease = false;
|
this.shouldResetParentOnRelease = false;
|
||||||
|
@ -1142,16 +1251,12 @@ function MyController(hand) {
|
||||||
|
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
|
|
||||||
if (SHOW_GRAB_SPHERE) {
|
|
||||||
this.grabSphereOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entityPropertyCache.clear();
|
this.entityPropertyCache.clear();
|
||||||
this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS);
|
this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
var candidateEntities = this.entityPropertyCache.getEntities();
|
var candidateEntities = this.entityPropertyCache.getEntities();
|
||||||
|
|
||||||
var equippableEntities = candidateEntities.filter(function (entity) {
|
var equippableEntities = candidateEntities.filter(function (entity) {
|
||||||
return _this.entityIsEquippable(entity, handPosition);
|
return _this.entityIsEquippableWithDistanceCheck(entity, handPosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
var entity;
|
var entity;
|
||||||
|
@ -1172,19 +1277,21 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var grabbableEntities = candidateEntities.filter(function (entity) {
|
||||||
|
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
|
||||||
|
});
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
if (rayPickInfo.entityID) {
|
if (rayPickInfo.entityID) {
|
||||||
candidateEntities.push(rayPickInfo.entityID);
|
|
||||||
this.entityPropertyCache.addEntity(rayPickInfo.entityID);
|
|
||||||
this.intersectionDistance = rayPickInfo.distance;
|
this.intersectionDistance = rayPickInfo.distance;
|
||||||
|
this.entityPropertyCache.updateEntity(rayPickInfo.entityID);
|
||||||
|
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||||
|
grabbableEntities.push(rayPickInfo.entityID);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.intersectionDistance = 0;
|
this.intersectionDistance = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var grabbableEntities = candidateEntities.filter(function (entity) {
|
|
||||||
return _this.entityIsNearGrabbable(entity, handPosition);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (grabbableEntities.length > 0) {
|
if (grabbableEntities.length > 0) {
|
||||||
// sort by distance
|
// sort by distance
|
||||||
grabbableEntities.sort(function (a, b) {
|
grabbableEntities.sort(function (a, b) {
|
||||||
|
@ -1725,11 +1832,11 @@ function MyController(hand) {
|
||||||
this.lastUnequipCheckTime = now;
|
this.lastUnequipCheckTime = now;
|
||||||
|
|
||||||
if (props.parentID == MyAvatar.sessionUUID &&
|
if (props.parentID == MyAvatar.sessionUUID &&
|
||||||
Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) {
|
Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) {
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
||||||
// need to fail the findEntities test.
|
// need to fail the findEntities test.
|
||||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
||||||
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
||||||
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
|
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
|
||||||
|
@ -2175,17 +2282,32 @@ function cleanup() {
|
||||||
Script.scriptEnding.connect(cleanup);
|
Script.scriptEnding.connect(cleanup);
|
||||||
Script.update.connect(update);
|
Script.update.connect(update);
|
||||||
|
|
||||||
|
if (!Menu.menuExists("Developer > Grab Script")) {
|
||||||
|
Menu.addMenu("Developer > Grab Script");
|
||||||
|
}
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Developer > Hands",
|
menuName: "Developer > Grab Script",
|
||||||
menuItemName: "Drop Without Shake",
|
menuItemName: "Drop Without Shake",
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: DROP_WITHOUT_SHAKE
|
isChecked: DROP_WITHOUT_SHAKE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Menu.addMenuItem({
|
||||||
|
menuName: "Developer > Grab Script",
|
||||||
|
menuItemName: "Draw Grab Boxes",
|
||||||
|
isCheckable: true,
|
||||||
|
isChecked: DRAW_GRAB_BOXES
|
||||||
|
});
|
||||||
|
|
||||||
function handleMenuItemEvent(menuItem) {
|
function handleMenuItemEvent(menuItem) {
|
||||||
if (menuItem === "Drop Without Shake") {
|
if (menuItem === "Drop Without Shake") {
|
||||||
DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake");
|
DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake");
|
||||||
}
|
}
|
||||||
|
if (menuItem === "Draw Grab Boxes") {
|
||||||
|
DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes");
|
||||||
|
DRAW_HAND_SPHERES = DRAW_GRAB_BOXES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.menuItemEvent.connect(handleMenuItemEvent);
|
Menu.menuItemEvent.connect(handleMenuItemEvent);
|
||||||
|
|
|
@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try
|
||||||
return isShaking;
|
return isShaking;
|
||||||
}
|
}
|
||||||
var NON_LINEAR_DIVISOR = 2;
|
var NON_LINEAR_DIVISOR = 2;
|
||||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||||
function updateSeeking() {
|
function updateSeeking(doNotStartSeeking) {
|
||||||
if (!Reticle.visible || isShakingMouse()) {
|
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
|
||||||
if (!isSeeking) {
|
if (!isSeeking) {
|
||||||
print('Start seeking mouse.');
|
print('Start seeking mouse.');
|
||||||
isSeeking = true;
|
isSeeking = true;
|
||||||
|
@ -224,8 +224,8 @@ function updateSeeking() {
|
||||||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||||
print('Cannot seek without lookAt position');
|
print('Cannot seek without lookAt position');
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
return;
|
return; // E.g., if parallel to location in HUD
|
||||||
} // E.g., if parallel to location in HUD
|
}
|
||||||
var copy = Reticle.position;
|
var copy = Reticle.position;
|
||||||
function updateDimension(axis) {
|
function updateDimension(axis) {
|
||||||
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
||||||
|
@ -353,6 +353,16 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg
|
||||||
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
||||||
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
|
clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () {
|
||||||
|
// Allow the reticle depth to be set correctly:
|
||||||
|
// Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move
|
||||||
|
// so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove.
|
||||||
|
// We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove
|
||||||
|
// after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse.
|
||||||
|
Script.setTimeout(function () {
|
||||||
|
Reticle.setPosition(Reticle.position);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
// Partial smoothed trigger is activation.
|
// Partial smoothed trigger is activation.
|
||||||
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
||||||
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
||||||
|
@ -386,6 +396,7 @@ function update() {
|
||||||
expireMouseCursor();
|
expireMouseCursor();
|
||||||
clearSystemLaser();
|
clearSystemLaser();
|
||||||
}
|
}
|
||||||
|
updateSeeking(true);
|
||||||
if (!handControllerLockOut.expired(now)) {
|
if (!handControllerLockOut.expired(now)) {
|
||||||
return off(); // Let them use mouse it in peace.
|
return off(); // Let them use mouse it in peace.
|
||||||
}
|
}
|
||||||
|
|
40
scripts/system/libraries/Xform.js
Normal file
40
scripts/system/libraries/Xform.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// Created by Anthony J. Thibault on 2016/06/21
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
// ctor
|
||||||
|
Xform = function(rot, pos) {
|
||||||
|
this.rot = rot;
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Xform.ident = function() {
|
||||||
|
return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0});
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.mul = function(lhs, rhs) {
|
||||||
|
var rot = Quat.multiply(lhs.rot, rhs.rot);
|
||||||
|
var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos));
|
||||||
|
return new Xform(rot, pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.inv = function() {
|
||||||
|
var invRot = Quat.inverse(this.rot);
|
||||||
|
var invPos = Vec3.multiply(-1, this.pos);
|
||||||
|
return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos));
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.mirrorX = function() {
|
||||||
|
return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w},
|
||||||
|
{x: -this.pos.x, y: this.pos.y, z: this.pos.z});
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.toString = function() {
|
||||||
|
var rot = this.rot;
|
||||||
|
var pos = this.pos;
|
||||||
|
return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")";
|
||||||
|
};
|
Loading…
Reference in a new issue