From 6b96cfad42f2ab88dd07dad1fb3880d217de96ee Mon Sep 17 00:00:00 2001 From: Trevor Berninger Date: Thu, 13 Apr 2017 13:04:28 -0700 Subject: [PATCH 01/65] Just the interaction JS scripts --- unpublishedScripts/interaction/Interaction.js | 179 ++++++++++++++++++ unpublishedScripts/interaction/NPCHelpers.js | 179 ++++++++++++++++++ unpublishedScripts/interaction/NPC_AC.js | 102 ++++++++++ unpublishedScripts/interaction/Sphinx.json | 159 ++++++++++++++++ 4 files changed, 619 insertions(+) create mode 100644 unpublishedScripts/interaction/Interaction.js create mode 100644 unpublishedScripts/interaction/NPCHelpers.js create mode 100644 unpublishedScripts/interaction/NPC_AC.js create mode 100644 unpublishedScripts/interaction/Sphinx.json diff --git a/unpublishedScripts/interaction/Interaction.js b/unpublishedScripts/interaction/Interaction.js new file mode 100644 index 0000000000..a488e9f279 --- /dev/null +++ b/unpublishedScripts/interaction/Interaction.js @@ -0,0 +1,179 @@ +// +// Interaction.js +// scripts/interaction +// +// Created by Trevor Berninger on 3/20/17. +// Copyright 2017 Limitless ltd. +// +// 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"); +}); diff --git a/unpublishedScripts/interaction/NPCHelpers.js b/unpublishedScripts/interaction/NPCHelpers.js new file mode 100644 index 0000000000..d36a71ea93 --- /dev/null +++ b/unpublishedScripts/interaction/NPCHelpers.js @@ -0,0 +1,179 @@ +// +// NPCHelpers.js +// scripts/interaction +// +// Created by Trevor Berninger on 3/20/17. +// Copyright 2017 Limitless ltd. +// +// 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); +} diff --git a/unpublishedScripts/interaction/NPC_AC.js b/unpublishedScripts/interaction/NPC_AC.js new file mode 100644 index 0000000000..6eaee6a6e3 --- /dev/null +++ b/unpublishedScripts/interaction/NPC_AC.js @@ -0,0 +1,102 @@ +// +// NPC_AC.js +// scripts/interaction +// +// Created by Trevor Berninger on 3/20/17. +// Copyright 2017 Limitless ltd. +// +// 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); +} diff --git a/unpublishedScripts/interaction/Sphinx.json b/unpublishedScripts/interaction/Sphinx.json new file mode 100644 index 0000000000..2a76417fd7 --- /dev/null +++ b/unpublishedScripts/interaction/Sphinx.json @@ -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": "" + } + ] +} From cd2665fc55e2dc7a39856e90ae643258e3c92555 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 24 Apr 2017 18:10:50 -0400 Subject: [PATCH 02/65] standardize Audio bg thread joins --- interface/src/Application.cpp | 11 +-- libraries/audio-client/src/AudioClient.cpp | 89 +++++++++++++--------- libraries/audio-client/src/AudioClient.h | 38 +++------ 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fbf28a8d99..c94b1bbd2f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1683,7 +1683,6 @@ void Application::updateHeartbeat() const { void Application::aboutToQuit() { emit beforeAboutToQuit(); - DependencyManager::get()->beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->isActive()) { @@ -1784,14 +1783,12 @@ void Application::cleanupBeforeQuit() { _snapshotSoundInjector->stop(); } - // stop audio after QML, as there are unexplained audio crashes originating in qtwebengine - - // stop the AudioClient, synchronously - QMetaObject::invokeMethod(DependencyManager::get().data(), - "stop", Qt::BlockingQueuedConnection); - + // FIXME: something else is holding a reference to AudioClient, + // so it must be explicitly synchronously stopped here + QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); // destroy Audio so it and its threads have a chance to go down safely + // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4a2de0a64b..6107fef516 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -76,42 +76,58 @@ using Mutex = std::mutex; using Lock = std::unique_lock; static Mutex _deviceMutex; -// background thread that continuously polls for device changes -class CheckDevicesThread : public QThread { +class BackgroundThread : public QThread { public: - const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; + BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {} + virtual void join() = 0; +protected: + AudioClient* _client; +}; - CheckDevicesThread(AudioClient* audioClient) - : _audioClient(audioClient) { - } +// background thread continuously polling device changes +class CheckDevicesThread : public BackgroundThread { +public: + CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {} - void beforeAboutToQuit() { - Lock lock(_checkDevicesMutex); + void join() override { _quit = true; + std::unique_lock lock(mutex); + cv.wait(lock, [&]{ return !running; }); } +protected: void run() override { - while (true) { - { - Lock lock(_checkDevicesMutex); - if (_quit) { - break; - } - _audioClient->checkDevices(); - } + while (!_quit) { + _client->checkDevices(); + + const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); } + std::lock_guard lock(mutex); + running = false; + cv.notify_one(); } private: - AudioClient* _audioClient { nullptr }; - Mutex _checkDevicesMutex; - bool _quit { false }; + std::atomic _quit { false }; + bool running { true }; + std::mutex mutex; + std::condition_variable cv; }; -void AudioInjectorsThread::prepare() { - _audio->prepareLocalAudioInjectors(); -} +// background thread buffering local injectors +class LocalInjectorsThread : public BackgroundThread { + Q_OBJECT +public: + LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {} + + void join() override { return; } + +private slots: + void prepare() { _client->prepareLocalAudioInjectors(); } +}; + +#include "AudioClient.moc" static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) { for (int i = 0; i < numSamples/2; i++) { @@ -179,7 +195,6 @@ AudioClient::AudioClient() : _inputToNetworkResampler(NULL), _networkToOutputResampler(NULL), _localToOutputResampler(NULL), - _localAudioThread(this), _audioLimiter(AudioConstants::SAMPLE_RATE, OUTPUT_CHANNEL_COUNT), _outgoingAvatarAudioSequenceNumber(0), _audioOutputIODevice(_localInjectorsStream, _receivedAudioStream, this), @@ -210,13 +225,14 @@ AudioClient::AudioClient() : // start a thread to detect any device changes _checkDevicesThread = new CheckDevicesThread(this); - _checkDevicesThread->setObjectName("CheckDevices Thread"); + _checkDevicesThread->setObjectName("AudioClient CheckDevices Thread"); _checkDevicesThread->setPriority(QThread::LowPriority); _checkDevicesThread->start(); // start a thread to process local injectors - _localAudioThread.setObjectName("LocalAudio Thread"); - _localAudioThread.start(); + _localInjectorsThread = new LocalInjectorsThread(this); + _localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread"); + _localInjectorsThread->start(); configureReverb(); @@ -231,18 +247,23 @@ AudioClient::AudioClient() : } AudioClient::~AudioClient() { - delete _checkDevicesThread; - stop(); if (_codec && _encoder) { _codec->releaseEncoder(_encoder); _encoder = nullptr; } } -void AudioClient::beforeAboutToQuit() { - static_cast(_checkDevicesThread)->beforeAboutToQuit(); -} +void AudioClient::customDeleter() { + stop(); // synchronously + static_cast(_checkDevicesThread)->join(); + delete _checkDevicesThread; + + static_cast(_localInjectorsThread)->join(); + delete _localInjectorsThread; + + deleteLater(); // asynchronously +} void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { qCDebug(audioclient) << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec; @@ -1527,8 +1548,8 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice _outputScratchBuffer = new int16_t[_outputPeriod]; // size local output mix buffer based on resampled network frame size - _networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); - _localOutputMixBuffer = new float[_networkPeriod]; + int networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); + _localOutputMixBuffer = new float[networkPeriod]; int localPeriod = _outputPeriod * 2; _localInjectorsStream.resizeForFrameSize(localPeriod); @@ -1691,7 +1712,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { } // prepare injectors for the next callback - QMetaObject::invokeMethod(&_audio->_localAudioThread, "prepare", Qt::QueuedConnection); + QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection); int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped); int framesPopped = samplesPopped / AudioConstants::STEREO; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 139749e8e8..6e69915f0a 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -71,19 +71,6 @@ class QIODevice; class Transform; class NLPacket; -class AudioInjectorsThread : public QThread { - Q_OBJECT - -public: - AudioInjectorsThread(AudioClient* audio) : _audio(audio) {} - -public slots : - void prepare(); - -private: - AudioClient* _audio; -}; - class AudioClient : public AbstractAudioInterface, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -186,8 +173,6 @@ public slots: void audioMixerKilled(); void toggleMute(); - void beforeAboutToQuit(); - virtual void setIsStereoInput(bool stereo) override; void toggleAudioNoiseReduction() { _isNoiseGateEnabled = !_isNoiseGateEnabled; } @@ -244,9 +229,7 @@ protected: AudioClient(); ~AudioClient(); - virtual void customDeleter() override { - deleteLater(); - } + virtual void customDeleter() override; private: void outputFormatChanged(); @@ -339,19 +322,17 @@ private: // for network audio (used by network audio thread) int16_t _networkScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; - // for local audio (used by audio injectors thread) - int _networkPeriod { 0 }; - float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; - int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; - float* _localOutputMixBuffer { NULL }; - AudioInjectorsThread _localAudioThread; - RecursiveMutex _localAudioMutex; - // for output audio (used by this thread) int _outputPeriod { 0 }; float* _outputMixBuffer { NULL }; int16_t* _outputScratchBuffer { NULL }; + // for local audio (used by audio injectors thread) + float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO]; + int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; + float* _localOutputMixBuffer { NULL }; + RecursiveMutex _localAudioMutex; + AudioLimiter _audioLimiter; // Adds Reverb @@ -394,12 +375,13 @@ private: QString _selectedCodecName; Encoder* _encoder { nullptr }; // for outbound mic stream - QThread* _checkDevicesThread { nullptr }; - RateCounter<> _silentOutbound; RateCounter<> _audioOutbound; RateCounter<> _silentInbound; RateCounter<> _audioInbound; + + QThread* _checkDevicesThread { nullptr }; + QThread* _localInjectorsThread { nullptr }; }; From 4a26785eda43c42879d5758c5e1110d6005cf46e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 24 Apr 2017 18:21:27 -0400 Subject: [PATCH 03/65] cleanup Audio bg threads in cleanupBeforeQuit --- interface/src/Application.cpp | 3 ++- libraries/audio-client/src/AudioClient.cpp | 21 +++++++++++++++------ libraries/audio-client/src/AudioClient.h | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c94b1bbd2f..ff6f85c8b2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1785,7 +1785,8 @@ void Application::cleanupBeforeQuit() { // FIXME: something else is holding a reference to AudioClient, // so it must be explicitly synchronously stopped here - QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(DependencyManager::get().data(), + "cleanupBeforeQuit", Qt::BlockingQueuedConnection); // destroy Audio so it and its threads have a chance to go down safely // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6107fef516..fbb578f560 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -254,15 +254,24 @@ AudioClient::~AudioClient() { } void AudioClient::customDeleter() { - stop(); // synchronously + deleteLater(); +} - static_cast(_checkDevicesThread)->join(); - delete _checkDevicesThread; +void AudioClient::cleanupBeforeQuit() { + // FIXME: this should be put in customDeleter, but there is still a reference to this when it is called, + // so this must be explicitly, synchronously stopped - static_cast(_localInjectorsThread)->join(); - delete _localInjectorsThread; + stop(); - deleteLater(); // asynchronously + if (_checkDevicesThread) { + static_cast(_checkDevicesThread)->join(); + delete _checkDevicesThread; + } + + if (_localInjectorsThread) { + static_cast(_localInjectorsThread)->join(); + delete _localInjectorsThread; + } } void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 6e69915f0a..f3038b4d62 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -158,6 +158,7 @@ public: public slots: void start(); void stop(); + void cleanupBeforeQuit(); void handleAudioEnvironmentDataPacket(QSharedPointer message); void handleAudioDataPacket(QSharedPointer message); From c27dd446ebfdfd2654f0993fe970b1ff437988ce Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 25 Apr 2017 14:59:30 -0400 Subject: [PATCH 04/65] rename to standard --- libraries/audio-client/src/AudioClient.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index fbb578f560..9e1195e460 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -90,29 +90,29 @@ public: CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {} void join() override { - _quit = true; - std::unique_lock lock(mutex); - cv.wait(lock, [&]{ return !running; }); + _shouldQuit = true; + std::unique_lock lock(_joinMutex); + _joinCondition.wait(lock, [&]{ return !_isRunning; }); } protected: void run() override { - while (!_quit) { + while (!_shouldQuit) { _client->checkDevices(); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); } - std::lock_guard lock(mutex); - running = false; - cv.notify_one(); + std::lock_guard lock(_joinCondition); + _isRunning = false; + _joinCondition.notify_one(); } private: - std::atomic _quit { false }; - bool running { true }; - std::mutex mutex; - std::condition_variable cv; + std::atomic _shouldQuit { false }; + bool _isRunning { true }; + std::mutex _joinMutex; + std::condition_variable _joinCondition; }; // background thread buffering local injectors From e9c5b8600367be234126f979088fd60ffe7789a5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 25 Apr 2017 16:02:43 -0400 Subject: [PATCH 05/65] use mutex, not cv --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9e1195e460..2c78fbceca 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -103,7 +103,7 @@ protected: const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS); } - std::lock_guard lock(_joinCondition); + std::lock_guard lock(_joinMutex); _isRunning = false; _joinCondition.notify_one(); } From 9e77771c997838f3fd7d10ca3efba695c50b1a6a Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 28 Apr 2017 12:00:01 -0700 Subject: [PATCH 06/65] Update WASAPI audio plugin for Qt5.6.2 --- cmake/externals/wasapi/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index d4d4b42e10..5da625e34d 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -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 2830a17388928253ef22ae9662f914a7 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 15cad1fee914264de3e7a084f499efd813dbfe3e Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 2 May 2017 21:33:58 +0100 Subject: [PATCH 07/65] saving work --- plugins/openvr/src/ViveControllerManager.cpp | 72 +++++++++++++++++++- plugins/openvr/src/ViveControllerManager.h | 16 ++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 86b37135d2..293885f8ee 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -10,6 +10,7 @@ // #include "ViveControllerManager.h" +#include #include #include @@ -41,9 +42,16 @@ static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; +const quint64 CALIBRATION_TIMELAPSE = 3000000; const char* ViveControllerManager::NAME { "OpenVR" }; +bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { + controller::Pose firstPose = firstPuck.second; + controller::Pose secondPose = secondPuck.second; + return (firstPose.translation.y < secondPose.translation.y); +} + bool ViveControllerManager::isSupported() const { return openVrSupported(); } @@ -125,6 +133,7 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); + _validTrackedObjects.clear(); // While the keyboard is open, we defer strictly to the keyboard values if (isOpenVrKeyboardShown()) { @@ -164,10 +173,10 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle numTrackedControllers++; } _trackedControllers = numTrackedControllers; + calibrate(inputCalibrationData); } void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { - uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; if (_system->IsTrackedDeviceConnected(deviceIndex) && @@ -185,12 +194,71 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); + _validTrackedObjects.push_back(std::make_pair(poseIndex, _poseStateMap[poseIndex])); } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; } } +void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { + quint64 currentTime = usecTimestampNow(); + + auto leftTrigger = _buttonPressedMap.find(controller::LT); + auto rightTrigger = _buttonPressedMap.find(controller::RT); + if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { + if (!_calibrated) { + auto puckCount = _validTrackedObjects.size(); + if (puckCount == 2) { + qDebug() << "-------------< configure feet <-------------"; + _config = Config::Feet; + } else if (puckCount == 3) { + qDebug() << "-------------> configure feet and hips <-------------"; + _config = Config::FeetAndHips; + } else if (puckCount >= 4) { + _config = Config::FeetHipsAndChest; + } else { + qDebug() << "Could not configure # pucks " << puckCount; + return; + } + + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + auto firstFoot = _validTrackedObjects[0]; + auto secondFoot = _validTrackedObjects[1]; + controller::Pose firstFootPose = firstFoot.second; + controller::Pose secondFootPose = secondFoot.second; + if (firstFootPose.translation.x > secondFootPose.translation.x) { + _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; + } else { + _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; + + } + + if (_config == Config::Feet) { + // done + } else if (_config == Config::FeetAndHips) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + } else if (_config == Config::FeetHipsAndChest) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[2].first; + } + + computePucksOffset(inputCalibration); + _calibrated = true; + + } else { + qDebug() << "---- de-calibrated ---"; + + } + + } +} + +void ViveControllerManager::InputDevice::computePucksOffset(const controller::InputCalibrationData& inputCalibration) { + +} void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) { if (_system->IsTrackedDeviceConnected(deviceIndex) && @@ -353,7 +421,7 @@ void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool lef float hapticTime = strength * MAX_HAPTIC_TIME; if (hapticTime < duration * 1000.0f) { _system->TriggerHapticPulse(deviceIndex, 0, hapticTime); - } + } float remainingHapticTime = duration - (hapticTime / 1000.0f + deltaTime * 1000.0f); // in milliseconds if (leftHand) { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index dc1883d5e4..5d844b6499 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -14,9 +14,11 @@ #include #include +#include +#include +#include #include - #include #include #include @@ -58,7 +60,8 @@ private: bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; void hapticsHelper(float deltaTime, bool leftHand); - + void calibrate(const controller::InputCalibrationData& inputCalibration); + void computePucksOffset(const controller::InputCalibrationData& inputCalibration); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); @@ -90,10 +93,14 @@ private: float _timer { 0.0f }; glm::vec2 _stick { 0.0f, 0.0f }; }; - + enum class Config { Feet, FeetAndHips, FeetHipsAndChest, NoConfig }; + Config _config { Config::NoConfig }; FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; + std::vector> _validTrackedObjects; + std::map _pucksOffset; + std::map _jointToPuckMap; // perform an action when the InputDevice mutex is acquired. using Locker = std::unique_lock; template @@ -101,10 +108,13 @@ private: int _trackedControllers { 0 }; vr::IVRSystem*& _system; + quint64 _calibrateAfterTimelapse { 0.f }; float _leftHapticStrength { 0.0f }; float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; + bool _calibrate { false }; + bool _calibrated { false }; mutable std::recursive_mutex _lock; friend class ViveControllerManager; From 8e2b25eacc80c8da62f80cb52dff03c0ecd309b2 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 3 May 2017 00:25:41 +0100 Subject: [PATCH 08/65] working on computing offsets --- plugins/openvr/src/ViveControllerManager.cpp | 34 +++++++++++++++----- plugins/openvr/src/ViveControllerManager.h | 3 +- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 293885f8ee..73cb26410c 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -46,6 +47,16 @@ const quint64 CALIBRATION_TIMELAPSE = 3000000; const char* ViveControllerManager::NAME { "OpenVR" }; +/*glm::mat4 computeDefualtHead(glm::mat4 centerOfEye, glm::mat4 jointHead) { + headOffset = glm::muliply(glm::inverse(centerOfEye), jointHead); + return glm:: + }*/ + +glm::mat4 computeOffset(glm::mat4 defaultRefrence, glm::mat4 defaultJointMat, controller::Pose puckPose) { + qDebug() << "-------------> computing offset <-------------"; + glm::mat4 puckMat = create + return mat4(); +} bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { controller::Pose firstPose = firstPuck.second; controller::Pose secondPose = secondPuck.second; @@ -203,11 +214,17 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { quint64 currentTime = usecTimestampNow(); - + auto leftTrigger = _buttonPressedMap.find(controller::LT); auto rightTrigger = _buttonPressedMap.find(controller::RT); if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { if (!_calibrated) { + glm::mat4 controllerToAvatar = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + glm::mat4 currentHead = inputCalibration.hmdSensorMat * controllerToAvatar; + glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(currentHead)); + glm::vec3 currentHeadPosition = extractTranslation(currentHead); + currentHead = createMatFromQuatAndPos(canceledRollAndPitch, currentHeadPosition); + glm::mat4 defaultRefrenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); auto puckCount = _validTrackedObjects.size(); if (puckCount == 2) { qDebug() << "-------------< configure feet <-------------"; @@ -229,23 +246,27 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr controller::Pose secondFootPose = secondFoot.second; if (firstFootPose.translation.x > secondFootPose.translation.x) { _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, secondFootPose); } else { _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - + _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, firstFootPose); } if (_config == Config::Feet) { // done } else if (_config == Config::FeetAndHips) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); } else if (_config == Config::FeetHipsAndChest) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[2].first; - } - - computePucksOffset(inputCalibration); + _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); + } _calibrated = true; } else { @@ -256,9 +277,6 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr } } -void ViveControllerManager::InputDevice::computePucksOffset(const controller::InputCalibrationData& inputCalibration) { - -} void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) { if (_system->IsTrackedDeviceConnected(deviceIndex) && diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 5d844b6499..966f1e879b 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,7 +61,6 @@ private: bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; void hapticsHelper(float deltaTime, bool leftHand); void calibrate(const controller::InputCalibrationData& inputCalibration); - void computePucksOffset(const controller::InputCalibrationData& inputCalibration); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); @@ -99,7 +98,7 @@ private: FilteredStick _filteredRightStick; std::vector> _validTrackedObjects; - std::map _pucksOffset; + std::map _pucksOffset; std::map _jointToPuckMap; // perform an action when the InputDevice mutex is acquired. using Locker = std::unique_lock; From fb502a7fede162834a1c7e495b805efb6f1a02ac Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 3 May 2017 00:37:35 +0100 Subject: [PATCH 09/65] add offset function --- plugins/openvr/src/ViveControllerManager.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 73cb26410c..29d036fd31 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -54,8 +54,9 @@ const char* ViveControllerManager::NAME { "OpenVR" }; glm::mat4 computeOffset(glm::mat4 defaultRefrence, glm::mat4 defaultJointMat, controller::Pose puckPose) { qDebug() << "-------------> computing offset <-------------"; - glm::mat4 puckMat = create - return mat4(); + glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); + glm::mat4 refrenceJointMat = defaultRefrence * defaultJointMat; + return glm::inverse(poseMat) * refrenceJointMat; } bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { controller::Pose firstPose = firstPuck.second; @@ -221,10 +222,11 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr if (!_calibrated) { glm::mat4 controllerToAvatar = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; glm::mat4 currentHead = inputCalibration.hmdSensorMat * controllerToAvatar; + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(currentHead)); glm::vec3 currentHeadPosition = extractTranslation(currentHead); currentHead = createMatFromQuatAndPos(canceledRollAndPitch, currentHeadPosition); - glm::mat4 defaultRefrenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); + glm::mat4 defaultRefrenceXform = currentHead * defaultHeadOffset; auto puckCount = _validTrackedObjects.size(); if (puckCount == 2) { qDebug() << "-------------< configure feet <-------------"; From 31f6038e457586d6ceaafb94b8e6eff55d8b45c7 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 3 May 2017 20:08:42 +0100 Subject: [PATCH 10/65] limbs tracking pucks --- interface/resources/controllers/vive.json | 6 ++- plugins/openvr/src/ViveControllerManager.cpp | 57 ++++++++++++++++---- plugins/openvr/src/ViveControllerManager.h | 2 + 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 4fbdb37abf..5bdffadbbf 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -35,6 +35,10 @@ { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] }, - { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] } + { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, + { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", "when": [ "Application.InHMD"] }, + { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", "when": [ "Application.InHMD"] }, + { "from": "Vive.Hips", "to" : "Standard.Hips", "when": [ "Application.InHMD"] }, + { "from": "Vive.Spine2", "to" : "Standard.Spine2", "when": [ "Application.InHMD"] } ] } diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 29d036fd31..1a1f6b0977 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -53,7 +53,7 @@ const char* ViveControllerManager::NAME { "OpenVR" }; }*/ glm::mat4 computeOffset(glm::mat4 defaultRefrence, glm::mat4 defaultJointMat, controller::Pose puckPose) { - qDebug() << "-------------> computing offset <-------------"; + ///qDebug() << "-------------> computing offset <-------------"; glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); glm::mat4 refrenceJointMat = defaultRefrence * defaultJointMat; return glm::inverse(poseMat) * refrenceJointMat; @@ -134,6 +134,7 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu userInputMapper->removeDevice(_inputDevice->_deviceID); _registeredWithInputMapper = false; _inputDevice->_poseStateMap.clear(); + //qDebug() << " ----------->>!!!!!!! clear pose state map !!!!!!!<<----------------"; } if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { @@ -186,6 +187,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle } _trackedControllers = numTrackedControllers; calibrate(inputCalibrationData); + updateCalibratedLimbs(); } void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { @@ -220,21 +222,28 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr auto rightTrigger = _buttonPressedMap.find(controller::RT); if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { if (!_calibrated) { + // conver the hmd head from sensor space to avatar space glm::mat4 controllerToAvatar = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 currentHead = inputCalibration.hmdSensorMat * controllerToAvatar; + glm::mat4 hmdHeadInAvatarSpace = inputCalibration.hmdSensorMat * controllerToAvatar; + + // cancel the roll and pitch for the hmd head + glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(hmdHeadInAvatarSpace)); + glm::vec3 hmdHeadPosition = extractTranslation(hmdHeadInAvatarSpace); + glm::mat4 hmdHeadMat = createMatFromQuatAndPos(canceledRollAndPitch, hmdHeadPosition); + + //calculate the offset from the centerEye to the head hoint glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(currentHead)); - glm::vec3 currentHeadPosition = extractTranslation(currentHead); - currentHead = createMatFromQuatAndPos(canceledRollAndPitch, currentHeadPosition); - glm::mat4 defaultRefrenceXform = currentHead * defaultHeadOffset; + + glm::mat4 defaultRefrenceXform = hmdHeadMat * defaultHeadOffset; auto puckCount = _validTrackedObjects.size(); if (puckCount == 2) { - qDebug() << "-------------< configure feet <-------------"; + //qDebug() << "-------------< configure feet <-------------"; _config = Config::Feet; } else if (puckCount == 3) { - qDebug() << "-------------> configure feet and hips <-------------"; + //qDebug() << "-------------> configure feet and hips <-------------"; _config = Config::FeetAndHips; } else if (puckCount >= 4) { + //qDebug() << "-------------> configure feet, hips and chest <---------------"; _config = Config::FeetHipsAndChest; } else { qDebug() << "Could not configure # pucks " << puckCount; @@ -242,17 +251,19 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr } std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + auto firstFoot = _validTrackedObjects[0]; auto secondFoot = _validTrackedObjects[1]; controller::Pose firstFootPose = firstFoot.second; controller::Pose secondFootPose = secondFoot.second; - if (firstFootPose.translation.x > secondFootPose.translation.x) { + if (firstFootPose.translation.x < secondFootPose.translation.x) { _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, secondFootPose); } else { _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + qDebug() << " --------> Printing out the offset pose <------------"; _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, firstFootPose); @@ -266,7 +277,7 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr } else if (_config == Config::FeetHipsAndChest) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); - _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[2].first; + _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); } _calibrated = true; @@ -279,6 +290,28 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr } } +void ViveControllerManager::InputDevice::updateCalibratedLimbs() { + _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); + _poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT); + _poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS); + _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); +} + +controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) { + auto puck = _jointToPuckMap.find(joint); + if (puck != _jointToPuckMap.end()) { + uint32_t puckIndex = puck->second; + controller::Pose puckPose = _poseStateMap[puckIndex]; + glm::mat4 puckOffset = _pucksOffset[puckIndex]; + puckPose.postTransform(puckOffset); + //qDebug() << "-----------> adding offset to puck <-------------- " << puckPose.valid; + return puckPose; + + } + //qDebug() << "---------> joint is not mapped to any thing <--------------"; + return controller::Pose(); +} + void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) { if (_system->IsTrackedDeviceConnected(deviceIndex) && @@ -492,6 +525,10 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI // 3d location of controller makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), + makePair(LEFT_FOOT, "LeftFoot"), + makePair(RIGHT_FOOT, "RightFoot"), + makePair(HIPS, "Hips"), + makePair(SPINE2, "Spine2"), // 16 tracked poses makePair(TRACKED_OBJECT_00, "TrackedObject00"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 966f1e879b..43dd982f80 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,6 +61,8 @@ private: bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; void hapticsHelper(float deltaTime, bool leftHand); void calibrate(const controller::InputCalibrationData& inputCalibration); + controller::Pose addOffsetToPuckPose(int joint); + void updateCalibratedLimbs(); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); From 5b5695663707f63c0c15e503262d3a664b281b6f Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 4 May 2017 12:38:17 -0700 Subject: [PATCH 11/65] Some minor handshake improvements --- scripts/system/makeUserConnection.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 78be54f774..7509b246ea 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -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))); } @@ -768,11 +769,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 && connectingId === senderID) { + // 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(); From a4125c1507e1e7f4defa73969579c70d0ae3ed5d Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 4 May 2017 16:33:50 -0700 Subject: [PATCH 12/65] logic tweak --- scripts/system/makeUserConnection.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 7509b246ea..6f7b746f18 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -760,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) { @@ -776,7 +779,7 @@ stopConnecting(); } } else { - 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; From 88dc6b5f61b7fb679611bda87ff682dbd57847de Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 5 May 2017 17:24:36 +1200 Subject: [PATCH 13/65] Fix tablet Web view "back" button not greying out when disabled --- .../qml/controls/TabletWebButton.qml | 8 +++--- .../resources/qml/controls/TabletWebView.qml | 28 ++++--------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/controls/TabletWebButton.qml b/interface/resources/qml/controls/TabletWebButton.qml index a5876d08dd..d016f71f2d 100644 --- a/interface/resources/qml/controls/TabletWebButton.qml +++ b/interface/resources/qml/controls/TabletWebButton.qml @@ -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 diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index d288872289..3a6643964b 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -8,6 +8,7 @@ import "../styles" as HifiStyles import "../styles-uit" import "../" import "." + Item { id: web HifiConstants { id: hifi } @@ -29,7 +30,6 @@ Item { property bool remove: false property var urlList: [] property var forwardList: [] - property int currentPage: -1 // used as a model for repeater property alias pagesModel: pagesModel @@ -51,21 +51,22 @@ Item { TabletWebButton { id: back - enabledColor: hifi.colors.baseGray - enabled: false + enabledColor: hifi.colors.darkGray + disabledColor: hifi.colors.lightGrayText + enabled: webview.canGoBack || web.urlList.length > 0 || web.forwardList.length > 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 { @@ -102,11 +103,6 @@ Item { id: pagesModel onCountChanged: { currentPage = count - 1; - if (currentPage > 0) { - back.enabledColor = hifi.colors.darkGray; - } else { - back.enabledColor = hifi.colors.baseGray; - } } } @@ -165,24 +161,12 @@ Item { 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: { From a31a861e193d9a4fb65475df03b16ff6cf58bb12 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 29 Mar 2017 15:37:53 -0700 Subject: [PATCH 14/65] fix typo: physcis --> physics --- libraries/physics/src/BulletUtil.h | 2 +- libraries/physics/src/CharacterController.cpp | 2 +- libraries/physics/src/CharacterController.h | 2 +- libraries/physics/src/CollisionRenderMeshCache.cpp | 2 +- libraries/physics/src/CollisionRenderMeshCache.h | 2 +- libraries/physics/src/ContactInfo.cpp | 2 +- libraries/physics/src/ContactInfo.h | 2 +- libraries/physics/src/ObjectAction.cpp | 2 +- libraries/physics/src/ObjectAction.h | 2 +- libraries/physics/src/ObjectMotionState.cpp | 2 +- libraries/physics/src/ObjectMotionState.h | 2 +- libraries/physics/src/PhysicalEntitySimulation.cpp | 2 +- libraries/physics/src/PhysicalEntitySimulation.h | 2 +- libraries/physics/src/PhysicsEngine.cpp | 2 +- libraries/physics/src/PhysicsEngine.h | 2 +- libraries/physics/src/ShapeFactory.cpp | 2 +- libraries/physics/src/ShapeFactory.h | 2 +- libraries/physics/src/ShapeManager.cpp | 2 +- libraries/physics/src/ShapeManager.h | 2 +- libraries/shared/src/BackgroundMode.h | 2 +- libraries/shared/src/ShapeInfo.cpp | 2 +- libraries/shared/src/ShapeInfo.h | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libraries/physics/src/BulletUtil.h b/libraries/physics/src/BulletUtil.h index b6fac74617..c456ed8af8 100644 --- a/libraries/physics/src/BulletUtil.h +++ b/libraries/physics/src/BulletUtil.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. diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 5c85f8fc50..751524c40b 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -1,6 +1,6 @@ // // CharacterControllerInterface.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.10.21 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 586ea175e6..aaae1c6492 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -1,6 +1,6 @@ // // CharacterControllerInterface.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.10.21 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/CollisionRenderMeshCache.cpp b/libraries/physics/src/CollisionRenderMeshCache.cpp index 3a1c4d0ea4..40a8a4aff9 100644 --- a/libraries/physics/src/CollisionRenderMeshCache.cpp +++ b/libraries/physics/src/CollisionRenderMeshCache.cpp @@ -1,6 +1,6 @@ // // CollisionRenderMeshCache.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2016.07.13 // Copyright 2016 High Fidelity, Inc. diff --git a/libraries/physics/src/CollisionRenderMeshCache.h b/libraries/physics/src/CollisionRenderMeshCache.h index 910b43996e..6a6857a5ae 100644 --- a/libraries/physics/src/CollisionRenderMeshCache.h +++ b/libraries/physics/src/CollisionRenderMeshCache.h @@ -1,6 +1,6 @@ // // CollisionRenderMeshCache.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2016.07.13 // Copyright 2016 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactInfo.cpp b/libraries/physics/src/ContactInfo.cpp index 085f746a73..7fdf6c854b 100644 --- a/libraries/physics/src/ContactInfo.cpp +++ b/libraries/physics/src/ContactInfo.cpp @@ -1,6 +1,6 @@ // // ContactEvent.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.01.20 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/ContactInfo.h b/libraries/physics/src/ContactInfo.h index 8d05f73b61..39fc011420 100644 --- a/libraries/physics/src/ContactInfo.h +++ b/libraries/physics/src/ContactInfo.h @@ -1,6 +1,6 @@ // // ContactEvent.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.01.20 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 5f5f763ca6..de14a46be4 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -1,6 +1,6 @@ // // ObjectAction.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Seth Alves 2015-6-2 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index fb141a4620..f71159ad88 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -1,6 +1,6 @@ // // ObjectAction.h -// libraries/physcis/src +// libraries/physics/src // // Created by Seth Alves 2015-6-2 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 38f079c1d4..503b39dc1c 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -1,6 +1,6 @@ // // ObjectMotionState.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.11.05 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 4230f636b3..645bd6fc14 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -1,6 +1,6 @@ // // ObjectMotionState.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.11.05 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 5081f981d4..2e69ff987c 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -1,6 +1,6 @@ // // PhysicalEntitySimulation.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.04.27 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index e0b15440bb..b9acf4cace 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -1,6 +1,6 @@ // // PhysicalEntitySimulation.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2015.04.27 // Copyright 2015 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 87a15eb264..3a02e95e7c 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -1,6 +1,6 @@ // // PhysicsEngine.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 07de0e7b5c..e9b29a43a4 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -1,6 +1,6 @@ // // PhysicsEngine.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 35e050024a..d209667966 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -1,6 +1,6 @@ // // ShapeFactory.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.12.01 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index a1022104dd..2bf79f390c 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -1,6 +1,6 @@ // // ShapeFactory.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.12.01 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index b61fb0037b..fd3e35d28a 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -1,6 +1,6 @@ // // ShapeManager.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index 261c06ddb9..ed81b5e8f8 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -1,6 +1,6 @@ // // ShapeManager.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/BackgroundMode.h b/libraries/shared/src/BackgroundMode.h index e6e585d9d8..0e0d684e62 100644 --- a/libraries/shared/src/BackgroundMode.h +++ b/libraries/shared/src/BackgroundMode.h @@ -1,6 +1,6 @@ // // BackgroundMode.h -// libraries/physcis/src +// libraries/physics/src // // Copyright 2015 High Fidelity, Inc. // diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index b8ea3a4272..583bceeaf2 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -1,6 +1,6 @@ // // ShapeInfo.cpp -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 98b397ee16..17e4703fc2 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -1,6 +1,6 @@ // // ShapeInfo.h -// libraries/physcis/src +// libraries/physics/src // // Created by Andrew Meadows 2014.10.29 // Copyright 2014 High Fidelity, Inc. From e21bd7a67adfe76a3c4d57b2e766040a6eeda76d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 11:21:34 -0700 Subject: [PATCH 15/65] help avatar walk up steps --- interface/src/Application.cpp | 7 - interface/src/Menu.cpp | 4 + interface/src/Menu.h | 2 +- interface/src/avatar/MyAvatar.cpp | 36 +- interface/src/avatar/MyAvatar.h | 6 +- .../src/avatar/MyCharacterController.cpp | 438 +++++++++++++++++- interface/src/avatar/MyCharacterController.h | 28 +- libraries/physics/src/CharacterController.cpp | 394 ++++++++++------ libraries/physics/src/CharacterController.h | 54 ++- .../physics/src/CharacterGhostObject.cpp | 415 +++++++++++++++++ libraries/physics/src/CharacterGhostObject.h | 103 ++++ libraries/physics/src/CharacterGhostShape.cpp | 31 ++ libraries/physics/src/CharacterGhostShape.h | 25 + libraries/physics/src/CharacterRayResult.cpp | 31 ++ libraries/physics/src/CharacterRayResult.h | 44 ++ .../physics/src/CharacterSweepResult.cpp | 42 ++ libraries/physics/src/CharacterSweepResult.h | 45 ++ 17 files changed, 1501 insertions(+), 204 deletions(-) mode change 100644 => 100755 interface/src/avatar/MyAvatar.cpp mode change 100644 => 100755 interface/src/avatar/MyCharacterController.cpp mode change 100644 => 100755 libraries/physics/src/CharacterController.cpp create mode 100755 libraries/physics/src/CharacterGhostObject.cpp create mode 100755 libraries/physics/src/CharacterGhostObject.h create mode 100644 libraries/physics/src/CharacterGhostShape.cpp create mode 100644 libraries/physics/src/CharacterGhostShape.h create mode 100755 libraries/physics/src/CharacterRayResult.cpp create mode 100644 libraries/physics/src/CharacterRayResult.h create mode 100755 libraries/physics/src/CharacterSweepResult.cpp create mode 100644 libraries/physics/src/CharacterSweepResult.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3be55e82cd..20d85f76cb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9688694287..7ac03ebd2e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -532,6 +532,10 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarCollisions, 0, true, + avatar.get(), SLOT(updateMotionBehaviorFromMenu()), + UNSPECIFIED_POSITION, "Developer"); + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 250d2241ac..72f823d3bd 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp old mode 100644 new mode 100755 index 3f3ce7d9e9..3de69d0d86 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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; @@ -588,8 +586,8 @@ void MyAvatar::simulate(float deltaTime) { } }); _characterController.setFlyingAllowed(flyingAllowed); - if (!_characterController.isEnabled() && !ghostingAllowed) { - _characterController.setEnabled(true); + if (!ghostingAllowed && _characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.setCollisionGroup(BULLET_COLLISION_GROUP_MY_AVATAR); } } @@ -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.getCollisionGroup() == 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()); @@ -1906,7 +1906,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 +1949,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,14 +2196,13 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - - setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); + setAvatarCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } -void MyAvatar::setCharacterControllerEnabled(bool enabled) { +void MyAvatar::setAvatarCollisionsEnabled(bool enabled) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled)); + QMetaObject::invokeMethod(this, "setAvatarCollisionsEnabled", Q_ARG(bool, enabled)); return; } @@ -2207,11 +2214,12 @@ void MyAvatar::setCharacterControllerEnabled(bool enabled) { ghostingAllowed = zone->getGhostingAllowed(); } } - _characterController.setEnabled(ghostingAllowed ? enabled : true); + int16_t group = enabled || !ghostingAllowed ? BULLET_COLLISION_GROUP_MY_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; + _characterController.setCollisionGroup(group); } -bool MyAvatar::getCharacterControllerEnabled() { - return _characterController.isEnabled(); +bool MyAvatar::getAvatarCollisionsEnabled() { + return _characterController.getCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } void MyAvatar::clearDriveKeys() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7c510f0556..a20730d87a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -128,7 +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 characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) + Q_PROPERTY(bool avatarCollisionsEnabled READ getAvatarCollisionsEnabled WRITE setAvatarCollisionsEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) public: @@ -470,8 +470,8 @@ public: bool hasDriveInput() const; - Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); - Q_INVOKABLE bool getCharacterControllerEnabled(); + Q_INVOKABLE void setAvatarCollisionsEnabled(bool enabled); + Q_INVOKABLE bool getAvatarCollisionsEnabled(); virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp old mode 100644 new mode 100755 index 6e52f4a949..e90022b2c5 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -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,414 @@ 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; +} + +glm::vec3 MyCharacterController::computeHMDStep(const glm::vec3& position, const glm::vec3& step) { + btVector3 stepDirection = glmToBullet(step); + btScalar stepLength = stepDirection.length(); + if (stepLength < FLT_EPSILON) { + return glm::vec3(0.0f); + } + stepDirection /= stepLength; + + // get _ghost ready for ray traces + btTransform transform = _rigidBody->getWorldTransform(); + btVector3 newPosition = glmToBullet(position); + transform.setOrigin(newPosition); + btMatrix3x3 rotation = transform.getBasis(); + _ghost.setWorldTransform(transform); + _ghost.refreshOverlappingPairCache(); + + // compute rotation that will orient local ray start points to face stepDirection + btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f); + btVector3 horizontalDirection = stepDirection - stepDirection.dot(_currentUp) * _currentUp; + btVector3 axis = forward.cross(horizontalDirection); + btScalar lengthAxis = axis.length(); + if (lengthAxis > FLT_EPSILON) { + // non-zero sideways component + btScalar angle = asinf(lengthAxis / horizontalDirection.length()); + if (stepDirection.dot(forward) < 0.0f) { + angle = PI - angle; + } + axis /= lengthAxis; + rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation; + } else if (stepDirection.dot(forward) < 0.0f) { + // backwards + rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation; + } + + CharacterRayResult rayResult(&_ghost); + btVector3 rayStart; + btVector3 rayEnd; + btVector3 penetration = btVector3(0.0f, 0.0f, 0.0f); + int32_t numPenetrations = 0; + + { // first we scan straight out from capsule center to see if we're stuck on anything + btScalar forwardRatio = 0.5f; + btScalar backRatio = 0.25f; + + btVector3 radial; + bool stuck = false; + for (int32_t i = 0; i < _topPoints.size(); ++i) { + rayStart = rotation * _topPoints[i]; + radial = rayStart - rayStart.dot(_currentUp) * _currentUp; + rayEnd = newPosition + rayStart + forwardRatio * radial; + rayStart += newPosition - backRatio * radial; + + // reset rayResult for next test + rayResult.m_closestHitFraction = 1.0f; + rayResult.m_collisionObject = nullptr; + + if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { + btScalar totalRatio = backRatio + forwardRatio; + btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * totalRatio - backRatio) / forwardRatio; + if (adjustedHitFraction < 0.0f) { + penetration += adjustedHitFraction * radial; + ++numPenetrations; + } else { + stuck = true; + } + } + } + if (numPenetrations > 0) { + if (numPenetrations > 1) { + penetration /= (btScalar)numPenetrations; + } + return bulletToGLM(penetration); + } else if (stuck) { + return glm::vec3(0.0f); + } + } + + // if we get here then we're not stuck pushing into any surface + // so now we scan to see if the way before us is "walkable" + + // 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.10f; // 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; + } + + // we push the step forward by stepMargin to help reduce accidental penetration + const btScalar MIN_STEP_MARGIN = 0.04f; + btScalar stepMargin = glm::max(_radius, MIN_STEP_MARGIN); + btScalar expandedStepLength = stepLength + forwardSlop + stepMargin; + + // loop over topPoints + bool walkable = true; + for (int32_t i = 0; i < _topPoints.size(); ++i) { + rayStart = newPosition + rotation * _topPoints[i]; + rayEnd = rayStart + expandedStepLength * stepDirection; + + // reset rayResult for next test + rayResult.m_closestHitFraction = 1.0f; + rayResult.m_collisionObject = nullptr; + + if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { + if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) { + walkable = false; + break; + } + } + } + + // scan the bottom + // TODO: implement sliding along sloped floors + bool steppingUp = false; + expandedStepLength = stepLength + MIN_FORWARD_SLOP + MIN_STEP_MARGIN; + for (int32_t i = _bottomPoints.size() - 1; i > -1; --i) { + rayStart = newPosition + rotation * _bottomPoints[i] - MIN_STEP_MARGIN * stepDirection; + rayEnd = rayStart + expandedStepLength * stepDirection; + + // reset rayResult for next test + rayResult.m_closestHitFraction = 1.0f; + rayResult.m_collisionObject = nullptr; + + if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { + btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - MIN_STEP_MARGIN) / (stepLength + MIN_FORWARD_SLOP); + if (adjustedHitFraction < 1.0f) { + steppingUp = true; + break; + } + } + } + + if (!walkable && steppingUp ) { + return glm::vec3(0.0f); + } + // else it might not be walkable, but we aren't steppingUp yet which means we can still move forward + + // TODO: slide up ramps and fall off edges (then we can remove the vertical follow of Avatar's RigidBody) + return step; +} + +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(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); + } + } + } + } +} diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index 265406bc6f..df9d31d3c5 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -24,10 +24,36 @@ 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); + + glm::vec3 computeHMDStep(const glm::vec3& position, const glm::vec3& step); + +protected: + void initRayShotgun(const btCollisionWorld* world); + +private: + btConvexHullShape* computeShape() const; protected: MyAvatar* _avatar { nullptr }; + + // shotgun scan data + btAlignedObjectArray _topPoints; + btAlignedObjectArray _bottomPoints; }; #endif // hifi_MyCharacterController_h diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp old mode 100644 new mode 100755 index 751524c40b..40ca3a0826 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -13,8 +13,8 @@ #include -#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); @@ -115,16 +112,25 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _dynamicsWorld = nullptr; } 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(shape)); } + _ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup)); + _ghost.setCollisionWorld(_dynamicsWorld); + _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); + _ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); + _ghost.setMinWallAngle(PI / 4.0f); + _ghost.setUpDirection(_currentUp); + _ghost.setMotorOnly(true); + _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } if (_dynamicsWorld) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { @@ -138,38 +144,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(contactManifold->getBody0()); - const btCollisionObject* obB = static_cast(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 +229,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 +272,28 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } _followTime += dt; + + float stepUpSpeed2 = _stepUpVelocity.length2(); + if (stepUpSpeed2 > FLT_EPSILON) { + // we step up with micro-teleports rather than applying velocity + // use a speed that would ballistically reach _stepHeight under gravity + _stepUpVelocity /= sqrtf(stepUpSpeed2); + btScalar minStepUpSpeed = sqrtf(fabsf(2.0f * _gravity * _stepHeight)); + + btTransform transform = _rigidBody->getWorldTransform(); + transform.setOrigin(transform.getOrigin() + (dt * minStepUpSpeed) * _stepUpVelocity); + _rigidBody->setWorldTransform(transform); + + // make sure the upward velocity is large enough to clear the very top of the step + const btScalar MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT = 0.5f; + minStepUpSpeed = MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT * sqrtf(fabsf(2.0f * _gravity * _minStepHeight)); + btScalar vDotUp = velocity.dot(_currentUp); + if (vDotUp < minStepUpSpeed) { + velocity += (minStepUpSpeed - vDotUp) * _stepUpVelocity; + } + } + _rigidBody->setLinearVelocity(velocity + _parentVelocity); + _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } void CharacterController::jump() { @@ -272,95 +335,96 @@ 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) { + if (_rigidBody) { + if (desiredState == State::Hover && _state != State::Hover) { + // hover enter _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); + } else if (_state == State::Hover && desiredState != State::Hover) { + // hover exit + if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f)); + } else { + _rigidBody->setGravity(_gravity * _currentUp); + } } } _state = desiredState; } } -void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) { - _boxScale = scale; - - 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 = 0.041f; // HACK: hardcoded now but should just larger than shape margin + const btScalar MAX_STEP_FRACTION_OF_HALF_HEIGHT = 0.56f; + _minStepHeight = DEFAULT_MIN_STEP_HEIGHT; + _maxStepHeight = MAX_STEP_FRACTION_OF_HALF_HEIGHT * (_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::setCollisionGroup(int16_t group) { + if (_collisionGroup != group) { + _collisionGroup = group; + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; + _ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup)); + } +} + +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); + _dynamicsWorld->addRigidBody(_rigidBody, _collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); + } + _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; + + if (_state != State::Hover && _rigidBody) { + _gravity = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0.0f : DEFAULT_CHARACTER_GRAVITY; + _rigidBody->setGravity(_gravity * _currentUp); } - SET_STATE(State::Hover, "setEnabled"); - _enabled = enabled; } } 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); - } + _ghost.setUpDirection(_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 +492,18 @@ 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) { + 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 +511,32 @@ 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) { + // 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 + motorVelocityWF = _stepNormal.cross(_stepPoint.cross(motorVelocityWF)); + btScalar doubleCrossLength2 = motorVelocityWF.length2(); + if (doubleCrossLength2 > FLT_EPSILON) { + // scale the motor in the correct direction and rotate back to motor-frame + motorVelocityWF *= (motorVelocity.length() / sqrtf(doubleCrossLength2)); + _stepUpVelocity += motorVelocityWF.rotate(axis, -angle); + } + } + } // 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 +546,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 +556,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 +579,8 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { velocities.reserve(_motors.size()); std::vector weights; weights.reserve(_motors.size()); + _targetVelocity = btVector3(0.0f, 0.0f, 0.0f); + _stepUpVelocity = btVector3(0.0f, 0.0f, 0.0f); for (int i = 0; i < (int)_motors.size(); ++i) { applyMotor(i, dt, velocity, velocities, weights); } @@ -507,14 +596,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 +616,54 @@ 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(); + // rayEnd is straight down MAX_FALL_HEIGHT + btScalar rayLength = _radius + MAX_FALL_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 + if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { switch (_state) { case State::Ground: if (!rayHasHit && !_hasSupport) { @@ -613,32 +703,47 @@ void CharacterController::preSimulation() { break; } case State::Hover: - if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { + btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp; + const btScalar MAX_WALKING_SPEED = 2.5f; + bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f); + + if ((_floorDistance < MIN_HOVER_HEIGHT) && + !(jumpButtonHeld || flyingFast || (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD)) { SET_STATE(State::InAir, "near ground"); } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { SET_STATE(State::Ground, "touching ground"); } break; } + } else { + // in collisionless state switch only between Ground and Hover states + if (rayHasHit) { + SET_STATE(State::Ground, "collisionless above ground"); + } else { + SET_STATE(State::Hover, "collisionless in air"); + } + } +} + +void CharacterController::preSimulation() { + if (_rigidBody) { + // slam body transform and remember velocity + _rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position))); + _preSimulationVelocity = _rigidBody->getLinearVelocity(); + + updateState(); } _previousFlags = _pendingFlags; _pendingFlags &= ~PENDING_FLAG_JUMP; - - _followTime = 0.0f; - _followLinearDisplacement = btVector3(0, 0, 0); - _followAngularDisplacement = btQuaternion::getIdentity(); } 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; @@ -655,7 +760,10 @@ void CharacterController::setFlyingAllowed(bool value) { _flyingAllowed = value; if (!_flyingAllowed && _state == State::Hover) { - SET_STATE(State::InAir, "flying not allowed"); + // disable normal state transitions while collisionless + if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { + SET_STATE(State::InAir, "flying not allowed"); + } } } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index aaae1c6492..8323284315 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -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 #include @@ -19,12 +19,18 @@ #include #include +#include +#include + #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 uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; +const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); const float DEFAULT_CHARACTER_GRAVITY = -5.0f; @@ -44,7 +50,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 +62,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 +72,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& velocities, std::vector& weights); + void setStepUpEnabled(bool enabled) { _stepUpEnabled = enabled; } void computeNewVelocity(btScalar dt, btVector3& velocity); void computeNewVelocity(btScalar dt, glm::vec3& velocity); @@ -103,12 +107,15 @@ 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 setCollisionGroup(int16_t group); + int16_t getCollisionGroup() const { return _collisionGroup; } + void handleChangedCollisionGroup(); bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); @@ -123,7 +130,7 @@ protected: #endif 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 _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 }; + btVector3 _stepUpVelocity { 0.0f, 0.0f, 0.0f }; + 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 { DEFAULT_CHARACTER_GRAVITY }; btScalar _jumpSpeed; btScalar _followTime; @@ -169,7 +189,6 @@ protected: btQuaternion _followAngularDisplacement; btVector3 _linearAcceleration; - std::atomic_bool _enabled; State _state; bool _isPushingUp; @@ -179,6 +198,7 @@ protected: uint32_t _previousFlags { 0 }; bool _flyingAllowed { true }; + int16_t _collisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; }; -#endif // hifi_CharacterControllerInterface_h +#endif // hifi_CharacterController_h diff --git a/libraries/physics/src/CharacterGhostObject.cpp b/libraries/physics/src/CharacterGhostObject.cpp new file mode 100755 index 0000000000..563605cd16 --- /dev/null +++ b/libraries/physics/src/CharacterGhostObject.cpp @@ -0,0 +1,415 @@ +// +// 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 +#include + +#include + +#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; +} + +void CharacterGhostObject::setUpDirection(const btVector3& up) { + btScalar length = up.length(); + if (length > FLT_EPSILON) { + _upDirection /= length; + } else { + _upDirection = btVector3(0.0f, 1.0f, 0.0f); + } +} + +void CharacterGhostObject::setMotorVelocity(const btVector3& velocity) { + _motorVelocity = velocity; + if (_motorOnly) { + _linearVelocity = _motorVelocity; + } +} + +// 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(shape)); + setCollisionShape(_ghostShape); +} + +void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) { + if (world != _world) { + removeFromWorld(); + _world = world; + addToWorld(); + } +} + +void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravity) { + bool oldOnFloor = _onFloor; + _onFloor = false; + _steppingUp = false; + assert(_world && _inWorld); + updateVelocity(dt, gravity); + + // resolve any penetrations before sweeping + int32_t MAX_LOOPS = 4; + int32_t numExtractions = 0; + btVector3 totalPosition(0.0f, 0.0f, 0.0f); + while (numExtractions < MAX_LOOPS) { + if (resolvePenetration(numExtractions)) { + numExtractions = 0; + break; + } + totalPosition += getWorldTransform().getOrigin(); + ++numExtractions; + } + if (numExtractions > 1) { + // penetration resolution was probably oscillating between opposing objects + // so we use the average of the solutions + totalPosition /= btScalar(numExtractions); + btTransform transform = getWorldTransform(); + transform.setOrigin(totalPosition); + setWorldTransform(transform); + + // TODO: figure out how to untrap character + } + btTransform startTransform = getWorldTransform(); + btVector3 startPosition = startTransform.getOrigin(); + if (_onFloor) { + // resolvePenetration() pushed the avatar out of a floor so + // we must updateTraction() before using _linearVelocity + updateTraction(startPosition); + } + + btScalar speed = _linearVelocity.length(); + btVector3 forwardSweep = dt * _linearVelocity; + btScalar stepDistance = dt * speed; + btScalar MIN_SWEEP_DISTANCE = 0.0001f; + if (stepDistance < MIN_SWEEP_DISTANCE) { + // not moving, no need to sweep + updateTraction(startPosition); + return; + } + + // augment forwardSweep to help slow moving sweeps get over steppable ledges + const btScalar MIN_OVERSHOOT = 0.04f; // default margin + if (overshoot < MIN_OVERSHOOT) { + overshoot = MIN_OVERSHOOT; + } + btScalar longSweepDistance = stepDistance + overshoot; + forwardSweep *= longSweepDistance / stepDistance; + + // step forward + CharacterSweepResult result(this); + btTransform nextTransform = startTransform; + nextTransform.setOrigin(startPosition + forwardSweep); + sweepTest(_characterShape, startTransform, nextTransform, result); // forward + + if (!result.hasHit()) { + nextTransform.setOrigin(startPosition + (stepDistance / longSweepDistance) * forwardSweep); + setWorldTransform(nextTransform); + updateTraction(nextTransform.getOrigin()); + return; + } + bool verticalOnly = btFabs(btFabs(_linearVelocity.dot(_upDirection)) - speed) < MIN_OVERSHOOT; + if (verticalOnly) { + // no need to step + nextTransform.setOrigin(startPosition + (result.m_closestHitFraction * stepDistance / longSweepDistance) * forwardSweep); + setWorldTransform(nextTransform); + + if (result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) { + _floorNormal = result.m_hitNormalWorld; + _floorContact = result.m_hitPointWorld; + _steppingUp = false; + _onFloor = true; + _hovering = false; + } + updateTraction(nextTransform.getOrigin()); + return; + } + + // check if this hit is obviously unsteppable + btVector3 hitFromBase = result.m_hitPointWorld - (startPosition - ((_radius + _halfHeight) * _upDirection)); + btScalar hitHeight = hitFromBase.dot(_upDirection); + if (hitHeight > _maxStepHeight) { + // shape can't step over the obstacle so move forward as much as possible before we bail + btVector3 forwardTranslation = result.m_closestHitFraction * forwardSweep; + btScalar forwardDistance = forwardTranslation.length(); + if (forwardDistance > stepDistance) { + forwardTranslation *= stepDistance / forwardDistance; + } + nextTransform.setOrigin(startPosition + forwardTranslation); + setWorldTransform(nextTransform); + _onFloor = _onFloor || oldOnFloor; + return; + } + // if we get here then we hit something that might be steppable + + // remember the forward sweep hit fraction for later + btScalar forwardSweepHitFraction = result.m_closestHitFraction; + + // figure out how high we can step up + btScalar availableStepHeight = measureAvailableStepHeight(); + + // raise by availableStepHeight before sweeping forward + result.resetHitHistory(); + startTransform.setOrigin(startPosition + availableStepHeight * _upDirection); + nextTransform.setOrigin(startTransform.getOrigin() + forwardSweep); + sweepTest(_characterShape, startTransform, nextTransform, result); + if (result.hasHit()) { + startTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * forwardSweep); + } else { + startTransform = nextTransform; + } + + // sweep down in search of future landing spot + result.resetHitHistory(); + btVector3 downSweep = (- availableStepHeight) * _upDirection; + nextTransform.setOrigin(startTransform.getOrigin() + downSweep); + sweepTest(_characterShape, startTransform, nextTransform, result); + if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) { + // can stand on future landing spot, so we interpolate toward it + _floorNormal = result.m_hitNormalWorld; + _floorContact = result.m_hitPointWorld; + _steppingUp = true; + _onFloor = true; + _hovering = false; + nextTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * downSweep); + btVector3 totalStep = nextTransform.getOrigin() - startPosition; + nextTransform.setOrigin(startPosition + (stepDistance / totalStep.length()) * totalStep); + updateTraction(nextTransform.getOrigin()); + } else { + // either there is no future landing spot, or there is but we can't stand on it + // in any case: we go forward as much as possible + nextTransform.setOrigin(startPosition + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep); + _onFloor = _onFloor || oldOnFloor; + updateTraction(nextTransform.getOrigin()); + } + setWorldTransform(nextTransform); +} + +bool CharacterGhostObject::sweepTest( + const btConvexShape* shape, + const btTransform& start, + const btTransform& end, + CharacterSweepResult& result) const { + if (_world && _inWorld) { + assert(shape); + btScalar allowedPenetration = _world->getDispatchInfo().m_allowedCcdPenetration; + convexSweepTest(shape, start, end, result, allowedPenetration); + return result.hasHit(); + } + return false; +} + +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::measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut) { + // minBoxOut and maxBoxOut will be updated with penetration envelope. + // If one of the corner points is <0,0,0> then the penetration is resolvable in a single step, + // but if the space spanned by the two corners extends in both directions along at least one + // component then we the object is sandwiched between two opposing objects. + + // We assume this object has just been moved to its current location, or else objects have been + // moved around it since the last step so we must update the overlapping pairs. + refreshOverlappingPairCache(); + + // compute collision details + btHashedOverlappingPairCache* pairCache = getOverlappingPairCache(); + _world->getDispatcher()->dispatchAllCollisionPairs(pairCache, _world->getDispatchInfo(), _world->getDispatcher()); + + // loop over contact manifolds to compute the penetration box + minBoxOut = btVector3(0.0f, 0.0f, 0.0f); + maxBoxOut = btVector3(0.0f, 0.0f, 0.0f); + btManifoldArray manifoldArray; + + int numPairs = pairCache->getNumOverlappingPairs(); + for (int i = 0; i < numPairs; i++) { + manifoldArray.resize(0); + btBroadphasePair* collisionPair = &(pairCache->getOverlappingPairArray()[i]); + + btCollisionObject* obj0 = static_cast(collisionPair->m_pProxy0->m_clientObject); + btCollisionObject* obj1 = static_cast(collisionPair->m_pProxy1->m_clientObject); + + if ((obj0 && !obj0->hasContactResponse()) && (obj1 && !obj1->hasContactResponse())) { + // we know this probe has no contact response + // but neither does the other object so skip this manifold + continue; + } + + if (!collisionPair->m_algorithm) { + // null m_algorithm means the two shape types don't know how to collide! + // shouldn't fall in here but just in case + continue; + } + + btScalar mostFloorPenetration = 0.0f; + collisionPair->m_algorithm->getAllContactManifolds(manifoldArray); + for (int j = 0; j < manifoldArray.size(); j++) { + btPersistentManifold* manifold = manifoldArray[j]; + btScalar directionSign = (manifold->getBody0() == this) ? btScalar(1.0) : btScalar(-1.0); + for (int p = 0; p < manifold->getNumContacts(); p++) { + const btManifoldPoint& pt = manifold->getContactPoint(p); + if (pt.getDistance() > 0.0f) { + continue; + } + + // normal always points from object to character + btVector3 normal = directionSign * pt.m_normalWorldOnB; + + btScalar penetrationDepth = pt.getDistance(); + if (penetrationDepth < mostFloorPenetration) { // remember penetrationDepth is negative + btScalar normalDotUp = normal.dot(_upDirection); + if (normalDotUp > _maxWallNormalUpComponent) { + mostFloorPenetration = penetrationDepth; + _floorNormal = normal; + if (directionSign > 0.0f) { + _floorContact = pt.m_positionWorldOnA; + } else { + _floorContact = pt.m_positionWorldOnB; + } + _onFloor = true; + } + } + + btVector3 penetration = (-penetrationDepth) * normal; + minBoxOut.setMin(penetration); + maxBoxOut.setMax(penetration); + } + } + } +} + +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; + } +} + +bool CharacterGhostObject::resolvePenetration(int numTries) { + btVector3 minBox, maxBox; + measurePenetration(minBox, maxBox); + btVector3 restore = maxBox + minBox; + if (restore.length2() > 0.0f) { + btTransform transform = getWorldTransform(); + transform.setOrigin(transform.getOrigin() + restore); + setWorldTransform(transform); + return false; + } + return true; +} + +void CharacterGhostObject::updateVelocity(btScalar dt, btScalar gravity) { + if (!_motorOnly) { + if (_hovering) { + _linearVelocity *= 0.999f; // HACK damping + } else { + _linearVelocity += (dt * gravity) * _upDirection; + } + } +} + +void CharacterGhostObject::updateHoverState(const btVector3& position) { + if (_onFloor) { + _hovering = false; + } else { + // cast a ray down looking for floor support + CharacterRayResult rayResult(this); + btScalar distanceToFeet = _radius + _halfHeight; + btScalar slop = 2.0f * getCollisionShape()->getMargin(); // slop to help ray start OUTSIDE the floor object + btVector3 startPos = position - ((distanceToFeet - slop) * _upDirection); + btVector3 endPos = startPos - (2.0f * distanceToFeet) * _upDirection; + rayTest(startPos, endPos, rayResult); + // we're hovering if the ray didn't hit anything or hit unstandable slope + _hovering = !rayResult.hasHit() || rayResult.m_hitNormalWorld.dot(_upDirection) < _maxWallNormalUpComponent; + } +} + +void CharacterGhostObject::updateTraction(const btVector3& position) { + updateHoverState(position); + if (_hovering || _motorOnly) { + _linearVelocity = _motorVelocity; + } else if (_onFloor) { + // compute a velocity that swings the shape around the _floorContact + btVector3 leverArm = _floorContact - position; + btVector3 pathDirection = leverArm.cross(_motorVelocity.cross(leverArm)); + btScalar pathLength = pathDirection.length(); + if (pathLength > FLT_EPSILON) { + _linearVelocity = (_motorVelocity.length() / pathLength) * pathDirection; + } else { + _linearVelocity = btVector3(0.0f, 0.0f, 0.0f); + } + } +} + +btScalar CharacterGhostObject::measureAvailableStepHeight() const { + CharacterSweepResult result(this); + btTransform transform = getWorldTransform(); + btTransform nextTransform = transform; + nextTransform.setOrigin(transform.getOrigin() + _maxStepHeight * _upDirection); + sweepTest(_characterShape, transform, nextTransform, result); + return result.m_closestHitFraction * _maxStepHeight; +} + diff --git a/libraries/physics/src/CharacterGhostObject.h b/libraries/physics/src/CharacterGhostObject.h new file mode 100755 index 0000000000..feb132a53e --- /dev/null +++ b/libraries/physics/src/CharacterGhostObject.h @@ -0,0 +1,103 @@ +// +// 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 + +#include +#include +#include + +#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 setMotorVelocity(const btVector3& velocity); + void setMinWallAngle(btScalar angle) { _maxWallNormalUpComponent = cosf(angle); } + void setMaxStepHeight(btScalar height) { _maxStepHeight = height; } + + void setLinearVelocity(const btVector3& velocity) { _linearVelocity = velocity; } + const btVector3& getLinearVelocity() const { return _linearVelocity; } + + void setCharacterShape(btConvexHullShape* shape); + + void setCollisionWorld(btCollisionWorld* world); + + void move(btScalar dt, btScalar overshoot, btScalar gravity); + + bool sweepTest(const btConvexShape* shape, + const btTransform& start, + const btTransform& end, + CharacterSweepResult& result) const; + + bool rayTest(const btVector3& start, + const btVector3& end, + CharacterRayResult& result) const; + + bool isHovering() const { return _hovering; } + void setHovering(bool hovering) { _hovering = hovering; } + void setMotorOnly(bool motorOnly) { _motorOnly = motorOnly; } + + bool hasSupport() const { return _onFloor; } + bool isSteppingUp() const { return _steppingUp; } + const btVector3& getFloorNormal() const { return _floorNormal; } + + void measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut); + void refreshOverlappingPairCache(); + +protected: + void removeFromWorld(); + void addToWorld(); + + bool resolvePenetration(int numTries); + void updateVelocity(btScalar dt, btScalar gravity); + void updateTraction(const btVector3& position); + btScalar measureAvailableStepHeight() const; + void updateHoverState(const btVector3& position); + +protected: + btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame + btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve + btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity + btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal + btVector3 _floorContact { 0.0f, 0.0f, 0.0f }; // internal, last floor contact point + btCollisionWorld* _world { nullptr }; // input, pointer to world + //btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape + btScalar _halfHeight { 0.0f }; + btScalar _radius { 0.0f }; + btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal + btScalar _maxStepHeight { 0.0f }; // input, max step height the character can climb + 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 + bool _hovering { false }; // internal, + bool _onFloor { false }; // output, is actually standing on floor + bool _steppingUp { false }; // output, future sweep hit a steppable ledge + bool _hasFloor { false }; // output, has floor underneath to fall on + bool _motorOnly { false }; // input, _linearVelocity slaves to _motorVelocity +}; + +#endif // hifi_CharacterGhostObject_h diff --git a/libraries/physics/src/CharacterGhostShape.cpp b/libraries/physics/src/CharacterGhostShape.cpp new file mode 100644 index 0000000000..09f4f0b80f --- /dev/null +++ b/libraries/physics/src/CharacterGhostShape.cpp @@ -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 + + +CharacterGhostShape::CharacterGhostShape(const btConvexHullShape* shape) : + btConvexHullShape(reinterpret_cast(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; +} diff --git a/libraries/physics/src/CharacterGhostShape.h b/libraries/physics/src/CharacterGhostShape.h new file mode 100644 index 0000000000..dc75c148d5 --- /dev/null +++ b/libraries/physics/src/CharacterGhostShape.h @@ -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 + +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 diff --git a/libraries/physics/src/CharacterRayResult.cpp b/libraries/physics/src/CharacterRayResult.cpp new file mode 100755 index 0000000000..7a81e9cca6 --- /dev/null +++ b/libraries/physics/src/CharacterRayResult.cpp @@ -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 + +#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); +} diff --git a/libraries/physics/src/CharacterRayResult.h b/libraries/physics/src/CharacterRayResult.h new file mode 100644 index 0000000000..e8b0bb7f99 --- /dev/null +++ b/libraries/physics/src/CharacterRayResult.h @@ -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 +#include + +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 diff --git a/libraries/physics/src/CharacterSweepResult.cpp b/libraries/physics/src/CharacterSweepResult.cpp new file mode 100755 index 0000000000..a5c4092b1d --- /dev/null +++ b/libraries/physics/src/CharacterSweepResult.cpp @@ -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 + +#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); +} diff --git a/libraries/physics/src/CharacterSweepResult.h b/libraries/physics/src/CharacterSweepResult.h new file mode 100644 index 0000000000..1e2898a3cf --- /dev/null +++ b/libraries/physics/src/CharacterSweepResult.h @@ -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 +#include + + +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 From c47e26174c3d2038bef316dd4d72e3afc8dc1e0c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 12:12:10 -0700 Subject: [PATCH 16/65] support collisionless avatar when playing recording --- interface/src/avatar/MyAvatar.cpp | 8 ++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3de69d0d86..2aa446045e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -163,12 +163,12 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } - _wasCharacterControllerEnabled = _characterController.isEnabled(); - _characterController.setEnabled(false); + _previousCollisionGroup = _characterController.getCollisionGroup(); + _characterController.setCollisionGroup(BULLET_COLLISION_GROUP_COLLISIONLESS); } else { clearRecordingBasis(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - _characterController.setEnabled(_wasCharacterControllerEnabled); + _characterController.setCollisionGroup(_previousCollisionGroup); } auto audioIO = DependencyManager::get(); @@ -2214,7 +2214,7 @@ void MyAvatar::setAvatarCollisionsEnabled(bool enabled) { ghostingAllowed = zone->getGhostingAllowed(); } } - int16_t group = enabled || !ghostingAllowed ? BULLET_COLLISION_GROUP_MY_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; + int16_t group = (enabled || !ghostingAllowed) ? BULLET_COLLISION_GROUP_MY_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; _characterController.setCollisionGroup(group); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a20730d87a..6b8518a90c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -614,7 +614,7 @@ private: SharedSoundPointer _collisionSound; MyCharacterController _characterController; - bool _wasCharacterControllerEnabled { true }; + int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; AvatarWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; From 94ee6d683882e7bd8762fa37fafe2c5d88c2c6a0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 13:24:12 -0700 Subject: [PATCH 17/65] fix driving motion of collisionless avatars --- interface/src/avatar/MyAvatar.cpp | 5 ++-- libraries/physics/src/CharacterController.cpp | 30 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2aa446045e..7104a14f0a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + // we can fly --> support vertical motion glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; direction += up; } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 40ca3a0826..95fcb27684 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -400,8 +400,12 @@ void CharacterController::handleChangedCollisionGroup() { } _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; - if (_state != State::Hover && _rigidBody) { - _gravity = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0.0f : DEFAULT_CHARACTER_GRAVITY; + if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _gravity = 0.0f; + } else if (_state != State::Hover) { + _gravity = (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) ? 0.0f : DEFAULT_CHARACTER_GRAVITY; + } + if (_rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); } } @@ -630,8 +634,12 @@ void CharacterController::updateState() { // rayStart is at center of bottom sphere btVector3 rayStart = _position; - // rayEnd is straight down MAX_FALL_HEIGHT - btScalar rayLength = _radius + MAX_FALL_HEIGHT; + btScalar rayLength = _radius; + if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { + rayLength += MAX_FALL_HEIGHT; + } else { + rayLength += MIN_HOVER_HEIGHT; + } btVector3 rayEnd = rayStart - rayLength * _currentUp; ClosestNotMe rayCallback(_rigidBody); @@ -663,6 +671,7 @@ void CharacterController::updateState() { 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: @@ -703,9 +712,8 @@ void CharacterController::updateState() { break; } case State::Hover: - btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp; - const btScalar MAX_WALKING_SPEED = 2.5f; - bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f); + btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); + bool flyingFast = _state == State::Hover && horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); if ((_floorDistance < MIN_HOVER_HEIGHT) && !(jumpButtonHeld || flyingFast || (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD)) { @@ -716,9 +724,13 @@ void CharacterController::updateState() { break; } } else { - // in collisionless state switch only between Ground and Hover states + // when collisionless: only switch between State::Ground and State::Hover if (rayHasHit) { - SET_STATE(State::Ground, "collisionless above ground"); + if (velocity.length() > (MAX_WALKING_SPEED)) { + SET_STATE(State::Hover, "collisionless in air"); + } else { + SET_STATE(State::Ground, "collisionless above ground"); + } } else { SET_STATE(State::Hover, "collisionless in air"); } From 559f5836c5c23bb0fa863792fa980f455863e5b1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 17:46:02 -0700 Subject: [PATCH 18/65] restore MyAvatar.setCharacterControllerEnabled() --- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++++ interface/src/avatar/MyAvatar.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7104a14f0a..12b41fa245 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2223,6 +2223,18 @@ bool MyAvatar::getAvatarCollisionsEnabled() { return _characterController.getCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } +void MyAvatar::setCharacterControllerEnabled(bool enabled) { + qCDebug(interfaceapp) << "MyAvatar.setCharacterControllerEnabled() is deprecated." + << "Use MyAvatar.setAvatarCollisionsEnabled() instead."; + setAvatarCollisionsEnabled(enabled); +} + +bool MyAvatar::getCharacterControllerEnabled() { + qCDebug(interfaceapp) << "MyAvatar.getCharacterControllerEnabled() is deprecated." + << "Use MyAvatar.getAvatarCollisionsEnabled() instead."; + return getAvatarCollisionsEnabled(); +} + void MyAvatar::clearDriveKeys() { _driveKeys.fill(0.0f); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6b8518a90c..b2d14de224 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -129,6 +129,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled) Q_PROPERTY(bool avatarCollisionsEnabled READ getAvatarCollisionsEnabled WRITE setAvatarCollisionsEnabled) + Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) public: @@ -472,6 +473,8 @@ public: Q_INVOKABLE void setAvatarCollisionsEnabled(bool enabled); Q_INVOKABLE bool getAvatarCollisionsEnabled(); + 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; From 0aa579225ce09763ac6d5957e860fcb93aa32e2f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 17:55:51 -0700 Subject: [PATCH 19/65] even more correct API: MyAvatar.collisionsEnabled --- interface/src/avatar/MyAvatar.cpp | 18 ++++++++---------- interface/src/avatar/MyAvatar.h | 8 ++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 12b41fa245..45b4930b3e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2197,13 +2197,13 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - setAvatarCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); + setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } -void MyAvatar::setAvatarCollisionsEnabled(bool enabled) { +void MyAvatar::setCollisionsEnabled(bool enabled) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAvatarCollisionsEnabled", Q_ARG(bool, enabled)); + QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled)); return; } @@ -2219,20 +2219,18 @@ void MyAvatar::setAvatarCollisionsEnabled(bool enabled) { _characterController.setCollisionGroup(group); } -bool MyAvatar::getAvatarCollisionsEnabled() { +bool MyAvatar::getCollisionsEnabled() { return _characterController.getCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; } void MyAvatar::setCharacterControllerEnabled(bool enabled) { - qCDebug(interfaceapp) << "MyAvatar.setCharacterControllerEnabled() is deprecated." - << "Use MyAvatar.setAvatarCollisionsEnabled() instead."; - setAvatarCollisionsEnabled(enabled); + qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead."; + setCollisionsEnabled(enabled); } bool MyAvatar::getCharacterControllerEnabled() { - qCDebug(interfaceapp) << "MyAvatar.getCharacterControllerEnabled() is deprecated." - << "Use MyAvatar.getAvatarCollisionsEnabled() instead."; - return getAvatarCollisionsEnabled(); + qCDebug(interfaceapp) << "MyAvatar.characterControllerEnabled is deprecated. Use MyAvatar.collisionsEnabled instead."; + return getCollisionsEnabled(); } void MyAvatar::clearDriveKeys() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b2d14de224..04fa37cb1d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -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,7 +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 avatarCollisionsEnabled READ getAvatarCollisionsEnabled WRITE setAvatarCollisionsEnabled) + Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) @@ -471,8 +471,8 @@ public: bool hasDriveInput() const; - Q_INVOKABLE void setAvatarCollisionsEnabled(bool enabled); - Q_INVOKABLE bool getAvatarCollisionsEnabled(); + Q_INVOKABLE void setCollisionsEnabled(bool enabled); + Q_INVOKABLE bool getCollisionsEnabled(); Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated From d34b667e82ea1b9f93e36670e356ffeee02c3fc3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 18:18:01 -0700 Subject: [PATCH 20/65] remove kinematic character controller implemention --- .../src/avatar/MyCharacterController.cpp | 152 --------- interface/src/avatar/MyCharacterController.h | 2 - libraries/physics/src/CharacterController.cpp | 4 - .../physics/src/CharacterGhostObject.cpp | 316 ------------------ libraries/physics/src/CharacterGhostObject.h | 41 --- 5 files changed, 515 deletions(-) diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index e90022b2c5..0cdbc77626 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -201,158 +201,6 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: return result.hitFraction < 1.0f; } -glm::vec3 MyCharacterController::computeHMDStep(const glm::vec3& position, const glm::vec3& step) { - btVector3 stepDirection = glmToBullet(step); - btScalar stepLength = stepDirection.length(); - if (stepLength < FLT_EPSILON) { - return glm::vec3(0.0f); - } - stepDirection /= stepLength; - - // get _ghost ready for ray traces - btTransform transform = _rigidBody->getWorldTransform(); - btVector3 newPosition = glmToBullet(position); - transform.setOrigin(newPosition); - btMatrix3x3 rotation = transform.getBasis(); - _ghost.setWorldTransform(transform); - _ghost.refreshOverlappingPairCache(); - - // compute rotation that will orient local ray start points to face stepDirection - btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f); - btVector3 horizontalDirection = stepDirection - stepDirection.dot(_currentUp) * _currentUp; - btVector3 axis = forward.cross(horizontalDirection); - btScalar lengthAxis = axis.length(); - if (lengthAxis > FLT_EPSILON) { - // non-zero sideways component - btScalar angle = asinf(lengthAxis / horizontalDirection.length()); - if (stepDirection.dot(forward) < 0.0f) { - angle = PI - angle; - } - axis /= lengthAxis; - rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation; - } else if (stepDirection.dot(forward) < 0.0f) { - // backwards - rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation; - } - - CharacterRayResult rayResult(&_ghost); - btVector3 rayStart; - btVector3 rayEnd; - btVector3 penetration = btVector3(0.0f, 0.0f, 0.0f); - int32_t numPenetrations = 0; - - { // first we scan straight out from capsule center to see if we're stuck on anything - btScalar forwardRatio = 0.5f; - btScalar backRatio = 0.25f; - - btVector3 radial; - bool stuck = false; - for (int32_t i = 0; i < _topPoints.size(); ++i) { - rayStart = rotation * _topPoints[i]; - radial = rayStart - rayStart.dot(_currentUp) * _currentUp; - rayEnd = newPosition + rayStart + forwardRatio * radial; - rayStart += newPosition - backRatio * radial; - - // reset rayResult for next test - rayResult.m_closestHitFraction = 1.0f; - rayResult.m_collisionObject = nullptr; - - if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { - btScalar totalRatio = backRatio + forwardRatio; - btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * totalRatio - backRatio) / forwardRatio; - if (adjustedHitFraction < 0.0f) { - penetration += adjustedHitFraction * radial; - ++numPenetrations; - } else { - stuck = true; - } - } - } - if (numPenetrations > 0) { - if (numPenetrations > 1) { - penetration /= (btScalar)numPenetrations; - } - return bulletToGLM(penetration); - } else if (stuck) { - return glm::vec3(0.0f); - } - } - - // if we get here then we're not stuck pushing into any surface - // so now we scan to see if the way before us is "walkable" - - // 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.10f; // 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; - } - - // we push the step forward by stepMargin to help reduce accidental penetration - const btScalar MIN_STEP_MARGIN = 0.04f; - btScalar stepMargin = glm::max(_radius, MIN_STEP_MARGIN); - btScalar expandedStepLength = stepLength + forwardSlop + stepMargin; - - // loop over topPoints - bool walkable = true; - for (int32_t i = 0; i < _topPoints.size(); ++i) { - rayStart = newPosition + rotation * _topPoints[i]; - rayEnd = rayStart + expandedStepLength * stepDirection; - - // reset rayResult for next test - rayResult.m_closestHitFraction = 1.0f; - rayResult.m_collisionObject = nullptr; - - if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { - if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) { - walkable = false; - break; - } - } - } - - // scan the bottom - // TODO: implement sliding along sloped floors - bool steppingUp = false; - expandedStepLength = stepLength + MIN_FORWARD_SLOP + MIN_STEP_MARGIN; - for (int32_t i = _bottomPoints.size() - 1; i > -1; --i) { - rayStart = newPosition + rotation * _bottomPoints[i] - MIN_STEP_MARGIN * stepDirection; - rayEnd = rayStart + expandedStepLength * stepDirection; - - // reset rayResult for next test - rayResult.m_closestHitFraction = 1.0f; - rayResult.m_collisionObject = nullptr; - - if (_ghost.rayTest(rayStart, rayEnd, rayResult)) { - btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - MIN_STEP_MARGIN) / (stepLength + MIN_FORWARD_SLOP); - if (adjustedHitFraction < 1.0f) { - steppingUp = true; - break; - } - } - } - - if (!walkable && steppingUp ) { - return glm::vec3(0.0f); - } - // else it might not be walkable, but we aren't steppingUp yet which means we can still move forward - - // TODO: slide up ramps and fall off edges (then we can remove the vertical follow of Avatar's RigidBody) - return step; -} - 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 diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index df9d31d3c5..6b38736352 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -40,8 +40,6 @@ public: /// return true if RayShotgun hits anything bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result); - glm::vec3 computeHMDStep(const glm::vec3& position, const glm::vec3& step); - protected: void initRayShotgun(const btCollisionWorld* world); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 95fcb27684..8150f8b45d 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -126,10 +126,6 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { _ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); - _ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); - _ghost.setMinWallAngle(PI / 4.0f); - _ghost.setUpDirection(_currentUp); - _ghost.setMotorOnly(true); _ghost.setWorldTransform(_rigidBody->getWorldTransform()); } if (_dynamicsWorld) { diff --git a/libraries/physics/src/CharacterGhostObject.cpp b/libraries/physics/src/CharacterGhostObject.cpp index 563605cd16..331485dd01 100755 --- a/libraries/physics/src/CharacterGhostObject.cpp +++ b/libraries/physics/src/CharacterGhostObject.cpp @@ -45,22 +45,6 @@ void CharacterGhostObject::setRadiusAndHalfHeight(btScalar radius, btScalar half _halfHeight = halfHeight; } -void CharacterGhostObject::setUpDirection(const btVector3& up) { - btScalar length = up.length(); - if (length > FLT_EPSILON) { - _upDirection /= length; - } else { - _upDirection = btVector3(0.0f, 1.0f, 0.0f); - } -} - -void CharacterGhostObject::setMotorVelocity(const btVector3& velocity) { - _motorVelocity = velocity; - if (_motorOnly) { - _linearVelocity = _motorVelocity; - } -} - // override of btCollisionObject::setCollisionShape() void CharacterGhostObject::setCharacterShape(btConvexHullShape* shape) { assert(shape); @@ -81,164 +65,6 @@ void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) { } } -void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravity) { - bool oldOnFloor = _onFloor; - _onFloor = false; - _steppingUp = false; - assert(_world && _inWorld); - updateVelocity(dt, gravity); - - // resolve any penetrations before sweeping - int32_t MAX_LOOPS = 4; - int32_t numExtractions = 0; - btVector3 totalPosition(0.0f, 0.0f, 0.0f); - while (numExtractions < MAX_LOOPS) { - if (resolvePenetration(numExtractions)) { - numExtractions = 0; - break; - } - totalPosition += getWorldTransform().getOrigin(); - ++numExtractions; - } - if (numExtractions > 1) { - // penetration resolution was probably oscillating between opposing objects - // so we use the average of the solutions - totalPosition /= btScalar(numExtractions); - btTransform transform = getWorldTransform(); - transform.setOrigin(totalPosition); - setWorldTransform(transform); - - // TODO: figure out how to untrap character - } - btTransform startTransform = getWorldTransform(); - btVector3 startPosition = startTransform.getOrigin(); - if (_onFloor) { - // resolvePenetration() pushed the avatar out of a floor so - // we must updateTraction() before using _linearVelocity - updateTraction(startPosition); - } - - btScalar speed = _linearVelocity.length(); - btVector3 forwardSweep = dt * _linearVelocity; - btScalar stepDistance = dt * speed; - btScalar MIN_SWEEP_DISTANCE = 0.0001f; - if (stepDistance < MIN_SWEEP_DISTANCE) { - // not moving, no need to sweep - updateTraction(startPosition); - return; - } - - // augment forwardSweep to help slow moving sweeps get over steppable ledges - const btScalar MIN_OVERSHOOT = 0.04f; // default margin - if (overshoot < MIN_OVERSHOOT) { - overshoot = MIN_OVERSHOOT; - } - btScalar longSweepDistance = stepDistance + overshoot; - forwardSweep *= longSweepDistance / stepDistance; - - // step forward - CharacterSweepResult result(this); - btTransform nextTransform = startTransform; - nextTransform.setOrigin(startPosition + forwardSweep); - sweepTest(_characterShape, startTransform, nextTransform, result); // forward - - if (!result.hasHit()) { - nextTransform.setOrigin(startPosition + (stepDistance / longSweepDistance) * forwardSweep); - setWorldTransform(nextTransform); - updateTraction(nextTransform.getOrigin()); - return; - } - bool verticalOnly = btFabs(btFabs(_linearVelocity.dot(_upDirection)) - speed) < MIN_OVERSHOOT; - if (verticalOnly) { - // no need to step - nextTransform.setOrigin(startPosition + (result.m_closestHitFraction * stepDistance / longSweepDistance) * forwardSweep); - setWorldTransform(nextTransform); - - if (result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) { - _floorNormal = result.m_hitNormalWorld; - _floorContact = result.m_hitPointWorld; - _steppingUp = false; - _onFloor = true; - _hovering = false; - } - updateTraction(nextTransform.getOrigin()); - return; - } - - // check if this hit is obviously unsteppable - btVector3 hitFromBase = result.m_hitPointWorld - (startPosition - ((_radius + _halfHeight) * _upDirection)); - btScalar hitHeight = hitFromBase.dot(_upDirection); - if (hitHeight > _maxStepHeight) { - // shape can't step over the obstacle so move forward as much as possible before we bail - btVector3 forwardTranslation = result.m_closestHitFraction * forwardSweep; - btScalar forwardDistance = forwardTranslation.length(); - if (forwardDistance > stepDistance) { - forwardTranslation *= stepDistance / forwardDistance; - } - nextTransform.setOrigin(startPosition + forwardTranslation); - setWorldTransform(nextTransform); - _onFloor = _onFloor || oldOnFloor; - return; - } - // if we get here then we hit something that might be steppable - - // remember the forward sweep hit fraction for later - btScalar forwardSweepHitFraction = result.m_closestHitFraction; - - // figure out how high we can step up - btScalar availableStepHeight = measureAvailableStepHeight(); - - // raise by availableStepHeight before sweeping forward - result.resetHitHistory(); - startTransform.setOrigin(startPosition + availableStepHeight * _upDirection); - nextTransform.setOrigin(startTransform.getOrigin() + forwardSweep); - sweepTest(_characterShape, startTransform, nextTransform, result); - if (result.hasHit()) { - startTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * forwardSweep); - } else { - startTransform = nextTransform; - } - - // sweep down in search of future landing spot - result.resetHitHistory(); - btVector3 downSweep = (- availableStepHeight) * _upDirection; - nextTransform.setOrigin(startTransform.getOrigin() + downSweep); - sweepTest(_characterShape, startTransform, nextTransform, result); - if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) { - // can stand on future landing spot, so we interpolate toward it - _floorNormal = result.m_hitNormalWorld; - _floorContact = result.m_hitPointWorld; - _steppingUp = true; - _onFloor = true; - _hovering = false; - nextTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * downSweep); - btVector3 totalStep = nextTransform.getOrigin() - startPosition; - nextTransform.setOrigin(startPosition + (stepDistance / totalStep.length()) * totalStep); - updateTraction(nextTransform.getOrigin()); - } else { - // either there is no future landing spot, or there is but we can't stand on it - // in any case: we go forward as much as possible - nextTransform.setOrigin(startPosition + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep); - _onFloor = _onFloor || oldOnFloor; - updateTraction(nextTransform.getOrigin()); - } - setWorldTransform(nextTransform); -} - -bool CharacterGhostObject::sweepTest( - const btConvexShape* shape, - const btTransform& start, - const btTransform& end, - CharacterSweepResult& result) const { - if (_world && _inWorld) { - assert(shape); - btScalar allowedPenetration = _world->getDispatchInfo().m_allowedCcdPenetration; - convexSweepTest(shape, start, end, result, allowedPenetration); - return result.hasHit(); - } - return false; -} - bool CharacterGhostObject::rayTest(const btVector3& start, const btVector3& end, CharacterRayResult& result) const { @@ -248,82 +74,6 @@ bool CharacterGhostObject::rayTest(const btVector3& start, return result.hasHit(); } -void CharacterGhostObject::measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut) { - // minBoxOut and maxBoxOut will be updated with penetration envelope. - // If one of the corner points is <0,0,0> then the penetration is resolvable in a single step, - // but if the space spanned by the two corners extends in both directions along at least one - // component then we the object is sandwiched between two opposing objects. - - // We assume this object has just been moved to its current location, or else objects have been - // moved around it since the last step so we must update the overlapping pairs. - refreshOverlappingPairCache(); - - // compute collision details - btHashedOverlappingPairCache* pairCache = getOverlappingPairCache(); - _world->getDispatcher()->dispatchAllCollisionPairs(pairCache, _world->getDispatchInfo(), _world->getDispatcher()); - - // loop over contact manifolds to compute the penetration box - minBoxOut = btVector3(0.0f, 0.0f, 0.0f); - maxBoxOut = btVector3(0.0f, 0.0f, 0.0f); - btManifoldArray manifoldArray; - - int numPairs = pairCache->getNumOverlappingPairs(); - for (int i = 0; i < numPairs; i++) { - manifoldArray.resize(0); - btBroadphasePair* collisionPair = &(pairCache->getOverlappingPairArray()[i]); - - btCollisionObject* obj0 = static_cast(collisionPair->m_pProxy0->m_clientObject); - btCollisionObject* obj1 = static_cast(collisionPair->m_pProxy1->m_clientObject); - - if ((obj0 && !obj0->hasContactResponse()) && (obj1 && !obj1->hasContactResponse())) { - // we know this probe has no contact response - // but neither does the other object so skip this manifold - continue; - } - - if (!collisionPair->m_algorithm) { - // null m_algorithm means the two shape types don't know how to collide! - // shouldn't fall in here but just in case - continue; - } - - btScalar mostFloorPenetration = 0.0f; - collisionPair->m_algorithm->getAllContactManifolds(manifoldArray); - for (int j = 0; j < manifoldArray.size(); j++) { - btPersistentManifold* manifold = manifoldArray[j]; - btScalar directionSign = (manifold->getBody0() == this) ? btScalar(1.0) : btScalar(-1.0); - for (int p = 0; p < manifold->getNumContacts(); p++) { - const btManifoldPoint& pt = manifold->getContactPoint(p); - if (pt.getDistance() > 0.0f) { - continue; - } - - // normal always points from object to character - btVector3 normal = directionSign * pt.m_normalWorldOnB; - - btScalar penetrationDepth = pt.getDistance(); - if (penetrationDepth < mostFloorPenetration) { // remember penetrationDepth is negative - btScalar normalDotUp = normal.dot(_upDirection); - if (normalDotUp > _maxWallNormalUpComponent) { - mostFloorPenetration = penetrationDepth; - _floorNormal = normal; - if (directionSign > 0.0f) { - _floorContact = pt.m_positionWorldOnA; - } else { - _floorContact = pt.m_positionWorldOnB; - } - _onFloor = true; - } - } - - btVector3 penetration = (-penetrationDepth) * normal; - minBoxOut.setMin(penetration); - maxBoxOut.setMax(penetration); - } - } - } -} - void CharacterGhostObject::refreshOverlappingPairCache() { assert(_world && _inWorld); btVector3 minAabb, maxAabb; @@ -347,69 +97,3 @@ void CharacterGhostObject::addToWorld() { _inWorld = true; } } - -bool CharacterGhostObject::resolvePenetration(int numTries) { - btVector3 minBox, maxBox; - measurePenetration(minBox, maxBox); - btVector3 restore = maxBox + minBox; - if (restore.length2() > 0.0f) { - btTransform transform = getWorldTransform(); - transform.setOrigin(transform.getOrigin() + restore); - setWorldTransform(transform); - return false; - } - return true; -} - -void CharacterGhostObject::updateVelocity(btScalar dt, btScalar gravity) { - if (!_motorOnly) { - if (_hovering) { - _linearVelocity *= 0.999f; // HACK damping - } else { - _linearVelocity += (dt * gravity) * _upDirection; - } - } -} - -void CharacterGhostObject::updateHoverState(const btVector3& position) { - if (_onFloor) { - _hovering = false; - } else { - // cast a ray down looking for floor support - CharacterRayResult rayResult(this); - btScalar distanceToFeet = _radius + _halfHeight; - btScalar slop = 2.0f * getCollisionShape()->getMargin(); // slop to help ray start OUTSIDE the floor object - btVector3 startPos = position - ((distanceToFeet - slop) * _upDirection); - btVector3 endPos = startPos - (2.0f * distanceToFeet) * _upDirection; - rayTest(startPos, endPos, rayResult); - // we're hovering if the ray didn't hit anything or hit unstandable slope - _hovering = !rayResult.hasHit() || rayResult.m_hitNormalWorld.dot(_upDirection) < _maxWallNormalUpComponent; - } -} - -void CharacterGhostObject::updateTraction(const btVector3& position) { - updateHoverState(position); - if (_hovering || _motorOnly) { - _linearVelocity = _motorVelocity; - } else if (_onFloor) { - // compute a velocity that swings the shape around the _floorContact - btVector3 leverArm = _floorContact - position; - btVector3 pathDirection = leverArm.cross(_motorVelocity.cross(leverArm)); - btScalar pathLength = pathDirection.length(); - if (pathLength > FLT_EPSILON) { - _linearVelocity = (_motorVelocity.length() / pathLength) * pathDirection; - } else { - _linearVelocity = btVector3(0.0f, 0.0f, 0.0f); - } - } -} - -btScalar CharacterGhostObject::measureAvailableStepHeight() const { - CharacterSweepResult result(this); - btTransform transform = getWorldTransform(); - btTransform nextTransform = transform; - nextTransform.setOrigin(transform.getOrigin() + _maxStepHeight * _upDirection); - sweepTest(_characterShape, transform, nextTransform, result); - return result.m_closestHitFraction * _maxStepHeight; -} - diff --git a/libraries/physics/src/CharacterGhostObject.h b/libraries/physics/src/CharacterGhostObject.h index feb132a53e..1e4625c6f6 100755 --- a/libraries/physics/src/CharacterGhostObject.h +++ b/libraries/physics/src/CharacterGhostObject.h @@ -33,71 +33,30 @@ public: void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight); void setUpDirection(const btVector3& up); - void setMotorVelocity(const btVector3& velocity); - void setMinWallAngle(btScalar angle) { _maxWallNormalUpComponent = cosf(angle); } - void setMaxStepHeight(btScalar height) { _maxStepHeight = height; } - - void setLinearVelocity(const btVector3& velocity) { _linearVelocity = velocity; } - const btVector3& getLinearVelocity() const { return _linearVelocity; } void setCharacterShape(btConvexHullShape* shape); void setCollisionWorld(btCollisionWorld* world); - void move(btScalar dt, btScalar overshoot, btScalar gravity); - - bool sweepTest(const btConvexShape* shape, - const btTransform& start, - const btTransform& end, - CharacterSweepResult& result) const; - bool rayTest(const btVector3& start, const btVector3& end, CharacterRayResult& result) const; - bool isHovering() const { return _hovering; } - void setHovering(bool hovering) { _hovering = hovering; } - void setMotorOnly(bool motorOnly) { _motorOnly = motorOnly; } - - bool hasSupport() const { return _onFloor; } - bool isSteppingUp() const { return _steppingUp; } - const btVector3& getFloorNormal() const { return _floorNormal; } - - void measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut); void refreshOverlappingPairCache(); protected: void removeFromWorld(); void addToWorld(); - bool resolvePenetration(int numTries); - void updateVelocity(btScalar dt, btScalar gravity); - void updateTraction(const btVector3& position); - btScalar measureAvailableStepHeight() const; - void updateHoverState(const btVector3& position); - protected: - btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame - btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve - btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity - btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal - btVector3 _floorContact { 0.0f, 0.0f, 0.0f }; // internal, last floor contact point btCollisionWorld* _world { nullptr }; // input, pointer to world - //btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape btScalar _halfHeight { 0.0f }; btScalar _radius { 0.0f }; - btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal - btScalar _maxStepHeight { 0.0f }; // input, max step height the character can climb 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 - bool _hovering { false }; // internal, - bool _onFloor { false }; // output, is actually standing on floor - bool _steppingUp { false }; // output, future sweep hit a steppable ledge - bool _hasFloor { false }; // output, has floor underneath to fall on - bool _motorOnly { false }; // input, _linearVelocity slaves to _motorVelocity }; #endif // hifi_CharacterGhostObject_h From e449c488882e318ad9214fbf21200a27ae27a010 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 30 Mar 2017 18:19:53 -0700 Subject: [PATCH 21/65] remove one more line of cruft --- libraries/physics/src/CharacterController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 8150f8b45d..d176958407 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -409,7 +409,6 @@ void CharacterController::handleChangedCollisionGroup() { void CharacterController::updateUpAxis(const glm::quat& rotation) { _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS); - _ghost.setUpDirection(_currentUp); if (_state != State::Hover && _rigidBody) { _rigidBody->setGravity(_gravity * _currentUp); } From 95a4bb9ef423006acdb0c4f5ec2ed7f0fe961eaf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 31 Mar 2017 10:42:44 -0700 Subject: [PATCH 22/65] fix transition height from hover to stand --- libraries/physics/src/CharacterController.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index d176958407..f98710ecc3 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -708,10 +708,9 @@ void CharacterController::updateState() { } case State::Hover: btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); - bool flyingFast = _state == State::Hover && horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); + bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - if ((_floorDistance < MIN_HOVER_HEIGHT) && - !(jumpButtonHeld || flyingFast || (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD)) { + if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { SET_STATE(State::InAir, "near ground"); } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { SET_STATE(State::Ground, "touching ground"); From fe401c7488ddeb9da58c224452153445848726dc Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 3 Apr 2017 16:32:59 -0700 Subject: [PATCH 23/65] improved management of collisionless avatar state --- interface/src/avatar/MyAvatar.cpp | 35 +++----- libraries/physics/src/CharacterController.cpp | 89 ++++++++++--------- libraries/physics/src/CharacterController.h | 9 +- 3 files changed, 68 insertions(+), 65 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 45b4930b3e..333c33ba3b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -163,12 +163,14 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } - _previousCollisionGroup = _characterController.getCollisionGroup(); - _characterController.setCollisionGroup(BULLET_COLLISION_GROUP_COLLISIONLESS); + _previousCollisionGroup = _characterController.computeCollisionGroup(); + _characterController.setCollisionless(true); } else { clearRecordingBasis(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - _characterController.setCollisionGroup(_previousCollisionGroup); + if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.setCollisionless(false); + } } auto audioIO = DependencyManager::get(); @@ -550,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 zone = entityTreeRenderer->myAvatarZone(); if (zone) { flyingAllowed = zone->getFlyingAllowed(); - ghostingAllowed = zone->getGhostingAllowed(); + collisionlessAllowed = zone->getGhostingAllowed(); } auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); @@ -586,9 +588,7 @@ void MyAvatar::simulate(float deltaTime) { } }); _characterController.setFlyingAllowed(flyingAllowed); - if (!ghostingAllowed && _characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { - _characterController.setCollisionGroup(BULLET_COLLISION_GROUP_MY_AVATAR); - } + _characterController.setCollisionlessAllowed(collisionlessAllowed); } updateAvatarEntities(); @@ -1448,7 +1448,7 @@ void MyAvatar::updateMotors() { glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover || - _characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getCameraOrientation(); } else { // non-hovering = walking: follow camera twist about vertical but not lift @@ -1884,7 +1884,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { glm::vec3 direction = forward + right; CharacterController::State state = _characterController.getState(); if (state == CharacterController::State::Hover || - _characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { // we can fly --> support vertical motion glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; direction += up; @@ -2207,20 +2207,13 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { return; } - bool ghostingAllowed = true; - auto entityTreeRenderer = qApp->getEntities(); - if (entityTreeRenderer) { - std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); - if (zone) { - ghostingAllowed = zone->getGhostingAllowed(); - } - } - int16_t group = (enabled || !ghostingAllowed) ? BULLET_COLLISION_GROUP_MY_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; - _characterController.setCollisionGroup(group); + _characterController.setCollisionless(!enabled); } bool MyAvatar::getCollisionsEnabled() { - return _characterController.getCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; + // 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) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index f98710ecc3..7d08675e9d 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -111,11 +111,12 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { } _dynamicsWorld = nullptr; } + int16_t collisionGroup = computeCollisionGroup(); if (world && _rigidBody) { // add to new world _dynamicsWorld = world; _pendingFlags &= ~PENDING_FLAG_JUMP; - _dynamicsWorld->addRigidBody(_rigidBody, _collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); + _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_gravity * _currentUp); @@ -123,7 +124,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); } - _ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup)); + _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); _ghost.setWorldTransform(_rigidBody->getWorldTransform()); @@ -331,20 +332,20 @@ void CharacterController::setState(State desiredState) { #ifdef DEBUG_STATE_CHANGE qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason; #endif - if (_rigidBody) { - if (desiredState == State::Hover && _state != State::Hover) { - // hover enter - _rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f)); - } else if (_state == State::Hover && desiredState != State::Hover) { - // hover exit - if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { - _rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f)); - } else { - _rigidBody->setGravity(_gravity * _currentUp); - } - } - } _state = desiredState; + updateGravity(); + } +} + +void CharacterController::updateGravity() { + int16_t collisionGroup = computeCollisionGroup(); + if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _gravity = 0.0f; + } else { + _gravity = DEFAULT_CHARACTER_GRAVITY; + } + if (_rigidBody) { + _rigidBody->setGravity(_gravity * _currentUp); } } @@ -379,11 +380,18 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const _shapeLocalOffset = minCorner + 0.5f * scale; } -void CharacterController::setCollisionGroup(int16_t group) { - if (_collisionGroup != group) { - _collisionGroup = group; +void CharacterController::setCollisionless(bool collisionless) { + if (collisionless != _collisionless) { + _collisionless = collisionless; _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; - _ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup)); + } +} + +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; } } @@ -392,18 +400,11 @@ void CharacterController::handleChangedCollisionGroup() { // ATM the easiest way to update collision groups is to remove/re-add the RigidBody if (_dynamicsWorld) { _dynamicsWorld->removeRigidBody(_rigidBody); - _dynamicsWorld->addRigidBody(_rigidBody, _collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); + int16_t collisionGroup = computeCollisionGroup(); + _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); } _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; - - if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { - _gravity = 0.0f; - } else if (_state != State::Hover) { - _gravity = (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) ? 0.0f : DEFAULT_CHARACTER_GRAVITY; - } - if (_rigidBody) { - _rigidBody->setGravity(_gravity * _currentUp); - } + updateGravity(); } } @@ -491,7 +492,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel btScalar angle = motor.rotation.getAngle(); btVector3 velocity = worldVelocity.rotate(axis, -angle); - if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS || + int16_t collisionGroup = computeCollisionGroup(); + if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS || _state == State::Hover || motor.hTimescale == motor.vTimescale) { // modify velocity btScalar tau = dt / motor.hTimescale; @@ -630,7 +632,8 @@ void CharacterController::updateState() { btVector3 rayStart = _position; btScalar rayLength = _radius; - if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { + int16_t collisionGroup = computeCollisionGroup(); + if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { rayLength += MAX_FALL_HEIGHT; } else { rayLength += MIN_HOVER_HEIGHT; @@ -667,7 +670,7 @@ void CharacterController::updateState() { // disable normal state transitions while collisionless const btScalar MAX_WALKING_SPEED = 2.65f; - if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { + if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { switch (_state) { case State::Ground: if (!rayHasHit && !_hasSupport) { @@ -719,14 +722,15 @@ void CharacterController::updateState() { } } else { // when collisionless: only switch between State::Ground and State::Hover + // and bypass state debugging if (rayHasHit) { if (velocity.length() > (MAX_WALKING_SPEED)) { - SET_STATE(State::Hover, "collisionless in air"); + _state = State::Hover; } else { - SET_STATE(State::Ground, "collisionless above ground"); + _state = State::Ground; } } else { - SET_STATE(State::Hover, "collisionless in air"); + _state = State::Hover; } } } @@ -762,14 +766,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) { - // disable normal state transitions while collisionless - if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { - SET_STATE(State::InAir, "flying not allowed"); - } + SET_STATE(State::InAir, "flying not allowed"); } } } + +void CharacterController::setCollisionlessAllowed(bool value) { + if (value != _collisionlessAllowed) { + _collisionlessAllowed = value; + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; + } +} diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 8323284315..fe0362cdc7 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -113,13 +113,14 @@ public: bool isEnabledAndReady() const { return _dynamicsWorld; } - void setCollisionGroup(int16_t group); - int16_t getCollisionGroup() const { return _collisionGroup; } + 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: @@ -129,6 +130,7 @@ protected: void setState(State state); #endif + void updateGravity(); void updateUpAxis(const glm::quat& rotation); bool checkForSupport(btCollisionWorld* collisionWorld); @@ -198,7 +200,8 @@ protected: uint32_t _previousFlags { 0 }; bool _flyingAllowed { true }; - int16_t _collisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; + bool _collisionlessAllowed { true }; + bool _collisionless { false }; }; #endif // hifi_CharacterController_h From 41ad25ade17fd55354df61331d07e5823df466db Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 27 Apr 2017 17:30:08 -0700 Subject: [PATCH 24/65] fix bad rebase of changed menu option --- interface/src/Menu.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7ac03ebd2e..bdbf2847d0 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -197,7 +197,7 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarCollisions, 0, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu())); // Avatar > AvatarBookmarks related menus -- Note: the AvatarBookmarks class adds its own submenus here. @@ -532,10 +532,6 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarCollisions, 0, true, - avatar.get(), SLOT(updateMotionBehaviorFromMenu()), - UNSPECIFIED_POSITION, "Developer"); - // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, From 63042eea3add8a55144c6f70fd75061cca3e1d1d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 27 Apr 2017 17:38:17 -0700 Subject: [PATCH 25/65] fix flawed fix of bad rebase --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bdbf2847d0..297f6069c7 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -197,7 +197,7 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarCollisions, 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. From 6e8d90e3b641b894e95f1ded30fcc617838ea1cf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 28 Apr 2017 11:44:08 -0700 Subject: [PATCH 26/65] restore clear of follow accumulators --- libraries/physics/src/CharacterController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 7d08675e9d..28fc28d2b6 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -746,6 +746,10 @@ void CharacterController::preSimulation() { _previousFlags = _pendingFlags; _pendingFlags &= ~PENDING_FLAG_JUMP; + + _followTime = 0.0f; + _followLinearDisplacement = btVector3(0, 0, 0); + _followAngularDisplacement = btQuaternion::getIdentity(); } void CharacterController::postSimulation() { From cd7be46b70d11a72e2cd0fb4bc2aa6c2722170e8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 1 May 2017 16:16:09 -0700 Subject: [PATCH 27/65] tweak threshold for minimum stepheight --- libraries/physics/src/CharacterController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 28fc28d2b6..bf1a6dbdba 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -363,7 +363,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) { _radius = radius; _halfHeight = halfHeight; - const btScalar DEFAULT_MIN_STEP_HEIGHT = 0.041f; // HACK: hardcoded now but should just larger than shape margin + const btScalar DEFAULT_MIN_STEP_HEIGHT = 0.005f; const btScalar MAX_STEP_FRACTION_OF_HALF_HEIGHT = 0.56f; _minStepHeight = DEFAULT_MIN_STEP_HEIGHT; _maxStepHeight = MAX_STEP_FRACTION_OF_HALF_HEIGHT * (_halfHeight + _radius); From 8ed93783bda9a21fb3a26d36f65953bf8e854af6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 May 2017 14:57:23 -0700 Subject: [PATCH 28/65] fix step up velocity calculation --- libraries/physics/src/CharacterController.cpp | 57 ++++++++++--------- libraries/physics/src/CharacterController.h | 2 +- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index bf1a6dbdba..e9ad3d63f5 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -270,23 +270,32 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar } _followTime += dt; - float stepUpSpeed2 = _stepUpVelocity.length2(); - if (stepUpSpeed2 > FLT_EPSILON) { - // we step up with micro-teleports rather than applying velocity - // use a speed that would ballistically reach _stepHeight under gravity - _stepUpVelocity /= sqrtf(stepUpSpeed2); - btScalar minStepUpSpeed = sqrtf(fabsf(2.0f * _gravity * _stepHeight)); + if (_steppingUp) { + // 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 + btVector3 horizontalStep = _stepPoint - _stepPoint.dot(_currentUp) * _currentUp; + float hDistance = sqrtf(_stepPoint.getX() * _stepPoint.getX() + _stepPoint.getZ() * _stepPoint.getZ()); + float targetSpeed = _targetVelocity.length(); + float timeToStep = hDistance / targetSpeed; + float stepUpSpeed = _stepHeight / timeToStep + 0.5f * _gravity * timeToStep; + const float MAX_STEP_UP_SPEED = 0.65f * targetSpeed; + if (stepUpSpeed > MAX_STEP_UP_SPEED) { + stepUpSpeed = MAX_STEP_UP_SPEED; + } - btTransform transform = _rigidBody->getWorldTransform(); - transform.setOrigin(transform.getOrigin() + (dt * minStepUpSpeed) * _stepUpVelocity); - _rigidBody->setWorldTransform(transform); - - // make sure the upward velocity is large enough to clear the very top of the step - const btScalar MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT = 0.5f; - minStepUpSpeed = MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT * sqrtf(fabsf(2.0f * _gravity * _minStepHeight)); btScalar vDotUp = velocity.dot(_currentUp); + if (stepUpSpeed > vDotUp) { + // don't have enough upward velocity to cover the step + + // we step up with micro-teleports rather than applying velocity + // use a speed that would ballistically reach _stepHeight under gravity + btTransform transform = _rigidBody->getWorldTransform(); + transform.setOrigin(transform.getOrigin() + (dt * stepUpSpeed) * _currentUp); + _rigidBody->setWorldTransform(transform); + } + float minStepUpSpeed = 0.0f; if (vDotUp < minStepUpSpeed) { - velocity += (minStepUpSpeed - vDotUp) * _stepUpVelocity; + velocity += (minStepUpSpeed - vDotUp) * _currentUp; } } _rigidBody->setLinearVelocity(velocity + _parentVelocity); @@ -363,10 +372,10 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) { _radius = radius; _halfHeight = halfHeight; - const btScalar DEFAULT_MIN_STEP_HEIGHT = 0.005f; - const btScalar MAX_STEP_FRACTION_OF_HALF_HEIGHT = 0.56f; - _minStepHeight = DEFAULT_MIN_STEP_HEIGHT; - _maxStepHeight = MAX_STEP_FRACTION_OF_HALF_HEIGHT * (_halfHeight + _radius); + 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 @@ -518,18 +527,12 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel btVector3 vTargetVelocity = motorVelocity.dot(up) * up; btVector3 hTargetVelocity = motorVelocity - vTargetVelocity; - if (_stepHeight > _minStepHeight) { + 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 - motorVelocityWF = _stepNormal.cross(_stepPoint.cross(motorVelocityWF)); - btScalar doubleCrossLength2 = motorVelocityWF.length2(); - if (doubleCrossLength2 > FLT_EPSILON) { - // scale the motor in the correct direction and rotate back to motor-frame - motorVelocityWF *= (motorVelocity.length() / sqrtf(doubleCrossLength2)); - _stepUpVelocity += motorVelocityWF.rotate(axis, -angle); - } + _steppingUp = true; } } @@ -581,7 +584,7 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { std::vector weights; weights.reserve(_motors.size()); _targetVelocity = btVector3(0.0f, 0.0f, 0.0f); - _stepUpVelocity = btVector3(0.0f, 0.0f, 0.0f); + _steppingUp = false; for (int i = 0; i < (int)_motors.size(); ++i) { applyMotor(i, dt, velocity, velocities, weights); } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index fe0362cdc7..d33154ef2e 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -170,7 +170,7 @@ protected: // data for walking up steps btVector3 _stepPoint { 0.0f, 0.0f, 0.0f }; btVector3 _stepNormal { 0.0f, 0.0f, 0.0f }; - btVector3 _stepUpVelocity { 0.0f, 0.0f, 0.0f }; + bool _steppingUp { false }; btScalar _stepHeight { 0.0f }; btScalar _minStepHeight { 0.0f }; btScalar _maxStepHeight { 0.0f }; From 2bfbb63906ebca10af305e85d96bd23e68922801 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 May 2017 15:48:15 -0700 Subject: [PATCH 29/65] remove unused variable --- libraries/physics/src/CharacterController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e9ad3d63f5..dae609121e 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -273,7 +273,6 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar if (_steppingUp) { // 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 - btVector3 horizontalStep = _stepPoint - _stepPoint.dot(_currentUp) * _currentUp; float hDistance = sqrtf(_stepPoint.getX() * _stepPoint.getX() + _stepPoint.getZ() * _stepPoint.getZ()); float targetSpeed = _targetVelocity.length(); float timeToStep = hDistance / targetSpeed; From 20c8356bb2b1e7e292f8783a5d5014774edeb43a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 5 May 2017 22:55:53 +0100 Subject: [PATCH 30/65] debugging rotation fix --- plugins/openvr/src/ViveControllerManager.cpp | 72 ++++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 1a1f6b0977..8d480a8e6d 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -1,6 +1,4 @@ -// -// ViveControllerManager.cpp -// input-plugins/src/input-plugins + // // Created by Sam Gondelman on 6/29/15. // Copyright 2013 High Fidelity, Inc. @@ -23,6 +21,9 @@ #include #include #include +#include +#include +#include #include @@ -52,11 +53,11 @@ const char* ViveControllerManager::NAME { "OpenVR" }; return glm:: }*/ -glm::mat4 computeOffset(glm::mat4 defaultRefrence, glm::mat4 defaultJointMat, controller::Pose puckPose) { +glm::mat4 computeOffset(glm::mat4 defaultToRefrenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { ///qDebug() << "-------------> computing offset <-------------"; glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); - glm::mat4 refrenceJointMat = defaultRefrence * defaultJointMat; - return glm::inverse(poseMat) * refrenceJointMat; + glm::mat4 refrenceJointMat = defaultToRefrenceMat * defaultJointMat; + return ( glm::inverse(poseMat) * refrenceJointMat); } bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { controller::Pose firstPose = firstPuck.second; @@ -64,6 +65,12 @@ bool sortPucksYPosition(std::pair firstPuck, std::pa return (firstPose.translation.y < secondPose.translation.y); } +void printPose(controller::Pose pose) { + qDebug() << "-------------> printing out controller::Pose <--------------"; + qDebug() << QString::fromStdString(glm::to_string(pose.translation)); + qDebug() << QString::fromStdString(glm::to_string(pose.rotation)); +} + bool ViveControllerManager::isSupported() const { return openVrSupported(); } @@ -223,18 +230,40 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { if (!_calibrated) { // conver the hmd head from sensor space to avatar space - glm::mat4 controllerToAvatar = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdHeadInAvatarSpace = inputCalibration.hmdSensorMat * controllerToAvatar; + qDebug() << " -------------------> begin:::InputDevice::calibrate <--------------------"; + glm::mat4 worldToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + glm::mat4 hmdHeadInAvatarSpace = worldToAvatarMat * inputCalibration.hmdSensorMat; // cancel the roll and pitch for the hmd head glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(hmdHeadInAvatarSpace)); glm::vec3 hmdHeadPosition = extractTranslation(hmdHeadInAvatarSpace); glm::mat4 hmdHeadMat = createMatFromQuatAndPos(canceledRollAndPitch, hmdHeadPosition); + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + glm::mat4 defaultRefrenceXform = hmdHeadMat * defaultHeadOffset; + + glm::quat tmpHmdHeadRotation = glmExtractRotation(hmdHeadMat); + /*qDebug() << "----------- hmd head position < ------"; + qDebug() << QString::fromStdString(glm::to_string(hmdHeadPosition)); + qDebug() << "<--------------- hmd head roatation <---------"; + qDebug() << QString::fromStdString(glm::to_string(canceledRollAndPitch));*/ //calculate the offset from the centerEye to the head hoint - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + //glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + glm::vec3 tmpHeadOffsetPosition = extractTranslation(defaultHeadOffset); + glm::quat tmpHeadOffsetRotation = glmExtractRotation(defaultHeadOffset); + + /*qDebug() << "----------------> head offset position <------------------"; + qDebug() << QString::fromStdString(glm::to_string(tmpHeadOffsetPosition)); + qDebug() << "---------------> head offset rotation <-----------------"; + qDebug() << QString::fromStdString(glm::to_string(tmpHeadOffsetRotation));*/ + + glm::vec3 defaultRefrencePosition = extractTranslation(defaultRefrenceXform); + glm::quat defaultRefrenceRotation = glmExtractRotation(defaultRefrenceXform); + qDebug() << "----------------> defaultToReference position <------------------"; + qDebug() << QString::fromStdString(glm::to_string(defaultRefrencePosition)); + qDebug() << "---------------> defaultToReference rotation <-----------------"; + qDebug() << QString::fromStdString(glm::to_string(defaultRefrenceRotation)); - glm::mat4 defaultRefrenceXform = hmdHeadMat * defaultHeadOffset; auto puckCount = _validTrackedObjects.size(); if (puckCount == 2) { //qDebug() << "-------------< configure feet <-------------"; @@ -246,7 +275,7 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr //qDebug() << "-------------> configure feet, hips and chest <---------------"; _config = Config::FeetHipsAndChest; } else { - qDebug() << "Could not configure # pucks " << puckCount; + //qDebug() << "Could not configure # pucks " << puckCount; return; } @@ -258,18 +287,33 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr controller::Pose secondFootPose = secondFoot.second; if (firstFootPose.translation.x < secondFootPose.translation.x) { _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + + qDebug() << " --------> Printing out the offset pose <------------"; + glm::mat4 offset = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); + qDebug() << "----------------> offset Position <------------------"; + qDebug() << QString::fromStdString(glm::to_string(extractTranslation(offset))); + qDebug() << "---------------> offset rotation <-----------------"; + qDebug() << QString::fromStdString(glm::to_string(glmExtractRotation(offset))); _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, secondFootPose); } else { _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; qDebug() << " --------> Printing out the offset pose <------------"; + glm::mat4 offset = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); + qDebug() << "----------------> offset Position <------------------"; + qDebug() << QString::fromStdString(glm::to_string(extractTranslation(offset))); + qDebug() << "---------------> offset rotation <-----------------"; + qDebug() << QString::fromStdString(glm::to_string(glmExtractRotation(offset))); _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, firstFootPose); } if (_config == Config::Feet) { + controller::Pose leftPose = addOffsetToPuckPose(controller::LEFT_FOOT); + qDebug() << "------------> InputDevice::updateCalibratedLimbs <---------"; + printPose(leftPose); // done } else if (_config == Config::FeetAndHips) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; @@ -279,7 +323,8 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); - } + } + qDebug() << "-------------------> end:::InputDevice::calibrate <--------------------"; _calibrated = true; } else { @@ -292,6 +337,9 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr void ViveControllerManager::InputDevice::updateCalibratedLimbs() { _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); + controller::Pose leftPose = addOffsetToPuckPose(controller::LEFT_FOOT); + //qDebug() << "------------> InputDevice::updateCalibratedLimbs <---------"; + //printPose(leftPose); _poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT); _poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS); _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); From 8813306857fd864d98998391bfdbd0a2154842b9 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 5 May 2017 23:59:38 +0100 Subject: [PATCH 31/65] fixed the wierd rotation issue --- plugins/openvr/src/ViveControllerManager.cpp | 62 +++----------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8d480a8e6d..fffdb7cd33 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -230,52 +230,30 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { if (!_calibrated) { // conver the hmd head from sensor space to avatar space - qDebug() << " -------------------> begin:::InputDevice::calibrate <--------------------"; glm::mat4 worldToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdHeadInAvatarSpace = worldToAvatarMat * inputCalibration.hmdSensorMat; + glm::mat4 hmdAvatarMat = worldToAvatarMat * inputCalibration.hmdSensorMat; // cancel the roll and pitch for the hmd head - glm::quat canceledRollAndPitch = cancelOutRollAndPitch(glmExtractRotation(hmdHeadInAvatarSpace)); - glm::vec3 hmdHeadPosition = extractTranslation(hmdHeadInAvatarSpace); - glm::mat4 hmdHeadMat = createMatFromQuatAndPos(canceledRollAndPitch, hmdHeadPosition); + glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); + glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); + glm::mat4 currentHead = createMatFromQuatAndPos(hmdRotation, hmdTranslation); + + // calculate the offset from the centerOfEye to defaultHeadMat glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - glm::mat4 defaultRefrenceXform = hmdHeadMat * defaultHeadOffset; - - glm::quat tmpHmdHeadRotation = glmExtractRotation(hmdHeadMat); - /*qDebug() << "----------- hmd head position < ------"; - qDebug() << QString::fromStdString(glm::to_string(hmdHeadPosition)); - qDebug() << "<--------------- hmd head roatation <---------"; - qDebug() << QString::fromStdString(glm::to_string(canceledRollAndPitch));*/ - //calculate the offset from the centerEye to the head hoint - //glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - glm::vec3 tmpHeadOffsetPosition = extractTranslation(defaultHeadOffset); - glm::quat tmpHeadOffsetRotation = glmExtractRotation(defaultHeadOffset); - - /*qDebug() << "----------------> head offset position <------------------"; - qDebug() << QString::fromStdString(glm::to_string(tmpHeadOffsetPosition)); - qDebug() << "---------------> head offset rotation <-----------------"; - qDebug() << QString::fromStdString(glm::to_string(tmpHeadOffsetRotation));*/ + currentHead *= defaultHeadOffset; - glm::vec3 defaultRefrencePosition = extractTranslation(defaultRefrenceXform); - glm::quat defaultRefrenceRotation = glmExtractRotation(defaultRefrenceXform); - qDebug() << "----------------> defaultToReference position <------------------"; - qDebug() << QString::fromStdString(glm::to_string(defaultRefrencePosition)); - qDebug() << "---------------> defaultToReference rotation <-----------------"; - qDebug() << QString::fromStdString(glm::to_string(defaultRefrenceRotation)); + // calculate the defaultToRefrenceXform + glm::mat4 defaultRefrenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); auto puckCount = _validTrackedObjects.size(); if (puckCount == 2) { - //qDebug() << "-------------< configure feet <-------------"; _config = Config::Feet; } else if (puckCount == 3) { - //qDebug() << "-------------> configure feet and hips <-------------"; _config = Config::FeetAndHips; } else if (puckCount >= 4) { - //qDebug() << "-------------> configure feet, hips and chest <---------------"; _config = Config::FeetHipsAndChest; } else { - //qDebug() << "Could not configure # pucks " << puckCount; return; } @@ -287,24 +265,11 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr controller::Pose secondFootPose = secondFoot.second; if (firstFootPose.translation.x < secondFootPose.translation.x) { _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - - qDebug() << " --------> Printing out the offset pose <------------"; - glm::mat4 offset = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); - qDebug() << "----------------> offset Position <------------------"; - qDebug() << QString::fromStdString(glm::to_string(extractTranslation(offset))); - qDebug() << "---------------> offset rotation <-----------------"; - qDebug() << QString::fromStdString(glm::to_string(glmExtractRotation(offset))); _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, secondFootPose); } else { _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - qDebug() << " --------> Printing out the offset pose <------------"; - glm::mat4 offset = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); - qDebug() << "----------------> offset Position <------------------"; - qDebug() << QString::fromStdString(glm::to_string(extractTranslation(offset))); - qDebug() << "---------------> offset rotation <-----------------"; - qDebug() << QString::fromStdString(glm::to_string(glmExtractRotation(offset))); _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, firstFootPose); @@ -312,7 +277,6 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr if (_config == Config::Feet) { controller::Pose leftPose = addOffsetToPuckPose(controller::LEFT_FOOT); - qDebug() << "------------> InputDevice::updateCalibratedLimbs <---------"; printPose(leftPose); // done } else if (_config == Config::FeetAndHips) { @@ -324,12 +288,9 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); } - qDebug() << "-------------------> end:::InputDevice::calibrate <--------------------"; _calibrated = true; } else { - qDebug() << "---- de-calibrated ---"; - } } @@ -337,9 +298,6 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr void ViveControllerManager::InputDevice::updateCalibratedLimbs() { _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); - controller::Pose leftPose = addOffsetToPuckPose(controller::LEFT_FOOT); - //qDebug() << "------------> InputDevice::updateCalibratedLimbs <---------"; - //printPose(leftPose); _poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT); _poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS); _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); @@ -352,11 +310,9 @@ controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joi controller::Pose puckPose = _poseStateMap[puckIndex]; glm::mat4 puckOffset = _pucksOffset[puckIndex]; puckPose.postTransform(puckOffset); - //qDebug() << "-----------> adding offset to puck <-------------- " << puckPose.valid; return puckPose; } - //qDebug() << "---------> joint is not mapped to any thing <--------------"; return controller::Pose(); } From 1b26ba22cbc323f1f46aebf09608981fad9dac96 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 6 May 2017 11:13:13 +1200 Subject: [PATCH 32/65] Fix browser history in goto "i" pages on tablet --- .../resources/qml/controls/TabletWebView.qml | 81 +++++-------------- 1 file changed, 22 insertions(+), 59 deletions(-) diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 3a6643964b..a718c59659 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -23,16 +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 @@ -53,7 +51,7 @@ Item { id: back enabledColor: hifi.colors.darkGray disabledColor: hifi.colors.lightGrayText - enabled: webview.canGoBack || web.urlList.length > 0 || web.forwardList.length > 0 + enabled: historyIndex > 0 text: "BACK" MouseArea { @@ -76,7 +74,6 @@ Item { } } - RalewaySemiBold { id: displayUrl color: hifi.colors.baseGray @@ -91,7 +88,6 @@ Item { } } - MouseArea { anchors.fill: parent preventStealing: true @@ -99,24 +95,10 @@ Item { } } - ListModel { - id: pagesModel - onCountChanged: { - currentPage = count - 1; - } - } - 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]); } } @@ -133,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); @@ -157,24 +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); - } - onUrlChanged: { loadUrl(url); - if (startingUp) { - web.initialPage = webview.url; - startingUp = false; - } } QtObject { @@ -242,6 +201,16 @@ Item { grantFeaturePermission(securityOrigin, feature, true); } + onUrlChanged: { + // Record history, skipping null and duplicate items. + var urlString = url + ""; + if (urlString.length > 0 && (historyIndex === -1 || urlString !== history[historyIndex])) { + historyIndex++; + history = history.slice(0, historyIndex); + history.push(urlString); + } + } + onLoadingChanged: { keyboardRaised = false; punctuationMode = false; @@ -261,17 +230,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); } } From a744db56aaebb7ad27c9a5ea697e69e8e22e9258 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Sat, 6 May 2017 00:21:52 +0100 Subject: [PATCH 33/65] removed dead code --- plugins/openvr/src/ViveControllerManager.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index fffdb7cd33..ff6edccf9f 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -48,29 +48,18 @@ const quint64 CALIBRATION_TIMELAPSE = 3000000; const char* ViveControllerManager::NAME { "OpenVR" }; -/*glm::mat4 computeDefualtHead(glm::mat4 centerOfEye, glm::mat4 jointHead) { - headOffset = glm::muliply(glm::inverse(centerOfEye), jointHead); - return glm:: - }*/ - glm::mat4 computeOffset(glm::mat4 defaultToRefrenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { - ///qDebug() << "-------------> computing offset <-------------"; glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); glm::mat4 refrenceJointMat = defaultToRefrenceMat * defaultJointMat; return ( glm::inverse(poseMat) * refrenceJointMat); } + bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { controller::Pose firstPose = firstPuck.second; controller::Pose secondPose = secondPuck.second; return (firstPose.translation.y < secondPose.translation.y); } -void printPose(controller::Pose pose) { - qDebug() << "-------------> printing out controller::Pose <--------------"; - qDebug() << QString::fromStdString(glm::to_string(pose.translation)); - qDebug() << QString::fromStdString(glm::to_string(pose.rotation)); -} - bool ViveControllerManager::isSupported() const { return openVrSupported(); } @@ -276,8 +265,6 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr } if (_config == Config::Feet) { - controller::Pose leftPose = addOffsetToPuckPose(controller::LEFT_FOOT); - printPose(leftPose); // done } else if (_config == Config::FeetAndHips) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; From f00a3cafb606c30600e0b6f079908d0369d8eaea Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 10:52:54 -0700 Subject: [PATCH 34/65] cleanup logic to be more readable --- libraries/physics/src/CharacterController.cpp | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index dae609121e..8671fcaa8c 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -273,28 +273,37 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar if (_steppingUp) { // 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 hDistance = sqrtf(_stepPoint.getX() * _stepPoint.getX() + _stepPoint.getZ() * _stepPoint.getZ()); - float targetSpeed = _targetVelocity.length(); - float timeToStep = hDistance / targetSpeed; - float stepUpSpeed = _stepHeight / timeToStep + 0.5f * _gravity * timeToStep; - const float MAX_STEP_UP_SPEED = 0.65f * targetSpeed; + float horizontalDistance = (_stepPoint - _stepPoint.dot(_currentUp) * _currentUp).length(); + float horizontalTargetSpeed = (_targetVelocity - _targetVelocity.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; } - btScalar vDotUp = velocity.dot(_currentUp); - if (stepUpSpeed > vDotUp) { - // don't have enough upward velocity to cover the step + // 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 - // we step up with micro-teleports rather than applying velocity - // use a speed that would ballistically reach _stepHeight under gravity + 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); } - float minStepUpSpeed = 0.0f; - if (vDotUp < minStepUpSpeed) { - velocity += (minStepUpSpeed - vDotUp) * _currentUp; + + // 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); From 0f643b7d66fc835322a71c9695f94431e2767c7f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 10:59:15 -0700 Subject: [PATCH 35/65] add check to avoid divide by zero --- libraries/physics/src/CharacterController.cpp | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 8671fcaa8c..efe51f8692 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -275,35 +275,37 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // to move over the _stepPoint at target speed float horizontalDistance = (_stepPoint - _stepPoint.dot(_currentUp) * _currentUp).length(); float horizontalTargetSpeed = (_targetVelocity - _targetVelocity.dot(_currentUp) * _currentUp).length(); - float timeToStep = horizontalDistance / horizontalTargetSpeed; - float stepUpSpeed = _stepHeight / timeToStep; + if (horizontalTargetSpeed > FLT_EPSILON) { + 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; - } + // 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 + // 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); - } + 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; + // 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); From d9e893ac2117dbfd4276a50cbc662c0aaabe206c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 11:02:11 -0700 Subject: [PATCH 36/65] minior optimization/cleanup --- libraries/physics/src/CharacterController.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index efe51f8692..fe6f882d47 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -271,11 +271,11 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar _followTime += dt; if (_steppingUp) { - // 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 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; From 7d0494dd564bcf75d1e915015ce11b5795cb1487 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 8 May 2017 12:15:03 -0700 Subject: [PATCH 37/65] use new images in api/v1/users response json --- scripts/system/pal.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 9229ec772a..254372fba7 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -369,21 +369,7 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca url += 'filter=connections'; // regardless of whether online } requestJSON(url, function (connectionsData) { - // The back end doesn't include the profile picture data, but we can add that here. - // For our current purposes, there's no need to be fancy and try to reduce latency by doing some number of requests in parallel, - // so these requests are all sequential. - var users = connectionsData.users; - function addPicture(index) { - if (index >= users.length) { - return callback(users); - } - var user = users[index]; - getProfilePicture(user.username, function (url) { - user.profileUrl = url; - addPicture(index + 1); - }); - } - addPicture(0); + callback(connectionsData.users); }); } @@ -397,7 +383,7 @@ function getConnectionData(domain) { // Update all the usernames that I am entit sessionId: formattedSessionId, userName: user.username, connection: user.connection, - profileUrl: user.profileUrl, + profileUrl: user.images.thumbnail, placeName: (user.location.root || user.location.domain || {}).name || '' }; } From e44c2c8da3c11acc1b164e4bd5ebddeda650a581 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 8 May 2017 12:47:32 -0700 Subject: [PATCH 38/65] init zero gravity to agree with init hover state --- libraries/physics/src/CharacterController.cpp | 1 + libraries/physics/src/CharacterController.h | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index fe6f882d47..e4ff1b0b44 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -361,6 +361,7 @@ void CharacterController::updateGravity() { 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) { diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index d33154ef2e..0a11fad0b7 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -32,8 +32,6 @@ const uint32_t PENDING_FLAG_JUMP = 1U << 3; const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); -const float DEFAULT_CHARACTER_GRAVITY = -5.0f; - class btRigidBody; class btCollisionWorld; class btDynamicsWorld; @@ -183,7 +181,7 @@ protected: bool _stepUpEnabled { true }; bool _hasSupport; - btScalar _gravity { DEFAULT_CHARACTER_GRAVITY }; + btScalar _gravity { 0.0f }; btScalar _jumpSpeed; btScalar _followTime; From 116175cf9fe422d8d525dc5ba9fa391f369dc75e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 9 May 2017 09:03:48 +1200 Subject: [PATCH 39/65] Fix going back from Twitter password recovery page --- interface/resources/qml/controls/TabletWebView.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index a718c59659..d939e088a8 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -204,6 +204,7 @@ Item { 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); From e670037198222d578b036c69f640926ef13cf36b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 8 May 2017 15:49:42 -0700 Subject: [PATCH 40/65] Adjust copyright on Limitless Sphinx script --- unpublishedScripts/interaction/Interaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/interaction/Interaction.js b/unpublishedScripts/interaction/Interaction.js index a488e9f279..bb763c01e7 100644 --- a/unpublishedScripts/interaction/Interaction.js +++ b/unpublishedScripts/interaction/Interaction.js @@ -3,7 +3,7 @@ // scripts/interaction // // Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. +// 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 From ef0e32643d68ec4e76ce2b5fd85b260eaf53bbb1 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 8 May 2017 18:51:17 -0400 Subject: [PATCH 41/65] update refactored logic to match previous application behavior --- interface/src/avatar/MySkeletonModel.cpp | 7 +++++ .../src/avatars-renderer/SkeletonModel.cpp | 29 +++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 1b9aa4dc18..245aae48dd 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -38,6 +38,13 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // make sure lookAt is not too close to face (avoid crosseyes) glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition(); + 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(_owningAvatar); Rig::HeadParameters headParams; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index e1e5dc4282..1aefc7e1f6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -73,20 +73,20 @@ void SkeletonModel::initJointStates() { // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - 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 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; - } - if (!_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 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; + } + // 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; @@ -118,9 +118,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromEyeParameters(eyeParams); } - - // evaluate AnimGraph animation and update jointStates. - Parent::updateRig(deltaTime, parentTransform); } void SkeletonModel::updateAttitude() { From 773e4346f965838e9f514fd366016f5cd7908c3e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 8 May 2017 15:59:29 -0700 Subject: [PATCH 42/65] Update copyright in sphinx scripts These were meant to be updated before the PR was merged, but I overlooked it. --- unpublishedScripts/interaction/NPCHelpers.js | 2 +- unpublishedScripts/interaction/NPC_AC.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unpublishedScripts/interaction/NPCHelpers.js b/unpublishedScripts/interaction/NPCHelpers.js index d36a71ea93..188178b281 100644 --- a/unpublishedScripts/interaction/NPCHelpers.js +++ b/unpublishedScripts/interaction/NPCHelpers.js @@ -3,7 +3,7 @@ // scripts/interaction // // Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. +// 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 diff --git a/unpublishedScripts/interaction/NPC_AC.js b/unpublishedScripts/interaction/NPC_AC.js index 6eaee6a6e3..eb2d9f4caf 100644 --- a/unpublishedScripts/interaction/NPC_AC.js +++ b/unpublishedScripts/interaction/NPC_AC.js @@ -3,7 +3,7 @@ // scripts/interaction // // Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. +// 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 From 23c3a7511610b6d0c5bd388398f0796505077b62 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 00:27:30 +0100 Subject: [PATCH 43/65] finally fixed rotation issue --- plugins/openvr/src/ViveControllerManager.cpp | 149 ++++++++++--------- plugins/openvr/src/ViveControllerManager.h | 2 +- 2 files changed, 79 insertions(+), 72 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index ff6edccf9f..5a44a432ec 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -1,4 +1,3 @@ - // // Created by Sam Gondelman on 6/29/15. // Copyright 2013 High Fidelity, Inc. @@ -22,7 +21,6 @@ #include #include #include -#include #include @@ -48,10 +46,10 @@ const quint64 CALIBRATION_TIMELAPSE = 3000000; const char* ViveControllerManager::NAME { "OpenVR" }; -glm::mat4 computeOffset(glm::mat4 defaultToRefrenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { +glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); - glm::mat4 refrenceJointMat = defaultToRefrenceMat * defaultJointMat; - return ( glm::inverse(poseMat) * refrenceJointMat); + glm::mat4 referenceJointMat = defaultToReferenceMat * defaultJointMat; + return glm::inverse(poseMat) * referenceJointMat; } bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { @@ -130,7 +128,6 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu userInputMapper->removeDevice(_inputDevice->_deviceID); _registeredWithInputMapper = false; _inputDevice->_poseStateMap.clear(); - //qDebug() << " ----------->>!!!!!!! clear pose state map !!!!!!!<<----------------"; } if (!_registeredWithInputMapper && _inputDevice->_trackedControllers > 0) { @@ -213,78 +210,88 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { quint64 currentTime = usecTimestampNow(); - + auto leftTrigger = _buttonPressedMap.find(controller::LT); auto rightTrigger = _buttonPressedMap.find(controller::RT); if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { - if (!_calibrated) { - // conver the hmd head from sensor space to avatar space - glm::mat4 worldToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdAvatarMat = worldToAvatarMat * inputCalibration.hmdSensorMat; + if (!_triggersPressedHandled) { + _triggersPressedHandled = true; + if (!_calibrated) { + // conver the hmd head from sensor space to avatar space + glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; + glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; - // cancel the roll and pitch for the hmd head - glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); - glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); - glm::mat4 currentHead = createMatFromQuatAndPos(hmdRotation, hmdTranslation); - - // calculate the offset from the centerOfEye to defaultHeadMat - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + // cancel the roll and pitch for the hmd head + glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); + glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); + glm::mat4 currentHead = createMatFromQuatAndPos(hmdRotation, hmdTranslation); + + // calculate the offset from the centerOfEye to defaultHeadMat + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + + currentHead = currentHead * defaultHeadOffset; + + // calculate the defaultToRefrenceXform + glm::mat4 defaultReferenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); + + auto puckCount = _validTrackedObjects.size(); + if (puckCount == 2) { + _config = Config::Feet; + } else if (puckCount == 3) { + _config = Config::FeetAndHips; + } else if (puckCount >= 4) { + _config = Config::FeetHipsAndChest; + } else { + return; + } + + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + + auto firstFoot = _validTrackedObjects[0]; + auto secondFoot = _validTrackedObjects[1]; + controller::Pose firstFootPose = firstFoot.second; + controller::Pose secondFootPose = secondFoot.second; + + if (firstFootPose.translation.x < secondFootPose.translation.x) { + _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultLeftFoot, firstFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultRightFoot, secondFootPose); + + } else { + _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultLeftFoot, secondFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultRightFoot, firstFootPose); + } + + if (_config == Config::Feet) { + // done + } else if (_config == Config::FeetAndHips) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); + } else if (_config == Config::FeetHipsAndChest) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); + _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; + _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); + } + _calibrated = true; - currentHead *= defaultHeadOffset; - - // calculate the defaultToRefrenceXform - glm::mat4 defaultRefrenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); - - auto puckCount = _validTrackedObjects.size(); - if (puckCount == 2) { - _config = Config::Feet; - } else if (puckCount == 3) { - _config = Config::FeetAndHips; - } else if (puckCount >= 4) { - _config = Config::FeetHipsAndChest; } else { - return; + _pucksOffset.clear(); + _jointToPuckMap.clear(); + _calibrated = false; } - - std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); - - auto firstFoot = _validTrackedObjects[0]; - auto secondFoot = _validTrackedObjects[1]; - controller::Pose firstFootPose = firstFoot.second; - controller::Pose secondFootPose = secondFoot.second; - if (firstFootPose.translation.x < secondFootPose.translation.x) { - _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, firstFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, secondFootPose); - } else { - _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultLeftFoot, secondFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultRightFoot, firstFootPose); - } - - if (_config == Config::Feet) { - // done - } else if (_config == Config::FeetAndHips) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); - } else if (_config == Config::FeetHipsAndChest) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); - _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; - _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultRefrenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); - } - _calibrated = true; - - } else { } - + } else { + _triggersPressedHandled = false; } } void ViveControllerManager::InputDevice::updateCalibratedLimbs() { - _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); + _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); _poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT); _poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS); _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); @@ -296,9 +303,9 @@ controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joi uint32_t puckIndex = puck->second; controller::Pose puckPose = _poseStateMap[puckIndex]; glm::mat4 puckOffset = _pucksOffset[puckIndex]; - puckPose.postTransform(puckOffset); - return puckPose; - + controller::Pose newPose = puckPose.postTransform(puckOffset); + return newPose; + } return controller::Pose(); } @@ -374,7 +381,7 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 _axisStateMap[isLeftHand ? LY : RY] = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { _axisStateMap[isLeftHand ? LT : RT] = x; - // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, + // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, // so we can expose that as an additional button if (x >= 1.0f) { _buttonPressedMap.insert(isLeftHand ? LT_CLICK : RT_CLICK); @@ -519,7 +526,7 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(LEFT_FOOT, "LeftFoot"), makePair(RIGHT_FOOT, "RightFoot"), makePair(HIPS, "Hips"), - makePair(SPINE2, "Spine2"), + makePair(SPINE2, "Spine2"), // 16 tracked poses makePair(TRACKED_OBJECT_00, "TrackedObject00"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 43dd982f80..1b0cf4213e 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -114,7 +114,7 @@ private: float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; - bool _calibrate { false }; + bool _triggersPressedHandled { false }; bool _calibrated { false }; mutable std::recursive_mutex _lock; From 00546c55bb2d15ea7bf6ae34fafe1b5693e34b6f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 00:30:06 +0100 Subject: [PATCH 44/65] minimize diff --- plugins/openvr/src/ViveControllerManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 5a44a432ec..00bf42b2d9 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -1,4 +1,7 @@ // +// ViveControllerManager.cpp +// input-plugins/src/input-plugins +// // Created by Sam Gondelman on 6/29/15. // Copyright 2013 High Fidelity, Inc. // From 03ba3f59441f8bee280b8525d5722e1a6fe91fdd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 00:31:13 +0100 Subject: [PATCH 45/65] minimize diff 2.0 --- plugins/openvr/src/ViveControllerManager.h | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 1b0cf4213e..b9d7c3ea01 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -109,7 +109,6 @@ private: int _trackedControllers { 0 }; vr::IVRSystem*& _system; - quint64 _calibrateAfterTimelapse { 0.f }; float _leftHapticStrength { 0.0f }; float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; From c3d57459d5f9bfeb9e4d518446bec08b0c586fd1 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 00:32:47 +0100 Subject: [PATCH 46/65] minimize diff 3.0 --- plugins/openvr/src/ViveControllerManager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 00bf42b2d9..e22195b028 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -212,8 +212,6 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde } void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { - quint64 currentTime = usecTimestampNow(); - auto leftTrigger = _buttonPressedMap.find(controller::LT); auto rightTrigger = _buttonPressedMap.find(controller::RT); if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { From b6f652e0eae8289dfb75f2af39da3f9172eb8b56 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 8 May 2017 20:05:14 -0400 Subject: [PATCH 47/65] use network period multiple for local audio buffer --- libraries/audio-client/src/AudioClient.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 680e9129aa..1266c4a499 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1525,14 +1525,23 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice connect(_audioOutput, &QAudioOutput::stateChanged, [&, frameSize, requestedSize](QAudio::State state) { if (state == QAudio::ActiveState) { // restrict device callback to _outputPeriod samples - _outputPeriod = (_audioOutput->periodSize() / AudioConstants::SAMPLE_SIZE) * 2; + _outputPeriod = _audioOutput->periodSize() / AudioConstants::SAMPLE_SIZE; + // device callback may exceed reported period, so double it to avoid stutter + _outputPeriod *= 2; + _outputMixBuffer = new float[_outputPeriod]; _outputScratchBuffer = new int16_t[_outputPeriod]; // size local output mix buffer based on resampled network frame size _networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); _localOutputMixBuffer = new float[_networkPeriod]; + + // local period should be at least twice the output period, + // in case two device reads happen before more data can be read (worst case) int localPeriod = _outputPeriod * 2; + // round up to an exact multiple of _networkPeriod + localPeriod = ((localPeriod + _networkPeriod - 1) / _networkPeriod) * _networkPeriod; + // this ensures lowest latency without stutter from underrun _localInjectorsStream.resizeForFrameSize(localPeriod); int bufferSize = _audioOutput->bufferSize(); From 73a3bf413bc3bdbd00cfb0f12768b0503803fa1a Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 8 May 2017 20:14:09 -0400 Subject: [PATCH 48/65] update local audio thread name --- libraries/audio-client/src/AudioClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 72cb438eab..1561eabf05 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1358,7 +1358,7 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) injectorBuffer->setParent(nullptr); - injectorBuffer->moveToThread(&_localAudioThread); + injectorBuffer->moveToThread(_localInjectorsThread); } else { qCDebug(audioclient) << "injector exists in active list already"; } From 807a6d583197a40a5cf04f977cde593d133fb5e2 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 8 May 2017 18:03:50 -0700 Subject: [PATCH 49/65] Bugfix: new threading changes in the Qt5.7.1 plugin were causing deadlock --- cmake/externals/wasapi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 5da625e34d..1bf195fc84 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -7,7 +7,7 @@ if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip - URL_MD5 2830a17388928253ef22ae9662f914a7 + URL_MD5 b01510437ea15527156bc25cdf733bd9 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 42d1498d207b085ccd117c201d4982ab4d8fa50b Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 8 May 2017 21:19:18 -0400 Subject: [PATCH 50/65] call Model::updateRig (not Parent::updateRig) --- interface/src/avatar/MySkeletonModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 245aae48dd..0b0d0901cd 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -147,6 +147,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto orientation = myAvatar->getLocalOrientation(); _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); + // evaluate AnimGraph animation and update jointStates. + Model::updateRig(deltaTime, parentTransform); + Rig::EyeParameters eyeParams; eyeParams.eyeLookAt = lookAt; eyeParams.eyeSaccade = head->getSaccade(); @@ -156,8 +159,5 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex; _rig->updateFromEyeParameters(eyeParams); - - // evaluate AnimGraph animation and update jointStates. - Parent::updateRig(deltaTime, parentTransform); } From 68ca0d921e9b4e7d16f5078e45684f0a74e1c059 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 8 May 2017 21:34:55 -0400 Subject: [PATCH 51/65] use assert rather than whole if block --- .../src/avatars-renderer/SkeletonModel.cpp | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 1aefc7e1f6..f48a8d4a84 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -73,51 +73,50 @@ void SkeletonModel::initJointStates() { // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - if (!_owningAvatar->isMyAvatar()) { - const FBXGeometry& geometry = getFBXGeometry(); + assert(!_owningAvatar->isMyAvatar()); + const FBXGeometry& geometry = getFBXGeometry(); - Head* head = _owningAvatar->getHead(); + 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 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; - } + // make sure lookAt is not too close to face (avoid crosseyes) + glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition(); + 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; + } - // 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); - } + _rig->updateFromEyeParameters(eyeParams); } void SkeletonModel::updateAttitude() { From 29b1ac3572b5f7d1d4f13e856a2ffdc2e011de11 Mon Sep 17 00:00:00 2001 From: humbletim Date: Mon, 8 May 2017 21:50:24 -0400 Subject: [PATCH 52/65] optimizations per feedback --- interface/src/avatar/MySkeletonModel.cpp | 2 +- .../avatars-renderer/src/avatars-renderer/SkeletonModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0b0d0901cd..e60481fc62 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -37,7 +37,7 @@ 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; diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index f48a8d4a84..d3453280ac 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -79,7 +79,7 @@ void SkeletonModel::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->getCorrectedLookAtPosition(); glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition(); float focusDistance = glm::length(focusOffset); const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f; From 34edc2345c4ef1b6996800b294deb53d5fd0a18d Mon Sep 17 00:00:00 2001 From: volansystech Date: Tue, 9 May 2017 22:07:32 +0530 Subject: [PATCH 53/65] Bug 4255: When you are in HMD and you go to file menu quit, it crashes and you have no menus when you come back. Fixed NPE of buttonProxy while doing addButtonProxyToQmlTablet on quitting the application. --- libraries/script-engine/src/TabletScriptingInterface.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 139fe0552d..644f1e6f0c 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -250,6 +250,10 @@ static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* if (QThread::currentThread() != qmlTablet->thread()) { connectionType = Qt::BlockingQueuedConnection; } + if (buttonProxy == NULL){ + qCCritical(scriptengine) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL"; + return; + } bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); if (!hasResult) { From bce9e9ea82a92b5f66aff1b7df8b743ff947ebe9 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 17:51:30 +0100 Subject: [PATCH 54/65] made requested chnages --- plugins/openvr/src/ViveControllerManager.cpp | 195 ++++++++++--------- plugins/openvr/src/ViveControllerManager.h | 5 +- 2 files changed, 110 insertions(+), 90 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index e22195b028..ddf55bf670 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -45,20 +45,20 @@ static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; -const quint64 CALIBRATION_TIMELAPSE = 3000000; +static const int MIN_PUCK_COUNT = 2; +static const int MIN_FEET_AND_HIPS = 3; +static const int MIN_FEET_HIPS_CHEST = 4; const char* ViveControllerManager::NAME { "OpenVR" }; -glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { +static glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaultJointMat, controller::Pose puckPose) { glm::mat4 poseMat = createMatFromQuatAndPos(puckPose.rotation, puckPose.translation); glm::mat4 referenceJointMat = defaultToReferenceMat * defaultJointMat; return glm::inverse(poseMat) * referenceJointMat; } -bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { - controller::Pose firstPose = firstPuck.second; - controller::Pose secondPose = secondPuck.second; - return (firstPose.translation.y < secondPose.translation.y); +static bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { + return (firstPuck.second.translation.y < firstPuck.second.translation.y); } bool ViveControllerManager::isSupported() const { @@ -182,7 +182,16 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle numTrackedControllers++; } _trackedControllers = numTrackedControllers; - calibrate(inputCalibrationData); + + if (checkForCalibrationEvent()) { + if (!_triggersPressedHandled) { + _triggersPressedHandled = true; + calibrateOrUncalibrate(inputCalibrationData); + } + } else { + _triggersPressedHandled = false; + } + updateCalibratedLimbs(); } @@ -211,102 +220,110 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde } } -void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { - auto leftTrigger = _buttonPressedMap.find(controller::LT); - auto rightTrigger = _buttonPressedMap.find(controller::RT); - if ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())) { - if (!_triggersPressedHandled) { - _triggersPressedHandled = true; - if (!_calibrated) { - // conver the hmd head from sensor space to avatar space - glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; - glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; - - // cancel the roll and pitch for the hmd head - glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); - glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); - glm::mat4 currentHead = createMatFromQuatAndPos(hmdRotation, hmdTranslation); - - // calculate the offset from the centerOfEye to defaultHeadMat - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - - currentHead = currentHead * defaultHeadOffset; - - // calculate the defaultToRefrenceXform - glm::mat4 defaultReferenceXform = currentHead * glm::inverse(inputCalibration.defaultHeadMat); - - auto puckCount = _validTrackedObjects.size(); - if (puckCount == 2) { - _config = Config::Feet; - } else if (puckCount == 3) { - _config = Config::FeetAndHips; - } else if (puckCount >= 4) { - _config = Config::FeetHipsAndChest; - } else { - return; - } - - std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); - - auto firstFoot = _validTrackedObjects[0]; - auto secondFoot = _validTrackedObjects[1]; - controller::Pose firstFootPose = firstFoot.second; - controller::Pose secondFootPose = secondFoot.second; - - if (firstFootPose.translation.x < secondFootPose.translation.x) { - _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultLeftFoot, firstFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultRightFoot, secondFootPose); - - } else { - _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; - _pucksOffset[secondFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultLeftFoot, secondFootPose); - _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; - _pucksOffset[firstFoot.first] = computeOffset(defaultReferenceXform, inputCalibration.defaultRightFoot, firstFootPose); - } - - if (_config == Config::Feet) { - // done - } else if (_config == Config::FeetAndHips) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); - } else if (_config == Config::FeetHipsAndChest) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultHips, _validTrackedObjects[2].second); - _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; - _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultReferenceXform, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); - } - _calibrated = true; - - } else { - _pucksOffset.clear(); - _jointToPuckMap.clear(); - _calibrated = false; - } - } +void ViveControllerManager::InputDevice::calibrateOrUncalibrate(const controller::InputCalibrationData& inputCalibration) { + if (!_calibrated) { + calibrate(inputCalibration); } else { - _triggersPressedHandled = false; + uncalibrate(); } } +bool ViveControllerManager::InputDevice::checkForCalibrationEvent() { + auto& leftTrigger = _buttonPressedMap.find(controller::LT); + auto& rightTrigger = _buttonPressedMap.find(controller::RT); + return ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())); +} + +void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { + // convert the hmd head from sensor space to avatar space + glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; + glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; + + // cancel the roll and pitch for the hmd head + glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); + glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); + glm::mat4 currentHmd = createMatFromQuatAndPos(hmdRotation, hmdTranslation); + + // calculate the offset from the centerOfEye to defaultHeadMat + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + + glm::mat4 currentHead = currentHmd * defaultHeadOffset; + + // calculate the defaultToRefrenceXform + glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); + + int puckCount = (int)_validTrackedObjects.size(); + if (puckCount == MIN_PUCK_COUNT) { + _config = Config::Feet; + } else if (puckCount == MIN_FEET_AND_HIPS) { + _config = Config::FeetAndHips; + } else if (puckCount >= MIN_FEET_HIPS_CHEST) { + _config = Config::FeetHipsAndChest; + } else { + return; + } + + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); + + int firstFootIndex = 0; + int secondFootIndex = 1; + + auto& firstFoot = _validTrackedObjects[firstFootIndex]; + auto& secondFoot = _validTrackedObjects[secondFootIndex]; + controller::Pose& firstFootPose = firstFoot.second; + controller::Pose& secondFootPose = secondFoot.second; + + if (firstFootPose.translation.x < secondFootPose.translation.x) { + _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); + + } else { + _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); + } + + if (_config == Config::Feet) { + // done + } else if (_config == Config::FeetAndHips) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); + } else if (_config == Config::FeetHipsAndChest) { + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); + _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; + _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); + } + _calibrated = true; +} + +void ViveControllerManager::InputDevice::uncalibrate() { + _pucksOffset.clear(); + _jointToPuckMap.clear(); + _calibrated = false; +} + void ViveControllerManager::InputDevice::updateCalibratedLimbs() { - _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); + _poseStateMap[controller::LEFT_FOOT] = addOffsetToPuckPose(controller::LEFT_FOOT); _poseStateMap[controller::RIGHT_FOOT] = addOffsetToPuckPose(controller::RIGHT_FOOT); _poseStateMap[controller::HIPS] = addOffsetToPuckPose(controller::HIPS); _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); } -controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) { +controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const { auto puck = _jointToPuckMap.find(joint); if (puck != _jointToPuckMap.end()) { uint32_t puckIndex = puck->second; - controller::Pose puckPose = _poseStateMap[puckIndex]; - glm::mat4 puckOffset = _pucksOffset[puckIndex]; - controller::Pose newPose = puckPose.postTransform(puckOffset); - return newPose; + auto puckPose = _poseStateMap.find(puckIndex); + auto puckOffset = _pucksOffset.find(puckIndex); + if ((puckPose != _poseStateMap.end()) && (puckOffset != _pucksOffset.end())) { + return puckPose->second.postTransform(puckOffset->second); + } } return controller::Pose(); } diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index b9d7c3ea01..04e2d2b4aa 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -60,9 +60,12 @@ private: bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override; void hapticsHelper(float deltaTime, bool leftHand); + void calibrateOrUncalibrate(const controller::InputCalibrationData& inputCalibration); void calibrate(const controller::InputCalibrationData& inputCalibration); - controller::Pose addOffsetToPuckPose(int joint); + void uncalibrate(); + controller::Pose addOffsetToPuckPose(int joint) const; void updateCalibratedLimbs(); + bool checkForCalibrationEvent(); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); From d688b60282047afd7e56ea66b9f2c6f42b64d4d3 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 18:02:12 +0100 Subject: [PATCH 55/65] no magic numbers --- plugins/openvr/src/ViveControllerManager.cpp | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index ddf55bf670..851d04497a 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -45,9 +45,13 @@ static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; -static const int MIN_PUCK_COUNT = 2; -static const int MIN_FEET_AND_HIPS = 3; -static const int MIN_FEET_HIPS_CHEST = 4; +static const int MIN_PUCK_COUNT = 2; +static const int MIN_FEET_AND_HIPS = 3; +static const int MIN_FEET_HIPS_CHEST = 4; +static const int FIRST_FOOT = 0; +static const int SECOND_FOOT = 1; +static const int HIP = 2; +static const int CHEST = 3; const char* ViveControllerManager::NAME { "OpenVR" }; @@ -266,11 +270,10 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); - int firstFootIndex = 0; - int secondFootIndex = 1; - auto& firstFoot = _validTrackedObjects[firstFootIndex]; - auto& secondFoot = _validTrackedObjects[secondFootIndex]; + + auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; + auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; controller::Pose& firstFootPose = firstFoot.second; controller::Pose& secondFootPose = secondFoot.second; @@ -290,12 +293,12 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr if (_config == Config::Feet) { // done } else if (_config == Config::FeetAndHips) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); } else if (_config == Config::FeetHipsAndChest) { - _jointToPuckMap[controller::HIPS] = _validTrackedObjects[2].first; + _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); - _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[3].first; + _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[CHEST].first; _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); } _calibrated = true; From 48c78584c30b5c03f92f2b8fc9dc117dd3c81612 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 9 May 2017 11:09:39 -0700 Subject: [PATCH 56/65] fix a bug that could cause OBJ models with external mtl references to hang the loading thread --- libraries/fbx/src/OBJReader.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 167cb8caac..1445d14d84 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -273,10 +273,9 @@ std::tuple requestData(QUrl& url) { return std::make_tuple(false, QByteArray()); } - request->send(); - QEventLoop loop; QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit); + request->send(); loop.exec(); if (request->getResult() == ResourceRequest::Success) { From c362ba91647cfbd7567112febca742ea4f8ffca2 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 19:13:47 +0100 Subject: [PATCH 57/65] better calibration event --- plugins/openvr/src/ViveControllerManager.cpp | 25 ++++++++++++++------ plugins/openvr/src/ViveControllerManager.h | 2 ++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 851d04497a..8fedf926dc 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; +const quint64 CALIBRATION_TIMELAPSE = 3 * USECS_PER_SECOND; static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; @@ -188,12 +190,19 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _trackedControllers = numTrackedControllers; if (checkForCalibrationEvent()) { - if (!_triggersPressedHandled) { + quint64 currentTime = usecTimestampNow(); + if (!_timeTilCalibrationSet) { + _timeTilCalibrationSet = true; + _timeTilCalibration = currentTime + CALIBRATION_TIMELAPSE; + } + + if (currentTime > _timeTilCalibration && !_triggersPressedHandled) { _triggersPressedHandled = true; calibrateOrUncalibrate(inputCalibrationData); } } else { _triggersPressedHandled = false; + _timeTilCalibrationSet = false; } updateCalibratedLimbs(); @@ -232,12 +241,6 @@ void ViveControllerManager::InputDevice::calibrateOrUncalibrate(const controller } } -bool ViveControllerManager::InputDevice::checkForCalibrationEvent() { - auto& leftTrigger = _buttonPressedMap.find(controller::LT); - auto& rightTrigger = _buttonPressedMap.find(controller::RT); - return ((leftTrigger != _buttonPressedMap.end()) && (rightTrigger != _buttonPressedMap.end())); -} - void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { // convert the hmd head from sensor space to avatar space glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; @@ -416,6 +419,14 @@ enum ViveButtonChannel { RIGHT_APP_MENU }; +bool ViveControllerManager::InputDevice::checkForCalibrationEvent() { + auto& endOfMap = _buttonPressedMap.end(); + auto& leftTrigger = _buttonPressedMap.find(controller::LT); + auto& rightTrigger = _buttonPressedMap.find(controller::RT); + auto& leftAppButton = _buttonPressedMap.find(LEFT_APP_MENU); + auto& rightAppButton = _buttonPressedMap.find(RIGHT_APP_MENU); + return ((leftTrigger != endOfMap && leftAppButton != endOfMap) && (rightTrigger != endOfMap && rightAppButton != endOfMap)); +} // These functions do translation from the Steam IDs to the standard controller IDs void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand) { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 04e2d2b4aa..9375fd20f0 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -112,12 +112,14 @@ private: int _trackedControllers { 0 }; vr::IVRSystem*& _system; + quint64 _timeTilCalibration { 0.0f }; float _leftHapticStrength { 0.0f }; float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; bool _triggersPressedHandled { false }; bool _calibrated { false }; + bool _timeTilCalibrationSet { false }; mutable std::recursive_mutex _lock; friend class ViveControllerManager; From 5b33e0233c59ba921ee73d9fa23df7d3b92c84b3 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 9 May 2017 13:27:42 -0700 Subject: [PATCH 58/65] fix typo in handshake script --- scripts/system/makeUserConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 6f7b746f18..52afc8883d 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -760,7 +760,7 @@ break; case "done": delete waitingList[senderID]; - if (connectionId !== senderID) { + if (connectingId !== senderID) { break; } if (state === STATES.CONNECTING) { From c478f1a7520d7ef0d4901eaaa2726bd6b043a24d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 9 May 2017 16:34:42 -0400 Subject: [PATCH 59/65] synchronously fill injector buffer on underrun --- libraries/audio-client/src/AudioClient.cpp | 61 +++++++++++++++----- libraries/audio-client/src/AudioClient.h | 12 ++-- libraries/audio/src/AbstractAudioInterface.h | 2 +- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1266c4a499..f56d31eea2 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1096,11 +1096,19 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { handleAudioInput(audioBuffer); } -void AudioClient::prepareLocalAudioInjectors() { +void AudioClient::prepareLocalAudioInjectors(std::unique_ptr localAudioLock) { + bool doSynchronously = localAudioLock.operator bool(); + if (!localAudioLock) { + localAudioLock.reset(new Lock(_localAudioMutex)); + } + int samplesNeeded = std::numeric_limits::max(); while (samplesNeeded > 0) { - // unlock between every write to allow device switching - Lock lock(_localAudioMutex); + if (!doSynchronously) { + // unlock between every write to allow device switching + localAudioLock->unlock(); + localAudioLock->lock(); + } // in case of a device switch, consider bufferCapacity volatile across iterations if (_outputPeriod == 0) { @@ -1154,16 +1162,16 @@ void AudioClient::prepareLocalAudioInjectors() { } bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { - - QVector injectorsToRemove; - - // lock the injector vector - Lock lock(_injectorsMutex); - - if (_activeLocalAudioInjectors.size() == 0) { + // check the flag for injectors before attempting to lock + if (!_localInjectorsAvailable.load(std::memory_order_acquire)) { return false; } + // lock the injectors + Lock lock(_injectorsMutex); + + QVector injectorsToRemove; + memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float)); for (AudioInjector* injector : _activeLocalAudioInjectors) { @@ -1242,6 +1250,9 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { _activeLocalAudioInjectors.removeOne(injector); } + // update the flag + _localInjectorsAvailable.exchange(!_activeLocalAudioInjectors.empty(), std::memory_order_release); + return true; } @@ -1329,6 +1340,9 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) { // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop()) injectorBuffer->setParent(nullptr); injectorBuffer->moveToThread(&_localAudioThread); + + // update the flag now that injectors are available + _localInjectorsAvailable.exchange(true, std::memory_order_release); } else { qCDebug(audioclient) << "injector exists in active list already"; } @@ -1455,7 +1469,7 @@ void AudioClient::outputNotify() { bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) { bool supportedFormat = false; - Lock lock(_localAudioMutex); + Lock localAudioLock(_localAudioMutex); _localSamplesAvailable.exchange(0, std::memory_order_release); // cleanup any previously initialized device @@ -1556,6 +1570,9 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice qCDebug(audioclient) << "local buffer (samples):" << localPeriod; disconnect(_audioOutput, &QAudioOutput::stateChanged, 0, 0); + + // unlock to avoid a deadlock with the device callback (which always succeeds this initialization) + localAudioLock.unlock(); } }); connect(_audioOutput, &QAudioOutput::notify, this, &AudioClient::outputNotify); @@ -1694,12 +1711,24 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { int injectorSamplesPopped = 0; { bool append = networkSamplesPopped > 0; - // this does not require a lock as of the only two functions adding to _localSamplesAvailable (samples count): + // check the samples we have available locklessly; this is possible because only two functions add to the 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)); + // - 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 then only see a zeroed samples count + int samplesAvailable = _audio->_localSamplesAvailable.load(std::memory_order_acquire); + + // if we do not have enough samples buffered despite having injectors, buffer them synchronously + if (samplesAvailable < samplesRequested && _audio->_localInjectorsAvailable.load(std::memory_order_acquire)) { + // try_to_lock, in case the device is being shut down already + std::unique_ptr localAudioLock(new Lock(_audio->_localAudioMutex, std::try_to_lock)); + if (localAudioLock->owns_lock()) { + _audio->prepareLocalAudioInjectors(std::move(localAudioLock)); + samplesAvailable = _audio->_localSamplesAvailable.load(std::memory_order_acquire); + } + } + + samplesRequested = std::min(samplesRequested, samplesAvailable); if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) { _audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release); qCDebug(audiostream, "Read %d samples from injectors (%d available, %d requested)", injectorSamplesPopped, _localInjectorsStream.samplesAvailable(), samplesRequested); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index aaedee7456..3e9ba0fed9 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -158,7 +158,7 @@ public: Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale); - void checkDevices(); + bool outputLocalInjector(AudioInjector* injector) override; static const float CALLBACK_ACCELERATOR_RATIO; @@ -198,8 +198,6 @@ public slots: int setOutputBufferSize(int numFrames, bool persist = true); - void prepareLocalAudioInjectors(); - bool outputLocalInjector(AudioInjector* injector) override; bool shouldLoopbackInjectors() override { return _shouldEchoToServer; } bool switchInputToAudioDevice(const QString& inputDeviceName); @@ -247,8 +245,13 @@ protected: } private: + friend class CheckDevicesThread; + friend class AudioInjectorsThread; + void outputFormatChanged(); void handleAudioInput(QByteArray& audioBuffer); + void checkDevices(); + void prepareLocalAudioInjectors(std::unique_ptr localAudioLock = nullptr); bool mixLocalAudioInjectors(float* mixBuffer); float azimuthForSource(const glm::vec3& relativePosition); float gainForSource(float distance, float volume); @@ -295,8 +298,9 @@ private: AudioRingBuffer _inputRingBuffer; LocalInjectorsStream _localInjectorsStream; // In order to use _localInjectorsStream as a lock-free pipe, - // use it with a single producer/consumer, and track available samples + // use it with a single producer/consumer, and track available samples and injectors std::atomic _localSamplesAvailable { 0 }; + std::atomic _localInjectorsAvailable { false }; MixedProcessedAudioStream _receivedAudioStream; bool _isStereoInput; diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 2e4611cd4e..2e14b9956b 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -32,12 +32,12 @@ public: const Transform& transform, glm::vec3 avatarBoundingBoxCorner, glm::vec3 avatarBoundingBoxScale, 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; +public slots: virtual bool shouldLoopbackInjectors() { return false; } virtual void setIsStereoInput(bool stereo) = 0; From 2be6d19dfe5399438611780b1d6cd6995c849abf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 9 May 2017 14:00:18 -0700 Subject: [PATCH 60/65] always call Head::simulate() for Avatar in view --- .../src/avatars-renderer/Avatar.cpp | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index be55653f64..664b0094f4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -369,23 +369,25 @@ void Avatar::simulate(float deltaTime, bool inView) { PerformanceTimer perfTimer("simulate"); { PROFILE_RANGE(simulation, "updateJoints"); - if (inView && _hasNewJointData) { - _skeletonModel->getRig()->copyJointsFromJointData(_jointData); - glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); - _skeletonModel->getRig()->computeExternalPoses(rootTransform); - _jointDataSimulationRate.increment(); - - _skeletonModel->simulate(deltaTime, true); - - locationChanged(); // joints changed, so if there are any children, update them. - _hasNewJointData = false; - - glm::vec3 headPosition = getPosition(); - if (!_skeletonModel->getHeadPosition(headPosition)) { - headPosition = getPosition(); - } + if (inView) { Head* head = getHead(); - head->setPosition(headPosition); + if (_hasNewJointData) { + _skeletonModel->getRig()->copyJointsFromJointData(_jointData); + glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); + _skeletonModel->getRig()->computeExternalPoses(rootTransform); + _jointDataSimulationRate.increment(); + + _skeletonModel->simulate(deltaTime, true); + + locationChanged(); // joints changed, so if there are any children, update them. + _hasNewJointData = false; + + glm::vec3 headPosition = getPosition(); + if (!_skeletonModel->getHeadPosition(headPosition)) { + headPosition = getPosition(); + } + head->setPosition(headPosition); + } head->setScale(getUniformScale()); head->simulate(deltaTime); } else { From 53e254152da25fc43362715c03b41e466e9238cf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 9 May 2017 22:30:29 +0100 Subject: [PATCH 61/65] fixed puck location --- plugins/openvr/src/ViveControllerManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8fedf926dc..db12d25e0d 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -41,7 +41,7 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; -const quint64 CALIBRATION_TIMELAPSE = 3 * USECS_PER_SECOND; +const quint64 CALIBRATION_TIMELAPSE = 2 * USECS_PER_SECOND; static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; @@ -297,12 +297,12 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr // done } else if (_config == Config::FeetAndHips) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); + _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second); } else if (_config == Config::FeetHipsAndChest) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; - _pucksOffset[_validTrackedObjects[2].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[2].second); + _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second); _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[CHEST].first; - _pucksOffset[_validTrackedObjects[3].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultSpine2, _validTrackedObjects[3].second); + _pucksOffset[_validTrackedObjects[CHEST].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultSpine2, _validTrackedObjects[CHEST].second); } _calibrated = true; } From cf3c10c5b114f09ac8647a89c53b34e7073bc866 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 9 May 2017 14:21:38 -0700 Subject: [PATCH 62/65] Fix the bugs, add the feature --- interface/resources/qml/hifi/Pal.qml | 2 +- scripts/system/pal.js | 57 +++++++++++++++++++--------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 1755d2fbec..8f6b00f459 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -844,7 +844,7 @@ Rectangle { boxSize: 24; onClicked: { var newValue = model.connection !== "friend"; - connectionsUserModel.setProperty(model.userIndex, styleData.role, newValue); + connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection")); connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName}); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 254372fba7..0500c13f9b 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -268,7 +268,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'refreshConnections': print('Refreshing Connections...'); - getConnectionData(); + getConnectionData(false); UserActivityLogger.palAction("refresh_connections", ""); break; case 'removeConnection': @@ -281,25 +281,27 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See print("Error: unable to remove connection", connectionUserName, error || response.status); return; } - getConnectionData(); + getConnectionData(false); }); break case 'removeFriend': friendUserName = message.params; + print("Removing " + friendUserName + " from friends."); request({ uri: METAVERSE_BASE + '/api/v1/user/friends/' + friendUserName, method: 'DELETE' }, function (error, response) { if (error || (response.status !== 'success')) { - print("Error: unable to unfriend", friendUserName, error || response.status); + print("Error: unable to unfriend " + friendUserName, error || response.status); return; } - getConnectionData(); + getConnectionData(friendUserName); }); break case 'addFriend': friendUserName = message.params; + print("Adding " + friendUserName + " to friends."); request({ uri: METAVERSE_BASE + '/api/v1/user/friends', method: 'POST', @@ -312,7 +314,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See print("Error: unable to friend " + friendUserName, error || response.status); return; } - getConnectionData(); // For now, just refresh all connection data. Later, just refresh the one friended row. + getConnectionData(friendUserName); } ); break; @@ -360,8 +362,6 @@ function getProfilePicture(username, callback) { // callback(url) if successfull }); } function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - // The back end doesn't do user connections yet. Fake it by getting all users that have made themselves accessible to us, - // and pretending that they are all connections. url = METAVERSE_BASE + '/api/v1/users?' if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces @@ -372,8 +372,19 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca callback(connectionsData.users); }); } - -function getConnectionData(domain) { // Update all the usernames that I am entitled to see, using my login but not dependent on canKick. +function getInfoAboutUser(specificUsername, callback) { + url = METAVERSE_BASE + '/api/v1/users?filter=connections' + requestJSON(url, function (connectionsData) { + for (user in connectionsData.users) { + if (connectionsData.users[user].username === specificUsername) { + callback(connectionsData.users[user]); + return; + } + } + callback(false); + }); +} +function getConnectionData(specificUsername, domain) { // Update all the usernames that I am entitled to see, using my login but not dependent on canKick. function frob(user) { // get into the right format var formattedSessionId = user.location.node_id || ''; if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) { @@ -387,15 +398,25 @@ function getConnectionData(domain) { // Update all the usernames that I am entit placeName: (user.location.root || user.location.domain || {}).name || '' }; } - getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { + if (specificUsername) { + getInfoAboutUser(specificUsername, function (user) { + if (user) { updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'connections', params: users.map(frob) }); - } - }); + } else { + print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); + } + }); + } else { + getAvailableConnections(domain, function (users) { + if (domain) { + users.forEach(function (user) { + updateUser(frob(user)); + }); + } else { + sendToQml({ method: 'connections', params: users.map(frob) }); + } + }); + } } // @@ -472,7 +493,7 @@ function populateNearbyUserList(selectData, oldAudioData) { data.push(avatarPalDatum); print('PAL data:', JSON.stringify(avatarPalDatum)); }); - getConnectionData(location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). + getConnectionData(false, location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain). conserveResources = Object.keys(avatarsOfInterest).length > 20; sendToQml({ method: 'nearbyUsers', params: data }); if (selectData) { From bb4c0d972d98f90a27976f3f038ad4fe003a347f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 10 May 2017 00:10:26 +0100 Subject: [PATCH 63/65] head routed through the vive input plugin --- interface/resources/controllers/vive.json | 3 +- plugins/openvr/src/ViveControllerManager.cpp | 30 ++++++++++++++++++++ plugins/openvr/src/ViveControllerManager.h | 3 ++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 5bdffadbbf..4491507a9c 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -39,6 +39,7 @@ { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", "when": [ "Application.InHMD"] }, { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", "when": [ "Application.InHMD"] }, { "from": "Vive.Hips", "to" : "Standard.Hips", "when": [ "Application.InHMD"] }, - { "from": "Vive.Spine2", "to" : "Standard.Spine2", "when": [ "Application.InHMD"] } + { "from": "Vive.Spine2", "to" : "Standard.Spine2", "when": [ "Application.InHMD"] }, + { "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] } ] } diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index db12d25e0d..8f6e1e8430 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -167,6 +167,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle // collect poses for all generic trackers for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { handleTrackedObject(i, inputCalibrationData); + handleHmd(i, inputCalibrationData); } // handle haptics @@ -334,6 +335,22 @@ controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joi return controller::Pose(); } +void ViveControllerManager::InputDevice::handleHmd(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { + uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; + + if (_system->IsTrackedDeviceConnected(deviceIndex) && + _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_HMD && + _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && + poseIndex <= controller::TRACKED_OBJECT_15) { + + const mat4& mat = _nextSimPoseData.poses[deviceIndex]; + const vec3 linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; + const vec3 angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; + + handleHeadPoseEvent(inputCalibrationData, mat, linearVelocity, angularVelocity); + } +} + void ViveControllerManager::InputDevice::handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand) { if (_system->IsTrackedDeviceConnected(deviceIndex) && @@ -456,6 +473,18 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } } +void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, + const vec3& linearVelocity, const vec3& angularVelocity) { + + glm::mat4 matYFlip = mat * Matrices::Y_180; + controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity); + + glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + controller::Pose hmdHeadPose = pose.transform(sensorToAvatar); + _poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset); +} + void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand) { @@ -559,6 +588,7 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(RIGHT_FOOT, "RightFoot"), makePair(HIPS, "Hips"), makePair(SPINE2, "Spine2"), + makePair(HEAD, "Head"), // 16 tracked poses makePair(TRACKED_OBJECT_00, "TrackedObject00"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 9375fd20f0..4e8b2b3a04 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -67,11 +67,14 @@ private: void updateCalibratedLimbs(); bool checkForCalibrationEvent(); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); + void handleHmd(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); + void handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, + const vec3& angularVelocity); void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { From 571f0d5951ae831bcbed682a3149e9753dba5083 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 10 May 2017 16:14:29 +0100 Subject: [PATCH 64/65] made requested changes --- plugins/openvr/src/ViveControllerManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8f6e1e8430..452272a5d4 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -340,8 +340,7 @@ void ViveControllerManager::InputDevice::handleHmd(uint32_t deviceIndex, const c if (_system->IsTrackedDeviceConnected(deviceIndex) && _system->GetTrackedDeviceClass(deviceIndex) == vr::TrackedDeviceClass_HMD && - _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && - poseIndex <= controller::TRACKED_OBJECT_15) { + _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid) { const mat4& mat = _nextSimPoseData.poses[deviceIndex]; const vec3 linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; @@ -476,6 +475,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) { + // perform a 180 flip to make tha HMD face the +z, which is the same direction of the head faces. glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity); From 0e1ef75d5b15627d147bd75237c176842cb28fdf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 10 May 2017 16:18:26 +0100 Subject: [PATCH 65/65] fix comment wording --- plugins/openvr/src/ViveControllerManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 452272a5d4..6e5697730b 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -475,7 +475,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) { - // perform a 180 flip to make tha HMD face the +z, which is the same direction of the head faces. + //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity);