mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 20:34:07 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into brown
This commit is contained in:
commit
c36035d3eb
96 changed files with 2252 additions and 663 deletions
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi7.zip
|
||||
URL_MD5 bc2861e50852dd590cdc773a14a041a7
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip
|
||||
URL_MD5 b01510437ea15527156bc25cdf733bd9
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -17,26 +17,26 @@ Rectangle {
|
|||
property alias pixelSize: label.font.pixelSize;
|
||||
property bool selected: false
|
||||
property bool hovered: false
|
||||
property bool enabled: false
|
||||
property int spacing: 2
|
||||
property var action: function () {}
|
||||
property string enabledColor: hifi.colors.blueHighlight
|
||||
property string disabledColor: hifi.colors.blueHighlight
|
||||
property string highlightColor: hifi.colors.blueHighlight;
|
||||
width: label.width + 64
|
||||
height: 32
|
||||
color: hifi.colors.white
|
||||
enabled: false
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
RalewaySemiBold {
|
||||
id: label;
|
||||
color: enabledColor
|
||||
color: enabled ? enabledColor : disabledColor
|
||||
font.pixelSize: 15;
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
verticalCenter: parent.verticalCenter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
|
|
|
@ -8,6 +8,7 @@ import "../styles" as HifiStyles
|
|||
import "../styles-uit"
|
||||
import "../"
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: web
|
||||
HifiConstants { id: hifi }
|
||||
|
@ -22,17 +23,14 @@ Item {
|
|||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
property bool isDesktop: false
|
||||
property string initialPage: ""
|
||||
property bool startingUp: true
|
||||
property alias webView: webview
|
||||
property alias profile: webview.profile
|
||||
property bool remove: false
|
||||
property var urlList: []
|
||||
property var forwardList: []
|
||||
|
||||
|
||||
property int currentPage: -1 // used as a model for repeater
|
||||
property alias pagesModel: pagesModel
|
||||
// Manage own browse history because WebEngineView history is wiped when a new URL is loaded via
|
||||
// onNewViewRequested, e.g., as happens when a social media share button is clicked.
|
||||
property var history: []
|
||||
property int historyIndex: -1
|
||||
|
||||
Rectangle {
|
||||
id: buttons
|
||||
|
@ -51,21 +49,22 @@ Item {
|
|||
|
||||
TabletWebButton {
|
||||
id: back
|
||||
enabledColor: hifi.colors.baseGray
|
||||
enabled: false
|
||||
enabledColor: hifi.colors.darkGray
|
||||
disabledColor: hifi.colors.lightGrayText
|
||||
enabled: historyIndex > 0
|
||||
text: "BACK"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: goBack()
|
||||
hoverEnabled: true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TabletWebButton {
|
||||
id: close
|
||||
enabledColor: hifi.colors.darkGray
|
||||
disabledColor: hifi.colors.lightGrayText
|
||||
enabled: true
|
||||
text: "CLOSE"
|
||||
|
||||
MouseArea {
|
||||
|
@ -75,7 +74,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
RalewaySemiBold {
|
||||
id: displayUrl
|
||||
color: hifi.colors.baseGray
|
||||
|
@ -90,7 +88,6 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
|
@ -98,29 +95,10 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: pagesModel
|
||||
onCountChanged: {
|
||||
currentPage = count - 1;
|
||||
if (currentPage > 0) {
|
||||
back.enabledColor = hifi.colors.darkGray;
|
||||
} else {
|
||||
back.enabledColor = hifi.colors.baseGray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (webview.canGoBack) {
|
||||
forwardList.push(webview.url);
|
||||
webview.goBack();
|
||||
} else if (web.urlList.length > 0) {
|
||||
var url = web.urlList.pop();
|
||||
loadUrl(url);
|
||||
} else if (web.forwardList.length > 0) {
|
||||
var url = web.forwardList.pop();
|
||||
loadUrl(url);
|
||||
web.forwardList = [];
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
loadUrl(history[historyIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,19 +115,12 @@ Item {
|
|||
}
|
||||
|
||||
function goForward() {
|
||||
if (currentPage < pagesModel.count - 1) {
|
||||
currentPage++;
|
||||
if (historyIndex < history.length - 1) {
|
||||
historyIndex++;
|
||||
loadUrl(history[historyIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
function gotoPage(url) {
|
||||
urlAppend(url)
|
||||
}
|
||||
|
||||
function isUrlLoaded(url) {
|
||||
return (pagesModel.get(currentPage).webUrl === url);
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
view.reloadAndBypassCache()
|
||||
view.setActiveFocusOnPress(true);
|
||||
|
@ -161,36 +132,8 @@ Item {
|
|||
web.url = webview.url;
|
||||
}
|
||||
|
||||
function onInitialPage(url) {
|
||||
return (url === webview.url);
|
||||
}
|
||||
|
||||
|
||||
function urlAppend(url) {
|
||||
var lurl = decodeURIComponent(url)
|
||||
if (lurl[lurl.length - 1] !== "/") {
|
||||
lurl = lurl + "/"
|
||||
}
|
||||
web.urlList.push(url);
|
||||
setBackButtonStatus();
|
||||
}
|
||||
|
||||
function setBackButtonStatus() {
|
||||
if (web.urlList.length > 0 || webview.canGoBack) {
|
||||
back.enabledColor = hifi.colors.darkGray;
|
||||
back.enabled = true;
|
||||
} else {
|
||||
back.enabledColor = hifi.colors.baseGray;
|
||||
back.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
onUrlChanged: {
|
||||
loadUrl(url);
|
||||
if (startingUp) {
|
||||
web.initialPage = webview.url;
|
||||
startingUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
|
@ -258,6 +201,17 @@ Item {
|
|||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onUrlChanged: {
|
||||
// Record history, skipping null and duplicate items.
|
||||
var urlString = url + "";
|
||||
urlString = urlString.replace(/\//g, "%2F"); // Consistent representation of "/"s to avoid false differences.
|
||||
if (urlString.length > 0 && (historyIndex === -1 || urlString !== history[historyIndex])) {
|
||||
historyIndex++;
|
||||
history = history.slice(0, historyIndex);
|
||||
history.push(urlString);
|
||||
}
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
keyboardRaised = false;
|
||||
punctuationMode = false;
|
||||
|
@ -277,17 +231,11 @@ Item {
|
|||
}
|
||||
|
||||
if (WebEngineView.LoadSucceededStatus == loadRequest.status) {
|
||||
if (startingUp) {
|
||||
web.initialPage = webview.url;
|
||||
startingUp = false;
|
||||
}
|
||||
webview.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onNewViewRequested: {
|
||||
var currentUrl = webview.url;
|
||||
urlAppend(currentUrl);
|
||||
request.openIn(webview);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4331,13 +4331,6 @@ void Application::update(float deltaTime) {
|
|||
if (nearbyEntitiesAreReadyForPhysics()) {
|
||||
_physicsEnabled = true;
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
} else {
|
||||
auto characterController = getMyAvatar()->getCharacterController();
|
||||
if (characterController) {
|
||||
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
|
||||
// a non-loading collision hull.
|
||||
characterController->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (domainLoadingInProgress) {
|
||||
|
@ -5461,7 +5454,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
|
||||
}
|
||||
|
||||
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
|
||||
|
||||
// hook our avatar and avatar hash map object into this script engine
|
||||
|
@ -5560,6 +5552,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
|
||||
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
|
||||
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
|
||||
|
||||
|
||||
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ Menu::Menu() {
|
|||
0, // QML Qt::Key_Apostrophe,
|
||||
qApp, SLOT(resetSensors()));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true,
|
||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableAvatarCollisions, 0, true,
|
||||
avatar.get(), SLOT(updateMotionBehaviorFromMenu()));
|
||||
|
||||
// Avatar > AvatarBookmarks related menus -- Note: the AvatarBookmarks class adds its own submenus here.
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace MenuOption {
|
|||
const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene";
|
||||
const QString EchoLocalAudio = "Echo Local Audio";
|
||||
const QString EchoServerAudio = "Echo Server Audio";
|
||||
const QString EnableCharacterController = "Collide with world";
|
||||
const QString EnableAvatarCollisions = "Enable Avatar Collisions";
|
||||
const QString EnableInverseKinematics = "Enable Inverse Kinematics";
|
||||
const QString EntityScriptServerLog = "Entity Script Server Log";
|
||||
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
||||
|
|
70
interface/src/avatar/MyAvatar.cpp
Normal file → Executable file
70
interface/src/avatar/MyAvatar.cpp
Normal file → Executable file
|
@ -150,8 +150,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
|
|||
// when we leave a domain we lift whatever restrictions that domain may have placed on our scale
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction);
|
||||
|
||||
_characterController.setEnabled(true);
|
||||
|
||||
_bodySensorMatrix = deriveBodyFromHMDSensor();
|
||||
|
||||
using namespace recording;
|
||||
|
@ -165,12 +163,14 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
|
|||
if (recordingInterface->getPlayFromCurrentLocation()) {
|
||||
setRecordingBasis();
|
||||
}
|
||||
_wasCharacterControllerEnabled = _characterController.isEnabled();
|
||||
_characterController.setEnabled(false);
|
||||
_previousCollisionGroup = _characterController.computeCollisionGroup();
|
||||
_characterController.setCollisionless(true);
|
||||
} else {
|
||||
clearRecordingBasis();
|
||||
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
|
||||
_characterController.setEnabled(_wasCharacterControllerEnabled);
|
||||
if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
_characterController.setCollisionless(false);
|
||||
}
|
||||
}
|
||||
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
|
@ -552,12 +552,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
bool flyingAllowed = true;
|
||||
bool ghostingAllowed = true;
|
||||
bool collisionlessAllowed = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
|
||||
if (zone) {
|
||||
flyingAllowed = zone->getFlyingAllowed();
|
||||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
collisionlessAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
auto now = usecTimestampNow();
|
||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||
|
@ -588,9 +588,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
});
|
||||
_characterController.setFlyingAllowed(flyingAllowed);
|
||||
if (!_characterController.isEnabled() && !ghostingAllowed) {
|
||||
_characterController.setEnabled(true);
|
||||
}
|
||||
_characterController.setCollisionlessAllowed(collisionlessAllowed);
|
||||
}
|
||||
|
||||
updateAvatarEntities();
|
||||
|
@ -1449,7 +1447,8 @@ void MyAvatar::updateMotors() {
|
|||
_characterController.clearMotors();
|
||||
glm::quat motorRotation;
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
motorRotation = getMyHead()->getCameraOrientation();
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
|
@ -1495,6 +1494,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
qDebug() << "Warning: getParentVelocity failed" << getID();
|
||||
parentVelocity = glm::vec3();
|
||||
}
|
||||
_characterController.handleChangedCollisionGroup();
|
||||
_characterController.setParentVelocity(parentVelocity);
|
||||
|
||||
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
||||
|
@ -1883,8 +1883,9 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
|
||||
glm::vec3 direction = forward + right;
|
||||
CharacterController::State state = _characterController.getState();
|
||||
if (state == CharacterController::State::Hover) {
|
||||
// we're flying --> support vertical motion
|
||||
if (state == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
// we can fly --> support vertical motion
|
||||
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
|
||||
direction += up;
|
||||
}
|
||||
|
@ -1906,7 +1907,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
float finalMaxMotorSpeed = getUniformScale() * MAX_ACTION_MOTOR_SPEED;
|
||||
float speedGrowthTimescale = 2.0f;
|
||||
float speedIncreaseFactor = 1.8f;
|
||||
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
|
||||
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor;
|
||||
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
|
||||
|
||||
if (_isPushing) {
|
||||
|
@ -1949,9 +1950,17 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
measureMotionDerivatives(deltaTime);
|
||||
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
|
||||
} else {
|
||||
// physics physics simulation updated elsewhere
|
||||
float speed2 = glm::length2(velocity);
|
||||
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
|
||||
|
||||
if (_moving) {
|
||||
// scan for walkability
|
||||
glm::vec3 position = getPosition();
|
||||
MyCharacterController::RayShotgunResult result;
|
||||
glm::vec3 step = deltaTime * (getRotation() * _actionMotorVelocity);
|
||||
_characterController.testRayShotgun(position, step, result);
|
||||
_characterController.setStepUpEnabled(result.walkable);
|
||||
}
|
||||
}
|
||||
|
||||
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
|
||||
|
@ -2188,30 +2197,33 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
|||
} else {
|
||||
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||
}
|
||||
|
||||
setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
|
||||
setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions));
|
||||
}
|
||||
|
||||
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
|
||||
void MyAvatar::setCollisionsEnabled(bool enabled) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled));
|
||||
QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
bool ghostingAllowed = true;
|
||||
auto entityTreeRenderer = qApp->getEntities();
|
||||
if (entityTreeRenderer) {
|
||||
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
|
||||
if (zone) {
|
||||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
}
|
||||
_characterController.setEnabled(ghostingAllowed ? enabled : true);
|
||||
_characterController.setCollisionless(!enabled);
|
||||
}
|
||||
|
||||
bool MyAvatar::getCollisionsEnabled() {
|
||||
// may return 'false' even though the collisionless option was requested
|
||||
// because the zone may disallow collisionless avatars
|
||||
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
}
|
||||
|
||||
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
|
||||
qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead.";
|
||||
setCollisionsEnabled(enabled);
|
||||
}
|
||||
|
||||
bool MyAvatar::getCharacterControllerEnabled() {
|
||||
return _characterController.isEnabled();
|
||||
qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead.";
|
||||
return getCollisionsEnabled();
|
||||
}
|
||||
|
||||
void MyAvatar::clearDriveKeys() {
|
||||
|
|
|
@ -96,7 +96,7 @@ class MyAvatar : public Avatar {
|
|||
* @property rightHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.rightHandPose
|
||||
* @property hmdLeanRecenterEnabled {bool} This can be used disable the hmd lean recenter behavior. This behavior is what causes your avatar
|
||||
* to follow your HMD as you walk around the room, in room scale VR. Disabling this is useful if you desire to pin the avatar to a fixed location.
|
||||
* @property characterControllerEnabled {bool} This can be used to disable collisions between the avatar and the world.
|
||||
* @property collisionsEnabled {bool} This can be used to disable collisions between the avatar and the world.
|
||||
* @property useAdvancedMovementControls {bool} Stores the user preference only, does not change user mappings, this is done in the defaultScript
|
||||
* "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js".
|
||||
*/
|
||||
|
@ -128,6 +128,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(float isAway READ getIsAway WRITE setAway)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled)
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls)
|
||||
|
||||
|
@ -470,8 +471,10 @@ public:
|
|||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||
Q_INVOKABLE void setCollisionsEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getCollisionsEnabled();
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated
|
||||
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
@ -614,7 +617,7 @@ private:
|
|||
SharedSoundPointer _collisionSound;
|
||||
|
||||
MyCharacterController _characterController;
|
||||
bool _wasCharacterControllerEnabled { true };
|
||||
int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
|
||||
|
||||
AvatarWeakPointer _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
|
286
interface/src/avatar/MyCharacterController.cpp
Normal file → Executable file
286
interface/src/avatar/MyCharacterController.cpp
Normal file → Executable file
|
@ -15,11 +15,15 @@
|
|||
|
||||
#include "MyAvatar.h"
|
||||
|
||||
// TODO: improve walking up steps
|
||||
// TODO: make avatars able to walk up and down steps/slopes
|
||||
// TODO: make avatars stand on steep slope
|
||||
// TODO: make avatars not snag on low ceilings
|
||||
|
||||
|
||||
void MyCharacterController::RayShotgunResult::reset() {
|
||||
hitFraction = 1.0f;
|
||||
walkable = true;
|
||||
}
|
||||
|
||||
MyCharacterController::MyCharacterController(MyAvatar* avatar) {
|
||||
|
||||
assert(avatar);
|
||||
|
@ -30,37 +34,33 @@ MyCharacterController::MyCharacterController(MyAvatar* avatar) {
|
|||
MyCharacterController::~MyCharacterController() {
|
||||
}
|
||||
|
||||
void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
CharacterController::setDynamicsWorld(world);
|
||||
if (world) {
|
||||
initRayShotgun(world);
|
||||
}
|
||||
}
|
||||
|
||||
void MyCharacterController::updateShapeIfNecessary() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
|
||||
|
||||
// compute new dimensions from avatar's bounding box
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
_halfHeight = 0.5f * _boxScale.y - _radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (_halfHeight < MIN_HALF_HEIGHT) {
|
||||
_halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
// NOTE: _shapeLocalOffset is already computed
|
||||
|
||||
if (_radius > 0.0f) {
|
||||
// create RigidBody if it doesn't exist
|
||||
if (!_rigidBody) {
|
||||
btCollisionShape* shape = computeShape();
|
||||
|
||||
// HACK: use some simple mass property defaults for now
|
||||
const float DEFAULT_AVATAR_MASS = 100.0f;
|
||||
const btScalar DEFAULT_AVATAR_MASS = 100.0f;
|
||||
const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f);
|
||||
|
||||
btCollisionShape* shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
_rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR);
|
||||
} else {
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
if (shape) {
|
||||
delete shape;
|
||||
}
|
||||
shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
shape = computeShape();
|
||||
_rigidBody->setCollisionShape(shape);
|
||||
}
|
||||
|
||||
|
@ -72,12 +72,262 @@ void MyCharacterController::updateShapeIfNecessary() {
|
|||
if (_state == State::Hover) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
//_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||
_rigidBody->setCollisionFlags(_rigidBody->getCollisionFlags() &
|
||||
~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT));
|
||||
} else {
|
||||
// TODO: handle this failure case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result) {
|
||||
btVector3 rayDirection = glmToBullet(step);
|
||||
btScalar stepLength = rayDirection.length();
|
||||
if (stepLength < FLT_EPSILON) {
|
||||
return false;
|
||||
}
|
||||
rayDirection /= stepLength;
|
||||
|
||||
// get _ghost ready for ray traces
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 newPosition = glmToBullet(position);
|
||||
transform.setOrigin(newPosition);
|
||||
_ghost.setWorldTransform(transform);
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
_ghost.refreshOverlappingPairCache();
|
||||
|
||||
CharacterRayResult rayResult(&_ghost);
|
||||
CharacterRayResult closestRayResult(&_ghost);
|
||||
btVector3 rayStart;
|
||||
btVector3 rayEnd;
|
||||
|
||||
// compute rotation that will orient local ray start points to face step direction
|
||||
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
|
||||
btVector3 adjustedDirection = rayDirection - rayDirection.dot(_currentUp) * _currentUp;
|
||||
btVector3 axis = forward.cross(adjustedDirection);
|
||||
btScalar lengthAxis = axis.length();
|
||||
if (lengthAxis > FLT_EPSILON) {
|
||||
// we're walking sideways
|
||||
btScalar angle = acosf(lengthAxis / adjustedDirection.length());
|
||||
if (rayDirection.dot(forward) < 0.0f) {
|
||||
angle = PI - angle;
|
||||
}
|
||||
axis /= lengthAxis;
|
||||
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
|
||||
} else if (rayDirection.dot(forward) < 0.0f) {
|
||||
// we're walking backwards
|
||||
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
|
||||
}
|
||||
|
||||
// scan the top
|
||||
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
|
||||
// The approximate extra distance can be derived with trigonometry.
|
||||
//
|
||||
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
|
||||
//
|
||||
// where: theta = max angle between floor normal and vertical
|
||||
//
|
||||
// if stepLength is not long enough we can add the difference.
|
||||
//
|
||||
btScalar cosTheta = _minFloorNormalDotUp;
|
||||
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
|
||||
const btScalar MIN_FORWARD_SLOP = 0.12f; // HACK: not sure why this is necessary to detect steepest walkable slope
|
||||
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
|
||||
if (forwardSlop < 0.0f) {
|
||||
// BIG step, no slop necessary
|
||||
forwardSlop = 0.0f;
|
||||
}
|
||||
|
||||
const btScalar backSlop = 0.04f;
|
||||
for (int32_t i = 0; i < _topPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _topPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
if (result.walkable) {
|
||||
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
||||
result.walkable = false;
|
||||
// the top scan wasn't walkable so don't bother scanning the bottom
|
||||
// remove both forwardSlop and backSlop
|
||||
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_state == State::Hover) {
|
||||
// scan the bottom just like the top
|
||||
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
if (result.walkable) {
|
||||
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
||||
result.walkable = false;
|
||||
// the bottom scan wasn't walkable
|
||||
// remove both forwardSlop and backSlop
|
||||
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// scan the bottom looking for nearest step point
|
||||
// remove forwardSlop
|
||||
result.hitFraction = (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop)) / (backSlop + stepLength);
|
||||
|
||||
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove backSlop
|
||||
// NOTE: backSlop removal can produce a NEGATIVE hitFraction!
|
||||
// which means the shape is actually in interpenetration
|
||||
result.hitFraction = ((closestRayResult.m_closestHitFraction * (backSlop + stepLength)) - backSlop) / stepLength;
|
||||
}
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
|
||||
btConvexHullShape* MyCharacterController::computeShape() const {
|
||||
// HACK: the avatar collides using convex hull with a collision margin equal to
|
||||
// the old capsule radius. Two points define a capsule and additional points are
|
||||
// spread out at chest level to produce a slight taper toward the feet. This
|
||||
// makes the avatar more likely to collide with vertical walls at a higher point
|
||||
// and thus less likely to produce a single-point collision manifold below the
|
||||
// _maxStepHeight when walking into against vertical surfaces --> fixes a bug
|
||||
// where the "walk up steps" feature would allow the avatar to walk up vertical
|
||||
// walls.
|
||||
const int32_t NUM_POINTS = 6;
|
||||
btVector3 points[NUM_POINTS];
|
||||
btVector3 xAxis = btVector3(1.0f, 0.0f, 0.0f);
|
||||
btVector3 yAxis = btVector3(0.0f, 1.0f, 0.0f);
|
||||
btVector3 zAxis = btVector3(0.0f, 0.0f, 1.0f);
|
||||
points[0] = _halfHeight * yAxis;
|
||||
points[1] = -_halfHeight * yAxis;
|
||||
points[2] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * zAxis;
|
||||
points[3] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * zAxis;
|
||||
points[4] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * xAxis;
|
||||
points[5] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * xAxis;
|
||||
btConvexHullShape* shape = new btConvexHullShape(reinterpret_cast<btScalar*>(points), NUM_POINTS);
|
||||
shape->setMargin(_radius);
|
||||
return shape;
|
||||
}
|
||||
|
||||
void MyCharacterController::initRayShotgun(const btCollisionWorld* world) {
|
||||
// In order to trace rays out from the avatar's shape surface we need to know where the start points are in
|
||||
// the local-frame. Since the avatar shape is somewhat irregular computing these points by hand is a hassle
|
||||
// so instead we ray-trace backwards to the avatar to find them.
|
||||
//
|
||||
// We trace back a regular grid (see below) of points against the shape and keep any that hit.
|
||||
// ___
|
||||
// + / + \ +
|
||||
// |+ +|
|
||||
// +| + | +
|
||||
// |+ +|
|
||||
// +| + | +
|
||||
// |+ +|
|
||||
// + \ + / +
|
||||
// ---
|
||||
// The shotgun will send rays out from these same points to see if the avatar's shape can proceed through space.
|
||||
|
||||
// helper class for simple ray-traces against character
|
||||
class MeOnlyResultCallback : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
MeOnlyResultCallback (btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
||||
_me = me;
|
||||
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DYNAMIC;
|
||||
m_collisionFilterMask = BULLET_COLLISION_MASK_DYNAMIC;
|
||||
}
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override {
|
||||
if (rayResult.m_collisionObject != _me) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||
}
|
||||
btRigidBody* _me;
|
||||
};
|
||||
|
||||
const btScalar fullHalfHeight = _radius + _halfHeight;
|
||||
const btScalar divisionLine = -fullHalfHeight + _maxStepHeight; // line between top and bottom
|
||||
const btScalar topHeight = fullHalfHeight - divisionLine;
|
||||
const btScalar slop = 0.02f;
|
||||
|
||||
const int32_t NUM_ROWS = 5; // must be odd number > 1
|
||||
const int32_t NUM_COLUMNS = 5; // must be odd number > 1
|
||||
btVector3 reach = (2.0f * _radius) * btVector3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
{ // top points
|
||||
_topPoints.clear();
|
||||
_topPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
||||
btScalar stepY = (topHeight - slop) / (btScalar)(NUM_ROWS - 1);
|
||||
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
||||
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 position = transform.getOrigin();
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
|
||||
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
||||
int32_t maxJ = NUM_COLUMNS;
|
||||
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
||||
if (i % 2 == 1) {
|
||||
// odd rows have one less point and start a halfStep closer
|
||||
maxJ -= 1;
|
||||
offsetX += 0.5f * stepX;
|
||||
}
|
||||
for (int32_t j = 0; j < maxJ; ++j) {
|
||||
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, divisionLine + (btScalar)(i) * stepY, 0.0f);
|
||||
btVector3 localRayStart = localRayEnd - reach;
|
||||
MeOnlyResultCallback result(_rigidBody);
|
||||
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
||||
if (result.m_closestHitFraction < 1.0f) {
|
||||
_topPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // bottom points
|
||||
_bottomPoints.clear();
|
||||
_bottomPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
||||
|
||||
btScalar steepestStepHitHeight = (_radius + 0.04f) * (1.0f - DEFAULT_MIN_FLOOR_NORMAL_DOT_UP);
|
||||
btScalar stepY = (_maxStepHeight - slop - steepestStepHitHeight) / (btScalar)(NUM_ROWS - 1);
|
||||
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
||||
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 position = transform.getOrigin();
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
|
||||
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
||||
int32_t maxJ = NUM_COLUMNS;
|
||||
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
||||
if (i % 2 == 1) {
|
||||
// odd rows have one less point and start a halfStep closer
|
||||
maxJ -= 1;
|
||||
offsetX += 0.5f * stepX;
|
||||
}
|
||||
for (int32_t j = 0; j < maxJ; ++j) {
|
||||
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, (divisionLine - slop) - (btScalar)(i) * stepY, 0.0f);
|
||||
btVector3 localRayStart = localRayEnd - reach;
|
||||
MeOnlyResultCallback result(_rigidBody);
|
||||
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
||||
if (result.m_closestHitFraction < 1.0f) {
|
||||
_bottomPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,34 @@ public:
|
|||
explicit MyCharacterController(MyAvatar* avatar);
|
||||
~MyCharacterController ();
|
||||
|
||||
virtual void updateShapeIfNecessary() override;
|
||||
void setDynamicsWorld(btDynamicsWorld* world) override;
|
||||
void updateShapeIfNecessary() override;
|
||||
|
||||
// Sweeping a convex shape through the physics simulation can be expensive when the obstacles are too
|
||||
// complex (e.g. small 20k triangle static mesh) so instead we cast several rays forward and if they
|
||||
// don't hit anything we consider it a clean sweep. Hence this "Shotgun" code.
|
||||
class RayShotgunResult {
|
||||
public:
|
||||
void reset();
|
||||
float hitFraction { 1.0f };
|
||||
bool walkable { true };
|
||||
};
|
||||
|
||||
/// return true if RayShotgun hits anything
|
||||
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
|
||||
|
||||
protected:
|
||||
void initRayShotgun(const btCollisionWorld* world);
|
||||
|
||||
private:
|
||||
btConvexHullShape* computeShape() const;
|
||||
|
||||
protected:
|
||||
MyAvatar* _avatar { nullptr };
|
||||
|
||||
// shotgun scan data
|
||||
btAlignedObjectArray<btVector3> _topPoints;
|
||||
btAlignedObjectArray<btVector3> _bottomPoints;
|
||||
};
|
||||
|
||||
#endif // hifi_MyCharacterController_h
|
||||
|
|
|
@ -37,7 +37,14 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
Head* head = _owningAvatar->getHead();
|
||||
|
||||
// make sure lookAt is not too close to face (avoid crosseyes)
|
||||
glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition();
|
||||
glm::vec3 lookAt = head->getLookAtPosition();
|
||||
glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition();
|
||||
float focusDistance = glm::length(focusOffset);
|
||||
const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f;
|
||||
if (focusDistance < MIN_LOOK_AT_FOCUS_DISTANCE && focusDistance > EPSILON) {
|
||||
lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset;
|
||||
}
|
||||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
|
||||
Rig::HeadParameters headParams;
|
||||
|
@ -152,7 +159,5 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||
|
||||
_rig->updateFromEyeParameters(eyeParams);
|
||||
|
||||
Parent::updateRig(deltaTime, parentTransform);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class AvatarInputs : public QQuickItem {
|
|||
|
||||
public:
|
||||
static AvatarInputs* getInstance();
|
||||
float loudnessToAudioLevel(float loudness);
|
||||
Q_INVOKABLE float loudnessToAudioLevel(float loudness);
|
||||
AvatarInputs(QQuickItem* parent = nullptr);
|
||||
void update();
|
||||
bool showAudioTools() const { return _showAudioTools; }
|
||||
|
|
|
@ -28,11 +28,15 @@ const int MAX_HISTORY_SIZE = 64;
|
|||
const QString COMMAND_STYLE = "color: #266a9b;";
|
||||
|
||||
const QString RESULT_SUCCESS_STYLE = "color: #677373;";
|
||||
const QString RESULT_INFO_STYLE = "color: #223bd1;";
|
||||
const QString RESULT_WARNING_STYLE = "color: #d13b22;";
|
||||
const QString RESULT_ERROR_STYLE = "color: #d13b22;";
|
||||
|
||||
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\"><</span>";
|
||||
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
|
||||
|
||||
const QString JSConsole::_consoleFileName { "about:console" };
|
||||
|
||||
JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
|
||||
QWidget(parent),
|
||||
_ui(new Ui::Console),
|
||||
|
@ -77,6 +81,8 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
|
|||
}
|
||||
if (_scriptEngine != NULL) {
|
||||
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
|
||||
disconnect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
|
||||
disconnect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
|
||||
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
|
||||
if (_ownScriptEngine) {
|
||||
_scriptEngine->deleteLater();
|
||||
|
@ -84,10 +90,12 @@ void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
|
|||
}
|
||||
|
||||
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
|
||||
_ownScriptEngine = scriptEngine == NULL;
|
||||
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(QString(), false) : scriptEngine;
|
||||
_ownScriptEngine = (scriptEngine == NULL);
|
||||
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false) : scriptEngine;
|
||||
|
||||
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
|
||||
connect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
|
||||
connect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
|
||||
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
|
||||
}
|
||||
|
||||
|
@ -107,11 +115,10 @@ void JSConsole::executeCommand(const QString& command) {
|
|||
|
||||
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
|
||||
QScriptValue result;
|
||||
static const QString filename = "JSConcole";
|
||||
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, result),
|
||||
Q_ARG(const QString&, command),
|
||||
Q_ARG(const QString&, filename));
|
||||
Q_ARG(const QString&, _consoleFileName));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -134,16 +141,26 @@ void JSConsole::commandFinished() {
|
|||
resetCurrentCommandHistory();
|
||||
}
|
||||
|
||||
void JSConsole::handleError(const QString& scriptName, const QString& message) {
|
||||
void JSConsole::handleError(const QString& message, const QString& scriptName) {
|
||||
Q_UNUSED(scriptName);
|
||||
appendMessage(GUTTER_ERROR, "<span style='" + RESULT_ERROR_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
|
||||
}
|
||||
|
||||
void JSConsole::handlePrint(const QString& scriptName, const QString& message) {
|
||||
void JSConsole::handlePrint(const QString& message, const QString& scriptName) {
|
||||
Q_UNUSED(scriptName);
|
||||
appendMessage("", message);
|
||||
}
|
||||
|
||||
void JSConsole::handleInfo(const QString& message, const QString& scriptName) {
|
||||
Q_UNUSED(scriptName);
|
||||
appendMessage("", "<span style='" + RESULT_INFO_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
|
||||
}
|
||||
|
||||
void JSConsole::handleWarning(const QString& message, const QString& scriptName) {
|
||||
Q_UNUSED(scriptName);
|
||||
appendMessage("", "<span style='" + RESULT_WARNING_STYLE + "'>" + message.toHtmlEscaped() + "</span>");
|
||||
}
|
||||
|
||||
void JSConsole::mouseReleaseEvent(QMouseEvent* event) {
|
||||
_ui->promptTextEdit->setFocus();
|
||||
}
|
||||
|
|
|
@ -47,8 +47,10 @@ protected:
|
|||
protected slots:
|
||||
void scrollToBottom();
|
||||
void resizeTextInput();
|
||||
void handlePrint(const QString& scriptName, const QString& message);
|
||||
void handleError(const QString& scriptName, const QString& message);
|
||||
void handlePrint(const QString& message, const QString& scriptName);
|
||||
void handleInfo(const QString& message, const QString& scriptName);
|
||||
void handleWarning(const QString& message, const QString& scriptName);
|
||||
void handleError(const QString& message, const QString& scriptName);
|
||||
void commandFinished();
|
||||
|
||||
private:
|
||||
|
@ -66,6 +68,7 @@ private:
|
|||
bool _ownScriptEngine;
|
||||
QString _rootCommand;
|
||||
ScriptEngine* _scriptEngine;
|
||||
static const QString _consoleFileName;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -81,6 +81,10 @@ QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& propert
|
|||
void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
||||
QVariantMap properties = originalProperties;
|
||||
|
||||
if (properties["name"].isValid()) {
|
||||
setName(properties["name"].toString());
|
||||
}
|
||||
|
||||
// carry over some legacy keys
|
||||
if (!properties["position"].isValid() && !properties["localPosition"].isValid()) {
|
||||
if (properties["p1"].isValid()) {
|
||||
|
@ -207,6 +211,9 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
}
|
||||
|
||||
QVariant Base3DOverlay::getProperty(const QString& property) {
|
||||
if (property == "name") {
|
||||
return _name;
|
||||
}
|
||||
if (property == "position" || property == "start" || property == "p1" || property == "point") {
|
||||
return vec3toVariant(getPosition());
|
||||
}
|
||||
|
|
|
@ -26,6 +26,9 @@ public:
|
|||
virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); }
|
||||
void setOverlayID(OverlayID overlayID) override { setID(overlayID); }
|
||||
|
||||
virtual QString getName() const override { return QString("Overlay:") + _name; }
|
||||
void setName(QString name) { _name = name; }
|
||||
|
||||
// getters
|
||||
virtual bool is3D() const override { return true; }
|
||||
|
||||
|
@ -74,6 +77,8 @@ protected:
|
|||
bool _drawInFront;
|
||||
bool _isAA;
|
||||
bool _isGrabbable { false };
|
||||
|
||||
QString _name;
|
||||
};
|
||||
|
||||
#endif // hifi_Base3DOverlay_h
|
||||
|
|
|
@ -288,3 +288,10 @@ void ModelOverlay::locationChanged(bool tellPhysics) {
|
|||
_model->setTranslation(getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
QString ModelOverlay::getName() const {
|
||||
if (_name != "") {
|
||||
return QString("Overlay:") + getType() + ":" + _name;
|
||||
}
|
||||
return QString("Overlay:") + getType() + ":" + _url.toString();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ public:
|
|||
static QString const TYPE;
|
||||
virtual QString getType() const override { return TYPE; }
|
||||
|
||||
virtual QString getName() const override;
|
||||
|
||||
ModelOverlay();
|
||||
ModelOverlay(const ModelOverlay* modelOverlay);
|
||||
|
||||
|
|
|
@ -1097,28 +1097,27 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
|
|||
}
|
||||
|
||||
void AudioClient::prepareLocalAudioInjectors() {
|
||||
if (_outputPeriod == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bufferCapacity = _localInjectorsStream.getSampleCapacity();
|
||||
if (_localToOutputResampler) {
|
||||
// avoid overwriting the buffer,
|
||||
// instead of failing on writes because the buffer is used as a lock-free pipe
|
||||
bufferCapacity -=
|
||||
_localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
|
||||
AudioConstants::STEREO;
|
||||
bufferCapacity += 1;
|
||||
}
|
||||
|
||||
int samplesNeeded = std::numeric_limits<int>::max();
|
||||
while (samplesNeeded > 0) {
|
||||
// lock for every write to avoid locking out the device callback
|
||||
// this lock is intentional - the buffer is only lock-free in its use in the device callback
|
||||
RecursiveLock lock(_localAudioMutex);
|
||||
// unlock between every write to allow device switching
|
||||
Lock lock(_localAudioMutex);
|
||||
|
||||
// in case of a device switch, consider bufferCapacity volatile across iterations
|
||||
if (_outputPeriod == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bufferCapacity = _localInjectorsStream.getSampleCapacity();
|
||||
int maxOutputSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * AudioConstants::STEREO;
|
||||
if (_localToOutputResampler) {
|
||||
maxOutputSamples =
|
||||
_localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
|
||||
AudioConstants::STEREO;
|
||||
}
|
||||
|
||||
samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed);
|
||||
if (samplesNeeded <= 0) {
|
||||
if (samplesNeeded < maxOutputSamples) {
|
||||
// avoid overwriting the buffer to prevent losing frames
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1168,16 +1167,18 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
|
||||
|
||||
for (AudioInjector* injector : _activeLocalAudioInjectors) {
|
||||
if (injector->getLocalBuffer()) {
|
||||
// the lock guarantees that injectorBuffer, if found, is invariant
|
||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
||||
if (injectorBuffer) {
|
||||
|
||||
static const int HRTF_DATASET_INDEX = 1;
|
||||
|
||||
int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
|
||||
// get one frame from the injector
|
||||
memset(_localScratchBuffer, 0, bytesToRead);
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) {
|
||||
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
|
||||
|
||||
if (injector->isAmbisonic()) {
|
||||
|
||||
|
@ -1317,15 +1318,17 @@ void AudioClient::setIsStereoInput(bool isStereoInput) {
|
|||
}
|
||||
|
||||
bool AudioClient::outputLocalInjector(AudioInjector* injector) {
|
||||
Lock lock(_injectorsMutex);
|
||||
if (injector->getLocalBuffer() && _audioInput ) {
|
||||
// just add it to the vector of active local injectors, if
|
||||
// not already there.
|
||||
// Since this is invoked with invokeMethod, there _should_ be
|
||||
// no reason to lock access to the vector of injectors.
|
||||
AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
|
||||
if (injectorBuffer) {
|
||||
// local injectors are on the AudioInjectorsThread, so we must guard access
|
||||
Lock lock(_injectorsMutex);
|
||||
if (!_activeLocalAudioInjectors.contains(injector)) {
|
||||
qCDebug(audioclient) << "adding new injector";
|
||||
_activeLocalAudioInjectors.append(injector);
|
||||
|
||||
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
|
||||
injectorBuffer->setParent(nullptr);
|
||||
injectorBuffer->moveToThread(&_localAudioThread);
|
||||
} else {
|
||||
qCDebug(audioclient) << "injector exists in active list already";
|
||||
}
|
||||
|
@ -1333,7 +1336,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
|
|||
return true;
|
||||
|
||||
} else {
|
||||
// no local buffer or audio
|
||||
// no local buffer
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1452,7 +1455,7 @@ void AudioClient::outputNotify() {
|
|||
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
|
||||
bool supportedFormat = false;
|
||||
|
||||
RecursiveLock lock(_localAudioMutex);
|
||||
Lock lock(_localAudioMutex);
|
||||
_localSamplesAvailable.exchange(0, std::memory_order_release);
|
||||
|
||||
// cleanup any previously initialized device
|
||||
|
@ -1681,8 +1684,12 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
|||
|
||||
int injectorSamplesPopped = 0;
|
||||
{
|
||||
RecursiveLock lock(_audio->_localAudioMutex);
|
||||
bool append = networkSamplesPopped > 0;
|
||||
// this does not require a lock as of the only two functions adding to _localSamplesAvailable (samples count):
|
||||
// - prepareLocalAudioInjectors will only increase samples count
|
||||
// - switchOutputToAudioDevice will zero samples count
|
||||
// stop the device, so that readData will exhaust the existing buffer or see a zeroed samples count
|
||||
// and start the device, which can only see a zeroed samples count
|
||||
samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire));
|
||||
if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) {
|
||||
_audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release);
|
||||
|
|
|
@ -96,8 +96,6 @@ public:
|
|||
using AudioPositionGetter = std::function<glm::vec3()>;
|
||||
using AudioOrientationGetter = std::function<glm::quat()>;
|
||||
|
||||
using RecursiveMutex = std::recursive_mutex;
|
||||
using RecursiveLock = std::unique_lock<RecursiveMutex>;
|
||||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
|
||||
|
@ -345,7 +343,7 @@ private:
|
|||
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
|
||||
float* _localOutputMixBuffer { NULL };
|
||||
AudioInjectorsThread _localAudioThread;
|
||||
RecursiveMutex _localAudioMutex;
|
||||
Mutex _localAudioMutex;
|
||||
|
||||
// for output audio (used by this thread)
|
||||
int _outputPeriod { 0 };
|
||||
|
|
|
@ -33,7 +33,11 @@ public:
|
|||
PacketType packetType, QString codecName = QString(""));
|
||||
|
||||
public slots:
|
||||
// threadsafe
|
||||
// moves injector->getLocalBuffer() to another thread (so removes its parent)
|
||||
// take care to delete it when ~AudioInjector, as parenting Qt semantics will not work
|
||||
virtual bool outputLocalInjector(AudioInjector* injector) = 0;
|
||||
|
||||
virtual bool shouldLoopbackInjectors() { return false; }
|
||||
|
||||
virtual void setIsStereoInput(bool stereo) = 0;
|
||||
|
|
|
@ -51,6 +51,10 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt
|
|||
{
|
||||
}
|
||||
|
||||
AudioInjector::~AudioInjector() {
|
||||
deleteLocalBuffer();
|
||||
}
|
||||
|
||||
bool AudioInjector::stateHas(AudioInjectorState state) const {
|
||||
return (_state & state) == state;
|
||||
}
|
||||
|
@ -87,11 +91,7 @@ void AudioInjector::finish() {
|
|||
|
||||
emit finished();
|
||||
|
||||
if (_localBuffer) {
|
||||
_localBuffer->stop();
|
||||
_localBuffer->deleteLater();
|
||||
_localBuffer = NULL;
|
||||
}
|
||||
deleteLocalBuffer();
|
||||
|
||||
if (stateHas(AudioInjectorState::PendingDelete)) {
|
||||
// we've been asked to delete after finishing, trigger a deleteLater here
|
||||
|
@ -163,7 +163,7 @@ bool AudioInjector::injectLocally() {
|
|||
if (_localAudioInterface) {
|
||||
if (_audioData.size() > 0) {
|
||||
|
||||
_localBuffer = new AudioInjectorLocalBuffer(_audioData, this);
|
||||
_localBuffer = new AudioInjectorLocalBuffer(_audioData);
|
||||
|
||||
_localBuffer->open(QIODevice::ReadOnly);
|
||||
_localBuffer->setShouldLoop(_options.loop);
|
||||
|
@ -172,7 +172,8 @@ bool AudioInjector::injectLocally() {
|
|||
_localBuffer->setCurrentOffset(_currentSendOffset);
|
||||
|
||||
// call this function on the AudioClient's thread
|
||||
success = QMetaObject::invokeMethod(_localAudioInterface, "outputLocalInjector", Q_ARG(AudioInjector*, this));
|
||||
// this will move the local buffer's thread to the LocalInjectorThread
|
||||
success = _localAudioInterface->outputLocalInjector(this);
|
||||
|
||||
if (!success) {
|
||||
qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface";
|
||||
|
@ -185,6 +186,14 @@ bool AudioInjector::injectLocally() {
|
|||
return success;
|
||||
}
|
||||
|
||||
void AudioInjector::deleteLocalBuffer() {
|
||||
if (_localBuffer) {
|
||||
_localBuffer->stop();
|
||||
_localBuffer->deleteLater();
|
||||
_localBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
|
||||
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
|
||||
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
||||
|
|
|
@ -52,6 +52,7 @@ class AudioInjector : public QObject {
|
|||
public:
|
||||
AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions);
|
||||
AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions);
|
||||
~AudioInjector();
|
||||
|
||||
bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); }
|
||||
|
||||
|
@ -99,6 +100,7 @@ private:
|
|||
int64_t injectNextFrame();
|
||||
bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*));
|
||||
bool injectLocally();
|
||||
void deleteLocalBuffer();
|
||||
|
||||
static AbstractAudioInterface* _localAudioInterface;
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
|
||||
#include "AudioInjectorLocalBuffer.h"
|
||||
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent) :
|
||||
QIODevice(parent),
|
||||
AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArray) :
|
||||
_rawAudioArray(rawAudioArray),
|
||||
_shouldLoop(false),
|
||||
_isStopped(false),
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
class AudioInjectorLocalBuffer : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray, QObject* parent);
|
||||
AudioInjectorLocalBuffer(const QByteArray& rawAudioArray);
|
||||
|
||||
void stop();
|
||||
|
||||
|
|
|
@ -73,12 +73,13 @@ void SkeletonModel::initJointStates() {
|
|||
|
||||
// Called within Model::simulate call, below.
|
||||
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
assert(!_owningAvatar->isMyAvatar());
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
||||
Head* head = _owningAvatar->getHead();
|
||||
|
||||
// make sure lookAt is not too close to face (avoid crosseyes)
|
||||
glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition();
|
||||
glm::vec3 lookAt = head->getCorrectedLookAtPosition();
|
||||
glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition();
|
||||
float focusDistance = glm::length(focusOffset);
|
||||
const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f;
|
||||
|
@ -86,41 +87,36 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset;
|
||||
}
|
||||
|
||||
if (!_owningAvatar->isMyAvatar()) {
|
||||
// no need to call Model::updateRig() because otherAvatars get their joint state
|
||||
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
|
||||
_needsUpdateClusterMatrices = true;
|
||||
// no need to call Model::updateRig() because otherAvatars get their joint state
|
||||
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
|
||||
_needsUpdateClusterMatrices = true;
|
||||
|
||||
// This is a little more work than we really want.
|
||||
//
|
||||
// Other avatars joint, including their eyes, should already be set just like any other joints
|
||||
// from the wire data. But when looking at me, we want the eyes to use the corrected lookAt.
|
||||
//
|
||||
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
|
||||
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
|
||||
// We will revisit that as priorities allow, and particularly after the new rig/animation/joints.
|
||||
// This is a little more work than we really want.
|
||||
//
|
||||
// Other avatars joint, including their eyes, should already be set just like any other joints
|
||||
// from the wire data. But when looking at me, we want the eyes to use the corrected lookAt.
|
||||
//
|
||||
// Thus this should really only be ... else if (_owningAvatar->getHead()->isLookingAtMe()) {...
|
||||
// However, in the !isLookingAtMe case, the eyes aren't rotating the way they should right now.
|
||||
// We will revisit that as priorities allow, and particularly after the new rig/animation/joints.
|
||||
|
||||
// If the head is not positioned, updateEyeJoints won't get the math right
|
||||
glm::quat headOrientation;
|
||||
_rig->getJointRotation(geometry.headJointIndex, headOrientation);
|
||||
glm::vec3 eulers = safeEulerAngles(headOrientation);
|
||||
head->setBasePitch(glm::degrees(-eulers.x));
|
||||
head->setBaseYaw(glm::degrees(eulers.y));
|
||||
head->setBaseRoll(glm::degrees(-eulers.z));
|
||||
// If the head is not positioned, updateEyeJoints won't get the math right
|
||||
glm::quat headOrientation;
|
||||
_rig->getJointRotation(geometry.headJointIndex, headOrientation);
|
||||
glm::vec3 eulers = safeEulerAngles(headOrientation);
|
||||
head->setBasePitch(glm::degrees(-eulers.x));
|
||||
head->setBaseYaw(glm::degrees(eulers.y));
|
||||
head->setBaseRoll(glm::degrees(-eulers.z));
|
||||
|
||||
Rig::EyeParameters eyeParams;
|
||||
eyeParams.eyeLookAt = lookAt;
|
||||
eyeParams.eyeSaccade = glm::vec3(0.0f);
|
||||
eyeParams.modelRotation = getRotation();
|
||||
eyeParams.modelTranslation = getTranslation();
|
||||
eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||
Rig::EyeParameters eyeParams;
|
||||
eyeParams.eyeLookAt = lookAt;
|
||||
eyeParams.eyeSaccade = glm::vec3(0.0f);
|
||||
eyeParams.modelRotation = getRotation();
|
||||
eyeParams.modelTranslation = getTranslation();
|
||||
eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
|
||||
eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
|
||||
|
||||
_rig->updateFromEyeParameters(eyeParams);
|
||||
}
|
||||
|
||||
// evaluate AnimGraph animation and update jointStates.
|
||||
Model::updateRig(deltaTime, parentTransform);
|
||||
_rig->updateFromEyeParameters(eyeParams);
|
||||
}
|
||||
|
||||
void SkeletonModel::updateAttitude() {
|
||||
|
@ -136,7 +132,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
if (fullUpdate) {
|
||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
|
||||
|
||||
Model::simulate(deltaTime, fullUpdate);
|
||||
Parent::simulate(deltaTime, fullUpdate);
|
||||
|
||||
// let rig compute the model offset
|
||||
glm::vec3 registrationPoint;
|
||||
|
@ -144,7 +140,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
setOffset(registrationPoint);
|
||||
}
|
||||
} else {
|
||||
Model::simulate(deltaTime, fullUpdate);
|
||||
Parent::simulate(deltaTime, fullUpdate);
|
||||
}
|
||||
|
||||
if (!isActive() || !_owningAvatar->isMyAvatar()) {
|
||||
|
|
|
@ -23,6 +23,7 @@ using SkeletonModelWeakPointer = std::weak_ptr<SkeletonModel>;
|
|||
|
||||
/// A skeleton loaded from a model.
|
||||
class SkeletonModel : public CauterizedModel {
|
||||
using Parent = CauterizedModel;
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
|
|
@ -357,6 +357,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
|
||||
public:
|
||||
|
||||
virtual QString getName() const override { return QString("Avatar:") + _displayName; }
|
||||
|
||||
static const QString FRAME_NAME;
|
||||
|
||||
static void fromFrame(const QByteArray& frameData, AvatarData& avatar, bool useFrameSkeleton = true);
|
||||
|
|
|
@ -281,7 +281,7 @@ public:
|
|||
float getAngularDamping() const;
|
||||
void setAngularDamping(float value);
|
||||
|
||||
QString getName() const;
|
||||
virtual QString getName() const override;
|
||||
void setName(const QString& value);
|
||||
QString getDebugName();
|
||||
|
||||
|
|
|
@ -407,9 +407,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
// return QUuid();
|
||||
// }
|
||||
|
||||
bool entityFound { false };
|
||||
_entityTree->withReadLock([&] {
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (entity) {
|
||||
entityFound = true;
|
||||
// make sure the properties has a type, so that the encode can know which properties to include
|
||||
properties.setType(entity->getType());
|
||||
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
|
||||
|
@ -464,6 +466,27 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
});
|
||||
}
|
||||
});
|
||||
if (!entityFound) {
|
||||
// we've made an edit to an entity we don't know about, or to a non-entity. If it's a known non-entity,
|
||||
// print a warning and don't send an edit packet to the entity-server.
|
||||
QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();
|
||||
if (parentFinder) {
|
||||
bool success;
|
||||
auto nestableWP = parentFinder->find(id, success, static_cast<SpatialParentTree*>(_entityTree.get()));
|
||||
if (success) {
|
||||
auto nestable = nestableWP.lock();
|
||||
if (nestable) {
|
||||
NestableType nestableType = nestable->getNestableType();
|
||||
if (nestableType == NestableType::Overlay || nestableType == NestableType::Avatar) {
|
||||
qCWarning(entities) << "attempted edit on non-entity: " << id << nestable->getName();
|
||||
return QUuid(); // null UUID to indicate failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// we queue edit packets even if we don't know about the entity. This is to allow AC agents
|
||||
// to edit entities they know only by ID.
|
||||
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
|
||||
return id;
|
||||
}
|
||||
|
@ -1515,6 +1538,24 @@ bool EntityScriptingInterface::isChildOfParent(QUuid childID, QUuid parentID) {
|
|||
return isChild;
|
||||
}
|
||||
|
||||
QString EntityScriptingInterface::getNestableType(QUuid id) {
|
||||
QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();
|
||||
if (!parentFinder) {
|
||||
return "unknown";
|
||||
}
|
||||
bool success;
|
||||
SpatiallyNestableWeakPointer objectWP = parentFinder->find(id, success);
|
||||
if (!success) {
|
||||
return "unknown";
|
||||
}
|
||||
SpatiallyNestablePointer object = objectWP.lock();
|
||||
if (!object) {
|
||||
return "unknown";
|
||||
}
|
||||
NestableType nestableType = object->getNestableType();
|
||||
return SpatiallyNestable::nestableTypeToString(nestableType);
|
||||
}
|
||||
|
||||
QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex) {
|
||||
QVector<QUuid> result;
|
||||
if (!_entityTree) {
|
||||
|
|
|
@ -304,6 +304,8 @@ public slots:
|
|||
Q_INVOKABLE QVector<QUuid> getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex);
|
||||
Q_INVOKABLE bool isChildOfParent(QUuid childID, QUuid parentID);
|
||||
|
||||
Q_INVOKABLE QString getNestableType(QUuid id);
|
||||
|
||||
Q_INVOKABLE QUuid getKeyboardFocusEntity() const;
|
||||
Q_INVOKABLE void setKeyboardFocusEntity(QUuid id);
|
||||
|
||||
|
|
|
@ -990,6 +990,17 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
entityItemID, properties);
|
||||
endDecode = usecTimestampNow();
|
||||
|
||||
EntityItemPointer existingEntity;
|
||||
if (!isAdd) {
|
||||
// search for the entity by EntityItemID
|
||||
startLookup = usecTimestampNow();
|
||||
existingEntity = findEntityByEntityItemID(entityItemID);
|
||||
endLookup = usecTimestampNow();
|
||||
if (!existingEntity) {
|
||||
// this is not an add-entity operation, and we don't know about the identified entity.
|
||||
validEditPacket = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
|
||||
bool passedWhiteList = false;
|
||||
|
@ -1036,12 +1047,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
||||
// an existing entity... handle appropriately
|
||||
if (validEditPacket) {
|
||||
|
||||
// search for the entity by EntityItemID
|
||||
startLookup = usecTimestampNow();
|
||||
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
|
||||
endLookup = usecTimestampNow();
|
||||
|
||||
startFilter = usecTimestampNow();
|
||||
bool wasChanged = false;
|
||||
// Having (un)lock rights bypasses the filter, unless it's a physics result.
|
||||
|
|
|
@ -149,6 +149,10 @@ void GLBackend::resetUniformStage() {
|
|||
|
||||
void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) {
|
||||
GLuint slot = batch._params[paramOffset + 3]._uint;
|
||||
if (slot >(GLuint)MAX_NUM_UNIFORM_BUFFERS) {
|
||||
qCDebug(gpugllogging) << "GLBackend::do_setUniformBuffer: Trying to set a uniform Buffer at slot #" << slot << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers();
|
||||
return;
|
||||
}
|
||||
BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
GLintptr rangeStart = batch._params[paramOffset + 1]._uint;
|
||||
GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint;
|
||||
|
@ -203,7 +207,7 @@ void GLBackend::resetResourceStage() {
|
|||
void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) {
|
||||
GLuint slot = batch._params[paramOffset + 1]._uint;
|
||||
if (slot >= (GLuint)MAX_NUM_RESOURCE_BUFFERS) {
|
||||
// "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" + slot + " which doesn't exist. MaxNumResourceBuffers = " + getMaxNumResourceBuffers());
|
||||
qCDebug(gpugllogging) << "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" << slot << " which doesn't exist. MaxNumResourceBuffers = " << getMaxNumResourceBuffers();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -233,7 +237,7 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) {
|
|||
void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
|
||||
GLuint slot = batch._params[paramOffset + 1]._uint;
|
||||
if (slot >= (GLuint) MAX_NUM_RESOURCE_TEXTURES) {
|
||||
// "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" + slot + " which doesn't exist. MaxNumResourceTextures = " + getMaxNumResourceTextures());
|
||||
qCDebug(gpugllogging) << "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
#include <DependencyManager.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <comdef.h>
|
||||
#include <Wbemidl.h>
|
||||
#include <Windows.h>
|
||||
#include <winreg.h>
|
||||
#endif //Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
@ -30,6 +30,9 @@
|
|||
#endif //Q_OS_MAC
|
||||
|
||||
static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint";
|
||||
|
||||
QUuid FingerprintUtils::_machineFingerprint { QUuid() };
|
||||
|
||||
QString FingerprintUtils::getMachineFingerprintString() {
|
||||
QString uuidString;
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -47,122 +50,32 @@ QString FingerprintUtils::getMachineFingerprintString() {
|
|||
#endif //Q_OS_MAC
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HRESULT hres;
|
||||
IWbemLocator *pLoc = NULL;
|
||||
|
||||
// initialize com. Interface already does, but other
|
||||
// users of this lib don't necessarily do so.
|
||||
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||
if (FAILED(hres)) {
|
||||
qCDebug(networking) << "Failed to initialize COM library!";
|
||||
return uuidString;
|
||||
}
|
||||
HKEY cryptoKey;
|
||||
|
||||
// initialize WbemLocator
|
||||
hres = CoCreateInstance(
|
||||
CLSID_WbemLocator,
|
||||
0,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IWbemLocator, (LPVOID *) &pLoc);
|
||||
// try and open the key that contains the machine GUID
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) {
|
||||
DWORD type;
|
||||
DWORD guidSize;
|
||||
|
||||
if (FAILED(hres)) {
|
||||
qCDebug(networking) << "Failed to initialize WbemLocator";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Connect to WMI through the IWbemLocator::ConnectServer method
|
||||
IWbemServices *pSvc = NULL;
|
||||
const char* MACHINE_GUID_KEY = "MachineGuid";
|
||||
|
||||
// Connect to the root\cimv2 namespace with
|
||||
// the current user and obtain pointer pSvc
|
||||
// to make IWbemServices calls.
|
||||
hres = pLoc->ConnectServer(
|
||||
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
|
||||
NULL, // User name. NULL = current user
|
||||
NULL, // User password. NULL = current
|
||||
0, // Locale. NULL indicates current
|
||||
NULL, // Security flags.
|
||||
0, // Authority (for example, Kerberos)
|
||||
0, // Context object
|
||||
&pSvc // pointer to IWbemServices proxy
|
||||
);
|
||||
// try and retrieve the size of the GUID value
|
||||
if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, &type, NULL, &guidSize) == ERROR_SUCCESS) {
|
||||
// make sure that the value is a string
|
||||
if (type == REG_SZ) {
|
||||
// retrieve the machine GUID and return that as our UUID string
|
||||
std::string machineGUID(guidSize / sizeof(char), '\0');
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pLoc->Release();
|
||||
qCDebug(networking) << "Failed to connect to WMI";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Set security levels on the proxy
|
||||
hres = CoSetProxyBlanket(
|
||||
pSvc, // Indicates the proxy to set
|
||||
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
|
||||
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
|
||||
NULL, // Server principal name
|
||||
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
|
||||
NULL, // client identity
|
||||
EOAC_NONE // proxy capabilities
|
||||
);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
qCDebug(networking) << "Failed to set security on proxy blanket";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Use the IWbemServices pointer to grab the Win32_BIOS stuff
|
||||
IEnumWbemClassObject* pEnumerator = NULL;
|
||||
hres = pSvc->ExecQuery(
|
||||
bstr_t("WQL"),
|
||||
bstr_t("SELECT * FROM Win32_ComputerSystemProduct"),
|
||||
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
|
||||
NULL,
|
||||
&pEnumerator);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
qCDebug(networking) << "query to get Win32_ComputerSystemProduct info";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Get the SerialNumber from the Win32_BIOS data
|
||||
IWbemClassObject *pclsObj;
|
||||
ULONG uReturn = 0;
|
||||
|
||||
SHORT sRetStatus = -100;
|
||||
|
||||
while (pEnumerator) {
|
||||
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
|
||||
|
||||
if(0 == uReturn){
|
||||
break;
|
||||
}
|
||||
|
||||
VARIANT vtProp;
|
||||
|
||||
// Get the value of the Name property
|
||||
hr = pclsObj->Get(L"UUID", 0, &vtProp, 0, 0);
|
||||
if (!FAILED(hres)) {
|
||||
switch (vtProp.vt) {
|
||||
case VT_BSTR:
|
||||
uuidString = QString::fromWCharArray(vtProp.bstrVal);
|
||||
break;
|
||||
if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, NULL,
|
||||
reinterpret_cast<LPBYTE>(&machineGUID[0]), &guidSize) == ERROR_SUCCESS) {
|
||||
uuidString = QString::fromStdString(machineGUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
VariantClear(&vtProp);
|
||||
|
||||
pclsObj->Release();
|
||||
RegCloseKey(cryptoKey);
|
||||
}
|
||||
pEnumerator->Release();
|
||||
|
||||
// Cleanup
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
|
||||
qCDebug(networking) << "Windows BIOS UUID: " << uuidString;
|
||||
#endif //Q_OS_WIN
|
||||
|
||||
return uuidString;
|
||||
|
@ -171,29 +84,36 @@ QString FingerprintUtils::getMachineFingerprintString() {
|
|||
|
||||
QUuid FingerprintUtils::getMachineFingerprint() {
|
||||
|
||||
QString uuidString = getMachineFingerprintString();
|
||||
if (_machineFingerprint.isNull()) {
|
||||
QString uuidString = getMachineFingerprintString();
|
||||
|
||||
// now, turn into uuid. A malformed string will
|
||||
// return QUuid() ("{00000...}"), which handles
|
||||
// any errors in getting the string
|
||||
QUuid uuid(uuidString);
|
||||
|
||||
// now, turn into uuid. A malformed string will
|
||||
// return QUuid() ("{00000...}"), which handles
|
||||
// any errors in getting the string
|
||||
QUuid uuid(uuidString);
|
||||
if (uuid == QUuid()) {
|
||||
// if you cannot read a fallback key cuz we aren't saving them, just generate one for
|
||||
// this session and move on
|
||||
if (DependencyManager::get<Setting::Manager>().isNull()) {
|
||||
return QUuid::createUuid();
|
||||
}
|
||||
// read fallback key (if any)
|
||||
Settings settings;
|
||||
uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString());
|
||||
qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString();
|
||||
if (uuid == QUuid()) {
|
||||
// no fallback yet, set one
|
||||
uuid = QUuid::createUuid();
|
||||
settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString());
|
||||
qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString();
|
||||
// if you cannot read a fallback key cuz we aren't saving them, just generate one for
|
||||
// this session and move on
|
||||
if (DependencyManager::get<Setting::Manager>().isNull()) {
|
||||
return QUuid::createUuid();
|
||||
}
|
||||
// read fallback key (if any)
|
||||
Settings settings;
|
||||
uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString());
|
||||
qCDebug(networking) << "read fallback maching fingerprint: " << uuid.toString();
|
||||
|
||||
if (uuid == QUuid()) {
|
||||
// no fallback yet, set one
|
||||
uuid = QUuid::createUuid();
|
||||
settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString());
|
||||
qCDebug(networking) << "no fallback machine fingerprint, setting it to: " << uuid.toString();
|
||||
}
|
||||
}
|
||||
|
||||
_machineFingerprint = uuid;
|
||||
}
|
||||
return uuid;
|
||||
|
||||
return _machineFingerprint;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
|
||||
private:
|
||||
static QString getMachineFingerprintString();
|
||||
static QUuid _machineFingerprint;
|
||||
};
|
||||
|
||||
#endif // hifi_FingerprintUtils_h
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// BulletUtil.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.02
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
423
libraries/physics/src/CharacterController.cpp
Normal file → Executable file
423
libraries/physics/src/CharacterController.cpp
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CharacterControllerInterface.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.10.21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
@ -13,8 +13,8 @@
|
|||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "PhysicsCollisionGroups.h"
|
||||
#include "ObjectMotionState.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
|
@ -62,10 +62,6 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
|
|||
}
|
||||
|
||||
CharacterController::CharacterController() {
|
||||
_halfHeight = 1.0f;
|
||||
|
||||
_enabled = false;
|
||||
|
||||
_floorDistance = MAX_FALL_HEIGHT;
|
||||
|
||||
_targetVelocity.setValue(0.0f, 0.0f, 0.0f);
|
||||
|
@ -107,6 +103,7 @@ bool CharacterController::needsAddition() const {
|
|||
|
||||
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
// remove from old world
|
||||
if (_dynamicsWorld) {
|
||||
if (_rigidBody) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
|
@ -114,17 +111,23 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
}
|
||||
_dynamicsWorld = nullptr;
|
||||
}
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
if (world && _rigidBody) {
|
||||
// add to new world
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
// Before adding the RigidBody to the world we must save its oldGravity to the side
|
||||
// because adding an object to the world will overwrite it with the default gravity.
|
||||
btVector3 oldGravity = _rigidBody->getGravity();
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
_dynamicsWorld->addAction(this);
|
||||
// restore gravity settings
|
||||
_rigidBody->setGravity(oldGravity);
|
||||
// restore gravity settings because adding an object to the world overwrites its gravity setting
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
|
||||
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
|
||||
}
|
||||
_ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup));
|
||||
_ghost.setCollisionWorld(_dynamicsWorld);
|
||||
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
|
@ -138,38 +141,78 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
}
|
||||
}
|
||||
|
||||
static const float COS_PI_OVER_THREE = cosf(PI / 3.0f);
|
||||
bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
||||
bool pushing = _targetVelocity.length2() > FLT_EPSILON;
|
||||
|
||||
btDispatcher* dispatcher = collisionWorld->getDispatcher();
|
||||
int numManifolds = dispatcher->getNumManifolds();
|
||||
bool hasFloor = false;
|
||||
|
||||
btTransform rotation = _rigidBody->getWorldTransform();
|
||||
rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
|
||||
|
||||
bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) const {
|
||||
int numManifolds = collisionWorld->getDispatcher()->getNumManifolds();
|
||||
for (int i = 0; i < numManifolds; i++) {
|
||||
btPersistentManifold* contactManifold = collisionWorld->getDispatcher()->getManifoldByIndexInternal(i);
|
||||
const btCollisionObject* obA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
|
||||
const btCollisionObject* obB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
|
||||
if (obA == _rigidBody || obB == _rigidBody) {
|
||||
btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i);
|
||||
if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) {
|
||||
bool characterIsFirst = _rigidBody == contactManifold->getBody0();
|
||||
int numContacts = contactManifold->getNumContacts();
|
||||
int stepContactIndex = -1;
|
||||
float highestStep = _minStepHeight;
|
||||
for (int j = 0; j < numContacts; j++) {
|
||||
btManifoldPoint& pt = contactManifold->getContactPoint(j);
|
||||
|
||||
// check to see if contact point is touching the bottom sphere of the capsule.
|
||||
// and the contact normal is not slanted too much.
|
||||
float contactPointY = (obA == _rigidBody) ? pt.m_localPointA.getY() : pt.m_localPointB.getY();
|
||||
btVector3 normal = (obA == _rigidBody) ? pt.m_normalWorldOnB : -pt.m_normalWorldOnB;
|
||||
if (contactPointY < -_halfHeight && normal.dot(_currentUp) > COS_PI_OVER_THREE) {
|
||||
return true;
|
||||
// check for "floor"
|
||||
btManifoldPoint& contact = contactManifold->getContactPoint(j);
|
||||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
|
||||
if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
|
||||
hasFloor = true;
|
||||
if (!pushing) {
|
||||
// we're not pushing against anything so we can early exit
|
||||
// (all we need to know is that there is a floor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pushing && _targetVelocity.dot(normal) < 0.0f) {
|
||||
// remember highest step obstacle
|
||||
if (!_stepUpEnabled || hitHeight > _maxStepHeight) {
|
||||
// this manifold is invalidated by point that is too high
|
||||
stepContactIndex = -1;
|
||||
break;
|
||||
} else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) {
|
||||
highestStep = hitHeight;
|
||||
stepContactIndex = j;
|
||||
hasFloor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stepContactIndex > -1 && highestStep > _stepHeight) {
|
||||
// remember step info for later
|
||||
btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex);
|
||||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
_stepNormal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
_stepHeight = highestStep;
|
||||
_stepPoint = rotation * pointOnCharacter; // rotate into world-frame
|
||||
}
|
||||
if (hasFloor && !(pushing && _stepUpEnabled)) {
|
||||
// early exit since all we need to know is that we're on a floor
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return hasFloor;
|
||||
}
|
||||
|
||||
void CharacterController::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) {
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
}
|
||||
|
||||
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
||||
// trace a ray straight down to see if we're standing on the ground
|
||||
const btTransform& xform = _rigidBody->getWorldTransform();
|
||||
const btTransform& transform = _rigidBody->getWorldTransform();
|
||||
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
|
||||
btVector3 rayStart = transform.getOrigin() - _halfHeight * _currentUp;
|
||||
|
||||
// rayEnd is some short distance outside bottom sphere
|
||||
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
||||
|
@ -183,21 +226,16 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
|||
if (rayCallback.hasHit()) {
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
}
|
||||
|
||||
_hasSupport = checkForSupport(collisionWorld);
|
||||
}
|
||||
|
||||
const btScalar MIN_TARGET_SPEED = 0.001f;
|
||||
const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED;
|
||||
|
||||
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
_stepHeight = _minStepHeight; // clears memory of last step obstacle
|
||||
_hasSupport = checkForSupport(collisionWorld);
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
||||
computeNewVelocity(dt, velocity);
|
||||
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
||||
|
||||
// Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform.
|
||||
// Rather than add this velocity to velocity the RigidBody, we explicitly teleport the RigidBody towards its goal.
|
||||
// This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate().
|
||||
|
||||
const float MINIMUM_TIME_REMAINING = 0.005f;
|
||||
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
||||
|
@ -231,6 +269,47 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
|||
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
||||
}
|
||||
_followTime += dt;
|
||||
|
||||
if (_steppingUp) {
|
||||
float horizontalTargetSpeed = (_targetVelocity - _targetVelocity.dot(_currentUp) * _currentUp).length();
|
||||
if (horizontalTargetSpeed > FLT_EPSILON) {
|
||||
// compute a stepUpSpeed that will reach the top of the step in the time it would take
|
||||
// to move over the _stepPoint at target speed
|
||||
float horizontalDistance = (_stepPoint - _stepPoint.dot(_currentUp) * _currentUp).length();
|
||||
float timeToStep = horizontalDistance / horizontalTargetSpeed;
|
||||
float stepUpSpeed = _stepHeight / timeToStep;
|
||||
|
||||
// magically clamp stepUpSpeed to a fraction of horizontalTargetSpeed
|
||||
// to prevent the avatar from moving unreasonably fast according to human eye
|
||||
const float MAX_STEP_UP_SPEED = 0.65f * horizontalTargetSpeed;
|
||||
if (stepUpSpeed > MAX_STEP_UP_SPEED) {
|
||||
stepUpSpeed = MAX_STEP_UP_SPEED;
|
||||
}
|
||||
|
||||
// add minimum velocity to counteract gravity's displacement during one step
|
||||
// Note: the 0.5 factor comes from the fact that we really want the
|
||||
// average velocity contribution from gravity during the step
|
||||
stepUpSpeed -= 0.5f * _gravity * timeToStep; // remember: _gravity is negative scalar
|
||||
|
||||
btScalar vDotUp = velocity.dot(_currentUp);
|
||||
if (vDotUp < stepUpSpeed) {
|
||||
// character doesn't have enough upward velocity to cover the step so we help using a "sky hook"
|
||||
// which uses micro-teleports rather than applying real velocity
|
||||
// to prevent the avatar from popping up after the step is done
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
transform.setOrigin(transform.getOrigin() + (dt * stepUpSpeed) * _currentUp);
|
||||
_rigidBody->setWorldTransform(transform);
|
||||
}
|
||||
|
||||
// don't allow the avatar to fall downward when stepping up
|
||||
// since otherwise this would tend to defeat the step-up behavior
|
||||
if (vDotUp < 0.0f) {
|
||||
velocity -= vDotUp * _currentUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
}
|
||||
|
||||
void CharacterController::jump() {
|
||||
|
@ -272,95 +351,100 @@ void CharacterController::setState(State desiredState) {
|
|||
#ifdef DEBUG_STATE_CHANGE
|
||||
qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason;
|
||||
#endif
|
||||
if (desiredState == State::Hover && _state != State::Hover) {
|
||||
// hover enter
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
}
|
||||
} else if (_state == State::Hover && desiredState != State::Hover) {
|
||||
// hover exit
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
_state = desiredState;
|
||||
updateGravity();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
|
||||
_boxScale = scale;
|
||||
void CharacterController::updateGravity() {
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
_gravity = 0.0f;
|
||||
} else {
|
||||
const float DEFAULT_CHARACTER_GRAVITY = -5.0f;
|
||||
_gravity = DEFAULT_CHARACTER_GRAVITY;
|
||||
}
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
}
|
||||
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) {
|
||||
float x = scale.x;
|
||||
float z = scale.z;
|
||||
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
float halfHeight = 0.5f * _boxScale.y - radius;
|
||||
float halfHeight = 0.5f * scale.y - radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (halfHeight < MIN_HALF_HEIGHT) {
|
||||
halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
|
||||
// compare dimensions
|
||||
float radiusDelta = glm::abs(radius - _radius);
|
||||
float heightDelta = glm::abs(halfHeight - _halfHeight);
|
||||
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
||||
// shape hasn't changed --> nothing to do
|
||||
} else {
|
||||
if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) {
|
||||
_radius = radius;
|
||||
_halfHeight = halfHeight;
|
||||
const btScalar DEFAULT_MIN_STEP_HEIGHT_FACTOR = 0.005f;
|
||||
const btScalar DEFAULT_MAX_STEP_HEIGHT_FACTOR = 0.65f;
|
||||
_minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
|
||||
_maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
|
||||
|
||||
if (_dynamicsWorld) {
|
||||
// must REMOVE from world prior to shape update
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
|
||||
// only need to ADD back when we happen to be enabled
|
||||
if (_enabled) {
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
|
||||
// it's ok to change offset immediately -- there are no thread safety issues here
|
||||
_shapeLocalOffset = corner + 0.5f * _boxScale;
|
||||
_shapeLocalOffset = minCorner + 0.5f * scale;
|
||||
}
|
||||
|
||||
void CharacterController::setEnabled(bool enabled) {
|
||||
if (enabled != _enabled) {
|
||||
if (enabled) {
|
||||
// Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit.
|
||||
// Setting the ADD bit here works for all cases so we don't even bother checking other bits.
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
void CharacterController::setCollisionless(bool collisionless) {
|
||||
if (collisionless != _collisionless) {
|
||||
_collisionless = collisionless;
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t CharacterController::computeCollisionGroup() const {
|
||||
if (_collisionless) {
|
||||
return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR;
|
||||
} else {
|
||||
return BULLET_COLLISION_GROUP_MY_AVATAR;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::handleChangedCollisionGroup() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) {
|
||||
// ATM the easiest way to update collision groups is to remove/re-add the RigidBody
|
||||
if (_dynamicsWorld) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
}
|
||||
SET_STATE(State::Hover, "setEnabled");
|
||||
_enabled = enabled;
|
||||
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP;
|
||||
updateGravity();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
btVector3 oldUp = _currentUp;
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
if (_state != State::Hover) {
|
||||
const btScalar MIN_UP_ERROR = 0.01f;
|
||||
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
if (_state != State::Hover && _rigidBody) {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setPositionAndOrientation(
|
||||
const glm::vec3& position,
|
||||
const glm::quat& orientation) {
|
||||
// TODO: update gravity if up has changed
|
||||
updateUpAxis(orientation);
|
||||
|
||||
btQuaternion bodyOrientation = glmToBullet(orientation);
|
||||
btVector3 bodyPosition = glmToBullet(position + orientation * _shapeLocalOffset);
|
||||
_characterBodyTransform = btTransform(bodyOrientation, bodyPosition);
|
||||
_rotation = glmToBullet(orientation);
|
||||
_position = glmToBullet(position + orientation * _shapeLocalOffset);
|
||||
}
|
||||
|
||||
void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
|
||||
if (_enabled && _rigidBody) {
|
||||
if (_rigidBody) {
|
||||
const btTransform& avatarTransform = _rigidBody->getWorldTransform();
|
||||
rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset;
|
||||
|
@ -428,16 +512,19 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
btScalar angle = motor.rotation.getAngle();
|
||||
btVector3 velocity = worldVelocity.rotate(axis, -angle);
|
||||
|
||||
if (_state == State::Hover || motor.hTimescale == motor.vTimescale) {
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ||
|
||||
_state == State::Hover || motor.hTimescale == motor.vTimescale) {
|
||||
// modify velocity
|
||||
btScalar tau = dt / motor.hTimescale;
|
||||
if (tau > 1.0f) {
|
||||
tau = 1.0f;
|
||||
}
|
||||
velocity += (motor.velocity - velocity) * tau;
|
||||
velocity += tau * (motor.velocity - velocity);
|
||||
|
||||
// rotate back into world-frame
|
||||
velocity = velocity.rotate(axis, angle);
|
||||
_targetVelocity += (tau * motor.velocity).rotate(axis, angle);
|
||||
|
||||
// store the velocity and weight
|
||||
velocities.push_back(velocity);
|
||||
|
@ -445,12 +532,26 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
} else {
|
||||
// compute local UP
|
||||
btVector3 up = _currentUp.rotate(axis, -angle);
|
||||
btVector3 motorVelocity = motor.velocity;
|
||||
|
||||
// save these non-adjusted components for later
|
||||
btVector3 vTargetVelocity = motorVelocity.dot(up) * up;
|
||||
btVector3 hTargetVelocity = motorVelocity - vTargetVelocity;
|
||||
|
||||
if (_stepHeight > _minStepHeight && !_steppingUp) {
|
||||
// there is a step --> compute velocity direction to go over step
|
||||
btVector3 motorVelocityWF = motorVelocity.rotate(axis, angle);
|
||||
if (motorVelocityWF.dot(_stepNormal) < 0.0f) {
|
||||
// the motor pushes against step
|
||||
_steppingUp = true;
|
||||
}
|
||||
}
|
||||
|
||||
// split velocity into horizontal and vertical components
|
||||
btVector3 vVelocity = velocity.dot(up) * up;
|
||||
btVector3 hVelocity = velocity - vVelocity;
|
||||
btVector3 vTargetVelocity = motor.velocity.dot(up) * up;
|
||||
btVector3 hTargetVelocity = motor.velocity - vTargetVelocity;
|
||||
btVector3 vMotorVelocity = motorVelocity.dot(up) * up;
|
||||
btVector3 hMotorVelocity = motorVelocity - vMotorVelocity;
|
||||
|
||||
// modify each component separately
|
||||
btScalar maxTau = 0.0f;
|
||||
|
@ -460,7 +561,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
tau = 1.0f;
|
||||
}
|
||||
maxTau = tau;
|
||||
hVelocity += (hTargetVelocity - hVelocity) * tau;
|
||||
hVelocity += (hMotorVelocity - hVelocity) * tau;
|
||||
}
|
||||
if (motor.vTimescale < MAX_CHARACTER_MOTOR_TIMESCALE) {
|
||||
btScalar tau = dt / motor.vTimescale;
|
||||
|
@ -470,11 +571,12 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
if (tau > maxTau) {
|
||||
maxTau = tau;
|
||||
}
|
||||
vVelocity += (vTargetVelocity - vVelocity) * tau;
|
||||
vVelocity += (vMotorVelocity - vVelocity) * tau;
|
||||
}
|
||||
|
||||
// add components back together and rotate into world-frame
|
||||
velocity = (hVelocity + vVelocity).rotate(axis, angle);
|
||||
_targetVelocity += maxTau * (hTargetVelocity + vTargetVelocity).rotate(axis, angle);
|
||||
|
||||
// store velocity and weights
|
||||
velocities.push_back(velocity);
|
||||
|
@ -492,6 +594,8 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
velocities.reserve(_motors.size());
|
||||
std::vector<btScalar> weights;
|
||||
weights.reserve(_motors.size());
|
||||
_targetVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
_steppingUp = false;
|
||||
for (int i = 0; i < (int)_motors.size(); ++i) {
|
||||
applyMotor(i, dt, velocity, velocities, weights);
|
||||
}
|
||||
|
@ -507,14 +611,18 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
for (size_t i = 0; i < velocities.size(); ++i) {
|
||||
velocity += (weights[i] / totalWeight) * velocities[i];
|
||||
}
|
||||
_targetVelocity /= totalWeight;
|
||||
}
|
||||
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// 'thrust' is applied at the very end
|
||||
_targetVelocity += dt * _linearAcceleration;
|
||||
velocity += dt * _linearAcceleration;
|
||||
_targetVelocity = velocity;
|
||||
// Note the differences between these two variables:
|
||||
// _targetVelocity = ideal final velocity according to input
|
||||
// velocity = real final velocity after motors are applied to current velocity
|
||||
}
|
||||
|
||||
void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
||||
|
@ -523,57 +631,60 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
|||
velocity = bulletToGLM(btVelocity);
|
||||
}
|
||||
|
||||
void CharacterController::preSimulation() {
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
quint64 now = usecTimestampNow();
|
||||
void CharacterController::updateState() {
|
||||
if (!_dynamicsWorld) {
|
||||
return;
|
||||
}
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||
|
||||
// slam body to where it is supposed to be
|
||||
_rigidBody->setWorldTransform(_characterBodyTransform);
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_preSimulationVelocity = velocity;
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = _position;
|
||||
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = _characterBodyTransform.getOrigin();
|
||||
btScalar rayLength = _radius;
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
||||
rayLength += MAX_FALL_HEIGHT;
|
||||
} else {
|
||||
rayLength += MIN_HOVER_HEIGHT;
|
||||
}
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||
const btScalar MAX_WALKING_SPEED = 2.5f;
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
bool rayHasHit = rayCallback.hasHit();
|
||||
quint64 now = usecTimestampNow();
|
||||
if (rayHasHit) {
|
||||
_rayHitStartTime = now;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||
} else {
|
||||
const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND;
|
||||
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
bool rayHasHit = rayCallback.hasHit();
|
||||
if (rayHasHit) {
|
||||
_rayHitStartTime = now;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||
if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||
rayHasHit = true;
|
||||
} else {
|
||||
_floorDistance = FLT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
// record a time stamp when the jump button was first pressed.
|
||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_jumpButtonDownStartTime = now;
|
||||
_jumpButtonDownCount++;
|
||||
}
|
||||
// record a time stamp when the jump button was first pressed.
|
||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_jumpButtonDownStartTime = now;
|
||||
_jumpButtonDownCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||
|
||||
btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp;
|
||||
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
||||
btVector3 velocity = _preSimulationVelocity;
|
||||
|
||||
// disable normal state transitions while collisionless
|
||||
const btScalar MAX_WALKING_SPEED = 2.65f;
|
||||
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
||||
switch (_state) {
|
||||
case State::Ground:
|
||||
if (!rayHasHit && !_hasSupport) {
|
||||
|
@ -613,6 +724,9 @@ void CharacterController::preSimulation() {
|
|||
break;
|
||||
}
|
||||
case State::Hover:
|
||||
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
|
||||
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
|
||||
|
||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||
SET_STATE(State::InAir, "near ground");
|
||||
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||
|
@ -620,6 +734,28 @@ void CharacterController::preSimulation() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// when collisionless: only switch between State::Ground and State::Hover
|
||||
// and bypass state debugging
|
||||
if (rayHasHit) {
|
||||
if (velocity.length() > (MAX_WALKING_SPEED)) {
|
||||
_state = State::Hover;
|
||||
} else {
|
||||
_state = State::Ground;
|
||||
}
|
||||
} else {
|
||||
_state = State::Hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::preSimulation() {
|
||||
if (_rigidBody) {
|
||||
// slam body transform and remember velocity
|
||||
_rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position)));
|
||||
_preSimulationVelocity = _rigidBody->getLinearVelocity();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
_previousFlags = _pendingFlags;
|
||||
|
@ -631,14 +767,11 @@ void CharacterController::preSimulation() {
|
|||
}
|
||||
|
||||
void CharacterController::postSimulation() {
|
||||
// postSimulation() exists for symmetry and just in case we need to do something here later
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_velocityChange = velocity - _preSimulationVelocity;
|
||||
if (_rigidBody) {
|
||||
_velocityChange = _rigidBody->getLinearVelocity() - _preSimulationVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
||||
if (!_rigidBody) {
|
||||
return false;
|
||||
|
@ -651,11 +784,17 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio
|
|||
}
|
||||
|
||||
void CharacterController::setFlyingAllowed(bool value) {
|
||||
if (_flyingAllowed != value) {
|
||||
if (value != _flyingAllowed) {
|
||||
_flyingAllowed = value;
|
||||
|
||||
if (!_flyingAllowed && _state == State::Hover) {
|
||||
SET_STATE(State::InAir, "flying not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setCollisionlessAllowed(bool value) {
|
||||
if (value != _collisionlessAllowed) {
|
||||
_collisionlessAllowed = value;
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CharacterControllerInterface.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.10.21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterControllerInterface_h
|
||||
#define hifi_CharacterControllerInterface_h
|
||||
#ifndef hifi_CharacterController_h
|
||||
#define hifi_CharacterController_h
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
@ -19,14 +19,18 @@
|
|||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PhysicsCollisionGroups.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
|
||||
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
||||
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
|
||||
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
|
||||
|
||||
const float DEFAULT_CHARACTER_GRAVITY = -5.0f;
|
||||
const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4;
|
||||
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
|
||||
|
||||
class btRigidBody;
|
||||
class btCollisionWorld;
|
||||
|
@ -44,7 +48,7 @@ public:
|
|||
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
void setDynamicsWorld(btDynamicsWorld* world);
|
||||
virtual void setDynamicsWorld(btDynamicsWorld* world);
|
||||
btCollisionObject* getCollisionObject() { return _rigidBody; }
|
||||
|
||||
virtual void updateShapeIfNecessary() = 0;
|
||||
|
@ -56,10 +60,7 @@ public:
|
|||
virtual void warp(const btVector3& origin) override { }
|
||||
virtual void debugDraw(btIDebugDraw* debugDrawer) override { }
|
||||
virtual void setUpInterpolate(bool value) override { }
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override {
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
}
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override;
|
||||
virtual void preStep(btCollisionWorld *collisionWorld) override;
|
||||
virtual void playerStep(btCollisionWorld *collisionWorld, btScalar dt) override;
|
||||
virtual bool canJump() const override { assert(false); return false; } // never call this
|
||||
|
@ -69,6 +70,7 @@ public:
|
|||
void clearMotors();
|
||||
void addMotor(const glm::vec3& velocity, const glm::quat& rotation, float horizTimescale, float vertTimescale = -1.0f);
|
||||
void applyMotor(int index, btScalar dt, btVector3& worldVelocity, std::vector<btVector3>& velocities, std::vector<btScalar>& weights);
|
||||
void setStepUpEnabled(bool enabled) { _stepUpEnabled = enabled; }
|
||||
void computeNewVelocity(btScalar dt, btVector3& velocity);
|
||||
void computeNewVelocity(btScalar dt, glm::vec3& velocity);
|
||||
|
||||
|
@ -103,16 +105,20 @@ public:
|
|||
};
|
||||
|
||||
State getState() const { return _state; }
|
||||
void updateState();
|
||||
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
||||
|
||||
bool isEnabled() const { return _enabled; } // thread-safe
|
||||
void setEnabled(bool enabled);
|
||||
bool isEnabledAndReady() const { return _enabled && _dynamicsWorld; }
|
||||
bool isEnabledAndReady() const { return _dynamicsWorld; }
|
||||
|
||||
void setCollisionless(bool collisionless);
|
||||
int16_t computeCollisionGroup() const;
|
||||
void handleChangedCollisionGroup();
|
||||
|
||||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
|
||||
void setFlyingAllowed(bool value);
|
||||
void setCollisionlessAllowed(bool value);
|
||||
|
||||
|
||||
protected:
|
||||
|
@ -122,8 +128,9 @@ protected:
|
|||
void setState(State state);
|
||||
#endif
|
||||
|
||||
void updateGravity();
|
||||
void updateUpAxis(const glm::quat& rotation);
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld) const;
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld);
|
||||
|
||||
protected:
|
||||
struct CharacterMotor {
|
||||
|
@ -136,6 +143,7 @@ protected:
|
|||
};
|
||||
|
||||
std::vector<CharacterMotor> _motors;
|
||||
CharacterGhostObject _ghost;
|
||||
btVector3 _currentUp;
|
||||
btVector3 _targetVelocity;
|
||||
btVector3 _parentVelocity;
|
||||
|
@ -144,6 +152,8 @@ protected:
|
|||
btTransform _followDesiredBodyTransform;
|
||||
btScalar _followTimeRemaining;
|
||||
btTransform _characterBodyTransform;
|
||||
btVector3 _position;
|
||||
btQuaternion _rotation;
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
|
||||
|
@ -155,13 +165,23 @@ protected:
|
|||
quint32 _jumpButtonDownCount;
|
||||
quint32 _takeoffJumpButtonID;
|
||||
|
||||
btScalar _halfHeight;
|
||||
btScalar _radius;
|
||||
// data for walking up steps
|
||||
btVector3 _stepPoint { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _stepNormal { 0.0f, 0.0f, 0.0f };
|
||||
bool _steppingUp { false };
|
||||
btScalar _stepHeight { 0.0f };
|
||||
btScalar _minStepHeight { 0.0f };
|
||||
btScalar _maxStepHeight { 0.0f };
|
||||
btScalar _minFloorNormalDotUp { DEFAULT_MIN_FLOOR_NORMAL_DOT_UP };
|
||||
|
||||
btScalar _halfHeight { 0.0f };
|
||||
btScalar _radius { 0.0f };
|
||||
|
||||
btScalar _floorDistance;
|
||||
bool _stepUpEnabled { true };
|
||||
bool _hasSupport;
|
||||
|
||||
btScalar _gravity;
|
||||
btScalar _gravity { 0.0f };
|
||||
|
||||
btScalar _jumpSpeed;
|
||||
btScalar _followTime;
|
||||
|
@ -169,7 +189,6 @@ protected:
|
|||
btQuaternion _followAngularDisplacement;
|
||||
btVector3 _linearAcceleration;
|
||||
|
||||
std::atomic_bool _enabled;
|
||||
State _state;
|
||||
bool _isPushingUp;
|
||||
|
||||
|
@ -179,6 +198,8 @@ protected:
|
|||
uint32_t _previousFlags { 0 };
|
||||
|
||||
bool _flyingAllowed { true };
|
||||
bool _collisionlessAllowed { true };
|
||||
bool _collisionless { false };
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterControllerInterface_h
|
||||
#endif // hifi_CharacterController_h
|
||||
|
|
99
libraries/physics/src/CharacterGhostObject.cpp
Executable file
99
libraries/physics/src/CharacterGhostObject.cpp
Executable file
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// CharacterGhostObject.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.08.26
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <PhysicsHelpers.h>
|
||||
|
||||
#include "CharacterRayResult.h"
|
||||
#include "CharacterGhostShape.h"
|
||||
|
||||
|
||||
CharacterGhostObject::~CharacterGhostObject() {
|
||||
removeFromWorld();
|
||||
if (_ghostShape) {
|
||||
delete _ghostShape;
|
||||
_ghostShape = nullptr;
|
||||
setCollisionShape(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setCollisionGroupAndMask(int16_t group, int16_t mask) {
|
||||
_collisionFilterGroup = group;
|
||||
_collisionFilterMask = mask;
|
||||
// TODO: if this probe is in the world reset ghostObject overlap cache
|
||||
}
|
||||
|
||||
void CharacterGhostObject::getCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
|
||||
group = _collisionFilterGroup;
|
||||
mask = _collisionFilterMask;
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight) {
|
||||
_radius = radius;
|
||||
_halfHeight = halfHeight;
|
||||
}
|
||||
|
||||
// override of btCollisionObject::setCollisionShape()
|
||||
void CharacterGhostObject::setCharacterShape(btConvexHullShape* shape) {
|
||||
assert(shape);
|
||||
// we create our own shape with an expanded Aabb for more reliable sweep tests
|
||||
if (_ghostShape) {
|
||||
delete _ghostShape;
|
||||
}
|
||||
|
||||
_ghostShape = new CharacterGhostShape(static_cast<const btConvexHullShape*>(shape));
|
||||
setCollisionShape(_ghostShape);
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) {
|
||||
if (world != _world) {
|
||||
removeFromWorld();
|
||||
_world = world;
|
||||
addToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterGhostObject::rayTest(const btVector3& start,
|
||||
const btVector3& end,
|
||||
CharacterRayResult& result) const {
|
||||
if (_world && _inWorld) {
|
||||
_world->rayTest(start, end, result);
|
||||
}
|
||||
return result.hasHit();
|
||||
}
|
||||
|
||||
void CharacterGhostObject::refreshOverlappingPairCache() {
|
||||
assert(_world && _inWorld);
|
||||
btVector3 minAabb, maxAabb;
|
||||
getCollisionShape()->getAabb(getWorldTransform(), minAabb, maxAabb);
|
||||
// this updates both pairCaches: world broadphase and ghostobject
|
||||
_world->getBroadphase()->setAabb(getBroadphaseHandle(), minAabb, maxAabb, _world->getDispatcher());
|
||||
}
|
||||
|
||||
void CharacterGhostObject::removeFromWorld() {
|
||||
if (_world && _inWorld) {
|
||||
_world->removeCollisionObject(this);
|
||||
_inWorld = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::addToWorld() {
|
||||
if (_world && !_inWorld) {
|
||||
assert(getCollisionShape());
|
||||
setCollisionFlags(getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE);
|
||||
_world->addCollisionObject(this, _collisionFilterGroup, _collisionFilterMask);
|
||||
_inWorld = true;
|
||||
}
|
||||
}
|
62
libraries/physics/src/CharacterGhostObject.h
Executable file
62
libraries/physics/src/CharacterGhostObject.h
Executable file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// CharacterGhostObject.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.08.26
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterGhostObject_h
|
||||
#define hifi_CharacterGhostObject_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "CharacterSweepResult.h"
|
||||
#include "CharacterRayResult.h"
|
||||
|
||||
class CharacterGhostShape;
|
||||
|
||||
class CharacterGhostObject : public btPairCachingGhostObject {
|
||||
public:
|
||||
CharacterGhostObject() { }
|
||||
~CharacterGhostObject();
|
||||
|
||||
void setCollisionGroupAndMask(int16_t group, int16_t mask);
|
||||
void getCollisionGroupAndMask(int16_t& group, int16_t& mask) const;
|
||||
|
||||
void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight);
|
||||
void setUpDirection(const btVector3& up);
|
||||
|
||||
void setCharacterShape(btConvexHullShape* shape);
|
||||
|
||||
void setCollisionWorld(btCollisionWorld* world);
|
||||
|
||||
bool rayTest(const btVector3& start,
|
||||
const btVector3& end,
|
||||
CharacterRayResult& result) const;
|
||||
|
||||
void refreshOverlappingPairCache();
|
||||
|
||||
protected:
|
||||
void removeFromWorld();
|
||||
void addToWorld();
|
||||
|
||||
protected:
|
||||
btCollisionWorld* _world { nullptr }; // input, pointer to world
|
||||
btScalar _halfHeight { 0.0f };
|
||||
btScalar _radius { 0.0f };
|
||||
btConvexHullShape* _characterShape { nullptr }; // input, shape of character
|
||||
CharacterGhostShape* _ghostShape { nullptr }; // internal, shape whose Aabb is used for overlap cache
|
||||
int16_t _collisionFilterGroup { 0 };
|
||||
int16_t _collisionFilterMask { 0 };
|
||||
bool _inWorld { false }; // internal, was added to world
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterGhostObject_h
|
31
libraries/physics/src/CharacterGhostShape.cpp
Normal file
31
libraries/physics/src/CharacterGhostShape.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// CharacterGhostShape.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.14
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "CharacterGhostShape.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
CharacterGhostShape::CharacterGhostShape(const btConvexHullShape* shape) :
|
||||
btConvexHullShape(reinterpret_cast<const btScalar*>(shape->getUnscaledPoints()), shape->getNumPoints(), sizeof(btVector3)) {
|
||||
assert(shape);
|
||||
assert(shape->getUnscaledPoints());
|
||||
assert(shape->getNumPoints() > 0);
|
||||
setMargin(shape->getMargin());
|
||||
}
|
||||
|
||||
void CharacterGhostShape::getAabb (const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const {
|
||||
btConvexHullShape::getAabb(t, aabbMin, aabbMax);
|
||||
// double the size of the Aabb by expanding both corners by half the extent
|
||||
btVector3 expansion = 0.5f * (aabbMax - aabbMin);
|
||||
aabbMin -= expansion;
|
||||
aabbMax += expansion;
|
||||
}
|
25
libraries/physics/src/CharacterGhostShape.h
Normal file
25
libraries/physics/src/CharacterGhostShape.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// CharacterGhostShape.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.14
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterGhostShape_h
|
||||
#define hifi_CharacterGhostShape_h
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btConvexHullShape.h>
|
||||
|
||||
class CharacterGhostShape : public btConvexHullShape {
|
||||
// Same as btConvexHullShape but reports an expanded Aabb for larger ghost overlap cache
|
||||
public:
|
||||
CharacterGhostShape(const btConvexHullShape* shape);
|
||||
|
||||
virtual void getAabb (const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const override;
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterGhostShape_h
|
31
libraries/physics/src/CharacterRayResult.cpp
Executable file
31
libraries/physics/src/CharacterRayResult.cpp
Executable file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// CharaterRayResult.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.05
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "CharacterRayResult.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
CharacterRayResult::CharacterRayResult (const CharacterGhostObject* character) :
|
||||
btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)),
|
||||
_character(character)
|
||||
{
|
||||
assert(_character);
|
||||
_character->getCollisionGroupAndMask(m_collisionFilterGroup, m_collisionFilterMask);
|
||||
}
|
||||
|
||||
btScalar CharacterRayResult::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) {
|
||||
if (rayResult.m_collisionObject == _character) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace);
|
||||
}
|
44
libraries/physics/src/CharacterRayResult.h
Normal file
44
libraries/physics/src/CharacterRayResult.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// CharaterRayResult.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.05
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterRayResult_h
|
||||
#define hifi_CharacterRayResult_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
class CharacterGhostObject;
|
||||
|
||||
class CharacterRayResult : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
CharacterRayResult (const CharacterGhostObject* character);
|
||||
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
|
||||
|
||||
protected:
|
||||
const CharacterGhostObject* _character;
|
||||
|
||||
// Note: Public data members inherited from ClosestRayResultCallback
|
||||
//
|
||||
// btVector3 m_rayFromWorld;//used to calculate hitPointWorld from hitFraction
|
||||
// btVector3 m_rayToWorld;
|
||||
// btVector3 m_hitNormalWorld;
|
||||
// btVector3 m_hitPointWorld;
|
||||
//
|
||||
// Note: Public data members inherited from RayResultCallback
|
||||
//
|
||||
// btScalar m_closestHitFraction;
|
||||
// const btCollisionObject* m_collisionObject;
|
||||
// short int m_collisionFilterGroup;
|
||||
// short int m_collisionFilterMask;
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterRayResult_h
|
42
libraries/physics/src/CharacterSweepResult.cpp
Executable file
42
libraries/physics/src/CharacterSweepResult.cpp
Executable file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// CharaterSweepResult.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.01
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "CharacterSweepResult.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
CharacterSweepResult::CharacterSweepResult(const CharacterGhostObject* character)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)),
|
||||
_character(character)
|
||||
{
|
||||
// set collision group and mask to match _character
|
||||
assert(_character);
|
||||
_character->getCollisionGroupAndMask(m_collisionFilterGroup, m_collisionFilterMask);
|
||||
}
|
||||
|
||||
btScalar CharacterSweepResult::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool useWorldFrame) {
|
||||
// skip objects that we shouldn't collide with
|
||||
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
if (convexResult.m_hitCollisionObject == _character) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
return ClosestConvexResultCallback::addSingleResult(convexResult, useWorldFrame);
|
||||
}
|
||||
|
||||
void CharacterSweepResult::resetHitHistory() {
|
||||
m_hitCollisionObject = nullptr;
|
||||
m_closestHitFraction = btScalar(1.0f);
|
||||
}
|
45
libraries/physics/src/CharacterSweepResult.h
Normal file
45
libraries/physics/src/CharacterSweepResult.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// CharaterSweepResult.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.01
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterSweepResult_h
|
||||
#define hifi_CharacterSweepResult_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
|
||||
class CharacterGhostObject;
|
||||
|
||||
class CharacterSweepResult : public btCollisionWorld::ClosestConvexResultCallback {
|
||||
public:
|
||||
CharacterSweepResult(const CharacterGhostObject* character);
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool useWorldFrame) override;
|
||||
void resetHitHistory();
|
||||
protected:
|
||||
const CharacterGhostObject* _character;
|
||||
|
||||
// NOTE: Public data members inherited from ClosestConvexResultCallback:
|
||||
//
|
||||
// btVector3 m_convexFromWorld; // unused except by btClosestNotMeConvexResultCallback
|
||||
// btVector3 m_convexToWorld; // unused except by btClosestNotMeConvexResultCallback
|
||||
// btVector3 m_hitNormalWorld;
|
||||
// btVector3 m_hitPointWorld;
|
||||
// const btCollisionObject* m_hitCollisionObject;
|
||||
//
|
||||
// NOTE: Public data members inherited from ConvexResultCallback:
|
||||
//
|
||||
// btScalar m_closestHitFraction;
|
||||
// short int m_collisionFilterGroup;
|
||||
// short int m_collisionFilterMask;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterSweepResult_h
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.13
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.13
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ContactEvent.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ContactEvent.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectAction.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectAction.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectMotionState.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.05
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectMotionState.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.05
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicsEngine.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicsEngine.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeFactory.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.12.01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeFactory.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.12.01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeManager.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeManager.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -28,10 +28,10 @@ public:
|
|||
const std::unordered_set<int>& getCauterizeBoneSet() const { return _cauterizeBoneSet; }
|
||||
void setCauterizeBoneSet(const std::unordered_set<int>& boneSet) { _cauterizeBoneSet = boneSet; }
|
||||
|
||||
void deleteGeometry() override;
|
||||
bool updateGeometry() override;
|
||||
void deleteGeometry() override;
|
||||
bool updateGeometry() override;
|
||||
|
||||
void createVisibleRenderItemSet() override;
|
||||
void createVisibleRenderItemSet() override;
|
||||
void createCollisionRenderItemSet() override;
|
||||
|
||||
virtual void updateClusterMatrices() override;
|
||||
|
@ -41,7 +41,7 @@ public:
|
|||
|
||||
protected:
|
||||
std::unordered_set<int> _cauterizeBoneSet;
|
||||
QVector<Model::MeshState> _cauterizeMeshStates;
|
||||
QVector<Model::MeshState> _cauterizeMeshStates;
|
||||
bool _isCauterized { false };
|
||||
bool _enableCauterization { false };
|
||||
};
|
||||
|
|
|
@ -573,7 +573,7 @@ bool Model::addToScene(const render::ScenePointer& scene,
|
|||
|
||||
bool somethingAdded = false;
|
||||
if (_collisionGeometry) {
|
||||
if (_collisionRenderItems.empty()) {
|
||||
if (_collisionRenderItemsMap.empty()) {
|
||||
foreach (auto renderItem, _collisionRenderItems) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
|
@ -583,7 +583,7 @@ bool Model::addToScene(const render::ScenePointer& scene,
|
|||
transaction.resetItem(item, renderPayload);
|
||||
_collisionRenderItemsMap.insert(item, renderPayload);
|
||||
}
|
||||
somethingAdded = !_collisionRenderItems.empty();
|
||||
somethingAdded = !_collisionRenderItemsMap.empty();
|
||||
}
|
||||
} else {
|
||||
if (_modelMeshRenderItemsMap.empty()) {
|
||||
|
@ -632,7 +632,7 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti
|
|||
transaction.removeItem(item);
|
||||
}
|
||||
_collisionRenderItems.clear();
|
||||
_collisionRenderItems.clear();
|
||||
_collisionRenderItemsMap.clear();
|
||||
_addedToScene = false;
|
||||
|
||||
_renderInfoVertexCount = 0;
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
#include <QDebug>
|
||||
#include <GLMHelpers.h>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "Mat4.h"
|
||||
|
||||
glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const {
|
||||
|
@ -66,10 +68,12 @@ glm::vec3 Mat4::getUp(const glm::mat4& m) const {
|
|||
return glm::vec3(m[0][1], m[1][1], m[2][1]);
|
||||
}
|
||||
|
||||
void Mat4::print(const QString& label, const glm::mat4& m) const {
|
||||
qCDebug(scriptengine) << qPrintable(label) <<
|
||||
"row0 =" << m[0][0] << "," << m[1][0] << "," << m[2][0] << "," << m[3][0] <<
|
||||
"row1 =" << m[0][1] << "," << m[1][1] << "," << m[2][1] << "," << m[3][1] <<
|
||||
"row2 =" << m[0][2] << "," << m[1][2] << "," << m[2][2] << "," << m[3][2] <<
|
||||
"row3 =" << m[0][3] << "," << m[1][3] << "," << m[2][3] << "," << m[3][3];
|
||||
void Mat4::print(const QString& label, const glm::mat4& m, bool transpose) const {
|
||||
glm::dmat4 out = transpose ? glm::transpose(m) : m;
|
||||
QString message = QString("%1 %2").arg(qPrintable(label));
|
||||
message = message.arg(glm::to_string(out).c_str());
|
||||
qCDebug(scriptengine) << message;
|
||||
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
|
||||
scriptEngine->print(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
/// Scriptable Mat4 object. Used exclusively in the JavaScript API
|
||||
class Mat4 : public QObject {
|
||||
class Mat4 : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
|
@ -43,7 +44,7 @@ public slots:
|
|||
glm::vec3 getRight(const glm::mat4& m) const;
|
||||
glm::vec3 getUp(const glm::mat4& m) const;
|
||||
|
||||
void print(const QString& label, const glm::mat4& m) const;
|
||||
void print(const QString& label, const glm::mat4& m, bool transpose = false) const;
|
||||
};
|
||||
|
||||
#endif // hifi_Mat4_h
|
||||
|
|
|
@ -15,7 +15,9 @@
|
|||
|
||||
#include <OctreeConstants.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "Quat.h"
|
||||
|
||||
quat Quat::normalize(const glm::quat& q) {
|
||||
|
@ -114,8 +116,17 @@ float Quat::dot(const glm::quat& q1, const glm::quat& q2) {
|
|||
return glm::dot(q1, q2);
|
||||
}
|
||||
|
||||
void Quat::print(const QString& label, const glm::quat& q) {
|
||||
qCDebug(scriptengine) << qPrintable(label) << q.x << "," << q.y << "," << q.z << "," << q.w;
|
||||
void Quat::print(const QString& label, const glm::quat& q, bool asDegrees) {
|
||||
QString message = QString("%1 %2").arg(qPrintable(label));
|
||||
if (asDegrees) {
|
||||
message = message.arg(glm::to_string(glm::dvec3(safeEulerAngles(q))).c_str());
|
||||
} else {
|
||||
message = message.arg(glm::to_string(glm::dquat(q)).c_str());
|
||||
}
|
||||
qCDebug(scriptengine) << message;
|
||||
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
|
||||
scriptEngine->print(message);
|
||||
}
|
||||
}
|
||||
|
||||
bool Quat::equal(const glm::quat& q1, const glm::quat& q2) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
/**jsdoc
|
||||
* A Quaternion
|
||||
|
@ -30,7 +31,7 @@
|
|||
*/
|
||||
|
||||
/// Scriptable interface a Quaternion helper class object. Used exclusively in the JavaScript API
|
||||
class Quat : public QObject {
|
||||
class Quat : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
|
@ -58,7 +59,7 @@ public slots:
|
|||
glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha);
|
||||
glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h);
|
||||
float dot(const glm::quat& q1, const glm::quat& q2);
|
||||
void print(const QString& label, const glm::quat& q);
|
||||
void print(const QString& label, const glm::quat& q, bool asDegrees = false);
|
||||
bool equal(const glm::quat& q1, const glm::quat& q2);
|
||||
glm::quat cancelOutRollAndPitch(const glm::quat& q);
|
||||
glm::quat cancelOutRoll(const glm::quat& q);
|
||||
|
|
|
@ -105,11 +105,11 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
|
|||
}
|
||||
message += context->argument(i).toString();
|
||||
}
|
||||
qCDebug(scriptengineScript).noquote() << "script:print()<<" << message; // noquote() so that \n is treated as newline
|
||||
qCDebug(scriptengineScript).noquote() << message; // noquote() so that \n is treated as newline
|
||||
|
||||
// FIXME - this approach neeeds revisiting. print() comes here, which ends up calling Script.print?
|
||||
engine->globalObject().property("Script").property("print")
|
||||
.call(engine->nullValue(), QScriptValueList({ message }));
|
||||
if (ScriptEngine *scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
|
||||
scriptEngine->print(message);
|
||||
}
|
||||
|
||||
return QScriptValue();
|
||||
}
|
||||
|
@ -472,6 +472,11 @@ void ScriptEngine::scriptInfoMessage(const QString& message) {
|
|||
emit infoMessage(message, getFilename());
|
||||
}
|
||||
|
||||
void ScriptEngine::scriptPrintedMessage(const QString& message) {
|
||||
qCDebug(scriptengine) << message;
|
||||
emit printedMessage(message, getFilename());
|
||||
}
|
||||
|
||||
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
|
||||
// callAnimationStateHandler requires that the type be registered.
|
||||
// These two are meaningful, if we ever do want to use them...
|
||||
|
|
|
@ -221,6 +221,7 @@ public:
|
|||
void scriptErrorMessage(const QString& message);
|
||||
void scriptWarningMessage(const QString& message);
|
||||
void scriptInfoMessage(const QString& message);
|
||||
void scriptPrintedMessage(const QString& message);
|
||||
|
||||
int getNumRunningEntityScripts() const;
|
||||
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
|
||||
|
|
|
@ -453,7 +453,8 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
|
|||
(scriptFilename.scheme() != "http" &&
|
||||
scriptFilename.scheme() != "https" &&
|
||||
scriptFilename.scheme() != "atp" &&
|
||||
scriptFilename.scheme() != "file")) {
|
||||
scriptFilename.scheme() != "file" &&
|
||||
scriptFilename.scheme() != "about")) {
|
||||
// deal with a "url" like c:/something
|
||||
scriptUrl = normalizeScriptURL(QUrl::fromLocalFile(scriptFilename.toString()));
|
||||
} else {
|
||||
|
@ -472,7 +473,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
|
|||
}, Qt::QueuedConnection);
|
||||
|
||||
|
||||
if (scriptFilename.isEmpty()) {
|
||||
if (scriptFilename.isEmpty() || !scriptUrl.isValid()) {
|
||||
launchScriptEngine(scriptEngine);
|
||||
} else {
|
||||
// connect to the appropriate signals of this script engine
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "ScriptEngine.h"
|
||||
#include "ScriptUUID.h"
|
||||
|
||||
QUuid ScriptUUID::fromString(const QString& s) {
|
||||
|
@ -36,6 +37,11 @@ bool ScriptUUID::isNull(const QUuid& id) {
|
|||
return id.isNull();
|
||||
}
|
||||
|
||||
void ScriptUUID::print(const QString& lable, const QUuid& id) {
|
||||
qCDebug(scriptengine) << qPrintable(lable) << id.toString();
|
||||
void ScriptUUID::print(const QString& label, const QUuid& id) {
|
||||
QString message = QString("%1 %2").arg(qPrintable(label));
|
||||
message = message.arg(id.toString());
|
||||
qCDebug(scriptengine) << message;
|
||||
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
|
||||
scriptEngine->print(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
#define hifi_ScriptUUID_h
|
||||
|
||||
#include <QUuid>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
/// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API
|
||||
class ScriptUUID : public QObject {
|
||||
class ScriptUUID : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
|
@ -26,7 +27,7 @@ public slots:
|
|||
QUuid generate();
|
||||
bool isEqual(const QUuid& idA, const QUuid& idB);
|
||||
bool isNull(const QUuid& id);
|
||||
void print(const QString& lable, const QUuid& id);
|
||||
void print(const QString& label, const QUuid& id);
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptUUID_h
|
||||
|
|
|
@ -14,20 +14,26 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <glm/gtx/string_cast.hpp>
|
||||
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "NumericalConstants.h"
|
||||
#include "Vec3.h"
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) {
|
||||
float radians = glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3));
|
||||
return glm::degrees(radians);
|
||||
}
|
||||
|
||||
|
||||
void Vec3::print(const QString& lable, const glm::vec3& v) {
|
||||
qCDebug(scriptengine) << qPrintable(lable) << v.x << "," << v.y << "," << v.z;
|
||||
void Vec3::print(const QString& label, const glm::vec3& v) {
|
||||
QString message = QString("%1 %2").arg(qPrintable(label));
|
||||
message = message.arg(glm::to_string(glm::dvec3(v)).c_str());
|
||||
qCDebug(scriptengine) << message;
|
||||
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
|
||||
scriptEngine->print(message);
|
||||
}
|
||||
}
|
||||
|
||||
bool Vec3::withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtScript/QScriptable>
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
|
@ -48,7 +49,7 @@
|
|||
*/
|
||||
|
||||
/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API
|
||||
class Vec3 : public QObject {
|
||||
class Vec3 : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec3 UNIT_X READ UNIT_X CONSTANT)
|
||||
Q_PROPERTY(glm::vec3 UNIT_Y READ UNIT_Y CONSTANT)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// BackgroundMode.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeInfo.cpp
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeInfo.h
|
||||
// libraries/physcis/src
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1138,3 +1138,17 @@ SpatiallyNestablePointer SpatiallyNestable::findByID(QUuid id, bool& success) {
|
|||
}
|
||||
return parentWP.lock();
|
||||
}
|
||||
|
||||
|
||||
QString SpatiallyNestable::nestableTypeToString(NestableType nestableType) {
|
||||
switch(nestableType) {
|
||||
case NestableType::Entity:
|
||||
return "entity";
|
||||
case NestableType::Avatar:
|
||||
return "avatar";
|
||||
case NestableType::Overlay:
|
||||
return "overlay";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,8 @@ public:
|
|||
virtual const QUuid getID() const;
|
||||
virtual void setID(const QUuid& id);
|
||||
|
||||
virtual QString getName() const { return "SpatiallyNestable"; }
|
||||
|
||||
virtual const QUuid getParentID() const;
|
||||
virtual void setParentID(const QUuid& parentID);
|
||||
|
||||
|
@ -62,6 +64,8 @@ public:
|
|||
static glm::vec3 localToWorldAngularVelocity(const glm::vec3& angularVelocity,
|
||||
const QUuid& parentID, int parentJointIndex, bool& success);
|
||||
|
||||
static QString nestableTypeToString(NestableType nestableType);
|
||||
|
||||
// world frame
|
||||
virtual const Transform getTransform(bool& success, int depth = 0) const;
|
||||
virtual const Transform getTransform() const;
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
print('<span style="color:red">Tests completed with ' +
|
||||
errorCount + ' ' + ERROR + '.<span>');
|
||||
}
|
||||
if (pending.length)
|
||||
if (pending.length) {
|
||||
print ('<span style="color:darkorange">disabled: <br /> '+
|
||||
pending.join('<br /> ')+'</span>');
|
||||
}
|
||||
print('Tests completed in ' + (endTime - startTime) + 'ms.');
|
||||
};
|
||||
this.suiteStarted = function(obj) {
|
||||
|
|
39
scripts/developer/tests/printTest.js
Normal file
39
scripts/developer/tests/printTest.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* eslint-env jasmine */
|
||||
|
||||
// this test generates sample print, Script.print, etc. output
|
||||
|
||||
main();
|
||||
|
||||
function main() {
|
||||
// to match with historical behavior, Script.print(message) output only triggers
|
||||
// the printedMessage signal (and therefore doesn't show up in the application log)
|
||||
Script.print('[Script.print] hello world');
|
||||
|
||||
// the rest of these should show up in both the application log and signaled print handlers
|
||||
print('[print]', 'hello', 'world');
|
||||
|
||||
// note: these trigger the equivalent of an emit
|
||||
Script.printedMessage('[Script.printedMessage] hello world', '{filename}');
|
||||
Script.infoMessage('[Script.infoMessage] hello world', '{filename}');
|
||||
Script.warningMessage('[Script.warningMessage] hello world', '{filename}');
|
||||
Script.errorMessage('[Script.errorMessage] hello world', '{filename}');
|
||||
|
||||
{
|
||||
Vec3.print('[Vec3.print]', Vec3.HALF);
|
||||
|
||||
var q = Quat.fromPitchYawRollDegrees(45, 45, 45);
|
||||
Quat.print('[Quat.print]', q);
|
||||
Quat.print('[Quat.print (euler)]', q, true);
|
||||
|
||||
function vec4(x,y,z,w) {
|
||||
return { x: x, y: y, z: z, w: w };
|
||||
}
|
||||
var m = Mat4.createFromColumns(
|
||||
vec4(1,2,3,4), vec4(5,6,7,8), vec4(9,10,11,12), vec4(13,14,15,16)
|
||||
);
|
||||
Mat4.print('[Mat4.print (col major)]', m);
|
||||
Mat4.print('[Mat4.print (row major)]', m, true);
|
||||
|
||||
Uuid.print('[Uuid.print]', Uuid.fromString(Uuid.toString(0)));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
/* eslint-env jasmine */
|
||||
|
||||
// Art3mis
|
||||
// eslint-disable-next-line max-len
|
||||
var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758";
|
||||
|
||||
var ORIGIN = {x: 0, y: 0, z: 0};
|
||||
|
@ -8,6 +10,15 @@ var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1};
|
|||
|
||||
describe("MyAvatar", function () {
|
||||
|
||||
// backup/restore current skeletonModelURL
|
||||
beforeAll(function() {
|
||||
this.oldURL = MyAvatar.skeletonModelURL;
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
MyAvatar.skeletonModelURL = this.oldURL;
|
||||
});
|
||||
|
||||
// reload the avatar from scratch before each test.
|
||||
beforeEach(function (done) {
|
||||
MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL;
|
||||
|
@ -20,12 +31,12 @@ describe("MyAvatar", function () {
|
|||
MyAvatar.position = ORIGIN;
|
||||
MyAvatar.orientation = ROT_IDENT;
|
||||
// give the avatar 1/2 a second to settle on the ground in the idle pose.
|
||||
Script.setTimeout(function () {
|
||||
Script.setTimeout(function () {
|
||||
done();
|
||||
}, 500);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}, 10000 /* timeout -- allow time to download avatar*/);
|
||||
|
||||
// makes the assumption that there is solid ground somewhat underneath the avatar.
|
||||
it("position and orientation getters", function () {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-env jasmine */
|
||||
|
||||
Script.include('../../../system/libraries/utils.js');
|
||||
|
||||
describe('Bind', function() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
/* eslint-env jasmine */
|
||||
|
||||
describe('Entity', function() {
|
||||
var center = Vec3.sum(
|
||||
MyAvatar.position,
|
||||
|
@ -19,6 +21,14 @@ describe('Entity', function() {
|
|||
},
|
||||
};
|
||||
|
||||
it('serversExist', function() {
|
||||
expect(Entities.serversExist()).toBe(true);
|
||||
});
|
||||
|
||||
it('canRezTmp', function() {
|
||||
expect(Entities.canRezTmp()).toBe(true);
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
boxEntity = Entities.addEntity(boxProps);
|
||||
});
|
||||
|
@ -62,4 +72,4 @@ describe('Entity', function() {
|
|||
props = Entities.getEntityProperties(boxEntity);
|
||||
expect(props.lastEdited).toBeGreaterThan(prevLastEdited);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
/* eslint-env jasmine */
|
||||
|
||||
// Include testing library
|
||||
Script.include('../../libraries/jasmine/jasmine.js');
|
||||
Script.include('../../libraries/jasmine/hifi-boot.js')
|
||||
Script.include('../../libraries/jasmine/hifi-boot.js');
|
||||
|
||||
// Include unit tests
|
||||
// FIXME: Figure out why jasmine done() is not working.
|
||||
// Script.include('avatarUnitTests.js');
|
||||
Script.include('avatarUnitTests.js');
|
||||
Script.include('bindUnitTest.js');
|
||||
Script.include('entityUnitTests.js');
|
||||
|
||||
describe("jasmine internal tests", function() {
|
||||
it('should support async .done()', function(done) {
|
||||
var start = new Date;
|
||||
Script.setTimeout(function() {
|
||||
expect((new Date - start)/1000).toBeCloseTo(0.5, 1);
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
// jasmine pending test
|
||||
xit('disabled test', function() {
|
||||
expect(false).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// invoke Script.stop (after any async tests complete)
|
||||
jasmine.getEnv().addReporter({ jasmineDone: Script.stop });
|
||||
|
||||
// Run the tests
|
||||
jasmine.getEnv().execute();
|
||||
Script.stop();
|
||||
|
|
|
@ -3881,6 +3881,7 @@ function MyController(hand) {
|
|||
// we appear to be holding something and this script isn't in a state that would be holding something.
|
||||
// unhook it. if we previously took note of this entity's parent, put it back where it was. This
|
||||
// works around some problems that happen when more than one hand or avatar is passing something around.
|
||||
var childType = Entities.getNestableType(childID);
|
||||
if (_this.previousParentID[childID]) {
|
||||
var previousParentID = _this.previousParentID[childID];
|
||||
var previousParentJointIndex = _this.previousParentJointIndex[childID];
|
||||
|
@ -3898,7 +3899,7 @@ function MyController(hand) {
|
|||
}
|
||||
_this.previouslyUnhooked[childID] = now;
|
||||
|
||||
if (Overlays.getProperty(childID, "grabbable")) {
|
||||
if (childType == "overlay" && Overlays.getProperty(childID, "grabbable")) {
|
||||
// only auto-unhook overlays that were flagged as grabbable. this avoids unhooking overlays
|
||||
// used in tutorial.
|
||||
Overlays.editOverlay(childID, {
|
||||
|
@ -3906,12 +3907,20 @@ function MyController(hand) {
|
|||
parentJointIndex: previousParentJointIndex
|
||||
});
|
||||
}
|
||||
Entities.editEntity(childID, { parentID: previousParentID, parentJointIndex: previousParentJointIndex });
|
||||
if (childType == "entity") {
|
||||
Entities.editEntity(childID, {
|
||||
parentID: previousParentID,
|
||||
parentJointIndex: previousParentJointIndex
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
Entities.editEntity(childID, { parentID: NULL_UUID });
|
||||
if (Overlays.getProperty(childID, "grabbable")) {
|
||||
Overlays.editOverlay(childID, { parentID: NULL_UUID });
|
||||
if (childType == "entity") {
|
||||
Entities.editEntity(childID, { parentID: NULL_UUID });
|
||||
} else if (childType == "overlay") {
|
||||
if (Overlays.getProperty(childID, "grabbable")) {
|
||||
Overlays.editOverlay(childID, { parentID: NULL_UUID });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,7 +275,8 @@ WebTablet.prototype.getLocation = function() {
|
|||
};
|
||||
|
||||
WebTablet.prototype.setHomeButtonTexture = function() {
|
||||
Entities.editEntity(this.tabletEntityID, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
|
||||
// TODO - is this still needed?
|
||||
// Entities.editEntity(this.tabletEntityID, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
|
||||
};
|
||||
|
||||
WebTablet.prototype.setURL = function (url) {
|
||||
|
@ -338,7 +339,8 @@ WebTablet.prototype.geometryChanged = function (geometry) {
|
|||
|
||||
// compute position, rotation & parentJointIndex of the tablet
|
||||
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
|
||||
Entities.editEntity(this.tabletEntityID, tabletProperties);
|
||||
// TODO -- is this still needed?
|
||||
// Entities.editEntity(this.tabletEntityID, tabletProperties);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -439,7 +441,8 @@ WebTablet.prototype.onHmdChanged = function () {
|
|||
var tabletProperties = {};
|
||||
// compute position, rotation & parentJointIndex of the tablet
|
||||
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
|
||||
Entities.editEntity(this.tabletEntityID, tabletProperties);
|
||||
// TODO -- is this still needed?
|
||||
// Entities.editEntity(this.tabletEntityID, tabletProperties);
|
||||
|
||||
// Full scene FXAA should be disabled on the overlay when the tablet in desktop mode.
|
||||
// This should make the text more readable.
|
||||
|
@ -530,7 +533,8 @@ WebTablet.prototype.cameraModeChanged = function (newMode) {
|
|||
var tabletProperties = {};
|
||||
// compute position, rotation & parentJointIndex of the tablet
|
||||
self.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
|
||||
Entities.editEntity(self.tabletEntityID, tabletProperties);
|
||||
// TODO -- is this still needed?
|
||||
// Entities.editEntity(self.tabletEntityID, tabletProperties);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -122,7 +122,8 @@
|
|||
function debug() {
|
||||
var stateString = "<" + STATE_STRINGS[state] + ">";
|
||||
var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]";
|
||||
print.apply(null, [].concat.apply([LABEL, stateString, JSON.stringify(waitingList), connecting],
|
||||
var current = "[" + currentHand + "/" + currentHandJointIndex + "]"
|
||||
print.apply(null, [].concat.apply([LABEL, stateString, current, JSON.stringify(waitingList), connecting],
|
||||
[].map.call(arguments, JSON.stringify)));
|
||||
}
|
||||
|
||||
|
@ -759,7 +760,10 @@
|
|||
break;
|
||||
case "done":
|
||||
delete waitingList[senderID];
|
||||
if (state === STATES.CONNECTING && connectingId === senderID) {
|
||||
if (connectionId !== senderID) {
|
||||
break;
|
||||
}
|
||||
if (state === STATES.CONNECTING) {
|
||||
// if they are done, and didn't connect us, terminate our
|
||||
// connecting
|
||||
if (message.connectionId !== MyAvatar.sessionUUID) {
|
||||
|
@ -768,11 +772,20 @@
|
|||
// value for isKeyboard, as we should not change the animation
|
||||
// state anyways (if any)
|
||||
startHandshake();
|
||||
} else {
|
||||
// they just created a connection request to us, and we are connecting to
|
||||
// them, so lets just stop connecting and make connection..
|
||||
makeConnection(connectingId);
|
||||
stopConnecting();
|
||||
}
|
||||
} else {
|
||||
// if waiting or inactive, lets clear the connecting id. If in makingConnection,
|
||||
// do nothing
|
||||
if (state !== STATES.MAKING_CONNECTION && connectingId === senderID) {
|
||||
if (state == STATES.MAKING_CONNECTION) {
|
||||
// we are making connection, they just started, so lets reset the
|
||||
// poll count just in case
|
||||
pollCount = 0;
|
||||
} else {
|
||||
// if waiting or inactive, lets clear the connecting id. If in makingConnection,
|
||||
// do nothing
|
||||
clearConnecting();
|
||||
if (state !== STATES.INACTIVE) {
|
||||
startHandshake();
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//
|
||||
|
||||
/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays,
|
||||
MyAvatar, Menu, Vec3 */
|
||||
MyAvatar, Menu, AvatarInputs, Vec3 */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
var tabletRezzed = false;
|
||||
|
@ -25,9 +25,18 @@
|
|||
var debugTablet = false;
|
||||
var tabletScalePercentage = 100.0;
|
||||
UIWebTablet = null;
|
||||
var MSECS_PER_SEC = 1000.0;
|
||||
var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone";
|
||||
var gTablet = null;
|
||||
|
||||
Script.include("../libraries/WebTablet.js");
|
||||
|
||||
function checkTablet() {
|
||||
if (gTablet === null) {
|
||||
gTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
}
|
||||
}
|
||||
|
||||
function tabletIsValid() {
|
||||
if (!UIWebTablet) {
|
||||
return false;
|
||||
|
@ -49,7 +58,8 @@
|
|||
}
|
||||
|
||||
function getTabletScalePercentageFromSettings() {
|
||||
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
|
||||
checkTablet()
|
||||
var toolbarMode = gTablet.toolbarMode;
|
||||
var tabletScalePercentage = DEFAULT_TABLET_SCALE;
|
||||
if (!toolbarMode) {
|
||||
if (HMD.active) {
|
||||
|
@ -77,6 +87,7 @@
|
|||
if (debugTablet) {
|
||||
print("TABLET rezzing");
|
||||
}
|
||||
checkTablet()
|
||||
|
||||
tabletScalePercentage = getTabletScalePercentageFromSettings();
|
||||
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml",
|
||||
|
@ -92,7 +103,8 @@
|
|||
}
|
||||
|
||||
function showTabletUI() {
|
||||
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = true;
|
||||
checkTablet()
|
||||
gTablet.tabletShown = true;
|
||||
|
||||
if (!tabletRezzed || !tabletIsValid()) {
|
||||
closeTabletUI();
|
||||
|
@ -114,7 +126,8 @@
|
|||
}
|
||||
|
||||
function hideTabletUI() {
|
||||
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = false;
|
||||
checkTablet()
|
||||
gTablet.tabletShown = false;
|
||||
if (!UIWebTablet) {
|
||||
return;
|
||||
}
|
||||
|
@ -130,7 +143,8 @@
|
|||
}
|
||||
|
||||
function closeTabletUI() {
|
||||
Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown = false;
|
||||
checkTablet()
|
||||
gTablet.tabletShown = false;
|
||||
if (UIWebTablet) {
|
||||
if (UIWebTablet.onClose) {
|
||||
UIWebTablet.onClose();
|
||||
|
@ -149,17 +163,19 @@
|
|||
print("TABLET closeTabletUI, UIWebTablet is null");
|
||||
}
|
||||
tabletRezzed = false;
|
||||
gTablet = null
|
||||
}
|
||||
|
||||
|
||||
function updateShowTablet() {
|
||||
var MSECS_PER_SEC = 1000.0;
|
||||
var now = Date.now();
|
||||
|
||||
checkTablet()
|
||||
|
||||
// close the WebTablet if it we go into toolbar mode.
|
||||
var tabletShown = Tablet.getTablet("com.highfidelity.interface.tablet.system").tabletShown;
|
||||
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
|
||||
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;
|
||||
var tabletShown = gTablet.tabletShown;
|
||||
var toolbarMode = gTablet.toolbarMode;
|
||||
var landscape = gTablet.landscape;
|
||||
|
||||
if (tabletShown && toolbarMode) {
|
||||
closeTabletUI();
|
||||
|
@ -167,18 +183,20 @@
|
|||
return;
|
||||
}
|
||||
|
||||
//TODO: move to tablet qml?
|
||||
if (tabletShown) {
|
||||
var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone";
|
||||
var currentMicEnabled = !Menu.isOptionChecked(MUTE_MICROPHONE_MENU_ITEM);
|
||||
var currentMicLevel = getMicLevel();
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
tablet.updateMicEnabled(currentMicEnabled);
|
||||
tablet.updateAudioBar(currentMicLevel);
|
||||
gTablet.updateMicEnabled(currentMicEnabled);
|
||||
gTablet.updateAudioBar(currentMicLevel);
|
||||
}
|
||||
|
||||
updateTabletWidthFromSettings();
|
||||
if (UIWebTablet) {
|
||||
UIWebTablet.setLandscape(landscape);
|
||||
if (validCheckTime - now > MSECS_PER_SEC/4) {
|
||||
//each 250ms should be just fine
|
||||
updateTabletWidthFromSettings();
|
||||
if (UIWebTablet) {
|
||||
UIWebTablet.setLandscape(landscape);
|
||||
}
|
||||
}
|
||||
|
||||
if (validCheckTime - now > MSECS_PER_SEC) {
|
||||
|
@ -217,21 +235,20 @@
|
|||
|
||||
// also cause the stylus model to be loaded
|
||||
var tmpStylusID = Overlays.addOverlay("model", {
|
||||
name: "stylus",
|
||||
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
|
||||
loadPriority: 10.0,
|
||||
position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})),
|
||||
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: false,
|
||||
lifetime: 3
|
||||
});
|
||||
name: "stylus",
|
||||
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
|
||||
loadPriority: 10.0,
|
||||
position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})),
|
||||
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: false,
|
||||
lifetime: 3
|
||||
});
|
||||
Script.setTimeout(function() {
|
||||
Overlays.deleteOverlay(tmpStylusID);
|
||||
}, 300);
|
||||
|
||||
} else if (!tabletShown) {
|
||||
hideTabletUI();
|
||||
}
|
||||
|
@ -246,7 +263,8 @@
|
|||
}
|
||||
if (channel === "home") {
|
||||
if (UIWebTablet) {
|
||||
Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape = false;
|
||||
checkTablet()
|
||||
gTablet.landscape = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -257,30 +275,10 @@
|
|||
|
||||
Script.setInterval(updateShowTablet, 100);
|
||||
|
||||
// Initialise variables used to calculate audio level
|
||||
var accumulatedLevel = 0.0;
|
||||
// Note: Might have to tweak the following two based on the rate we're getting the data
|
||||
var AVERAGING_RATIO = 0.05;
|
||||
|
||||
// Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js
|
||||
function getMicLevel() {
|
||||
var LOUDNESS_FLOOR = 11.0;
|
||||
var LOUDNESS_SCALE = 2.8 / 5.0;
|
||||
var LOG2 = Math.log(2.0);
|
||||
var micLevel = 0.0;
|
||||
accumulatedLevel = AVERAGING_RATIO * accumulatedLevel + (1 - AVERAGING_RATIO) * (MyAvatar.audioLoudness);
|
||||
// Convert to log base 2
|
||||
var logLevel = Math.log(accumulatedLevel + 1) / LOG2;
|
||||
|
||||
if (logLevel <= LOUDNESS_FLOOR) {
|
||||
micLevel = logLevel / LOUDNESS_FLOOR * LOUDNESS_SCALE;
|
||||
} else {
|
||||
micLevel = (logLevel - (LOUDNESS_FLOOR - 1.0)) * LOUDNESS_SCALE;
|
||||
}
|
||||
if (micLevel > 1.0) {
|
||||
micLevel = 1.0;
|
||||
}
|
||||
return micLevel;
|
||||
//reuse already existing C++ code
|
||||
return AvatarInputs.loudnessToAudioLevel(MyAvatar.audioLoudness)
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
|
|
|
@ -42,7 +42,7 @@ const appIcon = path.join(__dirname, '../resources/console.png');
|
|||
const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days
|
||||
const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/;
|
||||
|
||||
const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-28.tar.gz";
|
||||
const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-RC39.tar.gz";
|
||||
|
||||
function getBuildInfo() {
|
||||
var buildInfoPath = null;
|
||||
|
|
3
tutorial/Changelog.md
Normal file
3
tutorial/Changelog.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
* home-tutorial-34
|
||||
* Update tutorial to only start if `HMD.active`
|
||||
* Update builder's grid to use "Good - Sub-meshes" for collision shape type
|
179
unpublishedScripts/interaction/Interaction.js
Normal file
179
unpublishedScripts/interaction/Interaction.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
//
|
||||
// Interaction.js
|
||||
// scripts/interaction
|
||||
//
|
||||
// Created by Trevor Berninger on 3/20/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function(){
|
||||
print("loading interaction script");
|
||||
|
||||
var Avatar = false;
|
||||
var NPC = false;
|
||||
var previousNPC = false;
|
||||
var hasCenteredOnNPC = false;
|
||||
var distance = 10;
|
||||
var r = 8;
|
||||
var player = false;
|
||||
|
||||
var baselineX = 0;
|
||||
var baselineY = 0;
|
||||
var nodRange = 20;
|
||||
var shakeRange = 20;
|
||||
|
||||
var ticker = false;
|
||||
var heartbeatTimer = false;
|
||||
|
||||
function callOnNPC(message) {
|
||||
if(NPC)
|
||||
Messages.sendMessage("interactionComs", NPC + ":" + message);
|
||||
else
|
||||
Messages.sendMessage("interactionComs", previousNPC + ":" + message);
|
||||
}
|
||||
|
||||
LimitlessSpeechRecognition.onFinishedSpeaking.connect(function(speech) {
|
||||
print("Got: " + speech);
|
||||
callOnNPC("voiceData:" + speech);
|
||||
});
|
||||
|
||||
LimitlessSpeechRecognition.onReceivedTranscription.connect(function(speech) {
|
||||
callOnNPC("speaking");
|
||||
});
|
||||
|
||||
function setBaselineRotations(rot) {
|
||||
baselineX = rot.x;
|
||||
baselineY = rot.y;
|
||||
}
|
||||
|
||||
function findLookedAtNPC() {
|
||||
var intersection = AvatarList.findRayIntersection({origin: MyAvatar.position, direction: Quat.getFront(Camera.getOrientation())}, true);
|
||||
if (intersection.intersects && intersection.distance <= distance){
|
||||
var npcAvatar = AvatarList.getAvatar(intersection.avatarID);
|
||||
if (npcAvatar.displayName.search("NPC") != -1) {
|
||||
setBaselineRotations(Quat.safeEulerAngles(Camera.getOrientation()));
|
||||
return intersection.avatarID;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isStillFocusedNPC() {
|
||||
var avatar = AvatarList.getAvatar(NPC);
|
||||
if (avatar) {
|
||||
var avatarPosition = avatar.position;
|
||||
return Vec3.distance(MyAvatar.position, avatarPosition) <= distance && Math.abs(Quat.dot(Camera.getOrientation(), Quat.lookAtSimple(MyAvatar.position, avatarPosition))) > 0.6;
|
||||
}
|
||||
return false; // NPC reference died. Maybe it crashed or we teleported to a new world?
|
||||
}
|
||||
|
||||
function onWeLostFocus() {
|
||||
print("lost NPC: " + NPC);
|
||||
callOnNPC("onLostFocused");
|
||||
var baselineX = 0;
|
||||
var baselineY = 0;
|
||||
}
|
||||
|
||||
function onWeGainedFocus() {
|
||||
print("found NPC: " + NPC);
|
||||
callOnNPC("onFocused");
|
||||
var rotation = Quat.safeEulerAngles(Camera.getOrientation());
|
||||
baselineX = rotation.x;
|
||||
baselineY = rotation.y;
|
||||
LimitlessSpeechRecognition.setListeningToVoice(true);
|
||||
}
|
||||
|
||||
function checkFocus() {
|
||||
var newNPC = findLookedAtNPC();
|
||||
|
||||
if (NPC && newNPC != NPC && !isStillFocusedNPC()) {
|
||||
onWeLostFocus();
|
||||
previousNPC = NPC;
|
||||
NPC = false;
|
||||
}
|
||||
if (!NPC && newNPC != false) {
|
||||
NPC = newNPC;
|
||||
onWeGainedFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function checkGesture() {
|
||||
var rotation = Quat.safeEulerAngles(Camera.getOrientation());
|
||||
|
||||
var deltaX = Math.abs(rotation.x - baselineX);
|
||||
if (deltaX > 180) {
|
||||
deltaX -= 180;
|
||||
}
|
||||
var deltaY = Math.abs(rotation.y - baselineY);
|
||||
if (deltaY > 180) {
|
||||
deltaY -= 180;
|
||||
}
|
||||
|
||||
if (deltaX >= nodRange && deltaY <= shakeRange) {
|
||||
callOnNPC("onNodReceived");
|
||||
} else if (deltaY >= shakeRange && deltaX <= nodRange) {
|
||||
callOnNPC("onShakeReceived");
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
checkFocus();
|
||||
if (NPC) {
|
||||
checkGesture();
|
||||
}
|
||||
}
|
||||
|
||||
function heartbeat() {
|
||||
callOnNPC("beat");
|
||||
}
|
||||
|
||||
Messages.subscribe("interactionComs");
|
||||
|
||||
Messages.messageReceived.connect(function (channel, message, sender) {
|
||||
if(channel === "interactionComs" && player) {
|
||||
var codeIndex = message.search('clientexec');
|
||||
if(codeIndex != -1) {
|
||||
var code = message.substr(codeIndex+11);
|
||||
Script.evaluate(code, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.enterEntity = function(id) {
|
||||
player = true;
|
||||
print("Something entered me: " + id);
|
||||
LimitlessSpeechRecognition.setAuthKey("testKey");
|
||||
if (!ticker) {
|
||||
ticker = Script.setInterval(tick, 333);
|
||||
}
|
||||
if(!heartbeatTimer) {
|
||||
heartbeatTimer = Script.setInterval(heartbeat, 1000);
|
||||
}
|
||||
};
|
||||
this.leaveEntity = function(id) {
|
||||
LimitlessSpeechRecognition.setListeningToVoice(false);
|
||||
player = false;
|
||||
print("Something left me: " + id);
|
||||
if (previousNPC)
|
||||
Messages.sendMessage("interactionComs", previousNPC + ":leftArea");
|
||||
if (ticker) {
|
||||
ticker.stop();
|
||||
ticker = false;
|
||||
}
|
||||
if (heartbeatTimer) {
|
||||
heartbeatTimer.stop();
|
||||
heartbeatTimer = false;
|
||||
}
|
||||
};
|
||||
this.unload = function() {
|
||||
print("Okay. I'm Unloading!");
|
||||
if (ticker) {
|
||||
ticker.stop();
|
||||
ticker = false;
|
||||
}
|
||||
};
|
||||
print("finished loading interaction script");
|
||||
});
|
179
unpublishedScripts/interaction/NPCHelpers.js
Normal file
179
unpublishedScripts/interaction/NPCHelpers.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
//
|
||||
// NPCHelpers.js
|
||||
// scripts/interaction
|
||||
//
|
||||
// Created by Trevor Berninger on 3/20/17.
|
||||
// Copyright 2017 High Fidelity Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var audioInjector = false;
|
||||
var blocked = false;
|
||||
var playingResponseAnim = false;
|
||||
var storyURL = "";
|
||||
var _qid = "start";
|
||||
|
||||
print("TESTTEST");
|
||||
|
||||
function strContains(str, sub) {
|
||||
return str.search(sub) != -1;
|
||||
}
|
||||
|
||||
function callbackOnCondition(conditionFunc, ms, callback, count) {
|
||||
var thisCount = 0;
|
||||
if (typeof count !== 'undefined') {
|
||||
thisCount = count;
|
||||
}
|
||||
if (conditionFunc()) {
|
||||
callback();
|
||||
} else if (thisCount < 10) {
|
||||
Script.setTimeout(function() {
|
||||
callbackOnCondition(conditionFunc, ms, callback, thisCount + 1);
|
||||
}, ms);
|
||||
} else {
|
||||
print("callbackOnCondition timeout");
|
||||
}
|
||||
}
|
||||
|
||||
function playAnim(animURL, looping, onFinished) {
|
||||
print("got anim: " + animURL);
|
||||
print("looping: " + looping);
|
||||
// Start caching the animation if not already cached.
|
||||
AnimationCache.getAnimation(animURL);
|
||||
|
||||
// Tell the avatar to animate so that we can tell if the animation is ready without crashing
|
||||
Avatar.startAnimation(animURL, 30, 1, false, false, 0, 1);
|
||||
|
||||
// Continually check if the animation is ready
|
||||
callbackOnCondition(function(){
|
||||
var details = Avatar.getAnimationDetails();
|
||||
// if we are running the request animation and are past the first frame, the anim is loaded properly
|
||||
print("running: " + details.running);
|
||||
print("url and animURL: " + details.url.trim().replace(/ /g, "%20") + " | " + animURL.trim().replace(/ /g, "%20"));
|
||||
print("currentFrame: " + details.currentFrame);
|
||||
return details.running && details.url.trim().replace(/ /g, "%20") == animURL.trim().replace(/ /g, "%20") && details.currentFrame > 0;
|
||||
}, 250, function(){
|
||||
var timeOfAnim = ((AnimationCache.getAnimation(animURL).frames.length / 30) * 1000) + 100; // frames to miliseconds plus a small buffer
|
||||
print("animation loaded. length: " + timeOfAnim);
|
||||
// Start the animation again but this time with frame information
|
||||
Avatar.startAnimation(animURL, 30, 1, looping, true, 0, AnimationCache.getAnimation(animURL).frames.length);
|
||||
if (typeof onFinished !== 'undefined') {
|
||||
print("onFinished defined. setting the timeout with timeOfAnim");
|
||||
timers.push(Script.setTimeout(onFinished, timeOfAnim));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function playSound(soundURL, onFinished) {
|
||||
callbackOnCondition(function() {
|
||||
return SoundCache.getSound(soundURL).downloaded;
|
||||
}, 250, function() {
|
||||
if (audioInjector) {
|
||||
audioInjector.stop();
|
||||
}
|
||||
audioInjector = Audio.playSound(SoundCache.getSound(soundURL), {position: Avatar.position, volume: 1.0});
|
||||
if (typeof onFinished !== 'undefined') {
|
||||
audioInjector.finished.connect(onFinished);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function npcRespond(soundURL, animURL, onFinished) {
|
||||
if (typeof soundURL !== 'undefined' && soundURL != '') {
|
||||
print("npcRespond got soundURL!");
|
||||
playSound(soundURL, function(){
|
||||
print("sound finished");
|
||||
var animDetails = Avatar.getAnimationDetails();
|
||||
print("animDetails.lastFrame: " + animDetails.lastFrame);
|
||||
print("animDetails.currentFrame: " + animDetails.currentFrame);
|
||||
if (animDetails.lastFrame < animDetails.currentFrame + 1 || !playingResponseAnim) {
|
||||
onFinished();
|
||||
}
|
||||
audioInjector = false;
|
||||
});
|
||||
}
|
||||
if (typeof animURL !== 'undefined' && animURL != '') {
|
||||
print("npcRespond got animURL!");
|
||||
playingResponseAnim = true;
|
||||
playAnim(animURL, false, function() {
|
||||
print("anim finished");
|
||||
playingResponseAnim = false;
|
||||
print("injector: " + audioInjector);
|
||||
if (!audioInjector || !audioInjector.isPlaying()) {
|
||||
print("resetting Timer");
|
||||
print("about to call onFinished");
|
||||
onFinished();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function npcRespondBlocking(soundURL, animURL, onFinished) {
|
||||
print("blocking response requested");
|
||||
if (!blocked) {
|
||||
print("not already blocked");
|
||||
blocked = true;
|
||||
npcRespond(soundURL, animURL, function(){
|
||||
if (onFinished){
|
||||
onFinished();
|
||||
}blocked = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function npcContinueStory(soundURL, animURL, nextID, onFinished) {
|
||||
if (!nextID) {
|
||||
nextID = _qid;
|
||||
}
|
||||
npcRespondBlocking(soundURL, animURL, function(){
|
||||
if (onFinished){
|
||||
onFinished();
|
||||
}setQid(nextID);
|
||||
});
|
||||
}
|
||||
|
||||
function setQid(newQid) {
|
||||
print("setting quid");
|
||||
print("_qid: " + _qid);
|
||||
_qid = newQid;
|
||||
print("_qid: " + _qid);
|
||||
doActionFromServer("init");
|
||||
}
|
||||
|
||||
function runOnClient(code) {
|
||||
Messages.sendMessage("interactionComs", "clientexec:" + code);
|
||||
}
|
||||
|
||||
function doActionFromServer(action, data, useServerCache) {
|
||||
if (action == "start") {
|
||||
ignoreCount = 0;
|
||||
_qid = "start";
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "http://gserv_devel.studiolimitless.com/story", true);
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState == 4){
|
||||
if (xhr.status == 200) {
|
||||
print("200!");
|
||||
print("evaluating: " + xhr.responseText);
|
||||
Script.evaluate(xhr.responseText, "");
|
||||
} else if (xhr.status == 444) {
|
||||
print("Limitless Serv 444: API error: " + xhr.responseText);
|
||||
} else {
|
||||
print("HTTP Code: " + xhr.status + ": " + xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
var postData = "url=" + storyURL + "&action=" + action + "&qid=" + _qid;
|
||||
if (typeof data !== 'undefined' && data != '') {
|
||||
postData += "&data=" + data;
|
||||
}
|
||||
if (typeof useServerCache !== 'undefined' && !useServerCache) {
|
||||
postData += "&nocache=true";
|
||||
}
|
||||
print("Sending: " + postData);
|
||||
xhr.send(postData);
|
||||
}
|
102
unpublishedScripts/interaction/NPC_AC.js
Normal file
102
unpublishedScripts/interaction/NPC_AC.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// NPC_AC.js
|
||||
// scripts/interaction
|
||||
//
|
||||
// Created by Trevor Berninger on 3/20/17.
|
||||
// Copyright 2017 High Fidelity Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var currentlyUsedIndices = [];
|
||||
var timers = [];
|
||||
var currentlyEngaged = false;
|
||||
var questionNumber = 0;
|
||||
var heartbeatTimeout = false;
|
||||
function getRandomRiddle() {
|
||||
var randIndex = null;
|
||||
do {
|
||||
randIndex = Math.floor(Math.random() * 15) + 1;
|
||||
} while (randIndex in currentlyUsedIndices);
|
||||
|
||||
currentlyUsedIndices.push(randIndex);
|
||||
return randIndex.toString();
|
||||
}
|
||||
|
||||
Script.include("https://raw.githubusercontent.com/Delamare2112/hifi/Interaction/unpublishedScripts/interaction/NPCHelpers.js", function(){
|
||||
print("NPCHelpers included.");main();
|
||||
});
|
||||
|
||||
var idleAnim = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/idle.fbx";
|
||||
var FST = "https://s3.amazonaws.com/hifi-public/tony/fixed-sphinx/sphinx.fst";
|
||||
|
||||
Agent.isAvatar = true;
|
||||
Avatar.skeletonModelURL = FST;
|
||||
Avatar.displayName = "NPC";
|
||||
Avatar.position = {x: 0.3, y: -23.4, z: 8.0};
|
||||
Avatar.orientation = {x: 0, y: 1, z: 0, w: 0};
|
||||
// Avatar.position = {x: 1340.3555, y: 4.078, z: -420.1562};
|
||||
// Avatar.orientation = {x: 0, y: -0.707, z: 0, w: 0.707};
|
||||
Avatar.scale = 2;
|
||||
|
||||
Messages.subscribe("interactionComs");
|
||||
|
||||
function endInteraction() {
|
||||
print("ending interaction");
|
||||
blocked = false;
|
||||
currentlyEngaged = false;
|
||||
if(audioInjector)
|
||||
audioInjector.stop();
|
||||
for (var t in timers) {
|
||||
Script.clearTimeout(timers[t]);
|
||||
}
|
||||
if(_qid != "Restarting") {
|
||||
npcRespondBlocking(
|
||||
'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/EarlyExit_0' + (Math.floor(Math.random() * 2) + 1).toString() + '.wav',
|
||||
'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx',
|
||||
function(){
|
||||
Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
storyURL = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Sphinx.json";
|
||||
Messages.messageReceived.connect(function (channel, message, sender) {
|
||||
if(!strContains(message, 'beat'))
|
||||
print(sender + " -> NPC @" + Agent.sessionUUID + ": " + message);
|
||||
if (channel === "interactionComs" && strContains(message, Agent.sessionUUID)) {
|
||||
if (strContains(message, 'beat')) {
|
||||
if(heartbeatTimeout) {
|
||||
Script.clearTimeout(heartbeatTimeout);
|
||||
heartbeatTimeout = false;
|
||||
}
|
||||
heartbeatTimeout = Script.setTimeout(endInteraction, 1500);
|
||||
}
|
||||
else if (strContains(message, "onFocused") && !currentlyEngaged) {
|
||||
blocked = false;
|
||||
currentlyEngaged = true;
|
||||
currentlyUsedIndices = [];
|
||||
doActionFromServer("start");
|
||||
} else if (strContains(message, "leftArea")) {
|
||||
|
||||
} else if (strContains(message, "speaking")) {
|
||||
|
||||
} else {
|
||||
var voiceDataIndex = message.search("voiceData");
|
||||
if (voiceDataIndex != -1) {
|
||||
var words = message.substr(voiceDataIndex+10);
|
||||
if (!isNaN(_qid) && (strContains(words, "repeat") || (strContains(words, "say") && strContains(words, "again")))) {
|
||||
doActionFromServer("init");
|
||||
} else {
|
||||
doActionFromServer("words", words);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Script.update.connect(updateGem);
|
||||
Avatar.startAnimation("https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx", 0);
|
||||
}
|
159
unpublishedScripts/interaction/Sphinx.json
Normal file
159
unpublishedScripts/interaction/Sphinx.json
Normal file
|
@ -0,0 +1,159 @@
|
|||
{
|
||||
"Name": "10 Questions",
|
||||
"Defaults":
|
||||
{
|
||||
"Actions":
|
||||
{
|
||||
"positive": "var x=function(){if(questionNumber>=2){setQid('Finished');return;}var suffix=['A', 'B'][questionNumber++] + '_0' + (Math.floor(Math.random() * 2) + 2).toString() + '.wav';npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/RightAnswer'+suffix, 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/RightAnswerB_02.fbx', getRandomRiddle());};x();",
|
||||
"unknown": "var suffix=(Math.floor(Math.random() * 3) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/WrongAnswer_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/WrongAnswer_0' + suffix + '.fbx', getRandomRiddle());",
|
||||
"hint": "var suffix=(Math.floor(Math.random() * 2) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Hint_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hint_0' + suffix + '.fbx')"
|
||||
},
|
||||
"Responses":
|
||||
{
|
||||
"positive": ["yes","yup","yeah","yahoo","sure","affirmative","okay","aye","right","exactly","course","naturally","unquestionably","positively","yep","definitely","certainly","fine","absolutely","positive","love","fantastic"],
|
||||
"thinking": ["oh", "think about", "i know", "what was", "well", "not sure", "one before", "hold", "one moment", "one second", "1 second", "1 sec", "one sec"],
|
||||
"hint": ["hint", "heads"]
|
||||
}
|
||||
},
|
||||
"Story":
|
||||
[
|
||||
{
|
||||
"QID": "start",
|
||||
"init": "questionNumber=0;npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/HiFi_Sphinx_Anim_Combined_Entrance_Audio.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', getRandomRiddle());"
|
||||
},
|
||||
{
|
||||
"QID": "1",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Blackboard.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Blackboard.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["blackboard", "chalkboard", "chalk board", "slate"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "2",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Breath.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Breath.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["breath", "death"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "3",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Clock.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Clock.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["clock", "cock"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "4",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coffin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coffin.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["coffin", "casket", "possum"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "5",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coin.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["coin", "boing", "coinage", "coin piece", "change", "join"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "6",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Corn.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Corn.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["corn", "born", "maize", "maze", "means", "torn", "horn", "worn", "porn"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "7",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Darkness.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Darkness.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["darkness", "dark", "blackness"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "8",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gloves.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gloves.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["gloves", "love"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "9",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gold.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gold.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["gold", "old", "bold", "cold", "told"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "10",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_River.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_River.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["river", "bigger", "stream", "creek", "brook"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "11",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Secret.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Secret.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["secret"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "12",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Shadow.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Shadow.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["shadow"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "13",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Silence.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Silence.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["silence", "lance", "quiet"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "14",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Stairs.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Stairs.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["stairs", "steps", "stair", "stairwell", "there's", "stairway"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "15",
|
||||
"init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Umbrella.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Umbrella.fbx');",
|
||||
"responses":
|
||||
{
|
||||
"positive": ["umbrella"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"QID": "Finished",
|
||||
"init": "Script.clearTimeout(heartbeatTimeout);heartbeatTimeout = false;npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/ConclusionRight_02.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/ConclusionRight_02.fbx', function(){runOnClient('MyAvatar.goToLocation({x: 5, y: -29, z: -63}, true, true);');setQid('Restarting');});",
|
||||
"positive": "",
|
||||
"negative": "",
|
||||
"unknown": ""
|
||||
},
|
||||
{
|
||||
"QID": "Restarting",
|
||||
"init": "npcRespondBlocking('', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx', function(){Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0);_qid='';});",
|
||||
"positive": "",
|
||||
"negative": "",
|
||||
"unknown": ""
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue