From 27ecaeb1b6b18127c9a982b5ae5d803c47223457 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 15 Nov 2018 16:06:47 -0800 Subject: [PATCH 001/104] Add RC branch creation script --- tools/scripts/rc-branches.py | 241 +++++++++++++++++++++++++ tools/scripts/utils/__init__.py | 1 + tools/scripts/utils/git.py | 303 ++++++++++++++++++++++++++++++++ 3 files changed, 545 insertions(+) create mode 100755 tools/scripts/rc-branches.py create mode 100644 tools/scripts/utils/__init__.py create mode 100644 tools/scripts/utils/git.py diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py new file mode 100755 index 0000000000..a9e1c8c304 --- /dev/null +++ b/tools/scripts/rc-branches.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + +import logging +import os +import re +import sys + +from optparse import OptionParser + +from utils import git + +FORMAT = '[%(levelname)s] %(message)s' +logging.basicConfig(format=FORMAT, level=logging.DEBUG) + +remote_name = "upstream" +remote_master_branch = "{}/master".format(remote_name) + +def checkVersionBranches(version): + """Check the branches for a given version were created properly.""" + + repo = git.Repository(git.Repository.get_base_directory()) + + # Validate that user passed a valid version + VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$") + match = VERSION_RE.match(version) + if not match: + raise ValueError("Invalid version (should be X.Y.Z)") + + # Parse the version component and build proper version strings + major = int(match.group(1)) + minor = int(match.group(2)) + patch = int(match.group(3)) + version = "v{}.{}.{}".format(major, minor, patch) # clean up version + + is_major_release = False + is_minor_release = False + is_patch_release = False + if patch != 0: + is_patch_release = True + previous_version = "v{}.{}.{}".format(major, minor, patch - 1) + elif minor != 0: + is_minor_release = True + previous_version = "v{}.{}.{}".format(major, minor - 1, 0) + else: + is_major_release = True + raise ValueError("Major releases not yet supported") + + # Build the branch names + previous_rc_branch = "{}/{}-rc".format(remote_name, previous_version) + base_branch = "{}/{}-rc-base".format(remote_name, version) + rc_branch = "{}/{}-rc".format(remote_name, version) + + # Verify the branches' existance + if not repo.does_branch_exist(previous_rc_branch): + raise ValueError("Previous RC branch not found: {}".format(previous_rc_branch)) + + if not repo.does_branch_exist(base_branch): + raise ValueError("Base branch not found: {}".format(base_branch)) + + if not repo.does_branch_exist(rc_branch): + raise ValueError("RC branch not found: {}".format(rc_branch)) + + # Figure out SHA for each of the branches + previous_rc_commit = repo.git_rev_parse([previous_rc_branch]) + base_commit = repo.git_rev_parse([base_branch]) + rc_commit = repo.git_rev_parse([rc_branch]) + + # Check the base branch is an ancestor of the rc branch + if not repo.is_ancestor(base_commit, rc_commit): + raise ValueError("{} is not an ancesctor of {}".format(base_branch, rc_branch)) + + # Check that the base branch is the merge base of the previous and current RCs + merge_base = repo.get_merge_base(previous_rc_commit, rc_commit) + if base_commit != merge_base: + raise ValueError("Base branch is not the merge base between {} and {}".format(previous_rc_branch, rc_branch)) + + # For patch releases, warn if the base commit is not the previous RC commit + if is_patch_release: + if not repo.does_tag_exist(version): + logging.warning("The tag {} does not exist, which suggests {} hasn't yet been cut.".format(version, version)) + + if base_commit != previous_rc_commit: + logging.warning("Previous version has commits not in this patch"); + logging.warning("Type \"git diff {}..{}\" to see the commit list".format(base_commit, previous_rc_commit)); + + # Check base branch is part of the previous RC + previous_rc_base_commit = repo.get_merge_base(previous_rc_commit, remote_master_branch) + if repo.is_ancestor(base_commit, previous_rc_base_commit): + raise ValueError("{} is older than {}".format(base_branch, rc_branch)) + + print("[SUCCESS] Checked {}".format(version)) + +def createVersionBranches(version): + """Create the branches for a given version.""" + + repo = git.Repository(git.Repository.get_base_directory()) + + # Validate the user is on a local branch that has the right merge base + if repo.is_detached(): + raise ValueError("You must not run this script in a detached HEAD state") + + # Validate the user has no pending changes + if repo.is_working_tree_dirty(): + raise ValueError("Your working tree has pending changes. You must have a clean working tree before proceeding.") + + # Validate that user passed a valid version + VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$") + match = VERSION_RE.match(version) + if not match: + raise ValueError("Invalid version (should be X.Y.Z)") + + # Parse the version component and build proper version strings + major = int(match.group(1)) + minor = int(match.group(2)) + patch = int(match.group(3)) + version = "v{}.{}.{}".format(major, minor, patch) # clean up version + + is_major_release = False + is_minor_release = False + is_patch_release = False + if patch != 0: + is_patch_release = True + previous_version = "v{}.{}.{}".format(major, minor, patch - 1) + elif minor != 0: + is_minor_release = True + previous_version = "v{}.{}.{}".format(major, minor - 1, 0) + else: + is_major_release = True + raise ValueError("Major releases not yet supported") + + # Build the branch names + previous_rc_branch = "{}-rc".format(previous_version) + base_branch = "{}-rc-base".format(version) + rc_branch = "{}-rc".format(version) + remote_previous_rc_branch = "{}/{}".format(remote_name, previous_rc_branch) + remote_base_branch = "{}/{}".format(remote_name, base_branch) + remote_rc_branch = "{}/{}".format(remote_name, rc_branch) + + # Make sure the remote is up to date + repo.git_fetch([remote_name]) + + # Verify the previous RC branch exists + if not repo.does_branch_exist(remote_previous_rc_branch): + raise ValueError("Previous RC branch not found: {}".format(remote_previous_rc_branch)) + + # Verify the branches don't already exist + if repo.does_branch_exist(remote_base_branch): + raise ValueError("Base branch already exists: {}".format(remote_base_branch)) + + if repo.does_branch_exist(remote_rc_branch): + raise ValueError("RC branch already exists: {}".format(remote_rc_branch)) + + if repo.does_branch_exist(base_branch): + raise ValueError("Base branch already exists locally: {}".format(base_branch)) + + if repo.does_branch_exist(rc_branch): + raise ValueError("RC branch already exists locally: {}".format(rc_branch)) + + # Save current branch name + current_branch_name = repo.get_branch_name() + + # Create the RC branches + if is_patch_release: + + # Check tag exists, if it doesn't, print warning and ask for comfirmation + if not repo.does_tag_exist(version): + logging.warning("The tag {} does not exist, which suggests {} hasn't yet been cut.".format(version, version)) + logging.warning("Creating the branches now means that they will diverge from {} if anything is merge into it.".format(previous_version)) + logging.warning("This is not recommanded unless necessary.") + + validAnswer = False + askCount = 0 + while not validAnswer and askCount < 3: + answer = input("Are you sure you want to do this? [y/n]").strip().lower() + askCount += 1 + validAnswer = answer == "y" or answer == "n" + + if not validAnswer: + raise ValueError("Did not understand response") + + if answer == "n": + print("Aborting") + return + else: + print("Creating branches") + + repo.git_checkout(["-b", base_branch, remote_previous_rc_branch]) + repo.push_to_remote_branch(remote_name, base_branch) + repo.git_checkout(["-b", rc_branch, remote_previous_rc_branch]) + repo.push_to_remote_branch(remote_name, rc_branch) + else: + merge_base = repo.get_merge_base(remote_previous_rc_branch, remote_master_branch) + repo.git_checkout(["-b", base_branch, merge_base]) + repo.push_to_remote_branch(remote_name, base_branch) + repo.git_checkout(["-b", rc_branch, remote_master_branch]) + repo.push_to_remote_branch(remote_name, rc_branch) + + repo.git_checkout([current_branch_name]) + + print("[SUCCESS] Created {} and {}".format(base_branch, rc_branch)) + print("[SUCCESS] You can make the PR from the following webpage:") + print("[SUCCESS] https://github.com/highfidelity/hifi/compare/{}...{}".format(base_branch, rc_branch)) + +def usage(): + """Print usage.""" + print("rc-branches.py supports the following commands:") + print("\ncheck ") + print(" - version to check of the form \"X.Y.Z\"") + print("\ncreate ") + print(" - version to create branches for of the form \"X.Y.Z\"") + +def main(): + """Execute Main entry point.""" + global remote_name + + parser = OptionParser() + parser.add_option("-r", "--remote", type="string", dest="remote_name", default=remote_name) + (options, args) = parser.parse_args(args=sys.argv) + + remote_name = options.remote_name + + if len(args) < 3: + usage() + return + + command = args[1] + version = args[2] + + try: + if command == "check": + checkVersionBranches(version) + elif command == "create": + createVersionBranches(version) + else: + usage() + except Exception as ex: + logging.error(ex) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tools/scripts/utils/__init__.py b/tools/scripts/utils/__init__.py new file mode 100644 index 0000000000..4b7a2bb941 --- /dev/null +++ b/tools/scripts/utils/__init__.py @@ -0,0 +1 @@ +"""Empty.""" diff --git a/tools/scripts/utils/git.py b/tools/scripts/utils/git.py new file mode 100644 index 0000000000..156d2240c3 --- /dev/null +++ b/tools/scripts/utils/git.py @@ -0,0 +1,303 @@ +"""Module to run git commands on a repository.""" + +# Copied from https://github.com/mongodb/mongo under Apache 2.0 +# Modified by Clement Brisset on 11/14/18. + +import logging +import os +import sys + +# The subprocess32 module resolves the thread-safety issues of the subprocess module in Python 2.x +# when the _posixsubprocess C extension module is also available. Additionally, the _posixsubprocess +# C extension module avoids triggering invalid free() calls on Python's internal data structure for +# thread-local storage by skipping the PyOS_AfterFork() call when the 'preexec_fn' parameter isn't +# specified to subprocess.Popen(). See SERVER-22219 for more details. +# +# The subprocess32 module is untested on Windows and thus isn't recommended for use, even when it's +# installed. See https://github.com/google/python-subprocess32/blob/3.2.7/README.md#usage. +if os.name == "posix" and sys.version_info[0] == 2: + try: + import subprocess32 as subprocess + except ImportError: + import warnings + warnings.warn(("Falling back to using the subprocess module because subprocess32 isn't" + " available. When using the subprocess module, a child process may trigger" + " an invalid free(). See SERVER-22219 for more details."), RuntimeWarning) + import subprocess # type: ignore +else: + import subprocess + +LOGGER = logging.getLogger(__name__) + + +class Repository(object): # pylint: disable=too-many-public-methods + """Represent a local git repository.""" + + def __init__(self, directory): + """Initialize Repository.""" + self.directory = directory + + def git_add(self, args): + """Run a git add command.""" + return self._callgito("add", args) + + def git_cat_file(self, args): + """Run a git cat-file command.""" + return self._callgito("cat-file", args) + + def git_checkout(self, args): + """Run a git checkout command.""" + return self._callgito("checkout", args) + + def git_commit(self, args): + """Run a git commit command.""" + return self._callgito("commit", args) + + def git_diff(self, args): + """Run a git diff command.""" + return self._callgito("diff", args) + + def git_log(self, args): + """Run a git log command.""" + return self._callgito("log", args) + + def git_push(self, args): + """Run a git push command.""" + return self._callgito("push", args) + + def git_fetch(self, args): + """Run a git fetch command.""" + return self._callgito("fetch", args) + + def git_ls_files(self, args): + """Run a git ls-files command and return the result as a str.""" + return self._callgito("ls-files", args) + + def git_rebase(self, args): + """Run a git rebase command.""" + return self._callgito("rebase", args) + + def git_reset(self, args): + """Run a git reset command.""" + return self._callgito("reset", args) + + def git_rev_list(self, args): + """Run a git rev-list command.""" + return self._callgito("rev-list", args) + + def git_rev_parse(self, args): + """Run a git rev-parse command.""" + return self._callgito("rev-parse", args).rstrip() + + def git_rm(self, args): + """Run a git rm command.""" + return self._callgito("rm", args) + + def git_show(self, args): + """Run a git show command.""" + return self._callgito("show", args) + + def get_origin_url(self): + """Return the URL of the origin repository.""" + return self._callgito("config", ["--local", "--get", "remote.origin.url"]).rstrip() + + def get_branch_name(self): + """ + Get the current branch name, short form. + + This returns "master", not "refs/head/master". + Raises a GitException if the current branch is detached. + """ + branch = self.git_rev_parse(["--abbrev-ref", "HEAD"]) + if branch == "HEAD": + raise GitException("Branch is currently detached") + return branch + + def get_current_revision(self): + """Retrieve the current revision of the repository.""" + return self.git_rev_parse(["HEAD"]).rstrip() + + def configure(self, parameter, value): + """Set a local configuration parameter.""" + return self._callgito("config", ["--local", parameter, value]) + + def is_detached(self): + """Return True if the current working tree in a detached HEAD state.""" + # symbolic-ref returns 1 if the repo is in a detached HEAD state + return self._callgit("symbolic-ref", ["--quiet", "HEAD"]) == 1 + + def is_ancestor(self, parent_revision, child_revision): + """Return True if the specified parent hash an ancestor of child hash.""" + # If the common point between parent_revision and child_revision is + # parent_revision, then parent_revision is an ancestor of child_revision. + merge_base = self._callgito("merge-base", [parent_revision, child_revision]).rstrip() + return parent_revision == merge_base + + def is_commit(self, revision): + """Return True if the specified hash is a valid git commit.""" + # cat-file -e returns 0 if it is a valid hash + return not self._callgit("cat-file", ["-e", "{0}^{{commit}}".format(revision)]) + + def is_working_tree_dirty(self): + """Return True if the current working tree has changes.""" + # diff returns 1 if the working tree has local changes + return self._callgit("diff", ["--quiet"]) == 1 + + def does_branch_exist(self, branch): + """Return True if the branch exists.""" + # rev-parse returns 0 if the branch exists + return not self._callgit("rev-parse", ["--verify", "--quiet", branch]) + + def does_tag_exist(self, tag): + """Return True if the tag exists.""" + # rev-parse returns 0 if the tag exists + return not self._callgit("rev-parse", ["--verify", "--quiet", tag]) + + def get_merge_base(self, commit1, commit2 = "HEAD"): + """Get the merge base between 'commit' and HEAD.""" + return self._callgito("merge-base", [commit1, commit2]).rstrip() + + def commit_with_message(self, message): + """Commit the staged changes with the given message.""" + return self.git_commit(["--message", message]) + + def push_to_remote_branch(self, remote, remote_branch): + """Push the current branch to the specified remote repository and branch.""" + refspec = "{}:{}".format(self.get_branch_name(), remote_branch) + return self.git_push([remote, refspec]) + + def fetch_remote_branch(self, repository, branch): + """Fetch the changes from a remote branch.""" + return self.git_fetch([repository, branch]) + + def rebase_from_upstream(self, upstream, ignore_date=False): + """Rebase the repository on an upstream reference. + + If 'ignore_date' is True, the '--ignore-date' option is passed to git. + """ + args = [upstream] + if ignore_date: + args.append("--ignore-date") + return self.git_rebase(args) + + @staticmethod + def clone(url, directory, branch=None, depth=None): + """Clone the repository designed by 'url' into 'directory'. + + Return a Repository instance. + """ + params = ["git", "clone"] + if branch: + params += ["--branch", branch] + if depth: + params += ["--depth", depth] + params += [url, directory] + result = Repository._run_process("clone", params) + result.check_returncode() + return Repository(directory) + + @staticmethod + def get_base_directory(directory=None): + """Return the base directory of the repository the given directory belongs to. + + If no directory is specified, then the current working directory is used. + """ + if directory is not None: + params = ["git", "-C", directory] + else: + params = ["git"] + params.extend(["rev-parse", "--show-toplevel"]) + result = Repository._run_process("rev-parse", params) + result.check_returncode() + return os.path.normpath(result.stdout.rstrip()) + + @staticmethod + def current_repository(): + """Return the Repository the current working directory belongs to.""" + return Repository(Repository.get_base_directory()) + + def _callgito(self, cmd, args): + """Call git for this repository, and return the captured output.""" + result = self._run_cmd(cmd, args) + result.check_returncode() + return result.stdout + + def _callgit(self, cmd, args, raise_exception=False): + """ + Call git for this repository without capturing output. + + This is designed to be used when git returns non-zero exit codes. + """ + result = self._run_cmd(cmd, args) + if raise_exception: + result.check_returncode() + return result.returncode + + def _run_cmd(self, cmd, args): + """Run the git command and return a GitCommandResult instance.""" + + LOGGER.debug("Running: git {} {}".format(cmd, " ".join(args))) + params = ["git", cmd] + args + return self._run_process(cmd, params, cwd=self.directory) + + @staticmethod + def _run_process(cmd, params, cwd=None): + process = subprocess.Popen(params, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) + (stdout, stderr) = process.communicate() + if process.returncode: + if stdout: + LOGGER.error("Output of '%s': %s", " ".join(params), stdout.rstrip()) + if stderr: + LOGGER.error("Error output of '%s': %s", " ".join(params), stderr.rstrip()) + return GitCommandResult(cmd, params, process.returncode, stdout=stdout, stderr=stderr) + + +class GitException(Exception): + """Custom Exception for the git module. + + Args: + message: the exception message. + returncode: the return code of the failed git command, if any. + cmd: the git subcommand that was run, if any. + process_args: a list containing the git command and arguments (includes 'git' as its first + element) that were run, if any. + stderr: the error output of the git command. + """ + + def __init__( # pylint: disable=too-many-arguments + self, message, returncode=None, cmd=None, process_args=None, stdout=None, stderr=None): + """Initialize GitException.""" + Exception.__init__(self, message) + self.returncode = returncode + self.cmd = cmd + self.process_args = process_args + self.stdout = stdout + self.stderr = stderr + + +class GitCommandResult(object): + """The result of running git subcommand. + + Args: + cmd: the git subcommand that was executed (e.g. 'clone', 'diff'). + process_args: the full list of process arguments, starting with the 'git' command. + returncode: the return code. + stdout: the output of the command. + stderr: the error output of the command. + """ + + def __init__( # pylint: disable=too-many-arguments + self, cmd, process_args, returncode, stdout=None, stderr=None): + """Initialize GitCommandResult.""" + self.cmd = cmd + self.process_args = process_args + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + + def check_returncode(self): + """Raise GitException if the exit code is non-zero.""" + if self.returncode: + raise GitException("Command '{0}' failed with code '{1}'".format( + " ".join(self.process_args), self.returncode), self.returncode, self.cmd, + self.process_args, self.stdout, self.stderr) From 8b2c9e1c48f0e0f375f17c77a413ebf7fcec9ab5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Nov 2018 18:17:08 -0800 Subject: [PATCH 002/104] Apply suggestions from code review Changed wording in a few comments Co-Authored-By: Atlante45 --- tools/scripts/rc-branches.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py index a9e1c8c304..1d78fc0722 100755 --- a/tools/scripts/rc-branches.py +++ b/tools/scripts/rc-branches.py @@ -16,7 +16,7 @@ remote_name = "upstream" remote_master_branch = "{}/master".format(remote_name) def checkVersionBranches(version): - """Check the branches for a given version were created properly.""" + """Check that the branches for a given version were created properly.""" repo = git.Repository(git.Repository.get_base_directory()) @@ -77,7 +77,7 @@ def checkVersionBranches(version): # For patch releases, warn if the base commit is not the previous RC commit if is_patch_release: if not repo.does_tag_exist(version): - logging.warning("The tag {} does not exist, which suggests {} hasn't yet been cut.".format(version, version)) + logging.warning("The tag {} does not exist, which suggests {} has not been released.".format(version, version)) if base_commit != previous_rc_commit: logging.warning("Previous version has commits not in this patch"); @@ -164,9 +164,9 @@ def createVersionBranches(version): # Check tag exists, if it doesn't, print warning and ask for comfirmation if not repo.does_tag_exist(version): - logging.warning("The tag {} does not exist, which suggests {} hasn't yet been cut.".format(version, version)) - logging.warning("Creating the branches now means that they will diverge from {} if anything is merge into it.".format(previous_version)) - logging.warning("This is not recommanded unless necessary.") + logging.warning("The tag {} does not exist, which suggests {} has not yet been released.".format(version, version)) + logging.warning("Creating the branches now means that {} will diverge from {} if anything is merged into {}.".format(version, previous_version, previous_version)) + logging.warning("This is not recommended unless necessary.") validAnswer = False askCount = 0 From dc91b1c915847f2637e7ff53fe7fba3a16c963de Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 15 Nov 2018 18:42:15 -0800 Subject: [PATCH 003/104] CR --- tools/scripts/rc-branches.py | 161 +++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 75 deletions(-) diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py index 1d78fc0722..8db78a72bc 100755 --- a/tools/scripts/rc-branches.py +++ b/tools/scripts/rc-branches.py @@ -15,64 +15,88 @@ logging.basicConfig(format=FORMAT, level=logging.DEBUG) remote_name = "upstream" remote_master_branch = "{}/master".format(remote_name) + +class VersionParser: + """A parser for version numbers""" + def __init__(self, versionString): + # Validate that user passed a valid version + VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$") + match = VERSION_RE.match(versionString) + if not match: + raise ValueError("Invalid version (should be X.Y.Z)") + + # Parse the version component and build proper version strings + self.major = int(match.group(1)) + self.minor = int(match.group(2)) + self.patch = int(match.group(3)) + self.version = "v{}.{}.{}".format(self.major, self.minor, self.patch) # clean up version + + self.is_major_release = False + self.is_minor_release = False + self.is_patch_release = False + if self.patch != 0: + self.is_patch_release = True + self.previous_version = "v{}.{}.{}".format(self.major, self.minor, self.patch - 1) + elif self.minor != 0: + self.is_minor_release = True + self.previous_version = "v{}.{}.{}".format(self.major, self.minor - 1, 0) + else: + self.is_major_release = True + self.previous_version = "v{}.{}.{}".format(self.major - 1, 0, 0) + raise ValueError("Major releases not yet supported") + + # Build the branch names + self.previous_rc_branch = "{}-rc".format(self.previous_version) + self.base_branch = "{}-rc-base".format(self.version) + self.rc_branch = "{}-rc".format(self.version) + self.remote_previous_rc_branch = "{}/{}".format(remote_name, self.previous_rc_branch) + self.remote_base_branch = "{}/{}".format(remote_name, self.base_branch) + self.remote_rc_branch = "{}/{}".format(remote_name, self.rc_branch) + + def checkVersionBranches(version): """Check that the branches for a given version were created properly.""" + parser = VersionParser(version) + major = parser.major + minor = parser.minor + patch = parser.patch + previous_version = parser.previous_version + version = parser.version + + is_major_release = parser.is_major_release + is_minor_release = parser.is_minor_release + is_patch_release = parser.is_patch_release + + remote_previous_rc_branch = parser.remote_previous_rc_branch + remote_base_branch = parser.remote_base_branch + remote_rc_branch = parser.remote_rc_branch + repo = git.Repository(git.Repository.get_base_directory()) - # Validate that user passed a valid version - VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$") - match = VERSION_RE.match(version) - if not match: - raise ValueError("Invalid version (should be X.Y.Z)") - - # Parse the version component and build proper version strings - major = int(match.group(1)) - minor = int(match.group(2)) - patch = int(match.group(3)) - version = "v{}.{}.{}".format(major, minor, patch) # clean up version - - is_major_release = False - is_minor_release = False - is_patch_release = False - if patch != 0: - is_patch_release = True - previous_version = "v{}.{}.{}".format(major, minor, patch - 1) - elif minor != 0: - is_minor_release = True - previous_version = "v{}.{}.{}".format(major, minor - 1, 0) - else: - is_major_release = True - raise ValueError("Major releases not yet supported") - - # Build the branch names - previous_rc_branch = "{}/{}-rc".format(remote_name, previous_version) - base_branch = "{}/{}-rc-base".format(remote_name, version) - rc_branch = "{}/{}-rc".format(remote_name, version) - # Verify the branches' existance - if not repo.does_branch_exist(previous_rc_branch): - raise ValueError("Previous RC branch not found: {}".format(previous_rc_branch)) + if not repo.does_branch_exist(remote_previous_rc_branch): + raise ValueError("Previous RC branch not found: {}".format(remote_previous_rc_branch)) - if not repo.does_branch_exist(base_branch): - raise ValueError("Base branch not found: {}".format(base_branch)) + if not repo.does_branch_exist(remote_base_branch): + raise ValueError("Base branch not found: {}".format(remote_base_branch)) - if not repo.does_branch_exist(rc_branch): - raise ValueError("RC branch not found: {}".format(rc_branch)) + if not repo.does_branch_exist(remote_rc_branch): + raise ValueError("RC branch not found: {}".format(remote_rc_branch)) # Figure out SHA for each of the branches - previous_rc_commit = repo.git_rev_parse([previous_rc_branch]) - base_commit = repo.git_rev_parse([base_branch]) - rc_commit = repo.git_rev_parse([rc_branch]) + previous_rc_commit = repo.git_rev_parse([remote_previous_rc_branch]) + base_commit = repo.git_rev_parse([remote_base_branch]) + rc_commit = repo.git_rev_parse([remote_rc_branch]) # Check the base branch is an ancestor of the rc branch if not repo.is_ancestor(base_commit, rc_commit): - raise ValueError("{} is not an ancesctor of {}".format(base_branch, rc_branch)) + raise ValueError("{} is not an ancesctor of {}".format(remote_base_branch, remote_rc_branch)) # Check that the base branch is the merge base of the previous and current RCs merge_base = repo.get_merge_base(previous_rc_commit, rc_commit) if base_commit != merge_base: - raise ValueError("Base branch is not the merge base between {} and {}".format(previous_rc_branch, rc_branch)) + raise ValueError("Base branch is not the merge base between {} and {}".format(remote_previous_rc_branch, remote_rc_branch)) # For patch releases, warn if the base commit is not the previous RC commit if is_patch_release: @@ -86,13 +110,31 @@ def checkVersionBranches(version): # Check base branch is part of the previous RC previous_rc_base_commit = repo.get_merge_base(previous_rc_commit, remote_master_branch) if repo.is_ancestor(base_commit, previous_rc_base_commit): - raise ValueError("{} is older than {}".format(base_branch, rc_branch)) + raise ValueError("{} is older than {}".format(remote_base_branch, remote_rc_branch)) print("[SUCCESS] Checked {}".format(version)) def createVersionBranches(version): """Create the branches for a given version.""" + parser = VersionParser(version) + major = parser.major + minor = parser.minor + patch = parser.patch + previous_version = parser.previous_version + version = parser.version + + is_major_release = parser.is_major_release + is_minor_release = parser.is_minor_release + is_patch_release = parser.is_patch_release + + previous_rc_branch = parser.previous_rc_branch + base_branch = parser.base_branch + rc_branch = parser.rc_branch + remote_previous_rc_branch = parser.remote_previous_rc_branch + remote_base_branch = parser.remote_base_branch + remote_rc_branch = parser.remote_rc_branch + repo = git.Repository(git.Repository.get_base_directory()) # Validate the user is on a local branch that has the right merge base @@ -103,39 +145,6 @@ def createVersionBranches(version): if repo.is_working_tree_dirty(): raise ValueError("Your working tree has pending changes. You must have a clean working tree before proceeding.") - # Validate that user passed a valid version - VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)$") - match = VERSION_RE.match(version) - if not match: - raise ValueError("Invalid version (should be X.Y.Z)") - - # Parse the version component and build proper version strings - major = int(match.group(1)) - minor = int(match.group(2)) - patch = int(match.group(3)) - version = "v{}.{}.{}".format(major, minor, patch) # clean up version - - is_major_release = False - is_minor_release = False - is_patch_release = False - if patch != 0: - is_patch_release = True - previous_version = "v{}.{}.{}".format(major, minor, patch - 1) - elif minor != 0: - is_minor_release = True - previous_version = "v{}.{}.{}".format(major, minor - 1, 0) - else: - is_major_release = True - raise ValueError("Major releases not yet supported") - - # Build the branch names - previous_rc_branch = "{}-rc".format(previous_version) - base_branch = "{}-rc-base".format(version) - rc_branch = "{}-rc".format(version) - remote_previous_rc_branch = "{}/{}".format(remote_name, previous_rc_branch) - remote_base_branch = "{}/{}".format(remote_name, base_branch) - remote_rc_branch = "{}/{}".format(remote_name, rc_branch) - # Make sure the remote is up to date repo.git_fetch([remote_name]) @@ -200,6 +209,8 @@ def createVersionBranches(version): print("[SUCCESS] Created {} and {}".format(base_branch, rc_branch)) print("[SUCCESS] You can make the PR from the following webpage:") print("[SUCCESS] https://github.com/highfidelity/hifi/compare/{}...{}".format(base_branch, rc_branch)) + if is_patch_release: + print("[SUCCESS] NOTE: You will have to wait for the first fix to be merged into the RC branch to be able to create the PR") def usage(): """Print usage.""" From 75ac98bd456fe2e422846891222b06b036739858 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 21 Nov 2018 16:55:14 -0800 Subject: [PATCH 004/104] Fix tag warning to check the previous version --- tools/scripts/rc-branches.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py index 8db78a72bc..2825b81271 100755 --- a/tools/scripts/rc-branches.py +++ b/tools/scripts/rc-branches.py @@ -172,8 +172,8 @@ def createVersionBranches(version): if is_patch_release: # Check tag exists, if it doesn't, print warning and ask for comfirmation - if not repo.does_tag_exist(version): - logging.warning("The tag {} does not exist, which suggests {} has not yet been released.".format(version, version)) + if not repo.does_tag_exist(previous_version): + logging.warning("The tag {} does not exist, which suggests {} has not yet been released.".format(previous_version, previous_version)) logging.warning("Creating the branches now means that {} will diverge from {} if anything is merged into {}.".format(version, previous_version, previous_version)) logging.warning("This is not recommended unless necessary.") From d86fcbe5c6f8fb212881a24afc365670a2f44d44 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 21 Nov 2018 16:57:24 -0800 Subject: [PATCH 005/104] Use argparse instead of the deprecated optparse --- tools/scripts/rc-branches.py | 40 ++++++++++++++---------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py index 2825b81271..0ae397374e 100755 --- a/tools/scripts/rc-branches.py +++ b/tools/scripts/rc-branches.py @@ -5,7 +5,7 @@ import os import re import sys -from optparse import OptionParser +import argparse from utils import git @@ -212,38 +212,28 @@ def createVersionBranches(version): if is_patch_release: print("[SUCCESS] NOTE: You will have to wait for the first fix to be merged into the RC branch to be able to create the PR") -def usage(): - """Print usage.""" - print("rc-branches.py supports the following commands:") - print("\ncheck ") - print(" - version to check of the form \"X.Y.Z\"") - print("\ncreate ") - print(" - version to create branches for of the form \"X.Y.Z\"") - def main(): """Execute Main entry point.""" global remote_name - parser = OptionParser() - parser.add_option("-r", "--remote", type="string", dest="remote_name", default=remote_name) - (options, args) = parser.parse_args(args=sys.argv) + parser = argparse.ArgumentParser(description='RC branches tool', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog='''Example commands you can run:\n%(prog)s check 0.75.0\n%(prog)s create 0.75.1''') + parser.add_argument("command", help="command to execute", choices=["check", "create"]) + parser.add_argument("version", help="version of the form \"X.Y.Z\"") + parser.add_argument("--remote", dest="remote_name", default=remote_name, + help="git remote to use as reference") + args = parser.parse_args() - remote_name = options.remote_name - - if len(args) < 3: - usage() - return - - command = args[1] - version = args[2] + remote_name = args.remote_name try: - if command == "check": - checkVersionBranches(version) - elif command == "create": - createVersionBranches(version) + if args.command == "check": + checkVersionBranches(args.version) + elif args.command == "create": + createVersionBranches(args.version) else: - usage() + parser.print_help() except Exception as ex: logging.error(ex) sys.exit(1) From 4a8bbd79fe97aeef13f15120831d2d7bb3b252e8 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 30 Dec 2018 10:53:28 -0700 Subject: [PATCH 006/104] other avatars motion states --- interface/src/avatar/AvatarManager.cpp | 52 +- interface/src/avatar/AvatarManager.h | 1 + interface/src/avatar/DetailedMotionState.cpp | 207 +++++++ interface/src/avatar/DetailedMotionState.h | 95 ++++ interface/src/avatar/OtherAvatar.cpp | 38 ++ interface/src/avatar/OtherAvatar.h | 7 + .../src/avatars-renderer/Avatar.cpp | 32 +- .../src/avatars-renderer/Avatar.h | 4 + libraries/physics/src/MultiSphereShape.cpp | 527 ++++++++++++++++++ libraries/physics/src/MultiSphereShape.h | 102 ++++ libraries/physics/src/ObjectMotionState.cpp | 2 +- libraries/physics/src/ObjectMotionState.h | 3 +- 12 files changed, 1056 insertions(+), 14 deletions(-) create mode 100644 interface/src/avatar/DetailedMotionState.cpp create mode 100644 interface/src/avatar/DetailedMotionState.h create mode 100644 libraries/physics/src/MultiSphereShape.cpp create mode 100644 libraries/physics/src/MultiSphereShape.h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 7ca18ca258..e98e082b35 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -407,21 +407,51 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact if (isInPhysics) { transaction.objectsToRemove.push_back(avatar->_motionState); avatar->_motionState = nullptr; + auto& detailedMotionStates = avatar->getDetailedMotionStates(); + for (auto& mState : detailedMotionStates) { + if (mState) { + transaction.objectsToRemove.push_back(mState); + } + } + qCDebug(animation) << "Removing " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); + avatar->resetDetailedMotionStates(); + } else { - ShapeInfo shapeInfo; - avatar->computeShapeInfo(shapeInfo); - btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); - if (shape) { - AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); - motionState->setMass(avatar->computeMass()); - avatar->_motionState = motionState; - transaction.objectsToAdd.push_back(motionState); - } else { - failedShapeBuilds.insert(avatar); + { + ShapeInfo shapeInfo; + avatar->computeShapeInfo(shapeInfo); + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + if (shape) { + AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + motionState->setMass(avatar->computeMass()); + avatar->_motionState = motionState; + transaction.objectsToAdd.push_back(motionState); + } + else { + failedShapeBuilds.insert(avatar); + } + } + + { + for (int i = 0; i < avatar->getJointCount(); i++) { + avatar->addNewMotionState(avatar, i); + } + auto& detailedMotionStates = avatar->getDetailedMotionStates(); + for (auto& mState : detailedMotionStates) { + transaction.objectsToAdd.push_back(mState); + } + qCDebug(animation) << "Creating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } } } else if (isInPhysics) { transaction.objectsToChange.push_back(avatar->_motionState); + auto& detailedMotionStates = avatar->getDetailedMotionStates(); + for (auto& mState : detailedMotionStates) { + if (mState) { + transaction.objectsToChange.push_back(mState); + } + } + qCDebug(animation) << "Updating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } } _avatarsToChangeInPhysics.swap(failedShapeBuilds); @@ -519,7 +549,7 @@ void AvatarManager::deleteAllAvatars() { avatar->die(); if (avatar != _myAvatar) { auto otherAvatar = std::static_pointer_cast(avatar); - assert(!otherAvatar->_motionState); + assert(!otherAvatar->_motionState && !otherAvatar->_motionState2); } } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 75dbbc7abb..0fa7f19f2b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -29,6 +29,7 @@ #include // for SetOfEntities #include "AvatarMotionState.h" +#include "DetailedMotionState.h" #include "MyAvatar.h" #include "OtherAvatar.h" diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp new file mode 100644 index 0000000000..43cd77c558 --- /dev/null +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -0,0 +1,207 @@ +// +// DetailedMotionState.cpp +// interface/src/avatar/ +// +// Created by Andrew Meadows 2015.05.14 +// Copyright 2015 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 "DetailedMotionState.h" + +#include +#include +#include + + +DetailedMotionState::DetailedMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape, int jointIndex) : + ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) { + assert(_avatar); + _type = MOTIONSTATE_TYPE_DETAILED; + cacheShapeDiameter(); +} + +void DetailedMotionState::handleEasyChanges(uint32_t& flags) { + ObjectMotionState::handleEasyChanges(flags); + if (flags & Simulation::DIRTY_PHYSICS_ACTIVATION && !_body->isActive()) { + _body->activate(); + } +} + +bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { + return ObjectMotionState::handleHardAndEasyChanges(flags, engine); +} + +DetailedMotionState::~DetailedMotionState() { + assert(_avatar); + _avatar = nullptr; +} + +// virtual +uint32_t DetailedMotionState::getIncomingDirtyFlags() { + return _body ? _dirtyFlags : 0; +} + +void DetailedMotionState::clearIncomingDirtyFlags() { + if (_body) { + _dirtyFlags = 0; + } +} + +PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const { + // TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting) + return MOTION_TYPE_KINEMATIC; +} + +// virtual and protected +const btCollisionShape* DetailedMotionState::computeNewShape() { + ShapeInfo shapeInfo; + _avatar->computeShapeInfo(shapeInfo); + return getShapeManager()->getShape(shapeInfo); +} + +// virtual +bool DetailedMotionState::isMoving() const { + return false; +} + +// virtual +void DetailedMotionState::getWorldTransform(btTransform& worldTrans) const { + worldTrans.setOrigin(glmToBullet(getObjectPosition())); + worldTrans.setRotation(glmToBullet(getObjectRotation())); + if (_body) { + _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); + _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + } +} + +// virtual +void DetailedMotionState::setWorldTransform(const btTransform& worldTrans) { + const float SPRING_TIMESCALE = 0.5f; + float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE; + btVector3 currentPosition = worldTrans.getOrigin(); + btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition; + float distance = offsetToTarget.length(); + if ((1.0f - tau) * distance > _diameter) { + // the avatar body is far from its target --> slam position + btTransform newTransform; + newTransform.setOrigin(currentPosition + offsetToTarget); + newTransform.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(newTransform); + _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); + _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + } else { + // the avatar body is near its target --> slam velocity + btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; + _body->setLinearVelocity(velocity); + _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + // slam its rotation + btTransform newTransform = worldTrans; + newTransform.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(newTransform); + } +} + +// These pure virtual methods must be implemented for each MotionState type +// and make it possible to implement more complicated methods in this base class. + +// virtual +float DetailedMotionState::getObjectRestitution() const { + return 0.5f; +} + +// virtual +float DetailedMotionState::getObjectFriction() const { + return 0.5f; +} + +// virtual +float DetailedMotionState::getObjectLinearDamping() const { + return 0.5f; +} + +// virtual +float DetailedMotionState::getObjectAngularDamping() const { + return 0.5f; +} + +// virtual +glm::vec3 DetailedMotionState::getObjectPosition() const { + return _avatar->getJointPosition(_jointIndex); +} + +// virtual +glm::quat DetailedMotionState::getObjectRotation() const { + return _avatar->getWorldOrientation() * _avatar->getAbsoluteJointRotationInObjectFrame(_jointIndex); +} + +// virtual +glm::vec3 DetailedMotionState::getObjectLinearVelocity() const { + return _avatar->getWorldVelocity(); +} + +// virtual +glm::vec3 DetailedMotionState::getObjectAngularVelocity() const { + // HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant. + // Therefore, as optimization toward support for larger crowds we ignore it and return zero. + //return _avatar->getWorldAngularVelocity(); + return glm::vec3(0.0f); +} + +// virtual +glm::vec3 DetailedMotionState::getObjectGravity() const { + return _avatar->getAcceleration(); +} + +// virtual +const QUuid DetailedMotionState::getObjectID() const { + return _avatar->getSessionUUID(); +} + +QString DetailedMotionState::getName() const { + return _avatar->getName(); +} + +// virtual +QUuid DetailedMotionState::getSimulatorID() const { + return _avatar->getSessionUUID(); +} + +// virtual +void DetailedMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { + group = BULLET_COLLISION_GROUP_OTHER_AVATAR; + mask = Physics::getDefaultCollisionMask(group); +} + +// virtual +float DetailedMotionState::getMass() const { + return _avatar->computeMass(); +} + +void DetailedMotionState::cacheShapeDiameter() { + if (_shape) { + // shape is capsuleY + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + _diameter = (aabbMax - aabbMin).getX(); + } else { + _diameter = 0.0f; + } +} + +void DetailedMotionState::setRigidBody(btRigidBody* body) { + ObjectMotionState::setRigidBody(body); + if (_body) { + // remove angular dynamics from this body + _body->setAngularFactor(0.0f); + } +} + +void DetailedMotionState::setShape(const btCollisionShape* shape) { + ObjectMotionState::setShape(shape); + cacheShapeDiameter(); +} diff --git a/interface/src/avatar/DetailedMotionState.h b/interface/src/avatar/DetailedMotionState.h new file mode 100644 index 0000000000..1c5f224e4a --- /dev/null +++ b/interface/src/avatar/DetailedMotionState.h @@ -0,0 +1,95 @@ +// +// DetailedMotionState.h +// interface/src/avatar/ +// +// Created by Andrew Meadows 2015.05.14 +// Copyright 2015 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_DetailedMotionState_h +#define hifi_DetailedMotionState_h + +#include + +#include +#include + +#include "OtherAvatar.h" + +class DetailedMotionState : public ObjectMotionState { +public: + DetailedMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape, int jointIndex); + + virtual void handleEasyChanges(uint32_t& flags) override; + virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; + + virtual PhysicsMotionType getMotionType() const override { return _motionType; } + + virtual uint32_t getIncomingDirtyFlags() override; + virtual void clearIncomingDirtyFlags() override; + + virtual PhysicsMotionType computePhysicsMotionType() const override; + + virtual bool isMoving() const override; + + // this relays incoming position/rotation to the RigidBody + virtual void getWorldTransform(btTransform& worldTrans) const override; + + // this relays outgoing position/rotation to the EntityItem + virtual void setWorldTransform(const btTransform& worldTrans) override; + + + // These pure virtual methods must be implemented for each MotionState type + // and make it possible to implement more complicated methods in this base class. + + // pure virtual overrides from ObjectMotionState + virtual float getObjectRestitution() const override; + virtual float getObjectFriction() const override; + virtual float getObjectLinearDamping() const override; + virtual float getObjectAngularDamping() const override; + + virtual glm::vec3 getObjectPosition() const override; + virtual glm::quat getObjectRotation() const override; + virtual glm::vec3 getObjectLinearVelocity() const override; + virtual glm::vec3 getObjectAngularVelocity() const override; + virtual glm::vec3 getObjectGravity() const override; + + virtual const QUuid getObjectID() const override; + + virtual QString getName() const override; + virtual QUuid getSimulatorID() const override; + + void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); + + void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; } + + virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; + + virtual float getMass() const override; + + friend class AvatarManager; + friend class Avatar; + +protected: + void setRigidBody(btRigidBody* body) override; + void setShape(const btCollisionShape* shape) override; + void cacheShapeDiameter(); + + // the dtor had been made protected to force the compiler to verify that it is only + // ever called by the Avatar class dtor. + ~DetailedMotionState(); + + virtual bool isReadyToComputeShape() const override { return true; } + virtual const btCollisionShape* computeNewShape() override; + + OtherAvatarPointer _avatar; + float _diameter { 0.0f }; + + uint32_t _dirtyFlags; + int _jointIndex { -1 }; +}; + +#endif // hifi_DetailedMotionState_h diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index c2687fd525..30793f1696 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -10,6 +10,7 @@ #include "Application.h" #include "AvatarMotionState.h" +#include "DetailedMotionState.h" static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { @@ -107,10 +108,47 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { int32_t bytesRead = Avatar::parseDataFromBuffer(buffer); if (_moving && _motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); + for (auto mState : _detailedMotionStates) { + if (mState) { + mState->addDirtyFlags(Simulation::DIRTY_POSITION); + } + } } return bytesRead; } +void OtherAvatar::addNewMotionState(std::shared_ptr avatar, int jointIndex) { + std::lock_guard lock(_mStateLock); + if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { + auto& data = _multiSphereShapes[jointIndex].getSpheresData(); + std::vector positions; + std::vector radiuses; + for (auto& sphere : data) { + positions.push_back(glmToBullet(sphere._position)); + radiuses.push_back(sphere._radius); + } + btCollisionShape* shape = new btMultiSphereShape(positions.data(), radiuses.data(), (int)positions.size()); + if (shape) { + DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); + motionState->setMass(computeMass()); + assert(_detailedMotionStates.size() == jointIndex); + _detailedMotionStates.push_back(motionState); + } else { + _detailedMotionStates.push_back(nullptr); + } + } +} +const std::vector& OtherAvatar::getDetailedMotionStates() { + std::lock_guard lock(_mStateLock); + return _detailedMotionStates; +} +void OtherAvatar::resetDetailedMotionStates() { + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + _detailedMotionStates[i] = nullptr; + } + _detailedMotionStates.clear(); +} + void OtherAvatar::setWorkloadRegion(uint8_t region) { _workloadRegion = region; } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 5b72815757..a337d5d299 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -20,6 +20,7 @@ class AvatarManager; class AvatarMotionState; +class DetailedMotionState; class OtherAvatar : public Avatar { public: @@ -45,14 +46,20 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; + void addNewMotionState(std::shared_ptr avatar, int jointIndex); + const std::vector& getDetailedMotionStates(); + void resetDetailedMotionStates(); + friend AvatarManager; protected: std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; AvatarMotionState* _motionState { nullptr }; + std::vector _detailedMotionStates; int32_t _spaceIndex { -1 }; uint8_t _workloadRegion { workload::Region::INVALID }; + std::mutex _mStateLock; }; using OtherAvatarPointer = std::shared_ptr; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index fceb146470..011247b796 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -296,7 +296,9 @@ void Avatar::setTargetScale(float targetScale) { _targetScale = newValue; _scaleChanged = usecTimestampNow(); _isAnimatingScale = true; - + for (auto& sphere : _multiSphereShapes) { + sphere.setScale(_targetScale); + } emit targetScaleChanged(targetScale); } } @@ -1540,6 +1542,7 @@ void Avatar::setModelURLFinished(bool success) { // rig is ready void Avatar::rigReady() { buildUnscaledEyeHeightCache(); + computeMultiSphereShapes(); } // rig has been reset. @@ -1547,6 +1550,33 @@ void Avatar::rigReset() { clearUnscaledEyeHeightCache(); } +void Avatar::computeMultiSphereShapes() { + const Rig& rig = getSkeletonModel()->getRig(); + auto scale = extractScale(rig.getGeometryToRigTransform()); + const HFMModel& geometry = getSkeletonModel()->getHFMModel(); + int jointCount = rig.getJointStateCount(); + _multiSphereShapes.clear(); + _multiSphereShapes.reserve(jointCount); + for (int i = 0; i < jointCount; i++) { + const HFMJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; + std::vector btPoints; + int lineCount = (int)shapeInfo.debugLines.size(); + btPoints.reserve(lineCount); + for (size_t j = 0; j < lineCount; j++) { + const glm::vec3 &point = shapeInfo.debugLines[j]; + auto rigPoint = scale * point; + btVector3 btPoint = glmToBullet(rigPoint); + btPoints.push_back(btPoint); + } + auto jointName = rig.nameOfJoint(i).toUpper(); + MultiSphereShape multiSphereShape; + if (multiSphereShape.computeMultiSphereShape(jointName, btPoints, getSensorToWorldScale())) { + multiSphereShape.calculateDebugLines(); + } + _multiSphereShapes.push_back(multiSphereShape); + } +} + // create new model, can return an instance of a SoftAttachmentModel rather then Model static std::shared_ptr allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) { if (isSoft) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 8f70b12122..76a5a2a9d6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -29,6 +29,7 @@ #include #include "MetaModelPayload.h" +#include "MultiSphereShape.h" namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); @@ -507,6 +508,8 @@ protected: virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! + void computeMultiSphereShapes(); + const std::vector& getMultiSphereShapes() const { return _multiSphereShapes; } SkeletonModelPointer _skeletonModel; @@ -628,6 +631,7 @@ protected: static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); + std::vector _multiSphereShapes; }; #endif // hifi_Avatar_h diff --git a/libraries/physics/src/MultiSphereShape.cpp b/libraries/physics/src/MultiSphereShape.cpp new file mode 100644 index 0000000000..f0f56d7cf8 --- /dev/null +++ b/libraries/physics/src/MultiSphereShape.cpp @@ -0,0 +1,527 @@ +// +// MultiSphereShape.cpp +// libraries/physics/src +// +// Created by Luis Cuenca 5/11/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MultiSphereShape.h" + +void SphereRegion::translate(const glm::vec3& translation) { + for (auto &line : _lines) { + line.first += translation; + line.second += translation; + } +} +void SphereRegion::dump(std::vector>& outLines) { + for (auto &line : _lines) { + outLines.push_back(line); + } +} + +void SphereRegion::insertUnique(const glm::vec3& point, std::vector& pointSet) { + auto hit = std::find_if(pointSet.begin(), pointSet.end(), [point](const glm::vec3& pointFromSet) -> bool { + return (pointFromSet == point); + }); + if (hit == pointSet.end()) { + pointSet.push_back(point); + } +} + +void SphereRegion::extractEdges(bool reverseY) { + if (_lines.size() == 0) { + return; + } + float yVal = _lines[0].first.y; + for (size_t i = 0; i < _lines.size(); i++) { + yVal = reverseY ? glm::max(yVal, _lines[i].first.y) : glm::min(yVal, _lines[i].first.y); + } + for (size_t i = 0; i < _lines.size(); i++) { + auto line = _lines[i]; + auto p1 = line.first; + auto p2 = line.second; + auto vec = p1 - p2; + if (vec.z == 0.0f) { + insertUnique(p1, _edgesX); + insertUnique(p2, _edgesX); + } + else if (vec.y == 0.0f && p1.y == yVal && p2.y == yVal) { + insertUnique(p1, _edgesY); + insertUnique(p2, _edgesY); + } + else if (vec.x == 0.0f) { + insertUnique(p1, _edgesZ); + insertUnique(p2, _edgesZ); + } + } +} + +void SphereRegion::extractSphereRegion(std::vector>& outLines) { + for (size_t i = 0; i < outLines.size(); i++) { + auto &line = outLines[i]; + auto &p1 = line.first; + auto &p2 = line.second; + p1.x = glm::abs(p1.x) < 0.001f ? 0.0f : p1.x; + p1.y = glm::abs(p1.y) < 0.001f ? 0.0f : p1.y; + p1.z = glm::abs(p1.z) < 0.001f ? 0.0f : p1.z; + p2.x = glm::abs(p2.x) < 0.001f ? 0.0f : p2.x; + p2.y = glm::abs(p2.y) < 0.001f ? 0.0f : p2.y; + p2.z = glm::abs(p2.z) < 0.001f ? 0.0f : p2.z; + + glm::vec3 point1 = { p1.x != 0.0f ? glm::abs(p1.x) / p1.x : _direction.x, + p1.y != 0.0f ? glm::abs(p1.y) / p1.y : _direction.y, + p1.z != 0.0f ? glm::abs(p1.z) / p1.z : _direction.z }; + glm::vec3 point2 = { p2.x != 0.0f ? glm::abs(p2.x) / p2.x : _direction.x, + p2.y != 0.0f ? glm::abs(p2.y) / p2.y : _direction.y, + p2.z != 0.0f ? glm::abs(p2.z) / p2.z : _direction.z }; + if (point1 == _direction && point2 == _direction) { + _lines.push_back(line); + } + } +} + +CollisionShapeExtractionMode MultiSphereShape::getExtractionModeByName(const QString& name) { + CollisionShapeExtractionMode mode = CollisionShapeExtractionMode::Automatic; + bool isSim = name.indexOf("SIM") == 0; + bool isFlow = name.indexOf("FLOW") == 0; + bool isEye = name.indexOf("EYE") > -1; + bool isToe = name.indexOf("TOE") > -1; + bool isShoulder = name.indexOf("SHOULDER") > -1; + bool isNeck = name.indexOf("NECK") > -1; + bool isRightHand = name == "RIGHTHAND"; + bool isLeftHand = name == "LEFTHAND"; + bool isRightFinger = name.indexOf("RIGHTHAND") == 0 && !isRightHand; + bool isLeftFinger = name.indexOf("LEFTHAND") == 0 && !isLeftHand; + + //bool isFinger = + if (isNeck || isLeftFinger || isRightFinger) { + mode = CollisionShapeExtractionMode::SpheresY; + } else if (isShoulder) { + mode = CollisionShapeExtractionMode::SphereCollapse; + } else if (isRightHand || isLeftHand) { + mode = CollisionShapeExtractionMode::SpheresXY; + } + else if (isSim || isFlow || isEye || isToe) { + mode = CollisionShapeExtractionMode::None; + //qDebug() << "Trying to add " << (int)positions.size() << " spheres for " << jointName << " length: " << maxLength; + } + return mode; +} + +void MultiSphereShape::filterUniquePoints(const std::vector& kdop, std::vector& uniquePoints) { + for (size_t j = 0; j < kdop.size(); j++) { + btVector3 btPoint = kdop[j]; + auto hit = std::find_if(uniquePoints.begin(), uniquePoints.end(), [btPoint](const glm::vec3& point) -> bool { + return (btPoint.getX() == point.x + && btPoint.getY() == point.y + && btPoint.getZ() == point.z); + }); + if (hit == uniquePoints.end()) { + uniquePoints.push_back(bulletToGLM(btPoint)); + } + } +} + +bool MultiSphereShape::computeMultiSphereShape(const QString& name, const std::vector& kdop, float scale) { + _scale = scale; + _mode = getExtractionModeByName(name); + if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4 || kdop.size() > 200) { + return false; + } + std::vector points; + filterUniquePoints(kdop, points); + glm::vec3 min = glm::vec3(100.0f, 100.0f, 100.0f); + glm::vec3 max = glm::vec3(-100.0f, -100.0f, -100.0f); + _midPoint = glm::vec3(0.0f, 0.0f, 0.0f); + std::vector relPoints; + for (size_t i = 0; i < points.size(); i++) { + + min.x = points[i].x < min.x ? points[i].x : min.x; + min.y = points[i].y < min.y ? points[i].y : min.y; + min.z = points[i].z < min.z ? points[i].z : min.z; + + max.x = points[i].x > max.x ? points[i].x : max.x; + max.y = points[i].y > max.y ? points[i].y : max.y; + max.z = points[i].z > max.z ? points[i].z : max.z; + + _midPoint += points[i]; + } + + _midPoint /= (int)points.size(); + glm::vec3 dimensions = max - min; + + for (size_t i = 0; i < points.size(); i++) { + glm::vec3 relPoint = points[i] - _midPoint; + relPoints.push_back(relPoint); + } + CollisionShapeExtractionMode applyMode = _mode; + float xCorrector = dimensions.x > dimensions.y && dimensions.x > dimensions.z ? -1.0f + (dimensions.x / (0.5f * (dimensions.y + dimensions.z))) : 0.0f; + float yCorrector = dimensions.y > dimensions.x && dimensions.y > dimensions.z ? -1.0f + (dimensions.y / (0.5f * (dimensions.x + dimensions.z))) : 0.0f; + float zCorrector = dimensions.z > dimensions.x && dimensions.z > dimensions.y ? -1.0f + (dimensions.z / (0.5f * (dimensions.x + dimensions.y))) : 0.0f; + + float xyDif = glm::abs(dimensions.x - dimensions.y); + float xzDif = glm::abs(dimensions.x - dimensions.z); + float yzDif = glm::abs(dimensions.y - dimensions.z); + + float xyEpsilon = (0.05f + zCorrector) * glm::max(dimensions.x, dimensions.y); + float xzEpsilon = (0.05f + yCorrector) * glm::max(dimensions.x, dimensions.z); + float yzEpsilon = (0.05f + xCorrector) * glm::max(dimensions.y, dimensions.z); + + if (xyDif < 0.5f * xyEpsilon && xzDif < 0.5f * xzEpsilon && yzDif < 0.5f * yzEpsilon) { + applyMode = CollisionShapeExtractionMode::Sphere; + } + else if (xzDif < xzEpsilon) { + applyMode = dimensions.y > dimensions.z ? CollisionShapeExtractionMode::SpheresY : CollisionShapeExtractionMode::SpheresXZ; + } + else if (xyDif < xyEpsilon) { + applyMode = dimensions.z > dimensions.y ? CollisionShapeExtractionMode::SpheresZ : CollisionShapeExtractionMode::SpheresXY; + } + else if (yzDif < yzEpsilon) { + applyMode = dimensions.x > dimensions.y ? CollisionShapeExtractionMode::SpheresX : CollisionShapeExtractionMode::SpheresYZ; + } + else { + applyMode = CollisionShapeExtractionMode::SpheresXYZ; + } + + if (_mode != CollisionShapeExtractionMode::Automatic && applyMode != _mode) { + bool isModeSphereAxis = (_mode >= CollisionShapeExtractionMode::SpheresX && _mode <= CollisionShapeExtractionMode::SpheresZ); + bool isApplyModeComplex = (applyMode >= CollisionShapeExtractionMode::SpheresXY && applyMode <= CollisionShapeExtractionMode::SpheresXYZ); + applyMode = (isModeSphereAxis && isApplyModeComplex) ? CollisionShapeExtractionMode::Sphere : _mode; + } + + std::vector axes; + glm::vec3 axis, axis1, axis2; + SphereShapeData sphere; + switch (applyMode) { + case CollisionShapeExtractionMode::None: + break; + case CollisionShapeExtractionMode::Automatic: + break; + case CollisionShapeExtractionMode::Box: + break; + case CollisionShapeExtractionMode::Sphere: + sphere._radius = 0.5f * (dimensions.x + dimensions.y + dimensions.z) / 3.0f; + sphere._position = glm::vec3(0.0f); + _spheres.push_back(sphere); + break; + case CollisionShapeExtractionMode::SphereCollapse: + sphere._radius = 0.5f * glm::min(glm::min(dimensions.x, dimensions.y), dimensions.z); + sphere._position = glm::vec3(0.0f); + _spheres.push_back(sphere); + break; + case CollisionShapeExtractionMode::SpheresX: + axis = 0.5f* dimensions.x * Vectors::UNIT_NEG_X; + axes = { axis, -axis }; + break; + case CollisionShapeExtractionMode::SpheresY: + axis = 0.5f* dimensions.y * Vectors::UNIT_NEG_Y; + axes = { axis, -axis }; + break; + case CollisionShapeExtractionMode::SpheresZ: + axis = 0.5f* dimensions.z * Vectors::UNIT_NEG_Z; + axes = { axis, -axis }; + break; + case CollisionShapeExtractionMode::SpheresXY: + axis1 = glm::vec3(0.5f * dimensions.x, 0.5f * dimensions.y, 0.0f); + axis2 = glm::vec3(0.5f * dimensions.x, -0.5f * dimensions.y, 0.0f); + axes = { axis1, axis2, -axis1, -axis2 }; + break; + case CollisionShapeExtractionMode::SpheresYZ: + axis1 = glm::vec3(0.0f, 0.5f * dimensions.y, 0.5f * dimensions.z); + axis2 = glm::vec3(0.0f, 0.5f * dimensions.y, -0.5f * dimensions.z); + axes = { axis1, axis2, -axis1, -axis2 }; + break; + case CollisionShapeExtractionMode::SpheresXZ: + axis1 = glm::vec3(0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z); + axis2 = glm::vec3(-0.5f * dimensions.x, 0.0f, 0.5f * dimensions.z); + axes = { axis1, axis2, -axis1, -axis2 }; + break; + case CollisionShapeExtractionMode::SpheresXYZ: + for (size_t i = 0; i < CORNER_SIGNS.size(); i++) { + axes.push_back(0.5f * (dimensions * CORNER_SIGNS[i])); + } + break; + default: + break; + } + if (axes.size() > 0) { + spheresFromAxes(relPoints, axes, _spheres); + } + for (size_t i = 0; i < _spheres.size(); i++) { + _spheres[i]._position += _midPoint; + } + return _mode != CollisionShapeExtractionMode::None; +} + +void MultiSphereShape::spheresFromAxes(const std::vector& points, const std::vector& axes, std::vector& spheres) { + float maxRadius = 0.0f; + float maxAverageRadius = 0.0f; + float minAverageRadius = glm::length(points[0]); + size_t sphereCount = axes.size(); + spheres.clear(); + for (size_t j = 0; j < sphereCount; j++) { + SphereShapeData sphere = SphereShapeData(); + sphere._axis = axes[j]; + spheres.push_back(sphere); + } + for (size_t j = 0; j < sphereCount; j++) { + auto axis = _spheres[j]._axis; + float averageRadius = 0.0f; + float maxDistance = glm::length(axis); + glm::vec3 axisDir = glm::normalize(axis); + for (size_t i = 0; i < points.size(); i++) { + float dot = glm::dot(points[i], axisDir); + float distance = glm::length(points[i]); + if (dot > 0.0f) { + float distancePow = glm::pow(distance, 2); + float dotPow = glm::pow(dot, 2); + float radius = (dot / maxDistance) * glm::sqrt(distancePow - dotPow); + averageRadius += radius; + maxRadius = radius > maxRadius ? radius : maxRadius; + } + } + averageRadius /= (int)points.size(); + maxAverageRadius = averageRadius > maxAverageRadius ? averageRadius : maxAverageRadius; + minAverageRadius = averageRadius < minAverageRadius ? averageRadius : minAverageRadius; + spheres[j]._radius = averageRadius; + } + float radiusRatio = maxRadius / maxAverageRadius; + // Push the sphere into the bounding box + float contractionRatio = 0.8f; + for (size_t j = 0; j < sphereCount; j++) { + auto axis = _spheres[j]._axis; + float distance = glm::length(axis); + float correntionRatio = radiusRatio * (spheres[j]._radius / maxAverageRadius); + float radius = (correntionRatio < 0.8f * radiusRatio ? 0.8f * radiusRatio : correntionRatio) * spheres[j]._radius; + if (sphereCount > 3) { + distance = contractionRatio * distance; + } + spheres[j]._radius = radius; + if (distance - radius > 0.0f) { + spheres[j]._position = (distance - radius) * glm::normalize(axis); + } else { + spheres[j]._position = glm::vec3(0.0f); + } + } + // Collapse spheres if too close + if (sphereCount == 2) { + int maxRadiusIndex = spheres[0]._radius > spheres[1]._radius ? 0 : 1; + if (glm::length(spheres[0]._position - spheres[1]._position) < 0.2f * spheres[maxRadiusIndex]._radius) { + SphereShapeData newSphere; + newSphere._position = 0.5f * (spheres[0]._position + spheres[1]._position); + newSphere._radius = spheres[maxRadiusIndex]._radius; + spheres.clear(); + spheres.push_back(newSphere); + } + } +} + +void MultiSphereShape::connectSpheres(int index1, int index2, bool onlyEdges) { + auto sphere1 = _spheres[index1]._radius > _spheres[index2]._radius ? _spheres[index1] : _spheres[index2]; + auto sphere2 = _spheres[index1]._radius <= _spheres[index2]._radius ? _spheres[index1] : _spheres[index2]; + float distance = glm::length(sphere1._position - sphere2._position); + + auto axis = sphere1._position - sphere2._position; + + float angleOffset = glm::asin((sphere1._radius - sphere2._radius) / distance); + float percent1 = ((0.5f * PI) + angleOffset) / PI; + float percent2 = ((0.5f * PI) - angleOffset) / PI; + + std::vector edge1, edge2; + if (onlyEdges) { + std::vector> debugLines; + calculateSphereLines(debugLines, sphere1._position, sphere1._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(axis), percent1, &edge1); + calculateSphereLines(debugLines, sphere2._position, sphere2._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(-axis), percent2, &edge2); + } else { + calculateSphereLines(_debugLines, sphere1._position, sphere1._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(axis), percent1, &edge1); + calculateSphereLines(_debugLines, sphere2._position, sphere2._radius, DEFAULT_SPHERE_SUBDIVISIONS, glm::normalize(-axis), percent2, &edge2); + } + connectEdges(_debugLines, edge1, edge2); +} + +void MultiSphereShape::calculateDebugLines() { + if (_spheres.size() == 1) { + auto sphere = _spheres[0]; + calculateSphereLines(_debugLines, sphere._position, sphere._radius); + } else if (_spheres.size() == 2) { + connectSpheres(0, 1); + } else if (_spheres.size() == 4) { + std::vector axes; + axes.resize(8); + for (size_t i = 0; i < CORNER_SIGNS.size(); i++) { + for (size_t j = 0; j < 4; j++) { + auto axis = _spheres[j]._position - _midPoint; + glm::vec3 sign = { axis.x != 0.0f ? glm::abs(axis.x) / axis.x : 0.0f, + axis.x != 0.0f ? glm::abs(axis.y) / axis.y : 0.0f , + axis.z != 0.0f ? glm::abs(axis.z) / axis.z : 0.0f }; + bool add = false; + if (sign.x == 0) { + if (sign.y == CORNER_SIGNS[i].y && sign.z == CORNER_SIGNS[i].z) { + add = true; + } + } else if (sign.y == 0) { + if (sign.x == CORNER_SIGNS[i].x && sign.z == CORNER_SIGNS[i].z) { + add = true; + } + } else if (sign.z == 0) { + if (sign.x == CORNER_SIGNS[i].x && sign.y == CORNER_SIGNS[i].y) { + add = true; + } + } else if (sign == CORNER_SIGNS[i]) { + add = true; + } + if (add) { + axes[i] = axis; + break; + } + } + } + calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint); + } else if (_spheres.size() == 8) { + std::vector axes; + for (size_t i = 0; i < _spheres.size(); i++) { + axes.push_back(_spheres[i]._position - _midPoint); + } + calculateChamferBox(_debugLines, _spheres[0]._radius, axes, _midPoint); + } +} + +void MultiSphereShape::connectEdges(std::vector>& outLines, const std::vector& edge1, const std::vector& edge2, bool reverse) { + if (edge1.size() == edge2.size()) { + for (size_t i = 0; i < edge1.size(); i++) { + size_t j = reverse ? edge1.size() - i - 1 : i; + outLines.push_back({ edge1[i], edge2[j] }); + } + } +} + +void MultiSphereShape::calculateChamferBox(std::vector>& outLines, const float& radius, const std::vector& axes, const glm::vec3& translation) { + std::vector> sphereLines; + calculateSphereLines(sphereLines, glm::vec3(0.0f), radius); + + std::vector regions = { + SphereRegion({ 1.0f, 1.0f, 1.0f }), + SphereRegion({ -1.0f, 1.0f, 1.0f }), + SphereRegion({ -1.0f, 1.0f, -1.0f }), + SphereRegion({ 1.0f, 1.0f, -1.0f }), + SphereRegion({ 1.0f, -1.0f, 1.0f }), + SphereRegion({ -1.0f, -1.0f, 1.0f }), + SphereRegion({ -1.0f, -1.0f, -1.0f }), + SphereRegion({ 1.0f, -1.0f, -1.0f }) + }; + + assert(axes.size() == regions.size()); + + for (size_t i = 0; i < regions.size(); i++) { + regions[i].extractSphereRegion(sphereLines); + regions[i].translate(translation + axes[i]); + regions[i].extractEdges(axes[i].y < 0); + regions[i].dump(outLines); + } + + connectEdges(outLines, regions[0].getEdgesZ(), regions[1].getEdgesZ()); + connectEdges(outLines, regions[1].getEdgesX(), regions[2].getEdgesX()); + connectEdges(outLines, regions[2].getEdgesZ(), regions[3].getEdgesZ()); + connectEdges(outLines, regions[3].getEdgesX(), regions[0].getEdgesX()); + + connectEdges(outLines, regions[4].getEdgesZ(), regions[5].getEdgesZ()); + connectEdges(outLines, regions[5].getEdgesX(), regions[6].getEdgesX()); + connectEdges(outLines, regions[6].getEdgesZ(), regions[7].getEdgesZ()); + connectEdges(outLines, regions[7].getEdgesX(), regions[4].getEdgesX()); + + connectEdges(outLines, regions[0].getEdgesY(), regions[4].getEdgesY()); + connectEdges(outLines, regions[1].getEdgesY(), regions[5].getEdgesY()); + connectEdges(outLines, regions[2].getEdgesY(), regions[6].getEdgesY()); + connectEdges(outLines, regions[3].getEdgesY(), regions[7].getEdgesY()); + +} + +void MultiSphereShape::calculateSphereLines(std::vector>& outLines, const glm::vec3& center, const float& radius, + const int& subdivisions, const glm::vec3& direction, const float& percentage, std::vector* edge) { + + float uTotalAngle = percentage * PI; + float vTotalAngle = 2.0f * PI; + + int uSubdivisions = (int)glm::ceil(subdivisions * 0.5f * percentage); + int vSubdivisions = subdivisions; + + float uDeltaAngle = uTotalAngle / uSubdivisions; + float vDeltaAngle = vTotalAngle / vSubdivisions; + + float uAngle = 0.0f; + + glm::vec3 uAxis, vAxis; + glm::vec3 mainAxis = glm::normalize(direction); + if (mainAxis.y == 1.0f || mainAxis.y == -1.0f) { + uAxis = glm::vec3(1.0f, 0.0f, 0.0f); + vAxis = glm::vec3(0.0f, 0.0f, 1.0f); + } else { + uAxis = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), mainAxis)); + vAxis = glm::normalize(glm::cross(mainAxis, uAxis)); + if ((uAxis.z == 0 && uAxis.x < 0) || (uAxis.x == 0 && uAxis.z < 0)) { + uAxis = -uAxis; + } else if (uAxis.x < 0) { + uAxis = -uAxis; + } + if ((vAxis.z == 0 && vAxis.x < 0) || (vAxis.x == 0 && vAxis.z < 0)) { + vAxis = -vAxis; + } + else if (vAxis.x < 0) { + vAxis = -vAxis; + } + } + + std::vector> arcs; + auto origin = center; + for (int u = 0; u < uSubdivisions + 1; u++) { + std::vector arc; + glm::vec3 arcCenter = origin + mainAxis * (glm::cos(uAngle) * radius); + float vAngle = 0.0f; + for (int v = 0; v < vSubdivisions + 1; v++) { + float arcRadius = glm::abs(glm::sin(uAngle) * radius); + glm::vec3 arcPoint = arcCenter + (arcRadius * glm::cos(vAngle)) * uAxis + (arcRadius * glm::sin(vAngle)) * vAxis; + arc.push_back(arcPoint); + if (u == uSubdivisions && edge != nullptr) { + edge->push_back(arcPoint); + } + vAngle += vDeltaAngle; + } + arc.push_back(arc[0]); + arcs.push_back(arc); + uAngle += uDeltaAngle; + } + + for (size_t i = 1; i < arcs.size(); i++) { + auto arc1 = arcs[i]; + auto arc2 = arcs[i - 1]; + for (size_t j = 1; j < arc1.size(); j++) { + auto point1 = arc1[j]; + auto point2 = arc1[j - 1]; + auto point3 = arc2[j]; + std::pair line1 = { point1, point2 }; + std::pair line2 = { point1, point3 }; + outLines.push_back(line1); + outLines.push_back(line2); + } + } +} + +void MultiSphereShape::setScale(float scale) { + if (scale != _scale) { + float deltaScale = scale / _scale; + for (auto& sphere : _spheres) { + sphere._axis *= deltaScale; + sphere._position *= deltaScale; + sphere._radius *= deltaScale; + } + for (auto& line : _debugLines) { + line.first *= deltaScale; + line.second *= deltaScale; + } + _scale = scale; + } +} \ No newline at end of file diff --git a/libraries/physics/src/MultiSphereShape.h b/libraries/physics/src/MultiSphereShape.h new file mode 100644 index 0000000000..f8f251de42 --- /dev/null +++ b/libraries/physics/src/MultiSphereShape.h @@ -0,0 +1,102 @@ +// +// MultiSphereShape.h +// libraries/physics/src +// +// Created by Luis Cuenca 5/11/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MultiSphereShape_h +#define hifi_MultiSphereShape_h + +#include +#include +#include +#include "BulletUtil.h" + + +enum CollisionShapeExtractionMode { + None = 0, + Automatic, + Box, + Sphere, + SphereCollapse, + SpheresX, + SpheresY, + SpheresZ, + SpheresXY, + SpheresYZ, + SpheresXZ, + SpheresXYZ +}; + +struct SphereShapeData { + SphereShapeData() {} + glm::vec3 _position; + glm::vec3 _axis; + float _radius; +}; + +class SphereRegion { +public: + SphereRegion() {} + SphereRegion(const glm::vec3& direction) : _direction(direction) {} + void extractSphereRegion(std::vector>& outLines); + void extractEdges(bool reverseY = false); + void translate(const glm::vec3& translation); + void dump(std::vector>& outLines); + const glm::vec3& getDirection() const { return _direction; } + const std::vector& getEdgesX() const { return _edgesX; } + const std::vector& getEdgesY() const { return _edgesY; } + const std::vector& getEdgesZ() const { return _edgesZ; } +private: + void insertUnique(const glm::vec3& point, std::vector& pointSet); + + std::vector> _lines; + std::vector _edgesX; + std::vector _edgesY; + std::vector _edgesZ; + glm::vec3 _direction; +}; + +const int DEFAULT_SPHERE_SUBDIVISIONS = 16; + +const std::vector CORNER_SIGNS = { + glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(-1.0f, 1.0f, 1.0f), + glm::vec3(-1.0f, 1.0f, -1.0f), glm::vec3(1.0f, 1.0f, -1.0f), + glm::vec3(1.0f, -1.0f, 1.0f), glm::vec3(-1.0f, -1.0f, 1.0f), + glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec3(1.0f, -1.0f, -1.0f) }; + +class MultiSphereShape { +public: + MultiSphereShape() {}; + bool computeMultiSphereShape(const QString& name, const std::vector& points, float scale = 1.0f); + void calculateDebugLines(); + const std::vector& getSpheresData() const { return _spheres; } + const std::vector>& getDebugLines() const { return _debugLines; } + void setScale(float scale); + +private: + CollisionShapeExtractionMode getExtractionModeByName(const QString& name); + void filterUniquePoints(const std::vector& kdop, std::vector& uniquePoints); + void spheresFromAxes(const std::vector& points, const std::vector& axes, + std::vector& spheres); + + void calculateSphereLines(std::vector>& outLines, const glm::vec3& center, const float& radius, + const int& subdivisions = DEFAULT_SPHERE_SUBDIVISIONS, const glm::vec3& direction = Vectors::UNIT_Y, + const float& percentage = 1.0f, std::vector* edge = nullptr); + void calculateChamferBox(std::vector>& outLines, const float& radius, const std::vector& axes, const glm::vec3& translation); + void connectEdges(std::vector>& outLines, const std::vector& edge1, + const std::vector& edge2, bool reverse = false); + void connectSpheres(int index1, int index2, bool onlyEdges = false); + std::vector _spheres; + std::vector> _debugLines; + CollisionShapeExtractionMode _mode; + glm::vec3 _midPoint; + float _scale { 1.0f }; +}; + +#endif // hifi_MultiSphereShape_h \ No newline at end of file diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index acfb0c9236..4bc0389620 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -292,7 +292,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* if (!isReadyToComputeShape()) { return false; } - const btCollisionShape* newShape = computeNewShape(); + const btCollisionShape* newShape = _type != MOTIONSTATE_TYPE_DETAILED ? computeNewShape() : nullptr; if (!newShape) { qCDebug(physics) << "Warning: failed to generate new shape!"; // failed to generate new shape! --> keep old shape and remove shape-change flag diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 74173c3f47..a1dc0f8368 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -58,7 +58,8 @@ inline QString motionTypeToString(PhysicsMotionType motionType) { enum MotionStateType { MOTIONSTATE_TYPE_INVALID, MOTIONSTATE_TYPE_ENTITY, - MOTIONSTATE_TYPE_AVATAR + MOTIONSTATE_TYPE_AVATAR, + MOTIONSTATE_TYPE_DETAILED }; // The update flags trigger two varieties of updates: "hard" which require the body to be pulled From 21a4da4d5f5c3aeeee5f9df5198271e42dd877d8 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 3 Jan 2019 13:36:26 -0700 Subject: [PATCH 007/104] fix collisions for newly added avatars --- interface/src/avatar/AvatarManager.cpp | 13 ++++++------- interface/src/avatar/OtherAvatar.cpp | 6 +----- interface/src/avatar/OtherAvatar.h | 5 ++--- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e98e082b35..b80d53e6c3 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -417,7 +417,7 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact avatar->resetDetailedMotionStates(); } else { - { + if (avatar->_motionState == nullptr) { ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); @@ -427,21 +427,20 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact avatar->_motionState = motionState; transaction.objectsToAdd.push_back(motionState); } - else { - failedShapeBuilds.insert(avatar); - } } - - { + auto& detailedMotionStates = avatar->getDetailedMotionStates(); + if (detailedMotionStates.size() == 0) { for (int i = 0; i < avatar->getJointCount(); i++) { avatar->addNewMotionState(avatar, i); } - auto& detailedMotionStates = avatar->getDetailedMotionStates(); for (auto& mState : detailedMotionStates) { transaction.objectsToAdd.push_back(mState); } qCDebug(animation) << "Creating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } + if (avatar->_motionState == nullptr || detailedMotionStates.size() == 0) { + failedShapeBuilds.insert(avatar); + } } } else if (isInPhysics) { transaction.objectsToChange.push_back(avatar->_motionState); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 30793f1696..7495e5e2df 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -118,7 +118,6 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { } void OtherAvatar::addNewMotionState(std::shared_ptr avatar, int jointIndex) { - std::lock_guard lock(_mStateLock); if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); std::vector positions; @@ -138,10 +137,7 @@ void OtherAvatar::addNewMotionState(std::shared_ptr avatar, int joi } } } -const std::vector& OtherAvatar::getDetailedMotionStates() { - std::lock_guard lock(_mStateLock); - return _detailedMotionStates; -} + void OtherAvatar::resetDetailedMotionStates() { for (size_t i = 0; i < _detailedMotionStates.size(); i++) { _detailedMotionStates[i] = nullptr; diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index a337d5d299..18b93b8aad 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -39,7 +39,7 @@ public: int parseDataFromBuffer(const QByteArray& buffer) override; - bool isInPhysicsSimulation() const { return _motionState != nullptr; } + bool isInPhysicsSimulation() const { return _motionState != nullptr && _detailedMotionStates.size() > 0; } void rebuildCollisionShape() override; void setWorkloadRegion(uint8_t region); @@ -47,7 +47,7 @@ public: bool needsPhysicsUpdate() const; void addNewMotionState(std::shared_ptr avatar, int jointIndex); - const std::vector& getDetailedMotionStates(); + const std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void resetDetailedMotionStates(); friend AvatarManager; @@ -59,7 +59,6 @@ protected: std::vector _detailedMotionStates; int32_t _spaceIndex { -1 }; uint8_t _workloadRegion { workload::Region::INVALID }; - std::mutex _mStateLock; }; using OtherAvatarPointer = std::shared_ptr; From 5148b60c7355b091ed646e22841aeff97e6706e2 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 3 Jan 2019 14:43:15 -0800 Subject: [PATCH 008/104] Parameterize distance attenuation by reference distance (where gain = unity) --- assignment-client/src/audio/AudioMixerSlave.cpp | 11 +++++++---- libraries/audio-client/src/AudioClient.cpp | 5 +++-- libraries/audio/src/AudioHRTF.h | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 7a6ab9c3e2..bddadf6e26 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -727,9 +727,12 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi // distance attenuation: approximate, ignore zone-specific attenuations glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition(); float distance = glm::length(relativePosition); - return gain / distance; + + float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); + gain = gain / d; // avatar: skip master gain - it is constant for all streams + return gain; } float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream, @@ -774,9 +777,9 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, EPSILON, 1.0f); // calculate the attenuation using the distance to this node - // reference attenuation of 0dB at distance = 1.0m - gain *= fastExp2f(fastLog2f(g) * fastLog2f(std::max(distance, HRTF_NEARFIELD_MIN))); - gain = std::min(gain, 1.0f / HRTF_NEARFIELD_MIN); + // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF + float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); + gain *= fastExp2f(fastLog2f(g) * fastLog2f(d)); return gain; } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9bad7e2f45..82674ddc72 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1952,8 +1952,9 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { float AudioClient::gainForSource(float distance, float volume) { // attenuation = -6dB * log2(distance) - // reference attenuation of 0dB at distance = 1.0m - float gain = volume / std::max(distance, HRTF_NEARFIELD_MIN); + // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF + float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); + float gain = volume / d; return gain; } diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index eeef66e10c..952c4e4af6 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -30,6 +30,9 @@ static const float HRTF_NEARFIELD_MAX = 1.0f; // distance in meters static const float HRTF_NEARFIELD_MIN = 0.125f; // distance in meters static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters +// Distance attenuation +static const float ATTN_DISTANCE_REF = 1.0f; // distance where attn is 0dB + class AudioHRTF { public: From 53dece5451d5380f8e43e2f0929910c33bf6766b Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 3 Jan 2019 14:50:44 -0800 Subject: [PATCH 009/104] Clamp near-field distance attenuation to max gain --- assignment-client/src/audio/AudioMixerSlave.cpp | 2 ++ libraries/audio-client/src/AudioClient.cpp | 1 + libraries/audio/src/AudioHRTF.h | 1 + 3 files changed, 4 insertions(+) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index bddadf6e26..573dab030e 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -730,6 +730,7 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); gain = gain / d; + gain = std::min(gain, ATTN_GAIN_MAX); // avatar: skip master gain - it is constant for all streams return gain; @@ -780,6 +781,7 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); gain *= fastExp2f(fastLog2f(g) * fastLog2f(d)); + gain = std::min(gain, ATTN_GAIN_MAX); return gain; } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 82674ddc72..990834fcb8 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1955,6 +1955,7 @@ float AudioClient::gainForSource(float distance, float volume) { // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); float gain = volume / d; + gain = std::min(gain, ATTN_GAIN_MAX); return gain; } diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index 952c4e4af6..de104db226 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -32,6 +32,7 @@ static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters // Distance attenuation static const float ATTN_DISTANCE_REF = 1.0f; // distance where attn is 0dB +static const float ATTN_GAIN_MAX = 8.0f; // max gain allowed by distance attn (+18dB) class AudioHRTF { From 83e1efd2f0219fbec408f1fb9cebc1a32a43df43 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 3 Jan 2019 15:12:42 -0800 Subject: [PATCH 010/104] Improved reference distance = 2m --- libraries/audio/src/AudioHRTF.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.h b/libraries/audio/src/AudioHRTF.h index de104db226..7d23f4825a 100644 --- a/libraries/audio/src/AudioHRTF.h +++ b/libraries/audio/src/AudioHRTF.h @@ -31,8 +31,8 @@ static const float HRTF_NEARFIELD_MIN = 0.125f; // distance in meters static const float HRTF_HEAD_RADIUS = 0.0875f; // average human head in meters // Distance attenuation -static const float ATTN_DISTANCE_REF = 1.0f; // distance where attn is 0dB -static const float ATTN_GAIN_MAX = 8.0f; // max gain allowed by distance attn (+18dB) +static const float ATTN_DISTANCE_REF = 2.0f; // distance where attn is 0dB +static const float ATTN_GAIN_MAX = 16.0f; // max gain allowed by distance attn (+24dB) class AudioHRTF { From 4c502cdbc9f3a6780272e45af337e2a1cb0042e1 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 3 Jan 2019 15:17:41 -0800 Subject: [PATCH 011/104] Revert to simpler approximation for stream sorting --- assignment-client/src/audio/AudioMixerSlave.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 573dab030e..b699970560 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -727,13 +727,9 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi // distance attenuation: approximate, ignore zone-specific attenuations glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition(); float distance = glm::length(relativePosition); - - float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); - gain = gain / d; - gain = std::min(gain, ATTN_GAIN_MAX); + return gain / distance; // avatar: skip master gain - it is constant for all streams - return gain; } float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNodeStream, From f19201fc92d15d199506f7de1cf0ad2ec739a21e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 8 Jan 2019 14:09:13 -0700 Subject: [PATCH 012/104] Other avatar detailed collisions on motion state --- interface/src/avatar/AvatarManager.cpp | 13 ++-- interface/src/avatar/AvatarMotionState.cpp | 1 + interface/src/avatar/DetailedMotionState.cpp | 65 ++++--------------- interface/src/avatar/DetailedMotionState.h | 3 - interface/src/avatar/OtherAvatar.cpp | 27 +++++--- interface/src/avatar/OtherAvatar.h | 5 +- libraries/physics/src/ObjectMotionState.cpp | 18 +++-- libraries/shared/src/PhysicsCollisionGroups.h | 5 +- libraries/shared/src/PhysicsHelpers.cpp | 4 ++ 9 files changed, 62 insertions(+), 79 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b80d53e6c3..44cb1300aa 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -431,12 +431,13 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact auto& detailedMotionStates = avatar->getDetailedMotionStates(); if (detailedMotionStates.size() == 0) { for (int i = 0; i < avatar->getJointCount(); i++) { - avatar->addNewMotionState(avatar, i); + auto dMotionState = avatar->createDetailedMotionStateForJoint(avatar, i); + if (dMotionState) { + detailedMotionStates.push_back(dMotionState); + transaction.objectsToAdd.push_back(dMotionState); + } } - for (auto& mState : detailedMotionStates) { - transaction.objectsToAdd.push_back(mState); - } - qCDebug(animation) << "Creating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); + //qCDebug(animation) << "Creating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } if (avatar->_motionState == nullptr || detailedMotionStates.size() == 0) { failedShapeBuilds.insert(avatar); @@ -450,7 +451,7 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToChange.push_back(mState); } } - qCDebug(animation) << "Updating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); + //qCDebug(animation) << "Updating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } } _avatarsToChangeInPhysics.swap(failedShapeBuilds); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index ca67f634c8..27bf22b9d7 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -58,6 +58,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; _avatar->computeShapeInfo(shapeInfo); + qDebug() << "Creating new Capsule Shape"; return getShapeManager()->getShape(shapeInfo); } diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index 43cd77c558..a54787b1c4 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -20,7 +20,6 @@ DetailedMotionState::DetailedMotionState(OtherAvatarPointer avatar, const btColl ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) { assert(_avatar); _type = MOTIONSTATE_TYPE_DETAILED; - cacheShapeDiameter(); } void DetailedMotionState::handleEasyChanges(uint32_t& flags) { @@ -37,6 +36,9 @@ bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngin DetailedMotionState::~DetailedMotionState() { assert(_avatar); _avatar = nullptr; + if (_shape) { + delete _shape; + } } // virtual @@ -57,9 +59,8 @@ PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const { // virtual and protected const btCollisionShape* DetailedMotionState::computeNewShape() { - ShapeInfo shapeInfo; - _avatar->computeShapeInfo(shapeInfo); - return getShapeManager()->getShape(shapeInfo); + auto shape = _avatar->createDetailedCollisionShapeForJoint(_jointIndex); + return shape; } // virtual @@ -71,37 +72,11 @@ bool DetailedMotionState::isMoving() const { void DetailedMotionState::getWorldTransform(btTransform& worldTrans) const { worldTrans.setOrigin(glmToBullet(getObjectPosition())); worldTrans.setRotation(glmToBullet(getObjectRotation())); - if (_body) { - _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); - } } // virtual void DetailedMotionState::setWorldTransform(const btTransform& worldTrans) { - const float SPRING_TIMESCALE = 0.5f; - float tau = PHYSICS_ENGINE_FIXED_SUBSTEP / SPRING_TIMESCALE; - btVector3 currentPosition = worldTrans.getOrigin(); - btVector3 offsetToTarget = glmToBullet(getObjectPosition()) - currentPosition; - float distance = offsetToTarget.length(); - if ((1.0f - tau) * distance > _diameter) { - // the avatar body is far from its target --> slam position - btTransform newTransform; - newTransform.setOrigin(currentPosition + offsetToTarget); - newTransform.setRotation(glmToBullet(getObjectRotation())); - _body->setWorldTransform(newTransform); - _body->setLinearVelocity(glmToBullet(getObjectLinearVelocity())); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); - } else { - // the avatar body is near its target --> slam velocity - btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; - _body->setLinearVelocity(velocity); - _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); - // slam its rotation - btTransform newTransform = worldTrans; - newTransform.setRotation(glmToBullet(getObjectRotation())); - _body->setWorldTransform(newTransform); - } + _body->setWorldTransform(worldTrans); } // These pure virtual methods must be implemented for each MotionState type @@ -139,20 +114,17 @@ glm::quat DetailedMotionState::getObjectRotation() const { // virtual glm::vec3 DetailedMotionState::getObjectLinearVelocity() const { - return _avatar->getWorldVelocity(); + return glm::vec3(0.0f); } // virtual glm::vec3 DetailedMotionState::getObjectAngularVelocity() const { - // HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant. - // Therefore, as optimization toward support for larger crowds we ignore it and return zero. - //return _avatar->getWorldAngularVelocity(); return glm::vec3(0.0f); } // virtual glm::vec3 DetailedMotionState::getObjectGravity() const { - return _avatar->getAcceleration(); + return glm::vec3(0.0f); } // virtual @@ -161,7 +133,7 @@ const QUuid DetailedMotionState::getObjectID() const { } QString DetailedMotionState::getName() const { - return _avatar->getName(); + return _avatar->getName() + "_" + _jointIndex; } // virtual @@ -171,26 +143,13 @@ QUuid DetailedMotionState::getSimulatorID() const { // virtual void DetailedMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { - group = BULLET_COLLISION_GROUP_OTHER_AVATAR; + group = BULLET_COLLISION_GROUP_DETAILED_AVATAR; mask = Physics::getDefaultCollisionMask(group); } // virtual float DetailedMotionState::getMass() const { - return _avatar->computeMass(); -} - -void DetailedMotionState::cacheShapeDiameter() { - if (_shape) { - // shape is capsuleY - btVector3 aabbMin, aabbMax; - btTransform transform; - transform.setIdentity(); - _shape->getAabb(transform, aabbMin, aabbMax); - _diameter = (aabbMax - aabbMin).getX(); - } else { - _diameter = 0.0f; - } + return 0.0f; } void DetailedMotionState::setRigidBody(btRigidBody* body) { @@ -203,5 +162,5 @@ void DetailedMotionState::setRigidBody(btRigidBody* body) { void DetailedMotionState::setShape(const btCollisionShape* shape) { ObjectMotionState::setShape(shape); - cacheShapeDiameter(); } + diff --git a/interface/src/avatar/DetailedMotionState.h b/interface/src/avatar/DetailedMotionState.h index 1c5f224e4a..4ae780de8c 100644 --- a/interface/src/avatar/DetailedMotionState.h +++ b/interface/src/avatar/DetailedMotionState.h @@ -62,8 +62,6 @@ public: virtual QString getName() const override; virtual QUuid getSimulatorID() const override; - void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); - void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; } virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; @@ -76,7 +74,6 @@ public: protected: void setRigidBody(btRigidBody* body) override; void setShape(const btCollisionShape* shape) override; - void cacheShapeDiameter(); // the dtor had been made protected to force the compiler to verify that it is only // ever called by the Avatar class dtor. diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 7495e5e2df..00b89d0179 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -117,7 +117,7 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { return bytesRead; } -void OtherAvatar::addNewMotionState(std::shared_ptr avatar, int jointIndex) { +btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointIndex) { if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); std::vector positions; @@ -127,15 +127,19 @@ void OtherAvatar::addNewMotionState(std::shared_ptr avatar, int joi radiuses.push_back(sphere._radius); } btCollisionShape* shape = new btMultiSphereShape(positions.data(), radiuses.data(), (int)positions.size()); - if (shape) { - DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); - motionState->setMass(computeMass()); - assert(_detailedMotionStates.size() == jointIndex); - _detailedMotionStates.push_back(motionState); - } else { - _detailedMotionStates.push_back(nullptr); - } + return shape; } + return nullptr; +} + +DetailedMotionState* OtherAvatar::createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex) { + auto shape = createDetailedCollisionShapeForJoint(jointIndex); + if (shape) { + DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); + motionState->setMass(computeMass()); + return motionState; + } + return nullptr; } void OtherAvatar::resetDetailedMotionStates() { @@ -162,4 +166,9 @@ void OtherAvatar::rebuildCollisionShape() { if (_motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); } + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + if (_detailedMotionStates[i]) { + _detailedMotionStates[i]->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); + } + } } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 18b93b8aad..1e595eabdc 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -46,8 +46,9 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; - void addNewMotionState(std::shared_ptr avatar, int jointIndex); - const std::vector& getDetailedMotionStates() { return _detailedMotionStates; } + btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointIndex); + DetailedMotionState* createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex); + std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void resetDetailedMotionStates(); friend AvatarManager; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 4bc0389620..db36c5815d 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -195,10 +195,14 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { void ObjectMotionState::setShape(const btCollisionShape* shape) { if (_shape != shape) { if (_shape) { - getShapeManager()->releaseShape(_shape); + if (_type == MOTIONSTATE_TYPE_DETAILED) { + delete _shape; + } else { + getShapeManager()->releaseShape(_shape); + } } _shape = shape; - if (_body) { + if (_body && _type != MOTIONSTATE_TYPE_DETAILED) { updateCCDConfiguration(); } } @@ -292,7 +296,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* if (!isReadyToComputeShape()) { return false; } - const btCollisionShape* newShape = _type != MOTIONSTATE_TYPE_DETAILED ? computeNewShape() : nullptr; + const btCollisionShape* newShape = computeNewShape(); if (!newShape) { qCDebug(physics) << "Warning: failed to generate new shape!"; // failed to generate new shape! --> keep old shape and remove shape-change flag @@ -309,8 +313,12 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* if (_shape == newShape) { // the shape didn't actually change, so we clear the DIRTY_SHAPE flag flags &= ~Simulation::DIRTY_SHAPE; - // and clear the reference we just created - getShapeManager()->releaseShape(_shape); + if (_type == MOTIONSTATE_TYPE_DETAILED) { + delete _shape; + } else { + // and clear the reference we just created + getShapeManager()->releaseShape(_shape); + } } else { _body->setCollisionShape(const_cast(newShape)); setShape(newShape); diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index cae3918a3f..be641b5cd2 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -39,6 +39,8 @@ const int32_t BULLET_COLLISION_GROUP_DYNAMIC = 1 << 1; const int32_t BULLET_COLLISION_GROUP_KINEMATIC = 1 << 2; const int32_t BULLET_COLLISION_GROUP_MY_AVATAR = 1 << 3; const int32_t BULLET_COLLISION_GROUP_OTHER_AVATAR = 1 << 4; +const int32_t BULLET_COLLISION_GROUP_DETAILED_AVATAR = 1 << 5; +const int32_t BULLET_COLLISION_GROUP_DETAILED_RAY = 1 << 6; // ... const int32_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 31; @@ -64,7 +66,8 @@ const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISI // to move its avatar around correctly and to communicate its motion through the avatar-mixer. // Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR; - +const int32_t BULLET_COLLISION_MASK_DETAILED_AVATAR = BULLET_COLLISION_GROUP_DETAILED_RAY; +const int32_t BULLET_COLLISION_MASK_DETAILED_RAY = BULLET_COLLISION_GROUP_DETAILED_AVATAR; // COLLISIONLESS gets an empty mask. const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; diff --git a/libraries/shared/src/PhysicsHelpers.cpp b/libraries/shared/src/PhysicsHelpers.cpp index 988af98c46..092b9d078a 100644 --- a/libraries/shared/src/PhysicsHelpers.cpp +++ b/libraries/shared/src/PhysicsHelpers.cpp @@ -78,6 +78,10 @@ int32_t Physics::getDefaultCollisionMask(int32_t group) { return BULLET_COLLISION_MASK_MY_AVATAR; case BULLET_COLLISION_GROUP_OTHER_AVATAR: return BULLET_COLLISION_MASK_OTHER_AVATAR; + case BULLET_COLLISION_GROUP_DETAILED_AVATAR: + return BULLET_COLLISION_MASK_DETAILED_AVATAR; + case BULLET_COLLISION_GROUP_DETAILED_RAY: + return BULLET_COLLISION_MASK_DETAILED_RAY; default: break; }; From 95fca826a59516e7cb045f370c27ec6fe5c88ecd Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 9 Jan 2019 17:49:19 -0700 Subject: [PATCH 013/104] Add fit AABox for avatar and implement multisphere on shapeManager --- interface/src/avatar/AvatarMotionState.cpp | 1 - interface/src/avatar/DetailedMotionState.cpp | 4 --- interface/src/avatar/MyAvatar.cpp | 10 ++++++ interface/src/avatar/MyAvatar.h | 2 ++ interface/src/avatar/OtherAvatar.cpp | 18 +++------- .../src/avatars-renderer/Avatar.cpp | 35 +++++++++++++++++-- .../src/avatars-renderer/Avatar.h | 7 +++- libraries/physics/src/MultiSphereShape.cpp | 13 +++++++ libraries/physics/src/MultiSphereShape.h | 3 ++ libraries/physics/src/ObjectMotionState.cpp | 14 ++------ libraries/physics/src/ShapeFactory.cpp | 11 ++++++ libraries/shared/src/ShapeInfo.cpp | 31 ++++++++++++++-- libraries/shared/src/ShapeInfo.h | 10 ++++-- 13 files changed, 120 insertions(+), 39 deletions(-) diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index bb51c85eb6..3fa59ea967 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -59,7 +59,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; _avatar->computeShapeInfo(shapeInfo); - qDebug() << "Creating new Capsule Shape"; return getShapeManager()->getShape(shapeInfo); } diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index a54787b1c4..5d5592e9ab 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -36,9 +36,6 @@ bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngin DetailedMotionState::~DetailedMotionState() { assert(_avatar); _avatar = nullptr; - if (_shape) { - delete _shape; - } } // virtual @@ -163,4 +160,3 @@ void DetailedMotionState::setRigidBody(btRigidBody* body) { void DetailedMotionState::setShape(const btCollisionShape* shape) { ObjectMotionState::setShape(shape); } - diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e40fc7f9dd..394892735f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -423,6 +423,16 @@ void MyAvatar::clearIKJointLimitHistory() { _skeletonModel->getRig().clearIKJointLimitHistory(); } +QVariantMap MyAvatar::getBoundingBox() { + QVariantMap bbox; + auto avatarBBox = getFitBounds(); + auto center = avatarBBox.calcCenter(); + auto dimensions = avatarBBox.getDimensions(); + bbox["center"] = vec3toVariant(center); + bbox["dimensions"] = vec3toVariant(dimensions); + return bbox; +} + void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { assert(QThread::currentThread() == thread()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 17b71153ea..b70bcf7b30 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -311,6 +311,8 @@ public: */ Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe + Q_INVOKABLE QVariantMap getBoundingBox(); + void update(float deltaTime); virtual void postUpdate(float deltaTime, const render::ScenePointer& scene) override; void preDisplaySide(const RenderArgs* renderArgs); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 29d3e69c2a..b365c79d8f 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -108,25 +108,15 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { int32_t bytesRead = Avatar::parseDataFromBuffer(buffer); if (_moving && _motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); - for (auto mState : _detailedMotionStates) { - if (mState) { - mState->addDirtyFlags(Simulation::DIRTY_POSITION); - } - } } return bytesRead; } btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointIndex) { - if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { - auto& data = _multiSphereShapes[jointIndex].getSpheresData(); - std::vector positions; - std::vector radiuses; - for (auto& sphere : data) { - positions.push_back(glmToBullet(sphere._position)); - radiuses.push_back(sphere._radius); - } - btCollisionShape* shape = new btMultiSphereShape(positions.data(), radiuses.data(), (int)positions.size()); + ShapeInfo shapeInfo; + computeDetailedShapeInfo(shapeInfo, jointIndex); + if (shapeInfo.getType() != SHAPE_TYPE_NONE) { + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); return shape; } return nullptr; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f68a3d7cf4..a8d5f37136 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -956,6 +956,7 @@ void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { } fixupModelsInScene(scene); + updateFitBoundingBox(); } void Avatar::render(RenderArgs* renderArgs) { @@ -1670,7 +1671,7 @@ void Avatar::rigReset() { void Avatar::computeMultiSphereShapes() { const Rig& rig = getSkeletonModel()->getRig(); - auto scale = extractScale(rig.getGeometryToRigTransform()); + glm::vec3 scale = extractScale(rig.getGeometryToRigTransform()); const HFMModel& geometry = getSkeletonModel()->getHFMModel(); int jointCount = rig.getJointStateCount(); _multiSphereShapes.clear(); @@ -1688,13 +1689,28 @@ void Avatar::computeMultiSphereShapes() { } auto jointName = rig.nameOfJoint(i).toUpper(); MultiSphereShape multiSphereShape; - if (multiSphereShape.computeMultiSphereShape(jointName, btPoints, getSensorToWorldScale())) { + if (multiSphereShape.computeMultiSphereShape(jointName, btPoints)) { multiSphereShape.calculateDebugLines(); - } + multiSphereShape.setScale(getTargetScale()); +; } _multiSphereShapes.push_back(multiSphereShape); } } +void Avatar::updateFitBoundingBox() { + _fitBoundingBox = AABox(); + if (getJointCount() == _multiSphereShapes.size()) { + for (int i = 0; i < getJointCount(); i++) { + auto &shape = _multiSphereShapes[i]; + glm::vec3 jointPosition; + glm::quat jointRotation; + _skeletonModel->getJointPositionInWorldFrame(i, jointPosition); + _skeletonModel->getJointRotationInWorldFrame(i, jointRotation); + _fitBoundingBox += shape.updateBoundingBox(jointPosition, jointRotation); + } + } +} + // create new model, can return an instance of a SoftAttachmentModel rather then Model static std::shared_ptr allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) { if (isSoft) { @@ -1874,6 +1890,19 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { shapeInfo.setOffset(offset); } +void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) { + if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { + auto& data = _multiSphereShapes[jointIndex].getSpheresData(); + std::vector positions; + std::vector radiuses; + for (auto& sphere : data) { + positions.push_back(sphere._position); + radiuses.push_back(sphere._radius); + } + shapeInfo.setMultiSphere(positions, radiuses); + } +} + void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 41e1090015..a63c9b835a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -319,6 +319,7 @@ public: virtual void rebuildCollisionShape() = 0; virtual void computeShapeInfo(ShapeInfo& shapeInfo); + virtual void computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex); void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); float computeMass(); /**jsdoc @@ -397,6 +398,7 @@ public: float getBoundingRadius() const; AABox getRenderBounds() const; // THis call is accessible from rendering thread only to report the bounding box of the avatar during the frame. + AABox getFitBounds() const { return _fitBoundingBox; } void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene); void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene); @@ -440,6 +442,8 @@ public: void accumulateGrabPositions(std::map& grabAccumulators); + const std::vector& getMultiSphereShapes() const { return _multiSphereShapes; } + signals: void targetScaleChanged(float targetScale); @@ -508,7 +512,7 @@ protected: QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! void computeMultiSphereShapes(); - const std::vector& getMultiSphereShapes() const { return _multiSphereShapes; } + void updateFitBoundingBox(); SkeletonModelPointer _skeletonModel; @@ -633,6 +637,7 @@ protected: const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); std::vector _multiSphereShapes; + AABox _fitBoundingBox; AvatarGrabMap _avatarGrabs; }; diff --git a/libraries/physics/src/MultiSphereShape.cpp b/libraries/physics/src/MultiSphereShape.cpp index f0f56d7cf8..16094743a6 100644 --- a/libraries/physics/src/MultiSphereShape.cpp +++ b/libraries/physics/src/MultiSphereShape.cpp @@ -524,4 +524,17 @@ void MultiSphereShape::setScale(float scale) { } _scale = scale; } +} + +AABox& MultiSphereShape::updateBoundingBox(const glm::vec3& position, const glm::quat& rotation) { + _boundingBox = AABox(); + auto spheres = getSpheresData(); + for (size_t i = 0; i < spheres.size(); i++) { + auto sphere = spheres[i]; + auto worldPosition = position + rotation * sphere._position; + glm::vec3 corner = worldPosition - glm::vec3(sphere._radius); + glm::vec3 dimensions = glm::vec3(2.0f * sphere._radius); + _boundingBox += AABox(corner, dimensions); + } + return _boundingBox; } \ No newline at end of file diff --git a/libraries/physics/src/MultiSphereShape.h b/libraries/physics/src/MultiSphereShape.h index f8f251de42..d942d107b1 100644 --- a/libraries/physics/src/MultiSphereShape.h +++ b/libraries/physics/src/MultiSphereShape.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "BulletUtil.h" @@ -78,6 +79,7 @@ public: const std::vector& getSpheresData() const { return _spheres; } const std::vector>& getDebugLines() const { return _debugLines; } void setScale(float scale); + AABox& updateBoundingBox(const glm::vec3& position, const glm::quat& rotation); private: CollisionShapeExtractionMode getExtractionModeByName(const QString& name); @@ -97,6 +99,7 @@ private: CollisionShapeExtractionMode _mode; glm::vec3 _midPoint; float _scale { 1.0f }; + AABox _boundingBox; }; #endif // hifi_MultiSphereShape_h \ No newline at end of file diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index db36c5815d..0ab051fa96 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -195,11 +195,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { void ObjectMotionState::setShape(const btCollisionShape* shape) { if (_shape != shape) { if (_shape) { - if (_type == MOTIONSTATE_TYPE_DETAILED) { - delete _shape; - } else { - getShapeManager()->releaseShape(_shape); - } + getShapeManager()->releaseShape(_shape); } _shape = shape; if (_body && _type != MOTIONSTATE_TYPE_DETAILED) { @@ -313,12 +309,8 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* if (_shape == newShape) { // the shape didn't actually change, so we clear the DIRTY_SHAPE flag flags &= ~Simulation::DIRTY_SHAPE; - if (_type == MOTIONSTATE_TYPE_DETAILED) { - delete _shape; - } else { - // and clear the reference we just created - getShapeManager()->releaseShape(_shape); - } + // and clear the reference we just created + getShapeManager()->releaseShape(_shape); } else { _body->setCollisionShape(const_cast(newShape)); setShape(newShape); diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index d7ba2f0661..e86b1da496 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -284,6 +284,17 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) shape = new btSphereShape(radius); } break; + case SHAPE_TYPE_MULTISPHERE: { + std::vector positions; + std::vector radiuses; + auto sphereCollection = info.getSphereCollection(); + for (auto &sphereData : sphereCollection) { + positions.push_back(glmToBullet(sphereData.first)); + radiuses.push_back(sphereData.second); + } + shape = new btMultiSphereShape(positions.data(), radiuses.data(), (int)positions.size()); + } + break; case SHAPE_TYPE_ELLIPSOID: { glm::vec3 halfExtents = info.getHalfExtents(); float radius = halfExtents.x; diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 3426a79782..775849ccdc 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -38,6 +38,7 @@ * sub-meshes. * "static-mesh"The exact shape of the model. * "plane"A plane. + * "multisphere"A convex hull generated from a set of spheres. * * * @typedef {string} ShapeType @@ -59,7 +60,9 @@ const char* shapeTypeNames[] = { "simple-hull", "simple-compound", "static-mesh", - "ellipsoid" + "ellipsoid", + "circle", + "multisphere" }; static const size_t SHAPETYPE_NAME_COUNT = (sizeof(shapeTypeNames) / sizeof((shapeTypeNames)[0])); @@ -90,6 +93,7 @@ void ShapeInfo::clear() { _url.clear(); _pointCollection.clear(); _triangleIndices.clear(); + _sphereCollection.clear(); _halfExtents = glm::vec3(0.0f); _offset = glm::vec3(0.0f); _hashKey.clear(); @@ -106,6 +110,7 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString break; case SHAPE_TYPE_BOX: case SHAPE_TYPE_HULL: + case SHAPE_TYPE_MULTISPHERE: break; case SHAPE_TYPE_SPHERE: { float radius = glm::length(halfExtents) / SQUARE_ROOT_OF_3; @@ -144,6 +149,17 @@ void ShapeInfo::setSphere(float radius) { _hashKey.clear(); } +void ShapeInfo::setMultiSphere(const std::vector& centers, const std::vector& radiuses) { + _url = ""; + _type = SHAPE_TYPE_MULTISPHERE; + assert(centers.size() == radiuses.size() && centers.size() > 0); + for (size_t i = 0; i < centers.size(); i++) { + SphereData sphere = SphereData(centers[i], radiuses[i]); + _sphereCollection.push_back(sphere); + } + _hashKey.clear(); +} + void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { _pointCollection = pointCollection; _hashKey.clear(); @@ -170,6 +186,7 @@ uint32_t ShapeInfo::getNumSubShapes() const { case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_SIMPLE_COMPOUND: return _pointCollection.size(); + case SHAPE_TYPE_MULTISPHERE: case SHAPE_TYPE_SIMPLE_HULL: case SHAPE_TYPE_STATIC_MESH: assert(_pointCollection.size() == 1); @@ -257,7 +274,12 @@ const HashKey& ShapeInfo::getHash() const { // The key is not yet cached therefore we must compute it. _hashKey.hashUint64((uint64_t)_type); - if (_type != SHAPE_TYPE_SIMPLE_HULL) { + if (_type == SHAPE_TYPE_MULTISPHERE) { + for (auto &sphereData : _sphereCollection) { + _hashKey.hashVec3(sphereData.first); + _hashKey.hashFloat(sphereData.second); + } + } else if (_type != SHAPE_TYPE_SIMPLE_HULL) { _hashKey.hashVec3(_halfExtents); _hashKey.hashVec3(_offset); } else { @@ -283,9 +305,12 @@ const HashKey& ShapeInfo::getHash() const { if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) { uint64_t numHulls = (uint64_t)_pointCollection.size(); _hashKey.hashUint64(numHulls); + } else if (_type == SHAPE_TYPE_MULTISPHERE) { + uint64_t numSpheres = (uint64_t)_sphereCollection.size(); + _hashKey.hashUint64(numSpheres); } else if (_type == SHAPE_TYPE_SIMPLE_HULL) { _hashKey.hashUint64(1); - } + } } return _hashKey; } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index a2092c7f00..3bed7ef85e 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -47,7 +47,8 @@ enum ShapeType { SHAPE_TYPE_SIMPLE_COMPOUND, SHAPE_TYPE_STATIC_MESH, SHAPE_TYPE_ELLIPSOID, - SHAPE_TYPE_CIRCLE + SHAPE_TYPE_CIRCLE, + SHAPE_TYPE_MULTISPHERE }; class ShapeInfo { @@ -57,6 +58,8 @@ public: using PointList = QVector; using PointCollection = QVector; using TriangleIndices = QVector; + using SphereData = QPair; + using SphereCollection = QVector; static QString getNameForShapeType(ShapeType type); static ShapeType getShapeTypeForName(QString string); @@ -68,7 +71,8 @@ public: void setSphere(float radius); void setPointCollection(const PointCollection& pointCollection); void setCapsuleY(float radius, float cylinderHalfHeight); - void setOffset(const glm::vec3& offset); + void setMultiSphere(const std::vector& centers, const std::vector& radiuses); + void setOffset(const glm::vec3& offset); ShapeType getType() const { return _type; } @@ -78,6 +82,7 @@ public: PointCollection& getPointCollection() { return _pointCollection; } const PointCollection& getPointCollection() const { return _pointCollection; } + const SphereCollection& getSphereCollection() const { return _sphereCollection; } TriangleIndices& getTriangleIndices() { return _triangleIndices; } const TriangleIndices& getTriangleIndices() const { return _triangleIndices; } @@ -92,6 +97,7 @@ protected: void setHalfExtents(const glm::vec3& halfExtents); QUrl _url; // url for model of convex collision hulls + SphereCollection _sphereCollection; PointCollection _pointCollection; TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); From cc2546d7f2b128cbf078012b54c09e190793e1a2 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 21 Nov 2018 18:26:31 -0800 Subject: [PATCH 014/104] Switch to GitPython --- tools/scripts/Readme.md | 13 ++ tools/scripts/rc-branches.py | 156 +++++++--------- tools/scripts/requirements.txt | 1 + tools/scripts/utils/__init__.py | 1 - tools/scripts/utils/git.py | 303 -------------------------------- 5 files changed, 80 insertions(+), 394 deletions(-) create mode 100644 tools/scripts/Readme.md create mode 100644 tools/scripts/requirements.txt delete mode 100644 tools/scripts/utils/__init__.py delete mode 100644 tools/scripts/utils/git.py diff --git a/tools/scripts/Readme.md b/tools/scripts/Readme.md new file mode 100644 index 0000000000..75bdd928eb --- /dev/null +++ b/tools/scripts/Readme.md @@ -0,0 +1,13 @@ +## Setup + +Run the following command to install all the dependencies: + +```pip install -r requirements.txt``` + + +## Usage + +``` +./rc-branches.py check v0.76.1 +./rc-branches.py create v0.77.0 +``` diff --git a/tools/scripts/rc-branches.py b/tools/scripts/rc-branches.py index 0ae397374e..d72278b918 100755 --- a/tools/scripts/rc-branches.py +++ b/tools/scripts/rc-branches.py @@ -7,7 +7,7 @@ import sys import argparse -from utils import git +from git import Repo FORMAT = '[%(levelname)s] %(message)s' logging.basicConfig(format=FORMAT, level=logging.DEBUG) @@ -58,129 +58,102 @@ def checkVersionBranches(version): """Check that the branches for a given version were created properly.""" parser = VersionParser(version) - major = parser.major - minor = parser.minor - patch = parser.patch - previous_version = parser.previous_version - version = parser.version - is_major_release = parser.is_major_release - is_minor_release = parser.is_minor_release - is_patch_release = parser.is_patch_release - - remote_previous_rc_branch = parser.remote_previous_rc_branch - remote_base_branch = parser.remote_base_branch - remote_rc_branch = parser.remote_rc_branch - - repo = git.Repository(git.Repository.get_base_directory()) + repo = Repo(os.getcwd(), search_parent_directories=True) + assert not repo.bare # Verify the branches' existance - if not repo.does_branch_exist(remote_previous_rc_branch): - raise ValueError("Previous RC branch not found: {}".format(remote_previous_rc_branch)) + if parser.remote_previous_rc_branch not in repo.refs: + raise ValueError("Previous RC branch not found: {}".format(parser.remote_previous_rc_branch)) - if not repo.does_branch_exist(remote_base_branch): - raise ValueError("Base branch not found: {}".format(remote_base_branch)) + if parser.remote_base_branch not in repo.refs: + raise ValueError("Base branch not found: {}".format(parser.remote_base_branch)) - if not repo.does_branch_exist(remote_rc_branch): - raise ValueError("RC branch not found: {}".format(remote_rc_branch)) + if parser.remote_rc_branch not in repo.refs: + raise ValueError("RC branch not found: {}".format(parser.remote_rc_branch)) - # Figure out SHA for each of the branches - previous_rc_commit = repo.git_rev_parse([remote_previous_rc_branch]) - base_commit = repo.git_rev_parse([remote_base_branch]) - rc_commit = repo.git_rev_parse([remote_rc_branch]) + previous_rc = repo.refs[parser.remote_previous_rc_branch] + current_rc_base = repo.refs[parser.remote_base_branch] + current_rc = repo.refs[parser.remote_rc_branch] + master = repo.refs[remote_master_branch] # Check the base branch is an ancestor of the rc branch - if not repo.is_ancestor(base_commit, rc_commit): - raise ValueError("{} is not an ancesctor of {}".format(remote_base_branch, remote_rc_branch)) + if not repo.is_ancestor(current_rc_base, current_rc): + raise ValueError("{} is not an ancesctor of {}".format(current_rc_base, current_rc)) # Check that the base branch is the merge base of the previous and current RCs - merge_base = repo.get_merge_base(previous_rc_commit, rc_commit) - if base_commit != merge_base: - raise ValueError("Base branch is not the merge base between {} and {}".format(remote_previous_rc_branch, remote_rc_branch)) + merge_base = repo.merge_base(previous_rc, current_rc) + if current_rc_base.commit not in merge_base: + raise ValueError("Base branch is not the merge base between {} and {}".format(previous_rc, current_rc)) # For patch releases, warn if the base commit is not the previous RC commit - if is_patch_release: - if not repo.does_tag_exist(version): - logging.warning("The tag {} does not exist, which suggests {} has not been released.".format(version, version)) + if parser.is_patch_release: + if parser.previous_version not in repo.tags: + logging.warning("The tag {0} does not exist, which suggests {0} has not been released.".format(parser.previous_version)) - if base_commit != previous_rc_commit: + if current_rc_base.commit != previous_rc.commit: logging.warning("Previous version has commits not in this patch"); - logging.warning("Type \"git diff {}..{}\" to see the commit list".format(base_commit, previous_rc_commit)); + logging.warning("Type \"git diff {}..{}\" to see the commit list".format(current_rc_base, previous_rc)); # Check base branch is part of the previous RC - previous_rc_base_commit = repo.get_merge_base(previous_rc_commit, remote_master_branch) - if repo.is_ancestor(base_commit, previous_rc_base_commit): - raise ValueError("{} is older than {}".format(remote_base_branch, remote_rc_branch)) + previous_rc_base_commit = repo.merge_base(previous_rc, master) + if repo.is_ancestor(current_rc_base, previous_rc_base_commit): + raise ValueError("{} is older than {}".format(current_rc_base, previous_rc)) - print("[SUCCESS] Checked {}".format(version)) + print("[SUCCESS] Checked {}".format(parser.version)) def createVersionBranches(version): """Create the branches for a given version.""" parser = VersionParser(version) - major = parser.major - minor = parser.minor - patch = parser.patch - previous_version = parser.previous_version - version = parser.version - is_major_release = parser.is_major_release - is_minor_release = parser.is_minor_release - is_patch_release = parser.is_patch_release - - previous_rc_branch = parser.previous_rc_branch - base_branch = parser.base_branch - rc_branch = parser.rc_branch - remote_previous_rc_branch = parser.remote_previous_rc_branch - remote_base_branch = parser.remote_base_branch - remote_rc_branch = parser.remote_rc_branch - - repo = git.Repository(git.Repository.get_base_directory()) + repo = Repo(os.getcwd(), search_parent_directories=True) + assert not repo.bare # Validate the user is on a local branch that has the right merge base - if repo.is_detached(): + if repo.head.is_detached: raise ValueError("You must not run this script in a detached HEAD state") # Validate the user has no pending changes - if repo.is_working_tree_dirty(): + if repo.is_dirty(): raise ValueError("Your working tree has pending changes. You must have a clean working tree before proceeding.") # Make sure the remote is up to date - repo.git_fetch([remote_name]) + remote = repo.remotes[remote_name] + remote.fetch(prune=True) # Verify the previous RC branch exists - if not repo.does_branch_exist(remote_previous_rc_branch): - raise ValueError("Previous RC branch not found: {}".format(remote_previous_rc_branch)) + if parser.remote_previous_rc_branch not in repo.refs: + raise ValueError("Previous RC branch not found: {}".format(parser.remote_previous_rc_branch)) # Verify the branches don't already exist - if repo.does_branch_exist(remote_base_branch): - raise ValueError("Base branch already exists: {}".format(remote_base_branch)) + if parser.remote_base_branch in repo.refs: + raise ValueError("Base branch already exists: {}".format(parser.remote_base_branch)) - if repo.does_branch_exist(remote_rc_branch): - raise ValueError("RC branch already exists: {}".format(remote_rc_branch)) + if parser.remote_rc_branch in repo.refs: + raise ValueError("RC branch already exists: {}".format(parser.remote_rc_branch)) - if repo.does_branch_exist(base_branch): - raise ValueError("Base branch already exists locally: {}".format(base_branch)) + if parser.base_branch in repo.refs: + raise ValueError("Base branch already exists locally: {}".format(parser.base_branch)) - if repo.does_branch_exist(rc_branch): - raise ValueError("RC branch already exists locally: {}".format(rc_branch)) + if parser.rc_branch in repo.refs: + raise ValueError("RC branch already exists locally: {}".format(parser.rc_branch)) # Save current branch name - current_branch_name = repo.get_branch_name() + current_branch_name = repo.active_branch # Create the RC branches - if is_patch_release: - + if parser.is_patch_release: # Check tag exists, if it doesn't, print warning and ask for comfirmation - if not repo.does_tag_exist(previous_version): - logging.warning("The tag {} does not exist, which suggests {} has not yet been released.".format(previous_version, previous_version)) - logging.warning("Creating the branches now means that {} will diverge from {} if anything is merged into {}.".format(version, previous_version, previous_version)) + if parser.previous_version not in repo.tags: + logging.warning("The tag {0} does not exist, which suggests {0} has not yet been released.".format(parser.previous_version)) + logging.warning("Creating the branches now means that {0} will diverge from {1} if anything is merged into {1}.".format(parser.version, parser.previous_version)) logging.warning("This is not recommended unless necessary.") validAnswer = False askCount = 0 while not validAnswer and askCount < 3: - answer = input("Are you sure you want to do this? [y/n]").strip().lower() + answer = input("Are you sure you want to do this? [y/n] ").strip().lower() askCount += 1 validAnswer = answer == "y" or answer == "n" @@ -193,23 +166,26 @@ def createVersionBranches(version): else: print("Creating branches") - repo.git_checkout(["-b", base_branch, remote_previous_rc_branch]) - repo.push_to_remote_branch(remote_name, base_branch) - repo.git_checkout(["-b", rc_branch, remote_previous_rc_branch]) - repo.push_to_remote_branch(remote_name, rc_branch) + previous_rc = repo.refs[parser.remote_previous_rc_branch] + + repo.create_head(parser.base_branch, previous_rc) + remote.push("{0}:{0}".format(parser.base_branch)) + repo.create_head(parser.rc_branch, previous_rc) + remote.push("{0}:{0}".format(parser.rc_branch)) else: - merge_base = repo.get_merge_base(remote_previous_rc_branch, remote_master_branch) - repo.git_checkout(["-b", base_branch, merge_base]) - repo.push_to_remote_branch(remote_name, base_branch) - repo.git_checkout(["-b", rc_branch, remote_master_branch]) - repo.push_to_remote_branch(remote_name, rc_branch) + previous_rc = repo.refs[parser.remote_previous_rc_branch] + master = repo.refs[remote_master_branch] + merge_base = repo.merge_base(previous_rc, master) - repo.git_checkout([current_branch_name]) + repo.create_head(parser.base_branch, merge_base[0]) + remote.push("{0}:{0}".format(parser.base_branch)) + repo.create_head(parser.rc_branch, master) + remote.push("{0}:{0}".format(parser.rc_branch)) - print("[SUCCESS] Created {} and {}".format(base_branch, rc_branch)) + print("[SUCCESS] Created {} and {}".format(parser.base_branch, parser.rc_branch)) print("[SUCCESS] You can make the PR from the following webpage:") - print("[SUCCESS] https://github.com/highfidelity/hifi/compare/{}...{}".format(base_branch, rc_branch)) - if is_patch_release: + print("[SUCCESS] https://github.com/highfidelity/hifi/compare/{}...{}".format(parser.base_branch, parser.rc_branch)) + if parser.is_patch_release: print("[SUCCESS] NOTE: You will have to wait for the first fix to be merged into the RC branch to be able to create the PR") def main(): @@ -234,7 +210,7 @@ def main(): createVersionBranches(args.version) else: parser.print_help() - except Exception as ex: + except ValueError as ex: logging.error(ex) sys.exit(1) diff --git a/tools/scripts/requirements.txt b/tools/scripts/requirements.txt new file mode 100644 index 0000000000..64b1adaeeb --- /dev/null +++ b/tools/scripts/requirements.txt @@ -0,0 +1 @@ +GitPython diff --git a/tools/scripts/utils/__init__.py b/tools/scripts/utils/__init__.py deleted file mode 100644 index 4b7a2bb941..0000000000 --- a/tools/scripts/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty.""" diff --git a/tools/scripts/utils/git.py b/tools/scripts/utils/git.py deleted file mode 100644 index 156d2240c3..0000000000 --- a/tools/scripts/utils/git.py +++ /dev/null @@ -1,303 +0,0 @@ -"""Module to run git commands on a repository.""" - -# Copied from https://github.com/mongodb/mongo under Apache 2.0 -# Modified by Clement Brisset on 11/14/18. - -import logging -import os -import sys - -# The subprocess32 module resolves the thread-safety issues of the subprocess module in Python 2.x -# when the _posixsubprocess C extension module is also available. Additionally, the _posixsubprocess -# C extension module avoids triggering invalid free() calls on Python's internal data structure for -# thread-local storage by skipping the PyOS_AfterFork() call when the 'preexec_fn' parameter isn't -# specified to subprocess.Popen(). See SERVER-22219 for more details. -# -# The subprocess32 module is untested on Windows and thus isn't recommended for use, even when it's -# installed. See https://github.com/google/python-subprocess32/blob/3.2.7/README.md#usage. -if os.name == "posix" and sys.version_info[0] == 2: - try: - import subprocess32 as subprocess - except ImportError: - import warnings - warnings.warn(("Falling back to using the subprocess module because subprocess32 isn't" - " available. When using the subprocess module, a child process may trigger" - " an invalid free(). See SERVER-22219 for more details."), RuntimeWarning) - import subprocess # type: ignore -else: - import subprocess - -LOGGER = logging.getLogger(__name__) - - -class Repository(object): # pylint: disable=too-many-public-methods - """Represent a local git repository.""" - - def __init__(self, directory): - """Initialize Repository.""" - self.directory = directory - - def git_add(self, args): - """Run a git add command.""" - return self._callgito("add", args) - - def git_cat_file(self, args): - """Run a git cat-file command.""" - return self._callgito("cat-file", args) - - def git_checkout(self, args): - """Run a git checkout command.""" - return self._callgito("checkout", args) - - def git_commit(self, args): - """Run a git commit command.""" - return self._callgito("commit", args) - - def git_diff(self, args): - """Run a git diff command.""" - return self._callgito("diff", args) - - def git_log(self, args): - """Run a git log command.""" - return self._callgito("log", args) - - def git_push(self, args): - """Run a git push command.""" - return self._callgito("push", args) - - def git_fetch(self, args): - """Run a git fetch command.""" - return self._callgito("fetch", args) - - def git_ls_files(self, args): - """Run a git ls-files command and return the result as a str.""" - return self._callgito("ls-files", args) - - def git_rebase(self, args): - """Run a git rebase command.""" - return self._callgito("rebase", args) - - def git_reset(self, args): - """Run a git reset command.""" - return self._callgito("reset", args) - - def git_rev_list(self, args): - """Run a git rev-list command.""" - return self._callgito("rev-list", args) - - def git_rev_parse(self, args): - """Run a git rev-parse command.""" - return self._callgito("rev-parse", args).rstrip() - - def git_rm(self, args): - """Run a git rm command.""" - return self._callgito("rm", args) - - def git_show(self, args): - """Run a git show command.""" - return self._callgito("show", args) - - def get_origin_url(self): - """Return the URL of the origin repository.""" - return self._callgito("config", ["--local", "--get", "remote.origin.url"]).rstrip() - - def get_branch_name(self): - """ - Get the current branch name, short form. - - This returns "master", not "refs/head/master". - Raises a GitException if the current branch is detached. - """ - branch = self.git_rev_parse(["--abbrev-ref", "HEAD"]) - if branch == "HEAD": - raise GitException("Branch is currently detached") - return branch - - def get_current_revision(self): - """Retrieve the current revision of the repository.""" - return self.git_rev_parse(["HEAD"]).rstrip() - - def configure(self, parameter, value): - """Set a local configuration parameter.""" - return self._callgito("config", ["--local", parameter, value]) - - def is_detached(self): - """Return True if the current working tree in a detached HEAD state.""" - # symbolic-ref returns 1 if the repo is in a detached HEAD state - return self._callgit("symbolic-ref", ["--quiet", "HEAD"]) == 1 - - def is_ancestor(self, parent_revision, child_revision): - """Return True if the specified parent hash an ancestor of child hash.""" - # If the common point between parent_revision and child_revision is - # parent_revision, then parent_revision is an ancestor of child_revision. - merge_base = self._callgito("merge-base", [parent_revision, child_revision]).rstrip() - return parent_revision == merge_base - - def is_commit(self, revision): - """Return True if the specified hash is a valid git commit.""" - # cat-file -e returns 0 if it is a valid hash - return not self._callgit("cat-file", ["-e", "{0}^{{commit}}".format(revision)]) - - def is_working_tree_dirty(self): - """Return True if the current working tree has changes.""" - # diff returns 1 if the working tree has local changes - return self._callgit("diff", ["--quiet"]) == 1 - - def does_branch_exist(self, branch): - """Return True if the branch exists.""" - # rev-parse returns 0 if the branch exists - return not self._callgit("rev-parse", ["--verify", "--quiet", branch]) - - def does_tag_exist(self, tag): - """Return True if the tag exists.""" - # rev-parse returns 0 if the tag exists - return not self._callgit("rev-parse", ["--verify", "--quiet", tag]) - - def get_merge_base(self, commit1, commit2 = "HEAD"): - """Get the merge base between 'commit' and HEAD.""" - return self._callgito("merge-base", [commit1, commit2]).rstrip() - - def commit_with_message(self, message): - """Commit the staged changes with the given message.""" - return self.git_commit(["--message", message]) - - def push_to_remote_branch(self, remote, remote_branch): - """Push the current branch to the specified remote repository and branch.""" - refspec = "{}:{}".format(self.get_branch_name(), remote_branch) - return self.git_push([remote, refspec]) - - def fetch_remote_branch(self, repository, branch): - """Fetch the changes from a remote branch.""" - return self.git_fetch([repository, branch]) - - def rebase_from_upstream(self, upstream, ignore_date=False): - """Rebase the repository on an upstream reference. - - If 'ignore_date' is True, the '--ignore-date' option is passed to git. - """ - args = [upstream] - if ignore_date: - args.append("--ignore-date") - return self.git_rebase(args) - - @staticmethod - def clone(url, directory, branch=None, depth=None): - """Clone the repository designed by 'url' into 'directory'. - - Return a Repository instance. - """ - params = ["git", "clone"] - if branch: - params += ["--branch", branch] - if depth: - params += ["--depth", depth] - params += [url, directory] - result = Repository._run_process("clone", params) - result.check_returncode() - return Repository(directory) - - @staticmethod - def get_base_directory(directory=None): - """Return the base directory of the repository the given directory belongs to. - - If no directory is specified, then the current working directory is used. - """ - if directory is not None: - params = ["git", "-C", directory] - else: - params = ["git"] - params.extend(["rev-parse", "--show-toplevel"]) - result = Repository._run_process("rev-parse", params) - result.check_returncode() - return os.path.normpath(result.stdout.rstrip()) - - @staticmethod - def current_repository(): - """Return the Repository the current working directory belongs to.""" - return Repository(Repository.get_base_directory()) - - def _callgito(self, cmd, args): - """Call git for this repository, and return the captured output.""" - result = self._run_cmd(cmd, args) - result.check_returncode() - return result.stdout - - def _callgit(self, cmd, args, raise_exception=False): - """ - Call git for this repository without capturing output. - - This is designed to be used when git returns non-zero exit codes. - """ - result = self._run_cmd(cmd, args) - if raise_exception: - result.check_returncode() - return result.returncode - - def _run_cmd(self, cmd, args): - """Run the git command and return a GitCommandResult instance.""" - - LOGGER.debug("Running: git {} {}".format(cmd, " ".join(args))) - params = ["git", cmd] + args - return self._run_process(cmd, params, cwd=self.directory) - - @staticmethod - def _run_process(cmd, params, cwd=None): - process = subprocess.Popen(params, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - (stdout, stderr) = process.communicate() - if process.returncode: - if stdout: - LOGGER.error("Output of '%s': %s", " ".join(params), stdout.rstrip()) - if stderr: - LOGGER.error("Error output of '%s': %s", " ".join(params), stderr.rstrip()) - return GitCommandResult(cmd, params, process.returncode, stdout=stdout, stderr=stderr) - - -class GitException(Exception): - """Custom Exception for the git module. - - Args: - message: the exception message. - returncode: the return code of the failed git command, if any. - cmd: the git subcommand that was run, if any. - process_args: a list containing the git command and arguments (includes 'git' as its first - element) that were run, if any. - stderr: the error output of the git command. - """ - - def __init__( # pylint: disable=too-many-arguments - self, message, returncode=None, cmd=None, process_args=None, stdout=None, stderr=None): - """Initialize GitException.""" - Exception.__init__(self, message) - self.returncode = returncode - self.cmd = cmd - self.process_args = process_args - self.stdout = stdout - self.stderr = stderr - - -class GitCommandResult(object): - """The result of running git subcommand. - - Args: - cmd: the git subcommand that was executed (e.g. 'clone', 'diff'). - process_args: the full list of process arguments, starting with the 'git' command. - returncode: the return code. - stdout: the output of the command. - stderr: the error output of the command. - """ - - def __init__( # pylint: disable=too-many-arguments - self, cmd, process_args, returncode, stdout=None, stderr=None): - """Initialize GitCommandResult.""" - self.cmd = cmd - self.process_args = process_args - self.returncode = returncode - self.stdout = stdout - self.stderr = stderr - - def check_returncode(self): - """Raise GitException if the exit code is non-zero.""" - if self.returncode: - raise GitException("Command '{0}' failed with code '{1}'".format( - " ".join(self.process_args), self.returncode), self.returncode, self.cmd, - self.process_args, self.stdout, self.stderr) From 8cba72743e4feb98ecb4e62302e1d5b0be81b674 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 11 Jan 2019 08:46:20 -0800 Subject: [PATCH 015/104] changed the value for the top threshold that triggers a vertical reset, this stops unwanted recentering --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e40fc7f9dd..495bec4ec8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4252,7 +4252,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons } bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { - const float CYLINDER_TOP = 0.1f; + const float CYLINDER_TOP = 0.5f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; From 19701ef333fc389caa2eaa9f7f5634a1245b3b4e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 11 Jan 2019 15:36:07 -0700 Subject: [PATCH 016/104] Fix scale and add rayTest --- interface/src/Application.cpp | 12 +- interface/src/avatar/AvatarManager.cpp | 130 +++++++----------- interface/src/avatar/AvatarManager.h | 22 +++ interface/src/avatar/DetailedMotionState.cpp | 22 ++- interface/src/avatar/DetailedMotionState.h | 11 +- interface/src/avatar/MyAvatar.cpp | 4 + interface/src/avatar/MyAvatar.h | 3 + .../src/avatar/MyCharacterController.cpp | 114 ++++++++++++++- interface/src/avatar/MyCharacterController.h | 25 ++++ interface/src/avatar/OtherAvatar.h | 4 +- .../src/avatars-renderer/Avatar.cpp | 6 +- libraries/avatars/src/AvatarData.cpp | 2 + libraries/avatars/src/AvatarData.h | 1 + libraries/physics/src/CharacterController.cpp | 7 +- libraries/physics/src/CharacterController.h | 3 + libraries/physics/src/MultiSphereShape.cpp | 1 + 16 files changed, 272 insertions(+), 95 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d306c77cce..d2bb07501c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -159,6 +159,7 @@ #include "avatar/AvatarManager.h" #include "avatar/MyHead.h" #include "avatar/AvatarPackager.h" +#include "avatar/MyCharacterController.h" #include "CrashRecoveryHandler.h" #include "CrashHandler.h" #include "devices/DdeFaceTracker.h" @@ -2635,7 +2636,14 @@ Application::~Application() { avatarManager->handleProcessedPhysicsTransaction(transaction); avatarManager->deleteAllAvatars(); + + auto myCharacterController = getMyAvatar()->getCharacterController(); + myCharacterController->clearDetailedMotionStates(); + myCharacterController->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + myCharacterController->handleProcessedPhysicsTransaction(transaction); + _physicsEngine->setCharacterController(nullptr); // the _shapeManager should have zero references @@ -6123,7 +6131,9 @@ void Application::update(float deltaTime) { avatarManager->buildPhysicsTransaction(transaction); _physicsEngine->processTransaction(transaction); avatarManager->handleProcessedPhysicsTransaction(transaction); - + myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); + _physicsEngine->processTransaction(transaction); + myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction); myAvatar->prepareForPhysicsSimulation(); _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) { dynamic->prepareForPhysicsSimulation(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b5e14fd593..b6d697fd92 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -415,7 +415,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact } qCDebug(animation) << "Removing " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); avatar->resetDetailedMotionStates(); - } else { if (avatar->_motionState == nullptr) { ShapeInfo shapeInfo; @@ -549,7 +548,8 @@ void AvatarManager::deleteAllAvatars() { avatar->die(); if (avatar != _myAvatar) { auto otherAvatar = std::static_pointer_cast(avatar); - assert(!otherAvatar->_motionState && !otherAvatar->_motionState2); + assert(!otherAvatar->_motionState); + assert(otherAvatar->getDetailedMotionStates().size() == 0); } } } @@ -662,83 +662,21 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } - // It's better to intersect the ray against the avatar's actual mesh, but this is currently difficult to - // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code - // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking - // against the avatar is sort-of right, but you likely wont be able to pick against the arms. - - // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. - - std::vector sortedAvatars; - auto avatarHashCopy = getHashCopy(); - for (auto avatarData : avatarHashCopy) { - auto avatar = std::static_pointer_cast(avatarData); - if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || - (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { - continue; - } - - float distance = FLT_MAX; -#if 0 - // if we weren't picking against the capsule, we would want to pick against the avatarBounds... - SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); - AABox avatarBounds = avatarModel->getRenderableMeshBound(); - if (avatarBounds.contains(ray.origin)) { - distance = 0.0f; - } else { - float boundDistance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; - if (avatarBounds.findRayIntersection(ray.origin, ray.direction, boundDistance, face, surfaceNormal)) { - distance = boundDistance; - } - } -#else - glm::vec3 start; - glm::vec3 end; - float radius; - avatar->getCapsule(start, end, radius); - findRayCapsuleIntersection(ray.origin, ray.direction, start, end, radius, distance); -#endif - - if (distance < FLT_MAX) { - sortedAvatars.emplace_back(distance, avatar); - } + float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results + BoxFace face = BoxFace::UNKNOWN_FACE; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; + MyCharacterController::RayAvatarResult physicsResult = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(ray.direction), distance, QVector()); + if (physicsResult._intersect) { + result.intersects = true; + result.avatarID = physicsResult._intersectWithAvatar; + result.distance = physicsResult._distance; + result.surfaceNormal = physicsResult._intersectionNormal; + result.jointIndex = physicsResult._intersectWithJoint; + result.intersection = physicsResult._intersectionPoint; + result.extraInfo = extraInfo; + result.face = face; } - - if (sortedAvatars.size() > 1) { - static auto comparator = [](const SortedAvatar& left, const SortedAvatar& right) { return left.first < right.first; }; - std::sort(sortedAvatars.begin(), sortedAvatars.end(), comparator); - } - - for (auto it = sortedAvatars.begin(); it != sortedAvatars.end(); ++it) { - const SortedAvatar& sortedAvatar = *it; - // We can exit once avatarCapsuleDistance > bestDistance - if (sortedAvatar.first > result.distance) { - break; - } - - float distance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; - QVariantMap extraInfo; - SkeletonModelPointer avatarModel = sortedAvatar.second->getSkeletonModel(); - if (avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, ray.direction, distance, face, surfaceNormal, extraInfo, true)) { - if (distance < result.distance) { - result.intersects = true; - result.avatarID = sortedAvatar.second->getID(); - result.distance = distance; - result.face = face; - result.surfaceNormal = surfaceNormal; - result.extraInfo = extraInfo; - } - } - } - - if (result.intersects) { - result.intersection = ray.origin + ray.direction * result.distance; - } - return result; } @@ -836,6 +774,42 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector return result; } +RayToAvatarIntersectionResult AvatarManager::findSelfRayIntersection(const PickRay& ray, + const QScriptValue& jointIndexesToInclude, + const QScriptValue& jointIndexesToDiscard) { + QVector jointsToInclude; + QVector jointsToDiscard; + qVectorIntFromScriptValue(jointIndexesToInclude, jointsToInclude); + qVectorIntFromScriptValue(jointIndexesToDiscard, jointsToDiscard); + return findSelfRayIntersectionVector(ray, jointsToInclude, jointsToDiscard); +} + +RayToAvatarIntersectionResult AvatarManager::findSelfRayIntersectionVector(const PickRay& ray, + const QVector& jointIndexesToInclude, + const QVector& jointIndexesToDiscard) { + RayToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findSelfRayIntersectionVector", + Q_RETURN_ARG(RayToAvatarIntersectionResult, result), + Q_ARG(const PickRay&, ray), + Q_ARG(const QVector&, jointIndexesToInclude), + Q_ARG(const QVector&, jointIndexesToDiscard)); + return result; + } + glm::vec3 normDirection = glm::normalize(ray.direction); + auto jointCollisionResult = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(normDirection), 1.0f, jointIndexesToDiscard); + if (jointCollisionResult._intersectWithJoint > -1 && jointCollisionResult._distance > 0) { + result.intersects = true; + result.distance = jointCollisionResult._distance; + result.jointIndex = jointCollisionResult._intersectWithJoint; + result.avatarID = _myAvatar->getID(); + result.extraInfo = QVariantMap(); + result.intersection = jointCollisionResult._intersectionPoint; + result.surfaceNormal = jointCollisionResult._intersectionNormal; + } + return result; +} + // HACK float AvatarManager::getAvatarSortCoefficient(const QString& name) { if (name == "size") { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6717c301af..1db04b3d9e 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -165,6 +165,28 @@ public: const QVector& avatarsToInclude, const QVector& avatarsToDiscard); + /**jsdoc + * @function AvatarManager.findSelfRayIntersection + * @param {PickRay} ray + * @param {Uuid[]} [jointsToInclude=[]] + * @param {Uuid[]} [jointsToDiscard=[]] + * @returns {RayToAvatarIntersectionResult} + */ + Q_INVOKABLE RayToAvatarIntersectionResult findSelfRayIntersection(const PickRay& ray, + const QScriptValue& jointIndexesToInclude = QScriptValue(), + const QScriptValue& jointIndexesToDiscard = QScriptValue()); + + /**jsdoc + * @function AvatarManager.findSelfRayIntersectionVector + * @param {PickRay} ray + * @param {Uuid[]} jointsToInclude + * @param {Uuid[]} jointsToDiscard + * @returns {RayToAvatarIntersectionResult} + */ + Q_INVOKABLE RayToAvatarIntersectionResult findSelfRayIntersectionVector(const PickRay& ray, + const QVector& jointIndexesToInclude, + const QVector& jointIndexesToDiscard); + /**jsdoc * @function AvatarManager.getAvatarSortCoefficient * @param {string} name diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index 5d5592e9ab..8bf50b2cdc 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -2,8 +2,8 @@ // DetailedMotionState.cpp // interface/src/avatar/ // -// Created by Andrew Meadows 2015.05.14 -// Copyright 2015 High Fidelity, Inc. +// Created by Luis Cuenca 1/11/2019 +// Copyright 2019 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 @@ -14,9 +14,10 @@ #include #include #include +#include "MyAvatar.h" -DetailedMotionState::DetailedMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape, int jointIndex) : +DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) : ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) { assert(_avatar); _type = MOTIONSTATE_TYPE_DETAILED; @@ -56,7 +57,14 @@ PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const { // virtual and protected const btCollisionShape* DetailedMotionState::computeNewShape() { - auto shape = _avatar->createDetailedCollisionShapeForJoint(_jointIndex); + btCollisionShape* shape = nullptr; + if (!_avatar->isMyAvatar()) { + OtherAvatarPointer otherAvatar = std::static_pointer_cast(_avatar); + shape = otherAvatar->createDetailedCollisionShapeForJoint(_jointIndex); + } else { + std::shared_ptr myAvatar = std::static_pointer_cast(_avatar); + shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex); + } return shape; } @@ -160,3 +168,9 @@ void DetailedMotionState::setRigidBody(btRigidBody* body) { void DetailedMotionState::setShape(const btCollisionShape* shape) { ObjectMotionState::setShape(shape); } + +void DetailedMotionState::forceActive() { + if (_body) { + _body->setActivationState(ACTIVE_TAG); + } +} \ No newline at end of file diff --git a/interface/src/avatar/DetailedMotionState.h b/interface/src/avatar/DetailedMotionState.h index 4ae780de8c..a5bd777d07 100644 --- a/interface/src/avatar/DetailedMotionState.h +++ b/interface/src/avatar/DetailedMotionState.h @@ -2,8 +2,8 @@ // DetailedMotionState.h // interface/src/avatar/ // -// Created by Andrew Meadows 2015.05.14 -// Copyright 2015 High Fidelity, Inc. +// Created by Luis Cuenca 1/11/2019 +// Copyright 2019 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 @@ -21,7 +21,7 @@ class DetailedMotionState : public ObjectMotionState { public: - DetailedMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape, int jointIndex); + DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -67,6 +67,9 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; virtual float getMass() const override; + void forceActive(); + QUuid getAvatarID() { return _avatar->getID(); } + int getJointIndex() { return _jointIndex; } friend class AvatarManager; friend class Avatar; @@ -82,7 +85,7 @@ protected: virtual bool isReadyToComputeShape() const override { return true; } virtual const btCollisionShape* computeNewShape() override; - OtherAvatarPointer _avatar; + AvatarPointer _avatar; float _diameter { 0.0f }; uint32_t _dirtyFlags; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 394892735f..3be23f2b56 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4830,3 +4830,7 @@ void MyAvatar::releaseGrab(const QUuid& grabID) { _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID); } } + +std::shared_ptr MyAvatar::getSharedMe() { + return DependencyManager::get()->getMyAvatar(); +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b70bcf7b30..01a7403c9c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -37,6 +37,7 @@ class AvatarActionHold; class ModelItemID; class MyHead; +class DetailedMotionState; enum eyeContactTarget { LEFT_EYE, @@ -1206,6 +1207,8 @@ public: */ Q_INVOKABLE void releaseGrab(const QUuid& grabID); + std::shared_ptr getSharedMe(); + public slots: /**jsdoc diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 798dbc91ed..ba019e60b6 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -12,8 +12,10 @@ #include "MyCharacterController.h" #include +#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h" #include "MyAvatar.h" +#include "DetailedMotionState.h" // TODO: make avatars stand on steep slope // TODO: make avatars not snag on low ceilings @@ -44,8 +46,8 @@ void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) { void MyCharacterController::updateShapeIfNecessary() { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { _pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE; - if (_radius > 0.0f) { + // _pendingFlags |= PENDING_FLAG_RESET_DETAILED_SHAPES; // create RigidBody if it doesn't exist if (!_rigidBody) { btCollisionShape* shape = computeShape(); @@ -352,3 +354,113 @@ void MyCharacterController::updateMassProperties() { _rigidBody->setMassProps(mass, inertia); } + +btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int jointIndex) { + ShapeInfo shapeInfo; + _avatar->computeDetailedShapeInfo(shapeInfo, jointIndex); + if (shapeInfo.getType() != SHAPE_TYPE_NONE) { + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + return shape; + } + return nullptr; +} + +DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int jointIndex) { + auto shape = createDetailedCollisionShapeForJoint(jointIndex); + if (shape) { + DetailedMotionState* motionState = new DetailedMotionState(_avatar->getSharedMe(), shape, jointIndex); + motionState->setMass(_avatar->computeMass()); + return motionState; + } + return nullptr; +} + +void MyCharacterController::resetDetailedMotionStates() { + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + _detailedMotionStates[i] = nullptr; + } + _detailedMotionStates.clear(); +} + +void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + _detailedMotionStates[i]->forceActive(); + } + if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) { + _pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + transaction.objectsToRemove.push_back(_detailedMotionStates[i]); + _detailedMotionStates[i] = nullptr; + } + _detailedMotionStates.clear(); + } + if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) { + _pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION; + for (int i = 0; i < _avatar->getJointCount(); i++) { + auto dMotionState = createDetailedMotionStateForJoint(i); + if (dMotionState) { + _detailedMotionStates.push_back(dMotionState); + transaction.objectsToAdd.push_back(dMotionState); + } + } + } +} + +void MyCharacterController::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) { + // things on objectsToRemove are ready for delete + for (auto object : transaction.objectsToRemove) { + delete object; + } + transaction.clear(); +} + + +class ClosestDetailed : public btCollisionWorld::AllHitsRayResultCallback { +public: + ClosestDetailed() + : btCollisionWorld::AllHitsRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) { + // the RayResultCallback's group and mask must match MY_AVATAR + m_collisionFilterGroup = BULLET_COLLISION_GROUP_DETAILED_RAY; + m_collisionFilterMask = BULLET_COLLISION_MASK_DETAILED_RAY; + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override { + return AllHitsRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); + } +}; + +MyCharacterController::RayAvatarResult MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, + const QVector& jointsToExclude) const { + RayAvatarResult result; + if (_dynamicsWorld) { + btVector3 end = origin + length * direction; + ClosestDetailed rayCallback = ClosestDetailed(); + rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal; + rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest; + _dynamicsWorld->rayTest(origin, end, rayCallback); + if (rayCallback.m_hitFractions.size() > 0) { + int minIndex = 0; + float hitFraction = rayCallback.m_hitFractions[0]; + for (auto i = 1; i < rayCallback.m_hitFractions.size(); i++) { + if (hitFraction > rayCallback.m_hitFractions[i]) { + hitFraction = rayCallback.m_hitFractions[i]; + minIndex = i; + } + } + auto object = rayCallback.m_collisionObjects[minIndex]; + ObjectMotionState* motionState = static_cast(object->getUserPointer()); + if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) { + DetailedMotionState* detailedMotionState = dynamic_cast(motionState); + if (detailedMotionState) { + result._intersect = true; + result._intersectWithAvatar = detailedMotionState->getAvatarID(); + result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[minIndex]); + result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[minIndex]); + result._distance = length * hitFraction; + result._intersectWithJoint = detailedMotionState->getJointIndex(); + } + } + } + } + return result; +} \ No newline at end of file diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index fd9caface2..f5a510e5b5 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -15,9 +15,11 @@ #include //#include +#include class btCollisionShape; class MyAvatar; +class DetailedMotionState; class MyCharacterController : public CharacterController { public: @@ -42,6 +44,27 @@ public: void setDensity(btScalar density) { _density = density; } + btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex); + DetailedMotionState* createDetailedMotionStateForJoint(int jointIndex); + std::vector& getDetailedMotionStates() { return _detailedMotionStates; } + void clearDetailedMotionStates() { _pendingFlags |= PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; } + void resetDetailedMotionStates(); + + void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); + void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction); + + + struct RayAvatarResult { + bool _intersect { false }; + QUuid _intersectWithAvatar; + int _intersectWithJoint { -1 }; + float _distance { 0.0f }; + glm::vec3 _intersectionPoint; + glm::vec3 _intersectionNormal; + }; + RayAvatarResult rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, + const QVector& jointsToExclude) const; + protected: void initRayShotgun(const btCollisionWorld* world); void updateMassProperties() override; @@ -56,6 +79,8 @@ protected: btAlignedObjectArray _topPoints; btAlignedObjectArray _bottomPoints; btScalar _density { 1.0f }; + + std::vector _detailedMotionStates; }; #endif // hifi_MyCharacterController_h diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index cfe47d2f35..7b5f11c139 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -46,10 +46,11 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; - btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointIndex); + btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex); DetailedMotionState* createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex); std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void resetDetailedMotionStates(); + void updateCollisionGroup(bool myAvatarCollide); friend AvatarManager; @@ -64,5 +65,6 @@ protected: }; using OtherAvatarPointer = std::shared_ptr; +using AvatarPointer = std::shared_ptr; #endif // hifi_OtherAvatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a8d5f37136..098249902b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1671,7 +1671,7 @@ void Avatar::rigReset() { void Avatar::computeMultiSphereShapes() { const Rig& rig = getSkeletonModel()->getRig(); - glm::vec3 scale = extractScale(rig.getGeometryToRigTransform()); + glm::vec3 scale = extractScale(rig.getGeometryOffsetPose()); const HFMModel& geometry = getSkeletonModel()->getHFMModel(); int jointCount = rig.getJointStateCount(); _multiSphereShapes.clear(); @@ -1691,8 +1691,8 @@ void Avatar::computeMultiSphereShapes() { MultiSphereShape multiSphereShape; if (multiSphereShape.computeMultiSphereShape(jointName, btPoints)) { multiSphereShape.calculateDebugLines(); - multiSphereShape.setScale(getTargetScale()); -; } + multiSphereShape.setScale(_targetScale); + } _multiSphereShapes.push_back(multiSphereShape); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e72fa3a6eb..393948a467 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2898,6 +2898,7 @@ QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, c obj.setProperty("intersection", intersection); QScriptValue surfaceNormal = vec3ToScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); + obj.setProperty("jointIndex", value.jointIndex); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -2917,6 +2918,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra if (surfaceNormal.isValid()) { vec3FromScriptValue(surfaceNormal, value.surfaceNormal); } + value.jointIndex = object.property("jointIndex").toInt32(); value.extraInfo = object.property("extraInfo").toVariant().toMap(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b42c387f61..5513abab7f 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1614,6 +1614,7 @@ public: BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; + int jointIndex { -1 }; QVariantMap extraInfo; }; Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 8fd6d4eada..342a74b752 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -137,7 +137,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { if (_dynamicsWorld) { if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set - _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION; + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION | + PENDING_FLAG_ADD_DETAILED_TO_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; } else { _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION; } @@ -445,10 +446,10 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const if (_dynamicsWorld) { // must REMOVE from world prior to shape update - _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; } _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; - _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_ADD_DETAILED_TO_SIMULATION; } // it's ok to change offset immediately -- there are no thread safety issues here diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 50db2bea12..3566867f88 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -32,6 +32,9 @@ 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 uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; +const uint32_t PENDING_FLAG_ADD_DETAILED_TO_SIMULATION = 1U << 6; +const uint32_t PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION = 1U << 7; + const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); class btRigidBody; diff --git a/libraries/physics/src/MultiSphereShape.cpp b/libraries/physics/src/MultiSphereShape.cpp index 16094743a6..f361d67656 100644 --- a/libraries/physics/src/MultiSphereShape.cpp +++ b/libraries/physics/src/MultiSphereShape.cpp @@ -254,6 +254,7 @@ bool MultiSphereShape::computeMultiSphereShape(const QString& name, const std::v for (size_t i = 0; i < _spheres.size(); i++) { _spheres[i]._position += _midPoint; } + return _mode != CollisionShapeExtractionMode::None; } From 65896b3b6f6c9b35dcfb16feba1fbe6fb1d09933 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 13 Jan 2019 11:30:39 -0700 Subject: [PATCH 017/104] Fix shape LOD --- interface/src/avatar/AvatarManager.cpp | 23 +++--- interface/src/avatar/DetailedMotionState.cpp | 10 ++- interface/src/avatar/DetailedMotionState.h | 3 + interface/src/avatar/OtherAvatar.cpp | 73 +++++++++++++++++++- interface/src/avatar/OtherAvatar.h | 14 +++- 5 files changed, 103 insertions(+), 20 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index b6d697fd92..ffefcfa4a6 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -413,7 +413,7 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToRemove.push_back(mState); } } - qCDebug(animation) << "Removing " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); + qDebug() << "Removing " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); avatar->resetDetailedMotionStates(); } else { if (avatar->_motionState == nullptr) { @@ -427,20 +427,16 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToAdd.push_back(motionState); } } - auto& detailedMotionStates = avatar->getDetailedMotionStates(); - if (detailedMotionStates.size() == 0) { - for (int i = 0; i < avatar->getJointCount(); i++) { - auto dMotionState = avatar->createDetailedMotionStateForJoint(avatar, i); - if (dMotionState) { - detailedMotionStates.push_back(dMotionState); - transaction.objectsToAdd.push_back(dMotionState); - } + if (avatar->getDetailedMotionStates().size() == 0) { + avatar->createDetailedMotionStates(avatar); + for (auto dMotionState : avatar->getDetailedMotionStates()) { + transaction.objectsToAdd.push_back(dMotionState); + } + if (avatar->_motionState == nullptr || avatar->getDetailedMotionStates().size() == 0) { + failedShapeBuilds.insert(avatar); } - //qCDebug(animation) << "Creating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); - } - if (avatar->_motionState == nullptr || detailedMotionStates.size() == 0) { - failedShapeBuilds.insert(avatar); } + qDebug() << "Adding " << avatar->getDetailedMotionStates().size() << " detailed motion states from " << avatar->getSessionUUID(); } } else if (isInPhysics) { transaction.objectsToChange.push_back(avatar->_motionState); @@ -450,7 +446,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToChange.push_back(mState); } } - //qCDebug(animation) << "Updating " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); } } _avatarsToChangeInPhysics.swap(failedShapeBuilds); diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index 8bf50b2cdc..6d983cdfa1 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -60,7 +60,13 @@ const btCollisionShape* DetailedMotionState::computeNewShape() { btCollisionShape* shape = nullptr; if (!_avatar->isMyAvatar()) { OtherAvatarPointer otherAvatar = std::static_pointer_cast(_avatar); - shape = otherAvatar->createDetailedCollisionShapeForJoint(_jointIndex); + if (otherAvatar) { + if (_isAvatarCapsule) { + shape = otherAvatar->createCapsuleCollisionShape(); + } else { + shape = otherAvatar->createDetailedCollisionShapeForJoint(_jointIndex); + } + } } else { std::shared_ptr myAvatar = std::static_pointer_cast(_avatar); shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex); @@ -109,7 +115,7 @@ float DetailedMotionState::getObjectAngularDamping() const { // virtual glm::vec3 DetailedMotionState::getObjectPosition() const { - return _avatar->getJointPosition(_jointIndex); + return _isAvatarCapsule ? _avatar->getFitBounds().calcCenter() : _avatar->getJointPosition(_jointIndex); } // virtual diff --git a/interface/src/avatar/DetailedMotionState.h b/interface/src/avatar/DetailedMotionState.h index a5bd777d07..2671f9d75e 100644 --- a/interface/src/avatar/DetailedMotionState.h +++ b/interface/src/avatar/DetailedMotionState.h @@ -70,6 +70,7 @@ public: void forceActive(); QUuid getAvatarID() { return _avatar->getID(); } int getJointIndex() { return _jointIndex; } + void setIsAvatarCapsule(bool isAvatarCapsule) { _isAvatarCapsule = isAvatarCapsule; } friend class AvatarManager; friend class Avatar; @@ -90,6 +91,8 @@ protected: uint32_t _dirtyFlags; int _jointIndex { -1 }; + + bool _isAvatarCapsule { false }; }; #endif // hifi_DetailedMotionState_h diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index b365c79d8f..6fbdcf7c1f 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -122,6 +122,17 @@ btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointInd return nullptr; } +btCollisionShape* OtherAvatar::createCapsuleCollisionShape() { + ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); + shapeInfo.setOffset(glm::vec3(0.0f)); + if (shapeInfo.getType() != SHAPE_TYPE_NONE) { + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + return shape; + } + return nullptr; +} + DetailedMotionState* OtherAvatar::createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex) { auto shape = createDetailedCollisionShapeForJoint(jointIndex); if (shape) { @@ -132,6 +143,17 @@ DetailedMotionState* OtherAvatar::createDetailedMotionStateForJoint(std::shared_ return nullptr; } +DetailedMotionState* OtherAvatar::createCapsuleMotionState(std::shared_ptr avatar) { + auto shape = createCapsuleCollisionShape(); + if (shape) { + DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, -1); + motionState->setIsAvatarCapsule(true); + motionState->setMass(computeMass()); + return motionState; + } + return nullptr; +} + void OtherAvatar::resetDetailedMotionStates() { for (size_t i = 0; i < _detailedMotionStates.size(); i++) { _detailedMotionStates[i] = nullptr; @@ -141,15 +163,42 @@ void OtherAvatar::resetDetailedMotionStates() { void OtherAvatar::setWorkloadRegion(uint8_t region) { _workloadRegion = region; + QString printRegion = ""; + if (region == workload::Region::R1) { + printRegion = "R1"; + } else if (region == workload::Region::R2) { + printRegion = "R2"; + } else if (region == workload::Region::R3) { + printRegion = "R3"; + } else { + printRegion = "invalid"; + } + qDebug() << "Setting workload region to " << printRegion; + computeShapeLOD(); +} + +void OtherAvatar::computeShapeLOD() { + auto newBodyLOD = (_workloadRegion < workload::Region::R3 && !isDead()) ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape; + if (newBodyLOD != _bodyLOD) { + _bodyLOD = newBodyLOD; + if (isInPhysicsSimulation()) { + qDebug() << "Changing to body LOD " << (_bodyLOD == BodyLOD::MultiSphereShapes ? "MultiSpheres" : "Capsule"); + _needsReinsertion = true; + } + } +} + +bool OtherAvatar::isInPhysicsSimulation() const { + return _motionState != nullptr && _detailedMotionStates.size() > 0; } bool OtherAvatar::shouldBeInPhysicsSimulation() const { - return (_workloadRegion < workload::Region::R3 && !isDead()); + return !(isInPhysicsSimulation() && _needsReinsertion); } bool OtherAvatar::needsPhysicsUpdate() const { constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP; - return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)); + return (_needsReinsertion || _motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)); } void OtherAvatar::rebuildCollisionShape() { @@ -175,4 +224,22 @@ void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); } } -} \ No newline at end of file +} + +void OtherAvatar::createDetailedMotionStates(const std::shared_ptr& avatar){ + auto& detailedMotionStates = getDetailedMotionStates(); + if (_bodyLOD == BodyLOD::MultiSphereShapes) { + for (int i = 0; i < getJointCount(); i++) { + auto dMotionState = createDetailedMotionStateForJoint(avatar, i); + if (dMotionState) { + detailedMotionStates.push_back(dMotionState); + } + } + } else if (_bodyLOD == BodyLOD::CapsuleShape) { + auto dMotionState = createCapsuleMotionState(avatar); + if (dMotionState) { + detailedMotionStates.push_back(dMotionState); + } + } + _needsReinsertion = false; +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 7b5f11c139..782812b2cb 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -27,6 +27,11 @@ public: explicit OtherAvatar(QThread* thread); virtual ~OtherAvatar(); + enum BodyLOD { + CapsuleShape, + MultiSphereShapes + }; + virtual void instantiableAvatar() override { }; virtual void createOrb() override; virtual void indicateLoadingStatus(LoadingStatus loadingStatus) override; @@ -39,7 +44,7 @@ public: int parseDataFromBuffer(const QByteArray& buffer) override; - bool isInPhysicsSimulation() const { return _motionState != nullptr && _detailedMotionStates.size() > 0; } + bool isInPhysicsSimulation() const; void rebuildCollisionShape() override; void setWorkloadRegion(uint8_t region); @@ -47,9 +52,14 @@ public: bool needsPhysicsUpdate() const; btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex); + btCollisionShape* createCapsuleCollisionShape(); DetailedMotionState* createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex); + DetailedMotionState* createCapsuleMotionState(std::shared_ptr avatar); + void createDetailedMotionStates(const std::shared_ptr& avatar); std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void resetDetailedMotionStates(); + BodyLOD getBodyLOD() { return _bodyLOD; } + void computeShapeLOD(); void updateCollisionGroup(bool myAvatarCollide); @@ -62,6 +72,8 @@ protected: std::vector _detailedMotionStates; int32_t _spaceIndex { -1 }; uint8_t _workloadRegion { workload::Region::INVALID }; + BodyLOD _bodyLOD { BodyLOD::CapsuleShape }; + bool _needsReinsertion { false }; }; using OtherAvatarPointer = std::shared_ptr; From 98030435127d272e47aea68ac48918f691971c4d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 14 Jan 2019 15:40:37 -0800 Subject: [PATCH 018/104] web entity fixes --- interface/src/Application.cpp | 8 +- interface/src/ui/overlays/Web3DOverlay.cpp | 9 +- interface/src/ui/overlays/Web3DOverlay.h | 4 +- .../src/RenderableWebEntityItem.cpp | 134 +++++++----------- .../src/RenderableWebEntityItem.h | 5 +- 5 files changed, 66 insertions(+), 94 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3d0e135fed..e016b4afc7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2317,7 +2317,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; }); - render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { bool isTablet = url == TabletScriptingInterface::QML; if (htmlContent) { webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); @@ -2328,7 +2328,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (webSurface->getRootItem()) { rootItemLoadedFunctor(); } else { - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); } } else { // FIXME: the tablet should use the OffscreenQmlSurfaceCache @@ -2345,7 +2345,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (webSurface->getRootItem()) { rootItemLoadedFunctor(); } else { - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); } webSurface->load(url); cachedWebSurface = false; @@ -2354,7 +2354,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const uint8_t TABLET_FPS = 90; webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS); }); - render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { QQuickItem* rootItem = webSurface->getRootItem(); if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index de7ed45819..eb61ca7281 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -82,8 +82,7 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); - render::entities::WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); - _webSurface->resume(); + buildWebSurface(true); } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : @@ -114,12 +113,12 @@ void Web3DOverlay::destroyWebSurface() { } } -void Web3DOverlay::buildWebSurface() { +void Web3DOverlay::buildWebSurface(bool overrideWeb) { if (_webSurface) { return; } - render::entities::WebEntityRenderer::acquireWebSurface(_url, isWebContent(), _webSurface, _cachedWebSurface); + render::entities::WebEntityRenderer::acquireWebSurface(_url, overrideWeb || isWebContent(), _webSurface, _cachedWebSurface); onResizeWebSurface(); _webSurface->resume(); @@ -172,7 +171,7 @@ void Web3DOverlay::render(RenderArgs* args) { } if (!_webSurface) { - emit requestWebSurface(); + emit requestWebSurface(false); return; } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 632b9cda3f..4265c35699 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -57,7 +57,7 @@ public: Mouse }; - void buildWebSurface(); + void buildWebSurface(bool overrideWeb = false); void destroyWebSurface(); void onResizeWebSurface(); @@ -68,7 +68,7 @@ signals: void scriptEventReceived(const QVariant& message); void webEventReceived(const QVariant& message); void resizeWebSurface(); - void requestWebSurface(); + void requestWebSurface(bool overrideWeb); protected: Transform evalRenderTransform() override; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index c45f09757b..38d1320aa2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -79,14 +79,8 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); _texture->setSource(__FUNCTION__); - if (_currentWebCount < MAX_CONCURRENT_WEB_VIEWS) { - _currentWebCount++; - WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); - _contentType = ContentType::HtmlContent; - _fadeStartTime = usecTimestampNow(); - _webSurface->resume(); - qDebug() << "boop" << this << _webSurface << _webSurface->getRootItem(); - } + _contentType = ContentType::HtmlContent; + buildWebSurface(entity, ""); _timer.setInterval(MSECS_PER_SECOND); connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout); @@ -154,7 +148,7 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe bool WebEntityRenderer::needsRenderUpdate() const { if (resultWithReadLock([this] { - return _prevHasWebSurface != hasWebSurface() || _needsURLUpdate; + return !_webSurface; })) { return true; } @@ -181,30 +175,22 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene // destroy the existing surface (because surfaces don't support changing the root // object, so subsequent loads of content just overlap the existing content bool urlChanged = false; + auto newSourceURL = entity->getSourceUrl(); { - auto newSourceUrl = entity->getSourceUrl(); - auto newContentType = getContentType(newSourceUrl); + auto newContentType = getContentType(newSourceURL); ContentType currentContentType; withReadLock([&] { - urlChanged = _sourceURL != newSourceUrl; + urlChanged = _sourceURL != newSourceURL; currentContentType = _contentType; }); if (urlChanged) { withWriteLock([&] { - _needsURLUpdate = true; - _sourceURL = newSourceUrl; _contentType = newContentType; }); if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { - qDebug() << "boop2" << this << _webSurface << _webSurface->getRootItem(); destroyWebSurface(); - // If we destroyed the surface, the URL change will be implicitly handled by the re-creation - urlChanged = false; - withWriteLock([&] { - _needsURLUpdate = false; - }); } } } @@ -217,67 +203,63 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); if (_contentType == ContentType::NoContent) { - _prevHasWebSurface = false; return; } // This work must be done on the main thread - // If we couldn't create a new web surface, exit if (!_webSurface) { - qDebug() << "boop3" << this << _webSurface << _webSurface->getRootItem(); - buildWebSurface(entity); + buildWebSurface(entity, newSourceURL); } - _prevHasWebSurface = hasWebSurface(); - if (!_prevHasWebSurface) { - qDebug() << "boop4" << this << _webSurface << _webSurface->getRootItem(); - return; - } - - qDebug() << "boop6" << this << _webSurface << _webSurface->getRootItem(); - if (_needsURLUpdate && _contentType == ContentType::HtmlContent) { - qDebug() << "boop7" << this << _webSurface << _webSurface->getRootItem(); - _webSurface->getRootItem()->setProperty(URL_PROPERTY, _sourceURL); - _needsURLUpdate = false; - } - - { - auto scriptURL = entity->getScriptURL(); - if (_scriptURL != scriptURL) { - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); - _scriptURL = scriptURL; - } - } - - { - auto maxFPS = entity->getMaxFPS(); - if (_maxFPS != maxFPS) { - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity - if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(_maxFPS); + if (_webSurface && _webSurface->getRootItem()) { + if (_webSurface->getRootItem()) { + if (_contentType == ContentType::HtmlContent && urlChanged) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); + _sourceURL = newSourceURL; + } + + { + auto scriptURL = entity->getScriptURL(); + if (_scriptURL != scriptURL) { + _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); + _scriptURL = scriptURL; + } + } + + { + auto maxFPS = entity->getMaxFPS(); + if (_maxFPS != maxFPS) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity + if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(maxFPS); + } + _maxFPS = maxFPS; + } + } + + { + auto contextPosition = entity->getWorldPosition(); + if (_contextPosition != contextPosition) { + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(contextPosition)); + _contextPosition = contextPosition; + } } - _maxFPS = maxFPS; } - } - if (_contextPosition != entity->getWorldPosition()) { - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } - - void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { - withWriteLock([&] { - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); - updateModelTransformAndBound(); - _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getScaledDimensions()); + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + withWriteLock([&] { + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + _renderTransform.postScale(entity->getScaledDimensions()); + }); }); - }); + } }); } @@ -323,18 +305,14 @@ void WebEntityRenderer::doRender(RenderArgs* args) { batch.setResourceTexture(0, nullptr); } -bool WebEntityRenderer::hasWebSurface() const { - return (bool)_webSurface && _webSurface->getRootItem(); -} - -bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { +void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; - return false; + return; } ++_currentWebCount; - WebEntityRenderer::acquireWebSurface(_sourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); + WebEntityRenderer::acquireWebSurface(newSourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -344,8 +322,6 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) { emit DependencyManager::get()->webEventReceived(entityItemID, message); })); - - return _webSurface->getRootItem(); } void WebEntityRenderer::destroyWebSurface() { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 545a6c878a..4affb5819d 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -65,9 +65,8 @@ protected: private: void onTimeout(); - bool buildWebSurface(const TypedEntityPointer& entity); + void buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL); void destroyWebSurface(); - bool hasWebSurface() const; glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; int _geometryId{ 0 }; @@ -80,8 +79,6 @@ private: ContentType _contentType { ContentType::NoContent }; QSharedPointer _webSurface { nullptr }; - bool _prevHasWebSurface { false }; - bool _needsURLUpdate { false }; bool _cachedWebSurface { false }; gpu::TexturePointer _texture; From 71e7023a3e702789caee41b02fc8225714d6a57e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 15 Jan 2019 18:07:50 -0700 Subject: [PATCH 019/104] Refactor and optimizations --- interface/src/Application.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 144 +++++++++++------- interface/src/avatar/AvatarManager.h | 22 --- interface/src/avatar/DetailedMotionState.cpp | 13 +- interface/src/avatar/DetailedMotionState.h | 12 +- interface/src/avatar/MyAvatar.cpp | 19 +-- .../src/avatar/MyCharacterController.cpp | 38 +++-- interface/src/avatar/MyCharacterController.h | 6 +- interface/src/avatar/OtherAvatar.cpp | 115 +++++++++----- interface/src/avatar/OtherAvatar.h | 13 +- .../src/avatars-renderer/Avatar.cpp | 2 +- .../src/avatars-renderer/Avatar.h | 1 + libraries/physics/src/MultiSphereShape.cpp | 6 +- libraries/physics/src/MultiSphereShape.h | 9 +- libraries/physics/src/PhysicsEngine.cpp | 3 + .../physics/src/ThreadSafeDynamicsWorld.cpp | 42 +++++ .../physics/src/ThreadSafeDynamicsWorld.h | 3 + libraries/shared/src/DebugDraw.cpp | 10 ++ libraries/shared/src/DebugDraw.h | 10 ++ 19 files changed, 293 insertions(+), 177 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d2bb07501c..274b3131c9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2639,7 +2639,7 @@ Application::~Application() { auto myCharacterController = getMyAvatar()->getCharacterController(); myCharacterController->clearDetailedMotionStates(); - + myCharacterController->buildPhysicsTransaction(transaction); _physicsEngine->processTransaction(transaction); myCharacterController->handleProcessedPhysicsTransaction(transaction); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index ffefcfa4a6..80ffd32aff 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -407,6 +407,7 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact if (isInPhysics) { transaction.objectsToRemove.push_back(avatar->_motionState); avatar->_motionState = nullptr; + auto& detailedMotionStates = avatar->getDetailedMotionStates(); for (auto& mState : detailedMotionStates) { if (mState) { @@ -415,37 +416,39 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact } qDebug() << "Removing " << detailedMotionStates.size() << " detailed motion states from " << avatar->getSessionUUID(); avatar->resetDetailedMotionStates(); + } else { - if (avatar->_motionState == nullptr) { - ShapeInfo shapeInfo; - avatar->computeShapeInfo(shapeInfo); - btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); - if (shape) { - AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); - motionState->setMass(avatar->computeMass()); - avatar->_motionState = motionState; - transaction.objectsToAdd.push_back(motionState); - } + ShapeInfo shapeInfo; + avatar->computeShapeInfo(shapeInfo); + btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + if (shape) { + AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); + motionState->setMass(avatar->computeMass()); + avatar->_motionState = motionState; + transaction.objectsToAdd.push_back(motionState); + } else { + failedShapeBuilds.insert(avatar); } + if (avatar->getDetailedMotionStates().size() == 0) { avatar->createDetailedMotionStates(avatar); for (auto dMotionState : avatar->getDetailedMotionStates()) { transaction.objectsToAdd.push_back(dMotionState); } - if (avatar->_motionState == nullptr || avatar->getDetailedMotionStates().size() == 0) { - failedShapeBuilds.insert(avatar); - } } + qDebug() << "Adding " << avatar->getDetailedMotionStates().size() << " detailed motion states from " << avatar->getSessionUUID(); } } else if (isInPhysics) { transaction.objectsToChange.push_back(avatar->_motionState); + auto& detailedMotionStates = avatar->getDetailedMotionStates(); for (auto& mState : detailedMotionStates) { if (mState) { transaction.objectsToChange.push_back(mState); } } + } } _avatarsToChangeInPhysics.swap(failedShapeBuilds); @@ -656,21 +659,80 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic Q_ARG(const QVector&, avatarsToDiscard)); return result; } + + glm::vec3 rayDirectionInv = { ray.direction.x != 0 ? 1.0f / ray.direction.x : INFINITY, + ray.direction.y != 0 ? 1.0f / ray.direction.y : INFINITY, + ray.direction.z != 0 ? 1.0f / ray.direction.z : INFINITY }; float distance = (float)INT_MAX; // with FLT_MAX bullet rayTest does not return results BoxFace face = BoxFace::UNKNOWN_FACE; glm::vec3 surfaceNormal; QVariantMap extraInfo; - MyCharacterController::RayAvatarResult physicsResult = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(ray.direction), distance, QVector()); - if (physicsResult._intersect) { - result.intersects = true; - result.avatarID = physicsResult._intersectWithAvatar; - result.distance = physicsResult._distance; - result.surfaceNormal = physicsResult._intersectionNormal; - result.jointIndex = physicsResult._intersectWithJoint; - result.intersection = physicsResult._intersectionPoint; - result.extraInfo = extraInfo; - result.face = face; + std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(ray.direction), distance, QVector()); + + if (physicsResults.size() > 0) { + MyCharacterController::RayAvatarResult rayAvatarResult; + for (auto &hit : physicsResults) { + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(hit._intersectWithAvatar)) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(hit._intersectWithAvatar))) { + continue; + } + if (!hit._isBound) { + rayAvatarResult = hit; + break; + } else { + auto avatarMap = getHashCopy(); + auto avatarID = hit._intersectWithAvatar; + AvatarHash::iterator itr = avatarMap.find(avatarID); + if (itr != avatarMap.end()) { + const auto& avatar = std::static_pointer_cast(*itr); + auto &multiSpheres = avatar->getMultiSphereShapes(); + if (multiSpheres.size() > 0) { + std::vector boxHits; + for (auto i = 0; i < hit._boundJoints.size(); i++) { + assert(hit._boundJoints[i] < multiSpheres.size()); + auto &mSphere = multiSpheres[hit._boundJoints[i]]; + if (mSphere.isValid()) { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + auto &bbox = mSphere.getBoundingBox(); + if (bbox.findRayIntersection(ray.origin, ray.direction, rayDirectionInv, boundDistance, face, surfaceNormal)) { + MyCharacterController::RayAvatarResult boxHit; + boxHit._distance = boundDistance; + boxHit._intersect = true; + boxHit._intersectionNormal = surfaceNormal; + boxHit._intersectionPoint = ray.origin + boundDistance * glm::normalize(ray.direction); + boxHit._intersectWithAvatar = avatarID; + boxHit._intersectWithJoint = mSphere.getJointIndex(); + boxHits.push_back(boxHit); + } + } + } + if (boxHits.size() > 0) { + if (boxHits.size() > 1) { + std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA, + const MyCharacterController::RayAvatarResult& hitB) { + return hitA._distance < hitB._distance; + }); + } + rayAvatarResult = boxHits[0]; + break; + } + } + } + } + } + if (rayAvatarResult._intersect) { + result.intersects = true; + result.avatarID = rayAvatarResult._intersectWithAvatar; + result.distance = rayAvatarResult._distance; + result.surfaceNormal = rayAvatarResult._intersectionNormal; + result.jointIndex = rayAvatarResult._intersectWithJoint; + result.intersection = rayAvatarResult._intersectionPoint; + result.extraInfo = extraInfo; + result.face = face; + } } return result; } @@ -769,42 +831,6 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector return result; } -RayToAvatarIntersectionResult AvatarManager::findSelfRayIntersection(const PickRay& ray, - const QScriptValue& jointIndexesToInclude, - const QScriptValue& jointIndexesToDiscard) { - QVector jointsToInclude; - QVector jointsToDiscard; - qVectorIntFromScriptValue(jointIndexesToInclude, jointsToInclude); - qVectorIntFromScriptValue(jointIndexesToDiscard, jointsToDiscard); - return findSelfRayIntersectionVector(ray, jointsToInclude, jointsToDiscard); -} - -RayToAvatarIntersectionResult AvatarManager::findSelfRayIntersectionVector(const PickRay& ray, - const QVector& jointIndexesToInclude, - const QVector& jointIndexesToDiscard) { - RayToAvatarIntersectionResult result; - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(const_cast(this), "findSelfRayIntersectionVector", - Q_RETURN_ARG(RayToAvatarIntersectionResult, result), - Q_ARG(const PickRay&, ray), - Q_ARG(const QVector&, jointIndexesToInclude), - Q_ARG(const QVector&, jointIndexesToDiscard)); - return result; - } - glm::vec3 normDirection = glm::normalize(ray.direction); - auto jointCollisionResult = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(normDirection), 1.0f, jointIndexesToDiscard); - if (jointCollisionResult._intersectWithJoint > -1 && jointCollisionResult._distance > 0) { - result.intersects = true; - result.distance = jointCollisionResult._distance; - result.jointIndex = jointCollisionResult._intersectWithJoint; - result.avatarID = _myAvatar->getID(); - result.extraInfo = QVariantMap(); - result.intersection = jointCollisionResult._intersectionPoint; - result.surfaceNormal = jointCollisionResult._intersectionNormal; - } - return result; -} - // HACK float AvatarManager::getAvatarSortCoefficient(const QString& name) { if (name == "size") { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 1db04b3d9e..6717c301af 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -165,28 +165,6 @@ public: const QVector& avatarsToInclude, const QVector& avatarsToDiscard); - /**jsdoc - * @function AvatarManager.findSelfRayIntersection - * @param {PickRay} ray - * @param {Uuid[]} [jointsToInclude=[]] - * @param {Uuid[]} [jointsToDiscard=[]] - * @returns {RayToAvatarIntersectionResult} - */ - Q_INVOKABLE RayToAvatarIntersectionResult findSelfRayIntersection(const PickRay& ray, - const QScriptValue& jointIndexesToInclude = QScriptValue(), - const QScriptValue& jointIndexesToDiscard = QScriptValue()); - - /**jsdoc - * @function AvatarManager.findSelfRayIntersectionVector - * @param {PickRay} ray - * @param {Uuid[]} jointsToInclude - * @param {Uuid[]} jointsToDiscard - * @returns {RayToAvatarIntersectionResult} - */ - Q_INVOKABLE RayToAvatarIntersectionResult findSelfRayIntersectionVector(const PickRay& ray, - const QVector& jointIndexesToInclude, - const QVector& jointIndexesToDiscard); - /**jsdoc * @function AvatarManager.getAvatarSortCoefficient * @param {string} name diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index 6d983cdfa1..c08272184f 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -20,6 +20,7 @@ DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) : ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) { assert(_avatar); + _otherAvatar = std::static_pointer_cast(_avatar); _type = MOTIONSTATE_TYPE_DETAILED; } @@ -59,13 +60,8 @@ PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const { const btCollisionShape* DetailedMotionState::computeNewShape() { btCollisionShape* shape = nullptr; if (!_avatar->isMyAvatar()) { - OtherAvatarPointer otherAvatar = std::static_pointer_cast(_avatar); - if (otherAvatar) { - if (_isAvatarCapsule) { - shape = otherAvatar->createCapsuleCollisionShape(); - } else { - shape = otherAvatar->createDetailedCollisionShapeForJoint(_jointIndex); - } + if (_otherAvatar) { + shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints); } } else { std::shared_ptr myAvatar = std::static_pointer_cast(_avatar); @@ -115,7 +111,8 @@ float DetailedMotionState::getObjectAngularDamping() const { // virtual glm::vec3 DetailedMotionState::getObjectPosition() const { - return _isAvatarCapsule ? _avatar->getFitBounds().calcCenter() : _avatar->getJointPosition(_jointIndex); + auto bodyLOD = _otherAvatar->getBodyLOD(); + return bodyLOD == OtherAvatar::BodyLOD::Sphere ? _avatar->getFitBounds().calcCenter() : _avatar->getJointPosition(_jointIndex); } // virtual diff --git a/interface/src/avatar/DetailedMotionState.h b/interface/src/avatar/DetailedMotionState.h index 2671f9d75e..a9b4b4bb64 100644 --- a/interface/src/avatar/DetailedMotionState.h +++ b/interface/src/avatar/DetailedMotionState.h @@ -68,9 +68,10 @@ public: virtual float getMass() const override; void forceActive(); - QUuid getAvatarID() { return _avatar->getID(); } - int getJointIndex() { return _jointIndex; } - void setIsAvatarCapsule(bool isAvatarCapsule) { _isAvatarCapsule = isAvatarCapsule; } + QUuid getAvatarID() const { return _avatar->getID(); } + int getJointIndex() const { return _jointIndex; } + void setIsBound(bool isBound, std::vector boundJoints) { _isBound = isBound; _boundJoints = boundJoints; } + bool getIsBound(std::vector& boundJoints) const { boundJoints = _boundJoints; return _isBound; } friend class AvatarManager; friend class Avatar; @@ -91,8 +92,9 @@ protected: uint32_t _dirtyFlags; int _jointIndex { -1 }; - - bool _isAvatarCapsule { false }; + OtherAvatarPointer _otherAvatar { nullptr }; + bool _isBound { false }; + std::vector _boundJoints; }; #endif // hifi_DetailedMotionState_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3be23f2b56..4ae89ea2a1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2606,15 +2606,16 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { if (_skeletonModel && _skeletonModel->isLoaded()) { const Rig& rig = _skeletonModel->getRig(); const HFMModel& hfmModel = _skeletonModel->getHFMModel(); - for (int i = 0; i < rig.getJointStateCount(); i++) { - AnimPose jointPose; - rig.getAbsoluteJointPoseInRigFrame(i, jointPose); - const HFMJointShapeInfo& shapeInfo = hfmModel.joints[i].shapeInfo; - const AnimPose pose = rigToWorldPose * jointPose; - for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) { - glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]); - glm::vec3 pointB = pose.xformPoint(shapeInfo.debugLines[2 * j + 1]); - DebugDraw::getInstance().drawRay(pointA, pointB, DEBUG_COLORS[i % NUM_DEBUG_COLORS]); + int jointCount = rig.getJointStateCount(); + if (jointCount == _multiSphereShapes.size()) { + int count = 0; + for (int i = 0; i < jointCount; i++) { + AnimPose jointPose; + rig.getAbsoluteJointPoseInRigFrame(i, jointPose); + const AnimPose pose = rigToWorldPose * jointPose; + auto &multiSphere = _multiSphereShapes[i]; + auto debugLines = multiSphere.getDebugLines(); + DebugDraw::getInstance().drawRays(debugLines, DEBUG_COLORS[count++ % NUM_DEBUG_COLORS], pose.trans(), pose.rot()); } } } diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index ba019e60b6..a9c3c29051 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -429,9 +429,9 @@ public: } }; -MyCharacterController::RayAvatarResult MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, - const QVector& jointsToExclude) const { - RayAvatarResult result; +std::vector MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction, + const btScalar& length, const QVector& jointsToExclude) const { + std::vector foundAvatars; if (_dynamicsWorld) { btVector3 end = origin + length * direction; ClosestDetailed rayCallback = ClosestDetailed(); @@ -439,28 +439,26 @@ MyCharacterController::RayAvatarResult MyCharacterController::rayTest(const btVe rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest; _dynamicsWorld->rayTest(origin, end, rayCallback); if (rayCallback.m_hitFractions.size() > 0) { - int minIndex = 0; - float hitFraction = rayCallback.m_hitFractions[0]; - for (auto i = 1; i < rayCallback.m_hitFractions.size(); i++) { - if (hitFraction > rayCallback.m_hitFractions[i]) { - hitFraction = rayCallback.m_hitFractions[i]; - minIndex = i; - } - } - auto object = rayCallback.m_collisionObjects[minIndex]; - ObjectMotionState* motionState = static_cast(object->getUserPointer()); - if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) { - DetailedMotionState* detailedMotionState = dynamic_cast(motionState); - if (detailedMotionState) { + for (int i = 0; i < rayCallback.m_hitFractions.size(); i++) { + auto object = rayCallback.m_collisionObjects[i]; + ObjectMotionState* motionState = static_cast(object->getUserPointer()); + if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) { + DetailedMotionState* detailedMotionState = dynamic_cast(motionState); + MyCharacterController::RayAvatarResult result; result._intersect = true; result._intersectWithAvatar = detailedMotionState->getAvatarID(); - result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[minIndex]); - result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[minIndex]); - result._distance = length * hitFraction; + result._intersectionPoint = bulletToGLM(rayCallback.m_hitPointWorld[i]); + result._intersectionNormal = bulletToGLM(rayCallback.m_hitNormalWorld[i]); + result._distance = length * rayCallback.m_hitFractions[i]; result._intersectWithJoint = detailedMotionState->getJointIndex(); + result._isBound = detailedMotionState->getIsBound(result._boundJoints); + foundAvatars.push_back(result); } } + std::sort(foundAvatars.begin(), foundAvatars.end(), [](const RayAvatarResult& resultA, const RayAvatarResult& resultB) { + return resultA._distance < resultB._distance; + }); } } - return result; + return foundAvatars; } \ No newline at end of file diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index f5a510e5b5..3c727d017c 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -56,14 +56,16 @@ public: struct RayAvatarResult { bool _intersect { false }; + bool _isBound { false }; QUuid _intersectWithAvatar; int _intersectWithJoint { -1 }; float _distance { 0.0f }; glm::vec3 _intersectionPoint; glm::vec3 _intersectionNormal; + std::vector _boundJoints; }; - RayAvatarResult rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, - const QVector& jointsToExclude) const; + std::vector rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, + const QVector& jointsToExclude) const; protected: void initRayShotgun(const btCollisionWorld* world); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 6fbdcf7c1f..c9f0d20d0b 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -112,43 +112,62 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { return bytesRead; } -btCollisionShape* OtherAvatar::createDetailedCollisionShapeForJoint(int jointIndex) { +btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector& boundJoints) { ShapeInfo shapeInfo; - computeDetailedShapeInfo(shapeInfo, jointIndex); + isBound = false; + auto jointName = jointIndex > -1 && jointIndex < _multiSphereShapes.size() ? _multiSphereShapes[jointIndex].getJointName() : ""; + switch (_bodyLOD) { + case BodyLOD::Sphere: + shapeInfo.setSphere(0.5f * getFitBounds().getDimensions().y); + boundJoints.clear(); + for (auto &spheres : _multiSphereShapes) { + if (spheres.isValid()) { + boundJoints.push_back(spheres.getJointIndex()); + } + } + isBound = true; + break; + case BodyLOD::MultiSphereLow: + if (jointName.contains("RightHand", Qt::CaseInsensitive) || jointName.contains("LeftHand", Qt::CaseInsensitive)) { + if (jointName.size() <= QString("RightHand").size()) { + AABox handBound; + for (auto &spheres : _multiSphereShapes) { + if (spheres.isValid() && spheres.getJointName().contains(jointName, Qt::CaseInsensitive)) { + boundJoints.push_back(spheres.getJointIndex()); + handBound += spheres.getBoundingBox(); + } + } + shapeInfo.setSphere(0.5f * handBound.getLargestDimension()); + glm::vec3 jointPosition; + glm::quat jointRotation; + _skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition); + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation); + glm::vec3 positionOffset = glm::inverse(jointRotation) * (handBound.calcCenter() - jointPosition); + shapeInfo.setOffset(positionOffset); + isBound = true; + } + break; + } + case BodyLOD::MultiSphereHigh: + computeDetailedShapeInfo(shapeInfo, jointIndex); + break; + default: + break; + } if (shapeInfo.getType() != SHAPE_TYPE_NONE) { - btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); - return shape; + return const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); } return nullptr; } -btCollisionShape* OtherAvatar::createCapsuleCollisionShape() { - ShapeInfo shapeInfo; - computeShapeInfo(shapeInfo); - shapeInfo.setOffset(glm::vec3(0.0f)); - if (shapeInfo.getType() != SHAPE_TYPE_NONE) { - btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); - return shape; - } - return nullptr; -} - -DetailedMotionState* OtherAvatar::createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex) { - auto shape = createDetailedCollisionShapeForJoint(jointIndex); +DetailedMotionState* OtherAvatar::createMotionState(std::shared_ptr avatar, int jointIndex) { + bool isBound = false; + std::vector boundJoints; + btCollisionShape* shape = createCollisionShape(jointIndex, isBound, boundJoints); if (shape) { DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex); motionState->setMass(computeMass()); - return motionState; - } - return nullptr; -} - -DetailedMotionState* OtherAvatar::createCapsuleMotionState(std::shared_ptr avatar) { - auto shape = createCapsuleCollisionShape(); - if (shape) { - DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, -1); - motionState->setIsAvatarCapsule(true); - motionState->setMass(computeMass()); + motionState->setIsBound(isBound, boundJoints); return motionState; } return nullptr; @@ -178,11 +197,27 @@ void OtherAvatar::setWorkloadRegion(uint8_t region) { } void OtherAvatar::computeShapeLOD() { - auto newBodyLOD = (_workloadRegion < workload::Region::R3 && !isDead()) ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape; - if (newBodyLOD != _bodyLOD) { - _bodyLOD = newBodyLOD; + // auto newBodyLOD = _workloadRegion < workload::Region::R3 ? BodyLOD::MultiSphereShapes : BodyLOD::CapsuleShape; + // auto newBodyLOD = BodyLOD::CapsuleShape; + BodyLOD newLOD; + switch (_workloadRegion) { + case workload::Region::R1: + newLOD = BodyLOD::MultiSphereHigh; + break; + case workload::Region::R2: + newLOD = BodyLOD::MultiSphereLow; + break; + case workload::Region::UNKNOWN: + case workload::Region::INVALID: + case workload::Region::R3: + default: + newLOD = BodyLOD::Sphere; + break; + } + if (newLOD != _bodyLOD) { + _bodyLOD = newLOD; if (isInPhysicsSimulation()) { - qDebug() << "Changing to body LOD " << (_bodyLOD == BodyLOD::MultiSphereShapes ? "MultiSpheres" : "Capsule"); + qDebug() << "Changing to body LOD " << newLOD; _needsReinsertion = true; } } @@ -193,7 +228,7 @@ bool OtherAvatar::isInPhysicsSimulation() const { } bool OtherAvatar::shouldBeInPhysicsSimulation() const { - return !(isInPhysicsSimulation() && _needsReinsertion); + return !isDead() && !(isInPhysicsSimulation() && _needsReinsertion); } bool OtherAvatar::needsPhysicsUpdate() const { @@ -228,18 +263,18 @@ void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { void OtherAvatar::createDetailedMotionStates(const std::shared_ptr& avatar){ auto& detailedMotionStates = getDetailedMotionStates(); - if (_bodyLOD == BodyLOD::MultiSphereShapes) { + if (_bodyLOD == BodyLOD::Sphere) { + auto dMotionState = createMotionState(avatar, -1); + if (dMotionState) { + detailedMotionStates.push_back(dMotionState); + } + } else { for (int i = 0; i < getJointCount(); i++) { - auto dMotionState = createDetailedMotionStateForJoint(avatar, i); + auto dMotionState = createMotionState(avatar, i); if (dMotionState) { detailedMotionStates.push_back(dMotionState); } } - } else if (_bodyLOD == BodyLOD::CapsuleShape) { - auto dMotionState = createCapsuleMotionState(avatar); - if (dMotionState) { - detailedMotionStates.push_back(dMotionState); - } } _needsReinsertion = false; } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 782812b2cb..7b1b214c14 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -28,8 +28,9 @@ public: virtual ~OtherAvatar(); enum BodyLOD { - CapsuleShape, - MultiSphereShapes + Sphere = 0, + MultiSphereLow, // No finger joints + MultiSphereHigh // All joints }; virtual void instantiableAvatar() override { }; @@ -51,10 +52,8 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; - btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex); - btCollisionShape* createCapsuleCollisionShape(); - DetailedMotionState* createDetailedMotionStateForJoint(std::shared_ptr avatar, int jointIndex); - DetailedMotionState* createCapsuleMotionState(std::shared_ptr avatar); + btCollisionShape* createCollisionShape(int jointIndex, bool& isBound, std::vector& boundJoints); + DetailedMotionState* createMotionState(std::shared_ptr avatar, int jointIndex); void createDetailedMotionStates(const std::shared_ptr& avatar); std::vector& getDetailedMotionStates() { return _detailedMotionStates; } void resetDetailedMotionStates(); @@ -72,7 +71,7 @@ protected: std::vector _detailedMotionStates; int32_t _spaceIndex { -1 }; uint8_t _workloadRegion { workload::Region::INVALID }; - BodyLOD _bodyLOD { BodyLOD::CapsuleShape }; + BodyLOD _bodyLOD { BodyLOD::Sphere }; bool _needsReinsertion { false }; }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 098249902b..75f91babac 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1689,7 +1689,7 @@ void Avatar::computeMultiSphereShapes() { } auto jointName = rig.nameOfJoint(i).toUpper(); MultiSphereShape multiSphereShape; - if (multiSphereShape.computeMultiSphereShape(jointName, btPoints)) { + if (multiSphereShape.computeMultiSphereShape(i, jointName, btPoints)) { multiSphereShape.calculateDebugLines(); multiSphereShape.setScale(_targetScale); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a63c9b835a..2f14766245 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -320,6 +320,7 @@ public: virtual void computeShapeInfo(ShapeInfo& shapeInfo); virtual void computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex); + void getCapsule(glm::vec3& start, glm::vec3& end, float& radius); float computeMass(); /**jsdoc diff --git a/libraries/physics/src/MultiSphereShape.cpp b/libraries/physics/src/MultiSphereShape.cpp index f361d67656..30fd4b25ea 100644 --- a/libraries/physics/src/MultiSphereShape.cpp +++ b/libraries/physics/src/MultiSphereShape.cpp @@ -126,9 +126,11 @@ void MultiSphereShape::filterUniquePoints(const std::vector& kdop, st } } -bool MultiSphereShape::computeMultiSphereShape(const QString& name, const std::vector& kdop, float scale) { +bool MultiSphereShape::computeMultiSphereShape(int jointIndex, const QString& name, const std::vector& kdop, float scale) { _scale = scale; - _mode = getExtractionModeByName(name); + _jointIndex = jointIndex; + _name = name; + _mode = getExtractionModeByName(_name); if (_mode == CollisionShapeExtractionMode::None || kdop.size() < 4 || kdop.size() > 200) { return false; } diff --git a/libraries/physics/src/MultiSphereShape.h b/libraries/physics/src/MultiSphereShape.h index d942d107b1..e5f19ba91a 100644 --- a/libraries/physics/src/MultiSphereShape.h +++ b/libraries/physics/src/MultiSphereShape.h @@ -74,12 +74,16 @@ const std::vector CORNER_SIGNS = { class MultiSphereShape { public: MultiSphereShape() {}; - bool computeMultiSphereShape(const QString& name, const std::vector& points, float scale = 1.0f); + bool computeMultiSphereShape(int jointIndex, const QString& name, const std::vector& points, float scale = 1.0f); void calculateDebugLines(); const std::vector& getSpheresData() const { return _spheres; } const std::vector>& getDebugLines() const { return _debugLines; } void setScale(float scale); AABox& updateBoundingBox(const glm::vec3& position, const glm::quat& rotation); + const AABox& getBoundingBox() const { return _boundingBox; } + int getJointIndex() const { return _jointIndex; } + QString getJointName() const { return _name; } + bool isValid() const { return _spheres.size() > 0; } private: CollisionShapeExtractionMode getExtractionModeByName(const QString& name); @@ -94,6 +98,9 @@ private: void connectEdges(std::vector>& outLines, const std::vector& edge1, const std::vector& edge2, bool reverse = false); void connectSpheres(int index1, int index2, bool onlyEdges = false); + + int _jointIndex { -1 }; + QString _name; std::vector _spheres; std::vector> _debugLines; CollisionShapeExtractionMode _mode; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index bf6e2463e5..cb2823cdf1 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -328,6 +328,9 @@ void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { _activeStaticBodies.insert(object->getRigidBody()); } + if (object->getType() == MOTIONSTATE_TYPE_AVATAR) { + _dynamicsWorld->updateSingleAabb(object->_body); + } } // activeStaticBodies have changed (in an Easy way) and need their Aabbs updated // but we've configured Bullet to NOT update them automatically (for improved performance) diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 17a52f7cd9..816cab788a 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -172,3 +172,45 @@ void ThreadSafeDynamicsWorld::saveKinematicState(btScalar timeStep) { } } } + +void ThreadSafeDynamicsWorld::drawConnectedSpheres(btIDebugDraw* drawer, btScalar radius1, btScalar radius2, const btVector3& position1, const btVector3& position2, const btVector3& color) { + int stepDegrees = 30; + btVector3 direction = position2 - position1; + btVector3 xAxis = direction.cross(btVector3(0.0f, 1.0f, 0.0f)); + xAxis = xAxis.length() < EPSILON ? btVector3(1.0f, 0.0f, 0.0f) : xAxis.normalize(); + btVector3 zAxis = xAxis.cross(btVector3(0.0f, 1.0f, 0.0f)); + zAxis = (direction.normalize().getY() < EPSILON) ? btVector3(0.0f, 1.0f, 0.0f) : zAxis.normalize(); + for (int i = 0; i < 360; i += stepDegrees) { + float x1 = btSin(btScalar(i)*SIMD_RADS_PER_DEG) * radius1; + float z1 = btCos(btScalar(i)*SIMD_RADS_PER_DEG) * radius1; + float x2 = btSin(btScalar(i)*SIMD_RADS_PER_DEG) * radius2; + float z2 = btCos(btScalar(i)*SIMD_RADS_PER_DEG) * radius2; + + btVector3 addVector1 = xAxis * x1 + zAxis * z1; + btVector3 addVector2 = xAxis * x2 + zAxis * z2; + drawer->drawLine(position1 + addVector1, position2 + addVector2, color); + } +} + +void ThreadSafeDynamicsWorld::debugDrawObject(const btTransform& worldTransform, const btCollisionShape* shape, const btVector3& color) { + btCollisionWorld::debugDrawObject(worldTransform, shape, color); + if (shape->getShapeType() == MULTI_SPHERE_SHAPE_PROXYTYPE) { + const btMultiSphereShape* multiSphereShape = static_cast(shape); + for (int i = multiSphereShape->getSphereCount() - 1; i >= 0; i--) { + btTransform sphereTransform1, sphereTransform2; + sphereTransform1.setIdentity(); + sphereTransform2.setIdentity(); + int sphereIndex1 = i; + int sphereIndex2 = i > 0 ? i - 1 : multiSphereShape->getSphereCount() - 1; + sphereTransform1.setOrigin(multiSphereShape->getSpherePosition(sphereIndex1)); + sphereTransform2.setOrigin(multiSphereShape->getSpherePosition(sphereIndex2)); + sphereTransform1 = worldTransform * sphereTransform1; + sphereTransform2 = worldTransform * sphereTransform2; + getDebugDrawer()->drawSphere(multiSphereShape->getSphereRadius(sphereIndex1), sphereTransform1, color); + drawConnectedSpheres(getDebugDrawer(), multiSphereShape->getSphereRadius(sphereIndex1), multiSphereShape->getSphereRadius(sphereIndex2), sphereTransform1.getOrigin(), sphereTransform2.getOrigin(), color); + } + } else { + btCollisionWorld::debugDrawObject(worldTransform, shape, color); + } +} + diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.h b/libraries/physics/src/ThreadSafeDynamicsWorld.h index d8cee4d2de..021142d91e 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.h +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.h @@ -53,10 +53,13 @@ public: const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; } void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); } + virtual void debugDrawObject(const btTransform& worldTransform, const btCollisionShape* shape, const btVector3& color) override; private: // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState() void synchronizeMotionState(btRigidBody* body); + void drawConnectedSpheres(btIDebugDraw* drawer, btScalar radius1, btScalar radius2, const btVector3& position1, + const btVector3& position2, const btVector3& color); VectorOfMotionStates _changedMotionStates; VectorOfMotionStates _deactivatedStates; diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp index f17671da4d..1b2418f7c7 100644 --- a/libraries/shared/src/DebugDraw.cpp +++ b/libraries/shared/src/DebugDraw.cpp @@ -74,3 +74,13 @@ void DebugDraw::clearRays() { Lock lock(_mapMutex); _rays.clear(); } + +void DebugDraw::drawRays(const std::vector>& lines, + const glm::vec4& color, const glm::vec3& translation, const glm::quat& rotation) { + Lock lock(_mapMutex); + for (std::pair line : lines) { + auto point1 = translation + rotation * line.first; + auto point2 = translation + rotation * line.second; + _rays.push_back(Ray(point1, point2, color)); + } +} \ No newline at end of file diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 7dd19415c9..81acbf554c 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -47,6 +47,16 @@ public: * @param {Vec4} color - color of line, each component should be in the zero to one range. x = red, y = blue, z = green, w = alpha. */ Q_INVOKABLE void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color); + + /**jsdoc + * Draws a line in world space, but it will only be visible for a single frame. + * @function DebugDraw.drawRay + * @param {Vec3} start - start position of line in world space. + * @param {Vec3} end - end position of line in world space. + * @param {Vec4} color - color of line, each component should be in the zero to one range. x = red, y = blue, z = green, w = alpha. + */ + Q_INVOKABLE void drawRays(const std::vector>& lines, const glm::vec4& color, + const glm::vec3& translation = glm::vec3(0.0f, 0.0f, 0.0f), const glm::quat& rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)); /**jsdoc * Adds a debug marker to the world. This marker will be drawn every frame until it is removed with DebugDraw.removeMarker. From 67cc5bd7f2e9d71446261ed7fd30060d4ca80f58 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 16 Jan 2019 10:52:34 -0700 Subject: [PATCH 020/104] Fix warnings and wrong shapes position for myAvatar --- interface/src/avatar/AvatarManager.cpp | 2 +- interface/src/avatar/DetailedMotionState.cpp | 19 ++++++++++++++----- interface/src/avatar/MyAvatar.cpp | 13 +------------ interface/src/avatar/MyAvatar.h | 2 -- interface/src/avatar/OtherAvatar.cpp | 7 +++++-- .../src/avatars-renderer/Avatar.cpp | 6 +++--- libraries/physics/src/PhysicsEngine.cpp | 5 +---- 7 files changed, 25 insertions(+), 29 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 80ffd32aff..53f8a0bc7f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -689,7 +689,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic auto &multiSpheres = avatar->getMultiSphereShapes(); if (multiSpheres.size() > 0) { std::vector boxHits; - for (auto i = 0; i < hit._boundJoints.size(); i++) { + for (size_t i = 0; i < hit._boundJoints.size(); i++) { assert(hit._boundJoints[i] < multiSpheres.size()); auto &mSphere = multiSpheres[hit._boundJoints[i]]; if (mSphere.isValid()) { diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index c08272184f..4d8e3d5ec7 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -20,7 +20,9 @@ DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) : ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) { assert(_avatar); - _otherAvatar = std::static_pointer_cast(_avatar); + if (!_avatar->isMyAvatar()) { + _otherAvatar = std::static_pointer_cast(_avatar); + } _type = MOTIONSTATE_TYPE_DETAILED; } @@ -60,12 +62,14 @@ PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const { const btCollisionShape* DetailedMotionState::computeNewShape() { btCollisionShape* shape = nullptr; if (!_avatar->isMyAvatar()) { - if (_otherAvatar) { + if (_otherAvatar != nullptr) { shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints); } } else { std::shared_ptr myAvatar = std::static_pointer_cast(_avatar); - shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex); + if (myAvatar) { + shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex); + } } return shape; } @@ -111,8 +115,13 @@ float DetailedMotionState::getObjectAngularDamping() const { // virtual glm::vec3 DetailedMotionState::getObjectPosition() const { - auto bodyLOD = _otherAvatar->getBodyLOD(); - return bodyLOD == OtherAvatar::BodyLOD::Sphere ? _avatar->getFitBounds().calcCenter() : _avatar->getJointPosition(_jointIndex); + if (_otherAvatar != nullptr) { + auto bodyLOD = _otherAvatar->getBodyLOD(); + if (bodyLOD == OtherAvatar::BodyLOD::Sphere) { + return _avatar->getFitBounds().calcCenter(); + } + } + return _avatar->getJointPosition(_jointIndex); } // virtual diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 621fe1d479..670444baf0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -426,16 +426,6 @@ void MyAvatar::clearIKJointLimitHistory() { _skeletonModel->getRig().clearIKJointLimitHistory(); } -QVariantMap MyAvatar::getBoundingBox() { - QVariantMap bbox; - auto avatarBBox = getFitBounds(); - auto center = avatarBBox.calcCenter(); - auto dimensions = avatarBBox.getDimensions(); - bbox["center"] = vec3toVariant(center); - bbox["dimensions"] = vec3toVariant(dimensions); - return bbox; -} - void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { assert(QThread::currentThread() == thread()); @@ -3073,9 +3063,8 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { if (_skeletonModel && _skeletonModel->isLoaded()) { const Rig& rig = _skeletonModel->getRig(); - const HFMModel& hfmModel = _skeletonModel->getHFMModel(); int jointCount = rig.getJointStateCount(); - if (jointCount == _multiSphereShapes.size()) { + if (jointCount == (int)_multiSphereShapes.size()) { int count = 0; for (int i = 0; i < jointCount; i++) { AnimPose jointPose; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0cb5d04c02..f107a488a6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -312,8 +312,6 @@ public: */ Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe - Q_INVOKABLE QVariantMap getBoundingBox(); - void update(float deltaTime); virtual void postUpdate(float deltaTime, const render::ScenePointer& scene) override; void preDisplaySide(const RenderArgs* renderArgs); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 67e577ad6b..53fc826fb0 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -123,7 +123,10 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector& boundJoints) { ShapeInfo shapeInfo; isBound = false; - auto jointName = jointIndex > -1 && jointIndex < _multiSphereShapes.size() ? _multiSphereShapes[jointIndex].getJointName() : ""; + QString jointName = ""; + if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) { + jointName = _multiSphereShapes[jointIndex].getJointName(); + } switch (_bodyLOD) { case BodyLOD::Sphere: shapeInfo.setSphere(0.5f * getFitBounds().getDimensions().y); @@ -241,7 +244,7 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const { bool OtherAvatar::needsPhysicsUpdate() const { constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP; - return (_needsReinsertion || _motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)); + return (_needsReinsertion || (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST))); } void OtherAvatar::rebuildCollisionShape() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 09cd7d5176..f2252b2aa3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1440,7 +1440,7 @@ void Avatar::computeMultiSphereShapes() { std::vector btPoints; int lineCount = (int)shapeInfo.debugLines.size(); btPoints.reserve(lineCount); - for (size_t j = 0; j < lineCount; j++) { + for (int j = 0; j < lineCount; j++) { const glm::vec3 &point = shapeInfo.debugLines[j]; auto rigPoint = scale * point; btVector3 btPoint = glmToBullet(rigPoint); @@ -1458,7 +1458,7 @@ void Avatar::computeMultiSphereShapes() { void Avatar::updateFitBoundingBox() { _fitBoundingBox = AABox(); - if (getJointCount() == _multiSphereShapes.size()) { + if (getJointCount() == (int)_multiSphereShapes.size()) { for (int i = 0; i < getJointCount(); i++) { auto &shape = _multiSphereShapes[i]; glm::vec3 jointPosition; @@ -1650,7 +1650,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { } void Avatar::computeDetailedShapeInfo(ShapeInfo& shapeInfo, int jointIndex) { - if (jointIndex > -1 && jointIndex < _multiSphereShapes.size()) { + if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) { auto& data = _multiSphereShapes[jointIndex].getSpheresData(); std::vector positions; std::vector radiuses; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index cb2823cdf1..a571a81109 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -327,10 +327,7 @@ void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) } if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { _activeStaticBodies.insert(object->getRigidBody()); - } - if (object->getType() == MOTIONSTATE_TYPE_AVATAR) { - _dynamicsWorld->updateSingleAabb(object->_body); - } + } } // activeStaticBodies have changed (in an Easy way) and need their Aabbs updated // but we've configured Bullet to NOT update them automatically (for improved performance) From 328809f008a14c410f382e130f413b83ab7843b3 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 16 Jan 2019 11:10:38 -0800 Subject: [PATCH 021/104] More robust approximation of distance attenuation at extreme settings. Fixes artifacts that occur when attenuation zone coef = 1.0 and distance > 32m. --- libraries/shared/src/AudioHelpers.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/AudioHelpers.h b/libraries/shared/src/AudioHelpers.h index 1dcc11af0c..8f7f5f7f33 100644 --- a/libraries/shared/src/AudioHelpers.h +++ b/libraries/shared/src/AudioHelpers.h @@ -43,8 +43,9 @@ static inline float fastLog2f(float x) { } // -// for -127 <= x < 128, returns exp2(x) -// otherwise, returns undefined +// for -126 <= x < 128, returns exp2(x) +// for x < -126, returns 0 +// for x >= 128, returns undefined // // rel |error| < 9e-6, smooth (exact for x=N) // @@ -60,6 +61,7 @@ static inline float fastExp2f(float x) { x -= xi.i; // construct exp2(xi) as a float + xi.i &= ~(xi.i >> 31); // MAX(xi.i, 0) xi.i <<= IEEE754_MANT_BITS; // polynomial for exp2(x) over x=[0,1] From 7cade1e3543e01936edaaab1bb0eb173e676a539 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 16 Jan 2019 12:16:20 -0800 Subject: [PATCH 022/104] Clamp attenuation zone coefficients to a more reasonable limit of -60dB per doubling of distance --- assignment-client/src/audio/AudioMixerSlave.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index b699970560..3caa5584da 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -771,7 +771,8 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo } } // translate the zone setting to gain per log2(distance) - float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, EPSILON, 1.0f); + const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance) + float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f); // calculate the attenuation using the distance to this node // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF From 611998f799680e294ef44ce3f1289580b210e9e6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Jan 2019 13:34:10 -0800 Subject: [PATCH 023/104] add pulse group properties to shapes, particles, text, web, image, and grid --- .../src/RenderableEntityItem.cpp | 31 ++- .../src/RenderableEntityItem.h | 4 + .../src/RenderableGridEntityItem.cpp | 15 +- .../src/RenderableGridEntityItem.h | 1 + .../src/RenderableImageEntityItem.cpp | 16 +- .../src/RenderableImageEntityItem.h | 1 + .../RenderableParticleEffectEntityItem.cpp | 24 +- .../src/RenderableParticleEffectEntityItem.h | 2 + .../src/RenderableShapeEntityItem.cpp | 10 + .../src/RenderableShapeEntityItem.h | 1 + .../src/RenderableTextEntityItem.cpp | 43 +-- .../src/RenderableTextEntityItem.h | 2 + .../src/RenderableWebEntityItem.cpp | 8 +- .../src/RenderableWebEntityItem.h | 1 + libraries/entities/src/EntityItem.cpp | 4 +- .../entities/src/EntityItemProperties.cpp | 57 ++++ libraries/entities/src/EntityItemProperties.h | 2 + .../entities/src/EntityItemPropertiesMacros.h | 30 ++- libraries/entities/src/EntityPropertyFlags.h | 7 +- libraries/entities/src/GridEntityItem.cpp | 27 +- libraries/entities/src/GridEntityItem.h | 8 +- libraries/entities/src/ImageEntityItem.cpp | 27 +- libraries/entities/src/ImageEntityItem.h | 7 +- .../entities/src/ParticleEffectEntityItem.cpp | 25 ++ .../entities/src/ParticleEffectEntityItem.h | 3 + libraries/entities/src/PulsePropertyGroup.cpp | 249 ++++++++++++++++++ libraries/entities/src/PulsePropertyGroup.h | 99 +++++++ libraries/entities/src/ShapeEntityItem.cpp | 27 +- libraries/entities/src/ShapeEntityItem.h | 7 +- libraries/entities/src/TextEntityItem.cpp | 33 ++- libraries/entities/src/TextEntityItem.h | 7 +- libraries/entities/src/WebEntityItem.cpp | 27 +- libraries/entities/src/WebEntityItem.h | 7 +- libraries/networking/src/udt/PacketHeaders.h | 1 + libraries/octree/src/OctreePacketData.h | 2 + libraries/shared/src/PulseMode.cpp | 25 ++ libraries/shared/src/PulseMode.h | 41 +++ 37 files changed, 824 insertions(+), 57 deletions(-) create mode 100644 libraries/entities/src/PulsePropertyGroup.cpp create mode 100644 libraries/entities/src/PulsePropertyGroup.h create mode 100644 libraries/shared/src/PulseMode.cpp create mode 100644 libraries/shared/src/PulseMode.h diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 53e62ee35f..140fe897af 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,7 +141,7 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } -EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { +EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity), _created(entity->getCreated()) { connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { _needsRenderUpdate = true; emit requestRenderUpdate(); @@ -468,3 +468,32 @@ void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const st std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); } + +glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start) { + if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) { + return color; + } + + float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND); + float pulse = 0.5f * (glm::cos(t * (2.0f * M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); + float outPulse = (1.0f - pulse); + + glm::vec4 result = color; + if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) { + result.r *= pulse; + result.g *= pulse; + result.b *= pulse; + } else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) { + result.r *= outPulse; + result.g *= outPulse; + result.b *= outPulse; + } + + if (pulseProperties.getAlphaMode() == PulseMode::IN_PHASE) { + result.a *= pulse; + } else if (pulseProperties.getAlphaMode() == PulseMode::OUT_PHASE) { + result.a *= outPulse; + } + + return result; +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index fde63f78fa..d5e236a76a 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -62,6 +62,8 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override { return scriptable::ScriptableModelBase(); } + static glm::vec4 calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start); + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -151,6 +153,8 @@ protected: std::unordered_map _materials; std::mutex _materialsLock; + quint64 _created; + private: // The base class relies on comparing the model transform to the entity transform in order // to trigger an update, so the member must not be visible to derived classes as a modifiable diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index 4576358699..f05ba35c79 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -26,7 +26,7 @@ GridEntityRenderer::~GridEntityRenderer() { } bool GridEntityRenderer::isTransparent() const { - return Parent::isTransparent() || _alpha < 1.0f; + return Parent::isTransparent() || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; } bool GridEntityRenderer::needsRenderUpdate() const { @@ -55,6 +55,10 @@ bool GridEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + return false; }); @@ -65,6 +69,7 @@ void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen withWriteLock([&] { _color = entity->getColor(); _alpha = entity->getAlpha(); + _pulseProperties = entity->getPulseProperties(); _followCamera = entity->getFollowCamera(); _majorGridEvery = entity->getMajorGridEvery(); @@ -105,11 +110,12 @@ ShapeKey GridEntityRenderer::getShapeKey() { } void GridEntityRenderer::doRender(RenderArgs* args) { - glm::u8vec3 color; + glm::vec4 color; glm::vec3 dimensions; Transform renderTransform; withReadLock([&] { - color = _color; + color = glm::vec4(toGlm(_color), _alpha); + color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); dimensions = _dimensions; renderTransform = _renderTransform; }); @@ -141,12 +147,11 @@ void GridEntityRenderer::doRender(RenderArgs* args) { float majorGridColDivisions = dimensions.y / _majorGridEvery; float minorGridRowDivisions = dimensions.x / _minorGridEvery; float minorGridColDivisions = dimensions.y / _minorGridEvery; - glm::vec4 gridColor(toGlm(color), _alpha); const float MINOR_GRID_EDGE = 0.0025f; const float MAJOR_GRID_EDGE = 0.005f; DependencyManager::get()->renderGrid(*batch, minCorner, maxCorner, minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE, majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE, - gridColor, _geometryId); + color, _geometryId); } \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.h b/libraries/entities-renderer/src/RenderableGridEntityItem.h index 23f1f03d0d..45124fdb94 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.h +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.h @@ -36,6 +36,7 @@ private: glm::u8vec3 _color; float _alpha; + PulsePropertyGroup _pulseProperties; bool _followCamera; uint32_t _majorGridEvery; diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index c1d6d3211d..c565eb1f0a 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -26,7 +26,7 @@ ImageEntityRenderer::~ImageEntityRenderer() { } bool ImageEntityRenderer::isTransparent() const { - return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f; + return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; } bool ImageEntityRenderer::needsRenderUpdate() const { @@ -71,6 +71,10 @@ bool ImageEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + return false; }); @@ -97,6 +101,7 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _color = entity->getColor(); _alpha = entity->getAlpha(); + _pulseProperties = entity->getPulseProperties(); if (!_textureIsLoaded && _texture && _texture->isLoaded()) { _textureIsLoaded = true; @@ -135,13 +140,14 @@ ShapeKey ImageEntityRenderer::getShapeKey() { void ImageEntityRenderer::doRender(RenderArgs* args) { NetworkTexturePointer texture; QRect subImage; - glm::u8vec3 color; + glm::vec4 color; glm::vec3 dimensions; Transform transform; withReadLock([&] { texture = _texture; subImage = _subImage; - color = _color; + color = glm::vec4(toGlm(_color), _alpha); + color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); dimensions = _dimensions; transform = _renderTransform; }); @@ -211,11 +217,9 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); - glm::vec4 imageColor(toGlm(color), _alpha); - DependencyManager::get()->renderQuad( *batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, - imageColor, _geometryId + color, _geometryId ); batch->setResourceTexture(0, nullptr); diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h index 9bc990f6fa..a20533cc8c 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.h +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -44,6 +44,7 @@ private: glm::u8vec3 _color; float _alpha; + PulsePropertyGroup _pulseProperties; glm::vec3 _dimensions; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 351d72baf5..4ede5e5057 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -71,8 +71,11 @@ bool ParticleEffectEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedE return true; } - auto particleProperties = entity->getParticleProperties(); - if (particleProperties != _particleProperties) { + if (_particleProperties != entity->getParticleProperties()) { + return true; + } + + if (_pulseProperties != entity->getPulseProperties()) { return true; } @@ -95,6 +98,10 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi } }); } + + withWriteLock([&] { + _pulseProperties = entity->getPulseProperties(); + }); _emitting = entity->getIsEmitting(); bool hasTexture = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); @@ -142,10 +149,6 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn particleUniforms.radius.middle = _particleProperties.radius.gradient.target; particleUniforms.radius.finish = _particleProperties.radius.range.finish; particleUniforms.radius.spread = _particleProperties.radius.gradient.spread; - particleUniforms.color.start = _particleProperties.getColorStart(); - particleUniforms.color.middle = _particleProperties.getColorMiddle(); - particleUniforms.color.finish = _particleProperties.getColorFinish(); - particleUniforms.color.spread = _particleProperties.getColorSpread(); particleUniforms.spin.start = _particleProperties.spin.range.start; particleUniforms.spin.middle = _particleProperties.spin.gradient.target; particleUniforms.spin.finish = _particleProperties.spin.range.finish; @@ -158,6 +161,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn } ItemKey ParticleEffectEntityRenderer::getKey() { + // FIXME: implement isTransparent() for particles and an opaque pipeline if (_visible) { return ItemKey::Builder::transparentShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } else { @@ -334,12 +338,18 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; batch.setResourceTexture(0, _networkTexture->getGPUTexture()); - Transform transform; + Transform transform; // The particles are in world space, so the transform is unused, except for the rotation, which we use // if the particles are marked rotateWithEntity withReadLock([&] { transform.setRotation(_renderTransform.getRotation()); + auto& color = _uniformBuffer.edit().color; + color.start = EntityRenderer::calculatePulseColor(_particleProperties.getColorStart(), _pulseProperties, _created); + color.middle = EntityRenderer::calculatePulseColor(_particleProperties.getColorMiddle(), _pulseProperties, _created); + color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created); + color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created); }); + batch.setModelTransform(transform); batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index 7655918c58..853d5cac29 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -94,6 +94,8 @@ private: BufferView _uniformBuffer; quint64 _lastSimulated { 0 }; + PulsePropertyGroup _pulseProperties; + NetworkTexturePointer _networkTexture; ScenePointer _scene; }; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index c47904213b..a959131f7d 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -68,6 +68,10 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + return false; } @@ -84,6 +88,7 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce addMaterial(graphics::MaterialLayer(_material, 0), "0"); _shape = entity->getShape(); + _pulseProperties = entity->getPulseProperties(); }); void* key = (void*)this; @@ -114,6 +119,10 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint } bool ShapeEntityRenderer::isTransparent() const { + if (_pulseProperties.getAlphaMode() != PulseMode::NONE) { + return true; + } + if (_procedural.isEnabled() && _procedural.isFading()) { return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; } @@ -227,6 +236,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { mat = _materials["0"].top().material; if (mat) { outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); + outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created); if (_procedural.isReady()) { outColor = _procedural.getColor(outColor); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index a33f023213..0efcdbb360 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -40,6 +40,7 @@ private: Procedural _procedural; QString _lastUserData; entity::Shape _shape { entity::Sphere }; + PulsePropertyGroup _pulseProperties; std::shared_ptr _material; glm::vec3 _position; glm::vec3 _dimensions; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index d7da8e7e1a..5614e976b5 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -41,7 +41,7 @@ TextEntityRenderer::~TextEntityRenderer() { } bool TextEntityRenderer::isTransparent() const { - return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f; + return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; } ShapeKey TextEntityRenderer::getShapeKey() { @@ -104,6 +104,10 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + return false; } @@ -119,32 +123,39 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen } void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - _text = entity->getText(); - _lineHeight = entity->getLineHeight(); - _textColor = toGlm(entity->getTextColor()); - _textAlpha = entity->getTextAlpha(); - _backgroundColor = toGlm(entity->getBackgroundColor()); - _backgroundAlpha = entity->getBackgroundAlpha(); - _billboardMode = entity->getBillboardMode(); - _leftMargin = entity->getLeftMargin(); - _rightMargin = entity->getRightMargin(); - _topMargin = entity->getTopMargin(); - _bottomMargin = entity->getBottomMargin(); + withWriteLock([&] { + _pulseProperties = entity->getPulseProperties(); + _text = entity->getText(); + _lineHeight = entity->getLineHeight(); + _textColor = toGlm(entity->getTextColor()); + _textAlpha = entity->getTextAlpha(); + _backgroundColor = toGlm(entity->getBackgroundColor()); + _backgroundAlpha = entity->getBackgroundAlpha(); + _billboardMode = entity->getBillboardMode(); + _leftMargin = entity->getLeftMargin(); + _rightMargin = entity->getRightMargin(); + _topMargin = entity->getTopMargin(); + _bottomMargin = entity->getBottomMargin(); + }); } void TextEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); + glm::vec4 textColor; + glm::vec4 backgroundColor; Transform modelTransform; glm::vec3 dimensions; withReadLock([&] { modelTransform = _renderTransform; dimensions = _dimensions; - }); - float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - glm::vec4 textColor = glm::vec4(_textColor, fadeRatio * _textAlpha); - glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + textColor = glm::vec4(_textColor, fadeRatio * _textAlpha); + textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created); + backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); + backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created); + }); // Render background static const float SLIGHTLY_BEHIND = -0.005f; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 6b20705209..6c9e8324f8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -38,6 +38,8 @@ private: int _geometryID{ 0 }; std::shared_ptr _textRenderer; + PulsePropertyGroup _pulseProperties; + QString _text; float _lineHeight; glm::vec3 _textColor; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 38d1320aa2..2510e41780 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -97,7 +97,7 @@ WebEntityRenderer::~WebEntityRenderer() { bool WebEntityRenderer::isTransparent() const { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f; + return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; } bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { @@ -143,6 +143,10 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe return true; } + if (_pulseProperties != entity->getPulseProperties()) { + return true; + } + return false; } @@ -201,6 +205,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _dpi = entity->getDPI(); _color = entity->getColor(); _alpha = entity->getAlpha(); + _pulseProperties = entity->getPulseProperties(); if (_contentType == ContentType::NoContent) { return; @@ -293,6 +298,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); + color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); batch.setModelTransform(_renderTransform); }); batch.setResourceTexture(0, _texture); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 4affb5819d..5d97eb5b8e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -84,6 +84,7 @@ private: glm::u8vec3 _color; float _alpha { 1.0f }; + PulsePropertyGroup _pulseProperties; QString _sourceURL; uint16_t _dpi; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 90fc1b18e4..70b6191852 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -98,9 +98,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_RENDER_LAYER; requestedProperties += PROP_PRIMITIVE_MODE; requestedProperties += PROP_IGNORE_PICK_INTERSECTION; - withReadLock([&] { - requestedProperties += _grabProperties.getEntityProperties(params); - }); + requestedProperties += _grabProperties.getEntityProperties(params); // Physics requestedProperties += PROP_DENSITY; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index dfdc7933a9..195f168a9d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -41,6 +41,7 @@ BloomPropertyGroup EntityItemProperties::_staticBloom; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight; GrabPropertyGroup EntityItemProperties::_staticGrab; +PulsePropertyGroup EntityItemProperties::_staticPulse; EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1); @@ -503,6 +504,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); CHECK_PROPERTY_CHANGE(PROP_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha); + changedProperties += _pulse.getChangedProperties(); CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); // Particles @@ -1100,6 +1102,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point * up in the world. If true, they will point towards the entity's up vector, based on its orientation. + * @property {Entities.Pulse} pulse - The pulse-related properties. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1227,6 +1230,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.Shape} shape="Sphere" - The shape of the entity. * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the entity. + * @property {number} alpha=1 - The alpha of the shape. + * @property {Entities.Pulse} pulse - The pulse-related properties. * @example Create a cylinder. * var shape = Entities.addEntity({ * type: "Shape", @@ -1266,6 +1271,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} rightMargin=0.0 - The right margin, in meters. * @property {number} topMargin=0.0 - The top margin, in meters. * @property {number} bottomMargin=0.0 - The bottom margin, in meters. + * @property {Entities.Pulse} pulse - The pulse-related properties. * @example Create a text entity. * var text = Entities.addEntity({ * type: "Text", @@ -1295,6 +1301,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. * @property {WebInputMode} inputMode="touch" - The user input mode to use. + * @property {Entities.Pulse} pulse - The pulse-related properties. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1404,6 +1411,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * the full image in that dimension. * @property {Color} color=255,255,255 - The color of the image. * @property {number} alpha=1 - The alpha of the image. + * @property {Entities.Pulse} pulse - The pulse-related properties. * @example Create a image entity. * var image = Entities.addEntity({ * type: "Image", @@ -1427,6 +1435,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * line. Minimum value = 1. * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = * 0.001. + * @property {Entities.Pulse} pulse - The pulse-related properties. * @example Create a grid entity. * var grid = Entities.addEntity({ * type: "Grid", @@ -1564,6 +1573,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_PARTICLES, maxParticles); @@ -1637,6 +1647,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); } @@ -1652,6 +1663,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Text only if (_type == EntityTypes::Text) { + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_TEXT_COLOR, textColor, getTextColor(), u8vec3Color); @@ -1693,6 +1706,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); @@ -1757,6 +1771,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Image) { COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive); @@ -1777,6 +1792,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Grid) { COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + _pulse.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GRID_FOLLOW_CAMERA, followCamera); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAJOR_GRID_EVERY, majorGridEvery); @@ -1957,6 +1973,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(color, u8vec3Color, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); + _pulse.copyFromScriptValue(object, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); // Particles @@ -2218,6 +2235,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(compoundShapeURL); COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(alpha); + _pulse.merge(other._pulse); COPY_PROPERTY_IF_CHANGED(textures); // Particles @@ -2526,6 +2544,13 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString); ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, u8vec3Color); ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA, Alpha, alpha, float, particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA); + { // Pulse + ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_MIN, Pulse, pulse, Min, min); + ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_MAX, Pulse, pulse, Max, max); + ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_PERIOD, Pulse, pulse, Period, period); + ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_COLOR_MODE, Pulse, pulse, ColorMode, colorMode); + ADD_GROUP_PROPERTY_TO_MAP(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode); + } ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString); // Particles @@ -2926,6 +2951,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, properties.getMaxParticles()); @@ -2997,6 +3025,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy } if (properties.getType() == EntityTypes::Text) { + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, properties.getTextColor()); @@ -3058,6 +3090,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::Web) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); @@ -3092,6 +3127,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy properties.getType() == EntityTypes::Sphere) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } @@ -3112,6 +3150,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::Image) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive()); @@ -3124,6 +3165,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::Grid) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + _staticPulse.setProperties(properties); + _staticPulse.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, properties.getFollowCamera()); APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, properties.getMajorGridEvery()); @@ -3375,6 +3419,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, quint32, setMaxParticles); @@ -3446,6 +3491,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int } if (properties.getType() == EntityTypes::Text) { + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_COLOR, u8vec3Color, setTextColor); @@ -3496,6 +3543,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int } if (properties.getType() == EntityTypes::Web) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL); @@ -3529,6 +3580,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getType() == EntityTypes::Sphere) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } @@ -3549,6 +3602,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Image) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive); @@ -3561,6 +3615,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Grid) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + properties.getPulse().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery); @@ -3764,6 +3819,7 @@ void EntityItemProperties::markAllChanged() { _shapeTypeChanged = true; _colorChanged = true; _alphaChanged = true; + _pulse.markAllChanged(); _texturesChanged = true; _compoundShapeURLChanged = true; @@ -4228,6 +4284,7 @@ QList EntityItemProperties::listChangedProperties() { if (alphaChanged()) { out += "alpha"; } + getPulse().listChangedProperties(out); if (texturesChanged()) { out += "textures"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 92df22183a..d2df8bebe0 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -51,6 +51,7 @@ #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" #include "BloomPropertyGroup.h" +#include "PulsePropertyGroup.h" #include "MaterialMappingMode.h" #include "BillboardMode.h" @@ -229,6 +230,7 @@ public: DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, ""); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, particle::DEFAULT_COLOR); DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA); + DEFINE_PROPERTY_GROUP(Pulse, pulse, PulsePropertyGroup); DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); // Particles diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index fa14f52b48..7064f3e62e 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -383,13 +383,29 @@ inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) } \ } -#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ - QScriptValue P = object.property(#P); \ - if (P.isValid()) { \ - QString newValue = P.toVariant().toString(); \ - if (_defaultSettings || newValue != get##S##AsString()) { \ - set##S##FromString(newValue); \ - } \ +#define COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(P, S) \ + { \ + QScriptValue P = object.property(#P); \ + if (P.isValid()) { \ + QString newValue = P.toVariant().toString(); \ + if (_defaultSettings || newValue != get##S##AsString()) { \ + set##S##FromString(newValue); \ + } \ + } \ + } + +#define COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(G, P, S) \ + { \ + QScriptValue G = object.property(#G); \ + if (G.isValid()) { \ + QScriptValue P = G.property(#P); \ + if (P.isValid()) { \ + QString newValue = P.toVariant().toString(); \ + if (_defaultSettings || newValue != get##S##AsString()) { \ + set##S##FromString(newValue); \ + } \ + } \ + } \ } #define DEFINE_PROPERTY_GROUP(N, n, T) \ diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 5c136c4820..a59cbe854f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -106,12 +106,17 @@ enum EntityPropertyList { PROP_LOCAL_VELOCITY, PROP_LOCAL_ANGULAR_VELOCITY, PROP_LOCAL_DIMENSIONS, - + // These properties are used by multiple subtypes but aren't in the base EntityItem PROP_SHAPE_TYPE, PROP_COMPOUND_SHAPE_URL, PROP_COLOR, PROP_ALPHA, + PROP_PULSE_MIN, + PROP_PULSE_MAX, + PROP_PULSE_PERIOD, + PROP_PULSE_COLOR_MODE, + PROP_PULSE_ALPHA_MODE, PROP_TEXTURES, //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/libraries/entities/src/GridEntityItem.cpp b/libraries/entities/src/GridEntityItem.cpp index b4709239ed..d8d070416b 100644 --- a/libraries/entities/src/GridEntityItem.cpp +++ b/libraries/entities/src/GridEntityItem.cpp @@ -35,6 +35,9 @@ EntityItemProperties GridEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(followCamera, getFollowCamera); COPY_ENTITY_PROPERTY_TO_PROPERTIES(majorGridEvery, getMajorGridEvery); @@ -48,6 +51,10 @@ bool GridEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(followCamera, setFollowCamera); SET_ENTITY_PROPERTY_FROM_PROPERTIES(majorGridEvery, setMajorGridEvery); @@ -76,6 +83,13 @@ int GridEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); READ_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera); READ_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery); @@ -89,6 +103,7 @@ EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_GRID_FOLLOW_CAMERA; requestedProperties += PROP_MAJOR_GRID_EVERY; @@ -98,7 +113,7 @@ EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& p } void GridEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -109,6 +124,10 @@ void GridEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, getFollowCamera()); APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, getMajorGridEvery()); @@ -175,4 +194,10 @@ float GridEntityItem::getMinorGridEvery() const { return resultWithReadLock([&] { return _minorGridEvery; }); +} + +PulsePropertyGroup GridEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); } \ No newline at end of file diff --git a/libraries/entities/src/GridEntityItem.h b/libraries/entities/src/GridEntityItem.h index 6209aee73d..165d9b50f5 100644 --- a/libraries/entities/src/GridEntityItem.h +++ b/libraries/entities/src/GridEntityItem.h @@ -11,6 +11,8 @@ #include "EntityItem.h" +#include "PulsePropertyGroup.h" + class GridEntityItem : public EntityItem { using Pointer = std::shared_ptr; public: @@ -29,7 +31,7 @@ public: EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -59,9 +61,13 @@ public: void setMinorGridEvery(float minorGridEvery); float getMinorGridEvery() const; + PulsePropertyGroup getPulseProperties() const; + protected: glm::u8vec3 _color; float _alpha; + PulsePropertyGroup _pulseProperties; + bool _followCamera { true }; uint32_t _majorGridEvery { DEFAULT_MAJOR_GRID_EVERY }; float _minorGridEvery { DEFAULT_MINOR_GRID_EVERY }; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index cdff1b5390..1e8e4511cf 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -32,6 +32,9 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); @@ -47,6 +50,10 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); @@ -77,6 +84,13 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); @@ -92,6 +106,7 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_IMAGE_URL; requestedProperties += PROP_EMISSIVE; @@ -103,7 +118,7 @@ EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& } void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -114,6 +129,10 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, getImageURL()); APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); @@ -266,4 +285,10 @@ float ImageEntityItem::getAlpha() const { return resultWithReadLock([&] { return _alpha; }); +} + +PulsePropertyGroup ImageEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); } \ No newline at end of file diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 228f86ca03..05218016b3 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -11,6 +11,8 @@ #include "EntityItem.h" +#include "PulsePropertyGroup.h" + class ImageEntityItem : public EntityItem { using Pointer = std::shared_ptr; public: @@ -29,7 +31,7 @@ public: EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -72,6 +74,8 @@ public: void setAlpha(float alpha); float getAlpha() const; + PulsePropertyGroup getPulseProperties() const; + protected: QString _imageURL; bool _emissive { false }; @@ -81,6 +85,7 @@ protected: glm::u8vec3 _color; float _alpha; + PulsePropertyGroup _pulseProperties; }; #endif // hifi_ImageEntityItem_h diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 7426318979..b916ecc3de 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -412,6 +412,9 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); @@ -463,6 +466,10 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); @@ -535,6 +542,13 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); @@ -586,6 +600,7 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_TEXTURES; requestedProperties += PROP_MAX_PARTICLES; @@ -643,6 +658,10 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles()); @@ -786,4 +805,10 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const { } return result; +} + +PulsePropertyGroup ParticleEffectEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); } \ No newline at end of file diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 89f1e834ea..88ebd47d6e 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -16,6 +16,7 @@ #include "EntityItem.h" #include "ColorUtils.h" +#include "PulsePropertyGroup.h" namespace particle { static const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work @@ -341,9 +342,11 @@ public: virtual bool supportsDetailedIntersection() const override { return false; } particle::Properties getParticleProperties() const; + PulsePropertyGroup getPulseProperties() const; protected: particle::Properties _particleProperties; + PulsePropertyGroup _pulseProperties; bool _isEmitting { true }; ShapeType _shapeType { SHAPE_TYPE_NONE }; diff --git a/libraries/entities/src/PulsePropertyGroup.cpp b/libraries/entities/src/PulsePropertyGroup.cpp new file mode 100644 index 0000000000..54f81750da --- /dev/null +++ b/libraries/entities/src/PulsePropertyGroup.cpp @@ -0,0 +1,249 @@ +// +// PulsePropertyGroup.cpp +// +// Created by Sam Gondelman on 1/15/19 +// Copyright 2019 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 "PulsePropertyGroup.h" + +#include + +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +QHash stringToPulseModeLookup; + +void addPulseMode(PulseMode mode) { + stringToPulseModeLookup[PulseModeHelpers::getNameForPulseMode(mode)] = mode; +} + +void buildStringToPulseModeLookup() { + addPulseMode(PulseMode::NONE); + addPulseMode(PulseMode::IN_PHASE); + addPulseMode(PulseMode::OUT_PHASE); +} + +QString PulsePropertyGroup::getColorModeAsString() const { + return PulseModeHelpers::getNameForPulseMode(_colorMode); +} + +void PulsePropertyGroup::setColorModeFromString(const QString& pulseMode) { + if (stringToPulseModeLookup.empty()) { + buildStringToPulseModeLookup(); + } + auto pulseModeItr = stringToPulseModeLookup.find(pulseMode.toLower()); + if (pulseModeItr != stringToPulseModeLookup.end()) { + _colorMode = pulseModeItr.value(); + _colorModeChanged = true; + } +} + +QString PulsePropertyGroup::getAlphaModeAsString() const { + return PulseModeHelpers::getNameForPulseMode(_alphaMode); +} + +void PulsePropertyGroup::setAlphaModeFromString(const QString& pulseMode) { + if (stringToPulseModeLookup.empty()) { + buildStringToPulseModeLookup(); + } + auto pulseModeItr = stringToPulseModeLookup.find(pulseMode.toLower()); + if (pulseModeItr != stringToPulseModeLookup.end()) { + _alphaMode = pulseModeItr.value(); + _alphaModeChanged = true; + } +} + +void PulsePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, + EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MIN, Pulse, pulse, Min, min); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_MAX, Pulse, pulse, Max, max); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_PULSE_PERIOD, Pulse, pulse, Period, period); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PULSE_COLOR_MODE, Pulse, pulse, ColorMode, colorMode, getColorModeAsString); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PULSE_ALPHA_MODE, Pulse, pulse, AlphaMode, alphaMode, getAlphaModeAsString); +} + +void PulsePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, min, float, setMin); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, max, float, setMax); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(pulse, period, float, setPeriod); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(pulse, colorMode, ColorMode); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE_ENUM(pulse, alphaMode, AlphaMode); +} + +void PulsePropertyGroup::merge(const PulsePropertyGroup& other) { + COPY_PROPERTY_IF_CHANGED(min); + COPY_PROPERTY_IF_CHANGED(max); + COPY_PROPERTY_IF_CHANGED(period); + COPY_PROPERTY_IF_CHANGED(colorMode); + COPY_PROPERTY_IF_CHANGED(alphaMode); +} + +void PulsePropertyGroup::debugDump() const { + qCDebug(entities) << " PulsePropertyGroup: ---------------------------------------------"; + qCDebug(entities) << " _min:" << _min; + qCDebug(entities) << " _max:" << _max; + qCDebug(entities) << " _period:" << _period; + qCDebug(entities) << " _colorMode:" << getColorModeAsString(); + qCDebug(entities) << " _alphaMode:" << getAlphaModeAsString(); +} + +void PulsePropertyGroup::listChangedProperties(QList& out) { + if (minChanged()) { + out << "pulse-min"; + } + if (maxChanged()) { + out << "pulse-max"; + } + if (periodChanged()) { + out << "pulse-period"; + } + if (colorModeChanged()) { + out << "pulse-colorMode"; + } + if (alphaModeChanged()) { + out << "pulse-alphaMode"; + } +} + +bool PulsePropertyGroup::appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_PULSE_MIN, getMin()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_MAX, getMax()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_PERIOD, getPeriod()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, (uint32_t)getColorMode()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, (uint32_t)getAlphaMode()); + + return true; +} + +bool PulsePropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, + const unsigned char*& dataAt , int& processedBytes) { + + int bytesRead = 0; + bool overwriteLocalData = true; + bool somethingChanged = false; + + READ_ENTITY_PROPERTY(PROP_PULSE_MIN, float, setMin); + READ_ENTITY_PROPERTY(PROP_PULSE_MAX, float, setMax); + READ_ENTITY_PROPERTY(PROP_PULSE_PERIOD, float, setPeriod); + READ_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, PulseMode, setColorMode); + READ_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, PulseMode, setAlphaMode); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_MIN, Min); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_MAX, Max); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_PERIOD, Period); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_COLOR_MODE, ColorMode); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_PULSE_ALPHA_MODE, AlphaMode); + + processedBytes += bytesRead; + + Q_UNUSED(somethingChanged); + + return true; +} + +void PulsePropertyGroup::markAllChanged() { + _minChanged = true; + _maxChanged = true; + _periodChanged = true; + _colorModeChanged = true; + _alphaModeChanged = true; +} + +EntityPropertyFlags PulsePropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_PULSE_MIN, min); + CHECK_PROPERTY_CHANGE(PROP_PULSE_MAX, max); + CHECK_PROPERTY_CHANGE(PROP_PULSE_PERIOD, period); + CHECK_PROPERTY_CHANGE(PROP_PULSE_COLOR_MODE, colorMode); + CHECK_PROPERTY_CHANGE(PROP_PULSE_ALPHA_MODE, alphaMode); + + return changedProperties; +} + +void PulsePropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Min, getMin); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Max, getMax); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, Period, getPeriod); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, ColorMode, getColorMode); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Pulse, AlphaMode, getAlphaMode); +} + +bool PulsePropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Min, min, setMin); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Max, max, setMax); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, Period, period, setPeriod); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, ColorMode, colorMode, setColorMode); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Pulse, AlphaMode, alphaMode, setAlphaMode); + + return somethingChanged; +} + +EntityPropertyFlags PulsePropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_PULSE_MIN; + requestedProperties += PROP_PULSE_MAX; + requestedProperties += PROP_PULSE_PERIOD; + requestedProperties += PROP_PULSE_COLOR_MODE; + requestedProperties += PROP_PULSE_ALPHA_MODE; + + return requestedProperties; +} + +void PulsePropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_PULSE_MIN, getMin()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_MAX, getMax()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_PERIOD, getPeriod()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, (uint32_t)getColorMode()); + APPEND_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, (uint32_t)getAlphaMode()); +} + +int PulsePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_PULSE_MIN, float, setMin); + READ_ENTITY_PROPERTY(PROP_PULSE_MAX, float, setMax); + READ_ENTITY_PROPERTY(PROP_PULSE_PERIOD, float, setPeriod); + READ_ENTITY_PROPERTY(PROP_PULSE_COLOR_MODE, PulseMode, setColorMode); + READ_ENTITY_PROPERTY(PROP_PULSE_ALPHA_MODE, PulseMode, setAlphaMode); + + return bytesRead; +} + +bool PulsePropertyGroup::operator==(const PulsePropertyGroup& a) const { + return (a._min == _min) && + (a._max == _max) && + (a._period == _period) && + (a._colorMode == _colorMode) && + (a._alphaMode == _alphaMode); +} \ No newline at end of file diff --git a/libraries/entities/src/PulsePropertyGroup.h b/libraries/entities/src/PulsePropertyGroup.h new file mode 100644 index 0000000000..f54db39149 --- /dev/null +++ b/libraries/entities/src/PulsePropertyGroup.h @@ -0,0 +1,99 @@ +// +// PulsePropertyGroup.h +// +// Created by Sam Gondelman on 1/15/19 +// Copyright 2019 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_PulsePropertyGroup_h +#define hifi_PulsePropertyGroup_h + +#include + +#include + +#include + +#include "PropertyGroup.h" +#include "EntityItemPropertiesMacros.h" + +class EntityItemProperties; +class EncodeBitstreamParams; +class OctreePacketData; +class ReadBitstreamToTreeParams; + +/**jsdoc + * Pulse is defined by the following properties. + * @typedef {object} Entities.Pulse + * + * @property {number} min=0 - The minimum value of the pulse multiplier. + * @property {number} max=1 - The maximum value of the pulse multiplier. + * @property {number} period=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from + * min to max, then max to min in one period. + * @property {PulseMode} colorMode="none" - If "in", the color is pulsed in phase with the pulse period; if "out" + * the color is pulsed out of phase with the pulse period. + * @property {PulseMode} alphaMode="none" - If "in", the alpha is pulsed in phase with the pulse period; if "out" + * the alpha is pulsed out of phase with the pulse period. + */ + +class PulsePropertyGroup : public PropertyGroup { +public: + // EntityItemProperty related helpers + virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, + EntityItemProperties& defaultEntityProperties) const override; + virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; + + void merge(const PulsePropertyGroup& other); + + virtual void debugDump() const override; + virtual void listChangedProperties(QList& out) override; + + virtual bool appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, + const unsigned char*& dataAt, int& processedBytes) override; + virtual void markAllChanged() override; + virtual EntityPropertyFlags getChangedProperties() const override; + + // EntityItem related helpers + // methods for getting/setting all properties of an entity + virtual void getProperties(EntityItemProperties& propertiesOut) const override; + + // returns true if something changed + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + bool operator==(const PulsePropertyGroup& a) const; + bool operator!=(const PulsePropertyGroup& a) const { return !(*this == a); } + + DEFINE_PROPERTY(PROP_PULSE_MIN, Min, min, float, 0.0f); + DEFINE_PROPERTY(PROP_PULSE_MAX, Max, max, float, 1.0f); + DEFINE_PROPERTY(PROP_PULSE_PERIOD, Period, period, float, 1.0f); + DEFINE_PROPERTY_REF_ENUM(PROP_PULSE_COLOR_MODE, ColorMode, colorMode, PulseMode, PulseMode::NONE); + DEFINE_PROPERTY_REF_ENUM(PROP_PULSE_ALPHA_MODE, AlphaMode, alphaMode, PulseMode, PulseMode::NONE); +}; + +#endif // hifi_PulsePropertyGroup_h diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 7a7e211d0d..5d3f11cdc9 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -120,6 +120,9 @@ EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); properties.setShape(entity::stringFromShape(getShape())); properties._shapeChanged = false; @@ -160,6 +163,10 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); if (somethingChanged) { @@ -185,6 +192,13 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); return bytesRead; @@ -194,12 +208,13 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_SHAPE; return requestedProperties; } void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -209,6 +224,10 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); } @@ -419,4 +438,10 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { // This value specifies how the shape should be treated by physics calculations. ShapeType ShapeEntityItem::getShapeType() const { return _collisionShapeType; +} + +PulsePropertyGroup ShapeEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); } \ No newline at end of file diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 6e36e15a84..bd0f65d9e2 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -11,6 +11,8 @@ #include "EntityItem.h" +#include "PulsePropertyGroup.h" + namespace entity { enum Shape { Triangle, @@ -58,7 +60,7 @@ public: EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -101,9 +103,12 @@ public: std::shared_ptr getMaterial() { return _material; } + PulsePropertyGroup getPulseProperties() const; + protected: glm::u8vec3 _color; float _alpha { 1.0f }; + PulsePropertyGroup _pulseProperties; entity::Shape _shape { entity::Shape::Sphere }; //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 80c4c896bd..a743d0a7a9 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -49,6 +49,10 @@ void TextEntityItem::setUnscaledDimensions(const glm::vec3& value) { EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(text, getText); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColor); @@ -67,6 +71,11 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(text, setText); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor); @@ -101,6 +110,14 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); + READ_ENTITY_PROPERTY(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, glm::u8vec3, setTextColor); @@ -118,6 +135,8 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_TEXT; requestedProperties += PROP_LINE_HEIGHT; requestedProperties += PROP_TEXT_COLOR; @@ -129,11 +148,12 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_RIGHT_MARGIN; requestedProperties += PROP_TOP_MARGIN; requestedProperties += PROP_BOTTOM_MARGIN; + return requestedProperties; } void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -142,6 +162,11 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool successPropertyFits = true; + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); + APPEND_ENTITY_PROPERTY(PROP_TEXT, getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor()); @@ -345,3 +370,9 @@ float TextEntityItem::getBottomMargin() const { return _bottomMargin; }); } + +PulsePropertyGroup TextEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 5c22d8edf0..5ca6823052 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -14,6 +14,8 @@ #include "EntityItem.h" +#include "PulsePropertyGroup.h" + class TextEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -33,7 +35,7 @@ public: virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -94,6 +96,8 @@ public: float getBottomMargin() const; void setBottomMargin(float value); + PulsePropertyGroup getPulseProperties() const; + private: QString _text; float _lineHeight; @@ -101,6 +105,7 @@ private: float _textAlpha; glm::u8vec3 _backgroundColor; float _backgroundAlpha; + PulsePropertyGroup _pulseProperties; BillboardMode _billboardMode; float _leftMargin; float _rightMargin; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index c0cdb081a2..459d512311 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -45,6 +45,9 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + withReadLock([&] { + _pulseProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI); @@ -60,6 +63,10 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + withWriteLock([&] { + bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); + somethingChanged |= pulsePropertiesChanged; + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI); @@ -91,6 +98,13 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + withWriteLock([&] { + int bytesFromPulse = _pulseProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromPulse; + dataAt += bytesFromPulse; + }); READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI); @@ -105,6 +119,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += _pulseProperties.getEntityProperties(params); requestedProperties += PROP_SOURCE_URL; requestedProperties += PROP_DPI; @@ -115,7 +130,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa } void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -125,6 +140,10 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + withReadLock([&] { + _pulseProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI()); @@ -285,4 +304,10 @@ WebInputMode WebEntityItem::getInputMode() const { return resultWithReadLock([&] { return _inputMode; }); +} + +PulsePropertyGroup WebEntityItem::getPulseProperties() const { + return resultWithReadLock([&] { + return _pulseProperties; + }); } \ No newline at end of file diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index c566bcb5f7..a0a2d65253 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -11,6 +11,8 @@ #include "EntityItem.h" +#include "PulsePropertyGroup.h" + class WebEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -30,7 +32,7 @@ public: virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, @@ -75,9 +77,12 @@ public: void setInputMode(const WebInputMode& value); WebInputMode getInputMode() const; + PulsePropertyGroup getPulseProperties() const; + protected: glm::u8vec3 _color; float _alpha { 1.0f }; + PulsePropertyGroup _pulseProperties; QString _sourceUrl; uint16_t _dpi; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 38e45f0206..b5d4e6c8ad 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -258,6 +258,7 @@ enum class EntityVersion : PacketVersion { FixProtocolVersionBumpMismatch, MigrateOverlayRenderProperties, MissingWebEntityProperties, + PulseProperties, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 1dbb564fe9..7c299eb14a 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -38,6 +38,7 @@ #include "RenderLayer.h" #include "PrimitiveMode.h" #include "WebInputMode.h" +#include "PulseMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -269,6 +270,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, RenderLayer& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, PulseMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); diff --git a/libraries/shared/src/PulseMode.cpp b/libraries/shared/src/PulseMode.cpp new file mode 100644 index 0000000000..d8e938350a --- /dev/null +++ b/libraries/shared/src/PulseMode.cpp @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 1/15/19 +// Copyright 2019 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 "PulseMode.h" + +const char* pulseModeNames[] = { + "none", + "in", + "out" +}; + +static const size_t PULSE_MODE_NAMES = (sizeof(pulseModeNames) / sizeof(pulseModeNames[0])); + +QString PulseModeHelpers::getNameForPulseMode(PulseMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)PULSE_MODE_NAMES)) { + mode = (PulseMode)0; + } + + return pulseModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/PulseMode.h b/libraries/shared/src/PulseMode.h new file mode 100644 index 0000000000..7c823db68c --- /dev/null +++ b/libraries/shared/src/PulseMode.h @@ -0,0 +1,41 @@ +// +// Created by Sam Gondelman on 1/15/19. +// Copyright 2019 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_PulseMode_h +#define hifi_PulseMode_h + +#include "QString" + +/**jsdoc + *

Pulse modes for color and alpha pulsing.

+ * + * + * + * + * + * + * + * + * + *
ValueDescription
noneNo pulsing.
inPulse in phase with the pulse period.
outPulse out of phase with the pulse period.
+ * @typedef {string} PulseMode + */ + +enum class PulseMode { + NONE = 0, + IN_PHASE, + OUT_PHASE +}; + +class PulseModeHelpers { +public: + static QString getNameForPulseMode(PulseMode mode); +}; + +#endif // hifi_PulseMode_h + From 38357964192a422fcad5173d0013cd9797371d53 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Jan 2019 14:01:05 -0800 Subject: [PATCH 024/104] deprecate the new property so that people hopefully don't use it --- libraries/entities/src/EntityItemProperties.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 195f168a9d..a6cbae4bf1 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1102,7 +1102,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point * up in the world. If true, they will point towards the entity's up vector, based on its orientation. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1231,7 +1231,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the entity. * @property {number} alpha=1 - The alpha of the shape. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a cylinder. * var shape = Entities.addEntity({ * type: "Shape", @@ -1271,7 +1271,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} rightMargin=0.0 - The right margin, in meters. * @property {number} topMargin=0.0 - The top margin, in meters. * @property {number} bottomMargin=0.0 - The bottom margin, in meters. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a text entity. * var text = Entities.addEntity({ * type: "Text", @@ -1301,7 +1301,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. * @property {WebInputMode} inputMode="touch" - The user input mode to use. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1411,7 +1411,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * the full image in that dimension. * @property {Color} color=255,255,255 - The color of the image. * @property {number} alpha=1 - The alpha of the image. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a image entity. * var image = Entities.addEntity({ * type: "Image", @@ -1435,7 +1435,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * line. Minimum value = 1. * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = * 0.001. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a grid entity. * var grid = Entities.addEntity({ * type: "Grid", From 62b853816a64a2b87de144ffcce14dfb675d76e3 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 16 Jan 2019 14:01:05 -0800 Subject: [PATCH 025/104] deprecate the new property so that people hopefully don't use it --- .../entities-renderer/src/RenderableEntityItem.cpp | 4 ++-- libraries/entities/src/EntityItemProperties.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 140fe897af..1261604e29 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,7 +141,7 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } -EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity), _created(entity->getCreated()) { +EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) { connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { _needsRenderUpdate = true; emit requestRenderUpdate(); @@ -475,7 +475,7 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls } float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND); - float pulse = 0.5f * (glm::cos(t * (2.0f * M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); + float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); float outPulse = (1.0f - pulse); glm::vec4 result = color; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 195f168a9d..a6cbae4bf1 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1102,7 +1102,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point * up in the world. If true, they will point towards the entity's up vector, based on its orientation. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1231,7 +1231,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. * @property {Color} color=255,255,255 - The color of the entity. * @property {number} alpha=1 - The alpha of the shape. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a cylinder. * var shape = Entities.addEntity({ * type: "Shape", @@ -1271,7 +1271,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} rightMargin=0.0 - The right margin, in meters. * @property {number} topMargin=0.0 - The top margin, in meters. * @property {number} bottomMargin=0.0 - The bottom margin, in meters. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a text entity. * var text = Entities.addEntity({ * type: "Text", @@ -1301,7 +1301,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. * @property {WebInputMode} inputMode="touch" - The user input mode to use. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1411,7 +1411,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * the full image in that dimension. * @property {Color} color=255,255,255 - The color of the image. * @property {number} alpha=1 - The alpha of the image. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a image entity. * var image = Entities.addEntity({ * type: "Image", @@ -1435,7 +1435,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * line. Minimum value = 1. * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = * 0.001. - * @property {Entities.Pulse} pulse - The pulse-related properties. + * @property {Entities.Pulse} pulse - The pulse-related properties. Deprecated. * @example Create a grid entity. * var grid = Entities.addEntity({ * type: "Grid", From 2158e084d43821613ec1025674ea55c63d74b905 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 17 Jan 2019 14:00:54 -0700 Subject: [PATCH 026/104] Add avatar joints filter to intersection calculation --- interface/src/avatar/AvatarManager.cpp | 121 +++++++++++------- interface/src/avatar/AvatarManager.h | 8 +- interface/src/avatar/DetailedMotionState.cpp | 2 +- .../src/avatar/MyCharacterController.cpp | 3 + interface/src/avatar/OtherAvatar.cpp | 9 +- interface/src/raypick/RayPick.cpp | 2 +- 6 files changed, 94 insertions(+), 51 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 53f8a0bc7f..f24e1e72eb 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -640,23 +640,27 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude, - const QScriptValue& avatarIdsToDiscard) { + const QScriptValue& avatarIdsToDiscard, + const QScriptValue& jointIndicesToFilter) { QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); - - return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard); + QVector jointsToFilter; + qVectorIntFromScriptValue(jointIndicesToFilter, jointsToFilter); + return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard, jointsToFilter); } RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray, const QVector& avatarsToInclude, - const QVector& avatarsToDiscard) { + const QVector& avatarsToDiscard, + const QVector& jointIndicesToFilter) { RayToAvatarIntersectionResult result; if (QThread::currentThread() != thread()) { BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector", Q_RETURN_ARG(RayToAvatarIntersectionResult, result), Q_ARG(const PickRay&, ray), Q_ARG(const QVector&, avatarsToInclude), - Q_ARG(const QVector&, avatarsToDiscard)); + Q_ARG(const QVector&, avatarsToDiscard), + Q_ARG(const QVector&, jointIndicesToFilter)); return result; } @@ -672,55 +676,80 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic if (physicsResults.size() > 0) { MyCharacterController::RayAvatarResult rayAvatarResult; + AvatarPointer avatar = nullptr; for (auto &hit : physicsResults) { - if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(hit._intersectWithAvatar)) || - (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(hit._intersectWithAvatar))) { + auto avatarID = hit._intersectWithAvatar; + bool skipThisAvatar = (avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID)) && jointIndicesToFilter.size() == 0; + if (skipThisAvatar) { continue; } - if (!hit._isBound) { - rayAvatarResult = hit; - break; - } else { + if (!(_myAvatar->getSessionUUID() == avatarID)) { auto avatarMap = getHashCopy(); - auto avatarID = hit._intersectWithAvatar; AvatarHash::iterator itr = avatarMap.find(avatarID); if (itr != avatarMap.end()) { - const auto& avatar = std::static_pointer_cast(*itr); - auto &multiSpheres = avatar->getMultiSphereShapes(); - if (multiSpheres.size() > 0) { - std::vector boxHits; - for (size_t i = 0; i < hit._boundJoints.size(); i++) { - assert(hit._boundJoints[i] < multiSpheres.size()); - auto &mSphere = multiSpheres[hit._boundJoints[i]]; - if (mSphere.isValid()) { - float boundDistance = FLT_MAX; - BoxFace face; - glm::vec3 surfaceNormal; - auto &bbox = mSphere.getBoundingBox(); - if (bbox.findRayIntersection(ray.origin, ray.direction, rayDirectionInv, boundDistance, face, surfaceNormal)) { - MyCharacterController::RayAvatarResult boxHit; - boxHit._distance = boundDistance; - boxHit._intersect = true; - boxHit._intersectionNormal = surfaceNormal; - boxHit._intersectionPoint = ray.origin + boundDistance * glm::normalize(ray.direction); - boxHit._intersectWithAvatar = avatarID; - boxHit._intersectWithJoint = mSphere.getJointIndex(); - boxHits.push_back(boxHit); - } - } - } - if (boxHits.size() > 0) { - if (boxHits.size() > 1) { - std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA, - const MyCharacterController::RayAvatarResult& hitB) { - return hitA._distance < hitB._distance; - }); - } - rayAvatarResult = boxHits[0]; - break; + avatar = std::static_pointer_cast(*itr); + } + } else { + avatar = _myAvatar; + } + QVector jointsToDiscard; + if (avatar && jointIndicesToFilter.size() > 0) { + int jointCount = avatar->getJointCount(); + if (avatarsToInclude.size() > 0 && avatarsToInclude.contains(avatarID)) { + for (int i = 0; i < jointCount; i++) { + if (!jointIndicesToFilter.contains(i)) { + jointsToDiscard.push_back(i); } } - } + } else if (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID)) { + for (int i = 0; i < jointCount; i++) { + if (jointIndicesToFilter.contains(i)) { + jointsToDiscard.push_back(i); + } + } + } + } + if (!hit._isBound) { + if (!jointsToDiscard.contains(hit._intersectWithJoint)) { + rayAvatarResult = hit; + break; + } + } else if (avatar) { + auto &multiSpheres = avatar->getMultiSphereShapes(); + if (multiSpheres.size() > 0) { + std::vector boxHits; + for (size_t i = 0; i < hit._boundJoints.size(); i++) { + assert(hit._boundJoints[i] < multiSpheres.size()); + auto &mSphere = multiSpheres[hit._boundJoints[i]]; + if (mSphere.isValid() && !jointsToDiscard.contains(hit._boundJoints[i])) { + float boundDistance = FLT_MAX; + BoxFace face; + glm::vec3 surfaceNormal; + auto &bbox = mSphere.getBoundingBox(); + if (bbox.findRayIntersection(ray.origin, ray.direction, rayDirectionInv, boundDistance, face, surfaceNormal)) { + MyCharacterController::RayAvatarResult boxHit; + boxHit._distance = boundDistance; + boxHit._intersect = true; + boxHit._intersectionNormal = surfaceNormal; + boxHit._intersectionPoint = ray.origin + boundDistance * glm::normalize(ray.direction); + boxHit._intersectWithAvatar = avatarID; + boxHit._intersectWithJoint = hit._intersectWithJoint; + boxHits.push_back(boxHit); + } + } + } + if (boxHits.size() > 0) { + if (boxHits.size() > 1) { + std::sort(boxHits.begin(), boxHits.end(), [](const MyCharacterController::RayAvatarResult& hitA, + const MyCharacterController::RayAvatarResult& hitB) { + return hitA._distance < hitB._distance; + }); + } + rayAvatarResult = boxHits[0]; + break; + } + } } } if (rayAvatarResult._intersect) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6717c301af..93be716087 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -138,21 +138,25 @@ public: * @param {PickRay} ray * @param {Uuid[]} [avatarsToInclude=[]] * @param {Uuid[]} [avatarsToDiscard=[]] + * @param {uint[]} [jointIndicesToFilter=[] - If included/discarded avatars are provided only this joints corresponding to those avatars would be included/discarded. ] * @returns {RayToAvatarIntersectionResult} */ Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), - const QScriptValue& avatarIdsToDiscard = QScriptValue()); + const QScriptValue& avatarIdsToDiscard = QScriptValue(), + const QScriptValue& jointIndicesToFilter = QScriptValue()); /**jsdoc * @function AvatarManager.findRayIntersectionVector * @param {PickRay} ray * @param {Uuid[]} avatarsToInclude * @param {Uuid[]} avatarsToDiscard + * @param {uint[]} [jointIndicesToFilter=[] - If included/discarded avatars are provided only this joints corresponding to those avatars would be included/discarded. ] * @returns {RayToAvatarIntersectionResult} */ Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray, const QVector& avatarsToInclude, - const QVector& avatarsToDiscard); + const QVector& avatarsToDiscard, + const QVector& jointIndicesToFilter); /**jsdoc * @function AvatarManager.findParabolaIntersectionVector diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp index 4d8e3d5ec7..cec27108ca 100644 --- a/interface/src/avatar/DetailedMotionState.cpp +++ b/interface/src/avatar/DetailedMotionState.cpp @@ -182,7 +182,7 @@ void DetailedMotionState::setShape(const btCollisionShape* shape) { } void DetailedMotionState::forceActive() { - if (_body) { + if (_body && !_body->isActive()) { _body->setActivationState(ACTIVE_TAG); } } \ No newline at end of file diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 0141154f10..f1eb59d808 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -360,6 +360,9 @@ btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(in _avatar->computeDetailedShapeInfo(shapeInfo, jointIndex); if (shapeInfo.getType() != SHAPE_TYPE_NONE) { btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + if (shape) { + shape->setMargin(0.001f); + } return shape; } return nullptr; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 53fc826fb0..6447d8d8b5 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -114,6 +114,9 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const { int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { int32_t bytesRead = Avatar::parseDataFromBuffer(buffer); + for (size_t i = 0; i < _detailedMotionStates.size(); i++) { + _detailedMotionStates[i]->forceActive(); + } if (_moving && _motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); } @@ -166,7 +169,11 @@ btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBoun break; } if (shapeInfo.getType() != SHAPE_TYPE_NONE) { - return const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + auto shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); + if (shape) { + shape->setMargin(0.001f); + } + return shape; } return nullptr; } diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 507e45b470..85ffc97450 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -56,7 +56,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { } PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { - RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), QVector()); if (avatarRes.intersects) { return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { From 007e3ac577ee174e1a2e9459af51c190ad64bb72 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 17 Jan 2019 14:53:06 -0700 Subject: [PATCH 027/104] Fix warning and wrong ID --- interface/src/avatar/AvatarManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index f24e1e72eb..548b3d44a5 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -679,8 +679,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic AvatarPointer avatar = nullptr; for (auto &hit : physicsResults) { auto avatarID = hit._intersectWithAvatar; - bool skipThisAvatar = (avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) || - (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID)) && jointIndicesToFilter.size() == 0; + bool skipThisAvatar = ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatarID)) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatarID))) && jointIndicesToFilter.size() == 0; if (skipThisAvatar) { continue; } @@ -734,7 +734,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic boxHit._intersectionNormal = surfaceNormal; boxHit._intersectionPoint = ray.origin + boundDistance * glm::normalize(ray.direction); boxHit._intersectWithAvatar = avatarID; - boxHit._intersectWithJoint = hit._intersectWithJoint; + boxHit._intersectWithJoint = mSphere.getJointIndex(); boxHits.push_back(boxHit); } } From 3ffaced0f3588dd24568dfc930ce7be25656cf73 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 17 Jan 2019 13:59:33 -0800 Subject: [PATCH 028/104] Experimental attenuation curve: linear falloff, with prescribed propagation limit. A positive coefficient sets logarithmic falloff, as before. A negative coefficient sets linear falloff, where abs(coefficient) is the distance limit in meters. --- assignment-client/src/audio/AudioMixer.cpp | 2 +- .../src/audio/AudioMixerSlave.cpp | 30 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 77f416f31e..f80ce2f4c3 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -736,7 +736,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { float coefficient = coefficientObject.value(COEFFICIENT).toString().toFloat(&ok); - if (ok && coefficient >= 0.0f && coefficient <= 1.0f && + if (ok && coefficient <= 1.0f && itSource != end(_audioZones) && itListener != end(_audioZones)) { diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 3caa5584da..a7abea3704 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -770,15 +770,29 @@ float computeGain(float masterListenerGain, const AvatarAudioStream& listeningNo break; } } - // translate the zone setting to gain per log2(distance) - const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance) - float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f); - // calculate the attenuation using the distance to this node - // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF - float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); - gain *= fastExp2f(fastLog2f(g) * fastLog2f(d)); - gain = std::min(gain, ATTN_GAIN_MAX); + if (attenuationPerDoublingInDistance < 0.0f) { + // translate a negative zone setting to distance limit + const float MIN_DISTANCE_LIMIT = ATTN_DISTANCE_REF + 1.0f; // silent after 1m + float distanceLimit = std::max(-attenuationPerDoublingInDistance, MIN_DISTANCE_LIMIT); + + // calculate the LINEAR attenuation using the distance to this node + // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF + float d = distance - ATTN_DISTANCE_REF; + gain *= std::max(1.0f - d / (distanceLimit - ATTN_DISTANCE_REF), 0.0f); + gain = std::min(gain, ATTN_GAIN_MAX); + + } else { + // translate a positive zone setting to gain per log2(distance) + const float MIN_ATTENUATION_COEFFICIENT = 0.001f; // -60dB per log2(distance) + float g = glm::clamp(1.0f - attenuationPerDoublingInDistance, MIN_ATTENUATION_COEFFICIENT, 1.0f); + + // calculate the LOGARITHMIC attenuation using the distance to this node + // reference attenuation of 0dB at distance = ATTN_DISTANCE_REF + float d = (1.0f / ATTN_DISTANCE_REF) * std::max(distance, HRTF_NEARFIELD_MIN); + gain *= fastExp2f(fastLog2f(g) * fastLog2f(d)); + gain = std::min(gain, ATTN_GAIN_MAX); + } return gain; } From 43244193e889e539b2a65eaab4bd9b274142769f Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 18 Jan 2019 12:46:18 -0700 Subject: [PATCH 029/104] Mesh picking against default pose with transformed ray --- interface/src/avatar/AvatarManager.cpp | 42 +++++++++-- interface/src/avatar/AvatarManager.h | 6 +- interface/src/avatar/MyAvatar.cpp | 71 ------------------ interface/src/avatar/MyAvatar.h | 50 ------------- interface/src/raypick/RayPick.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 73 +++++++++++++++++++ .../src/avatars-renderer/Avatar.h | 51 +++++++++++++ 7 files changed, 165 insertions(+), 130 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 548b3d44a5..2c6f51cd4a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -43,6 +43,7 @@ #include "InterfaceLogging.h" #include "Menu.h" #include "MyAvatar.h" +#include "DebugDraw.h" #include "SceneScriptingInterface.h" // 50 times per second - target is 45hz, but this helps account for any small deviations @@ -641,18 +642,20 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude, const QScriptValue& avatarIdsToDiscard, - const QScriptValue& jointIndicesToFilter) { + const QScriptValue& jointIndicesToFilter, + bool pickAgainstMesh) { QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); QVector jointsToFilter; qVectorIntFromScriptValue(jointIndicesToFilter, jointsToFilter); - return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard, jointsToFilter); + return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard, jointsToFilter, pickAgainstMesh); } RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray, const QVector& avatarsToInclude, const QVector& avatarsToDiscard, - const QVector& jointIndicesToFilter) { + const QVector& jointIndicesToFilter, + bool pickAgainstMesh) { RayToAvatarIntersectionResult result; if (QThread::currentThread() != thread()) { BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector", @@ -660,7 +663,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic Q_ARG(const PickRay&, ray), Q_ARG(const QVector&, avatarsToInclude), Q_ARG(const QVector&, avatarsToDiscard), - Q_ARG(const QVector&, jointIndicesToFilter)); + Q_ARG(const QVector&, jointIndicesToFilter), + Q_ARG(bool, pickAgainstMesh)); return result; } @@ -673,7 +677,9 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic glm::vec3 surfaceNormal; QVariantMap extraInfo; std::vector physicsResults = _myAvatar->getCharacterController()->rayTest(glmToBullet(ray.origin), glmToBullet(ray.direction), distance, QVector()); - + glm::vec3 transformedRayPoint; + glm::vec3 transformedRayDirection; + if (physicsResults.size() > 0) { MyCharacterController::RayAvatarResult rayAvatarResult; AvatarPointer avatar = nullptr; @@ -752,13 +758,37 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic } } } + if (rayAvatarResult._intersect) { + if (pickAgainstMesh) { + glm::vec3 localRayOrigin = avatar->worldToJointPoint(ray.origin, rayAvatarResult._intersectWithJoint); + glm::vec3 localRayPoint = avatar->worldToJointPoint(ray.origin + ray.direction, rayAvatarResult._intersectWithJoint); + + auto avatarOrientation = avatar->getWorldOrientation(); + auto avatarPosition = avatar->getWorldPosition(); + + auto jointOrientation = avatarOrientation * avatar->getAbsoluteDefaultJointRotationInObjectFrame(rayAvatarResult._intersectWithJoint); + auto jointPosition = avatarPosition + (avatarOrientation * avatar->getAbsoluteDefaultJointTranslationInObjectFrame(rayAvatarResult._intersectWithJoint)); + + auto defaultFrameRayOrigin = jointPosition + jointOrientation * localRayOrigin; + auto defaultFrameRayPoint = jointPosition + jointOrientation * localRayPoint; + auto defaultFrameRayDirection = defaultFrameRayPoint - defaultFrameRayOrigin; + + if (avatar->getSkeletonModel()->findRayIntersectionAgainstSubMeshes(defaultFrameRayOrigin, defaultFrameRayDirection, distance, face, surfaceNormal, extraInfo, true, false)) { + auto newDistance = glm::length(vec3FromVariant(extraInfo["worldIntersectionPoint"]) - defaultFrameRayOrigin); + rayAvatarResult._distance = newDistance; + rayAvatarResult._intersectionPoint = ray.origin + newDistance * glm::normalize(ray.direction); + rayAvatarResult._intersectionNormal = surfaceNormal; + extraInfo["worldIntersectionPoint"] = vec3toVariant(rayAvatarResult._intersectionPoint); + } + } + result.intersects = true; result.avatarID = rayAvatarResult._intersectWithAvatar; result.distance = rayAvatarResult._distance; result.surfaceNormal = rayAvatarResult._intersectionNormal; result.jointIndex = rayAvatarResult._intersectWithJoint; - result.intersection = rayAvatarResult._intersectionPoint; + result.intersection = ray.origin + rayAvatarResult._distance * glm::normalize(ray.direction); result.extraInfo = extraInfo; result.face = face; } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 93be716087..5bc31d74b3 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -144,7 +144,8 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue(), - const QScriptValue& jointIndicesToFilter = QScriptValue()); + const QScriptValue& jointIndicesToFilter = QScriptValue(), + bool pickAgainstMesh = false); /**jsdoc * @function AvatarManager.findRayIntersectionVector * @param {PickRay} ray @@ -156,7 +157,8 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray, const QVector& avatarsToInclude, const QVector& avatarsToDiscard, - const QVector& jointIndicesToFilter); + const QVector& jointIndicesToFilter, + bool pickAgainstMesh); /**jsdoc * @function AvatarManager.findParabolaIntersectionVector diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 670444baf0..641b2dff00 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1122,77 +1122,6 @@ controller::Pose MyAvatar::getRightHandTipPose() const { return pose; } -glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { - glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - if (jointIndex != -1) { - if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { - _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); - } else { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - } - glm::vec3 modelOffset = position - jointPos; - glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; - - return jointSpacePosition; -} - -glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - - glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; - return jointSpaceDir; -} - -glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; - return jointSpaceRot; -} - -glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { - glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - - if (jointIndex != -1) { - if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { - _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); - } else { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - } - - glm::vec3 worldOffset = jointRot * jointSpacePos; - glm::vec3 worldPos = jointPos + worldOffset; - - return worldPos; -} - -glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - glm::vec3 worldDir = jointRot * jointSpaceDir; - return worldDir; -} - -glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { - glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified - if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { - qWarning() << "Invalid joint index specified: " << jointIndex; - } - glm::quat worldRot = jointRot * jointSpaceRot; - return worldRot; -} - // virtual void MyAvatar::render(RenderArgs* renderArgs) { // don't render if we've been asked to disable local rendering diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f107a488a6..5bc9c14f1d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -784,56 +784,6 @@ public: */ Q_INVOKABLE controller::Pose getRightHandTipPose() const; - // world-space to avatar-space rigconversion functions - /**jsdoc - * @function MyAvatar.worldToJointPoint - * @param {Vec3} position - * @param {number} [jointIndex=-1] - * @returns {Vec3} - */ - Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; - - /**jsdoc - * @function MyAvatar.worldToJointDirection - * @param {Vec3} direction - * @param {number} [jointIndex=-1] - * @returns {Vec3} - */ - Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; - - /**jsdoc - * @function MyAvatar.worldToJointRotation - * @param {Quat} rotation - * @param {number} [jointIndex=-1] - * @returns {Quat} - */ - Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; - - - /**jsdoc - * @function MyAvatar.jointToWorldPoint - * @param {vec3} position - * @param {number} [jointIndex=-1] - * @returns {Vec3} - */ - Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; - - /**jsdoc - * @function MyAvatar.jointToWorldDirection - * @param {Vec3} direction - * @param {number} [jointIndex=-1] - * @returns {Vec3} - */ - Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; - - /**jsdoc - * @function MyAvatar.jointToWorldRotation - * @param {Quat} rotation - * @param {number} [jointIndex=-1] - * @returns {Quat} - */ - Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; - AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } void updateLookAtTargetAvatar(); void computeMyLookAtTarget(const AvatarHash& hash); diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 85ffc97450..7d6c0b6eef 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -56,7 +56,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { } PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { - RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), QVector()); + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs(), QVector(), false); if (avatarRes.intersects) { return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f2252b2aa3..a34c17550f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1283,6 +1283,79 @@ glm::vec3 Avatar::getAbsoluteJointScaleInObjectFrame(int index) const { } } + +glm::vec3 Avatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { + glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + glm::vec3 modelOffset = position - jointPos; + glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; + + return jointSpacePosition; +} + +glm::vec3 Avatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + + glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; + return jointSpaceDir; +} + +glm::quat Avatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; + return jointSpaceRot; +} + +glm::vec3 Avatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { + glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + + glm::vec3 worldOffset = jointRot * jointSpacePos; + glm::vec3 worldPos = jointPos + worldOffset; + + return worldPos; +} + +glm::vec3 Avatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::vec3 worldDir = jointRot * jointSpaceDir; + return worldDir; +} + +glm::quat Avatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { + glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat worldRot = jointRot * jointSpaceRot; + return worldRot; +} + + void Avatar::invalidateJointIndicesCache() const { QWriteLocker writeLock(&_modelJointIndicesCacheLock); _modelJointsCached = false; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index db2908fc5d..c0c61992b1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -223,12 +223,63 @@ public: */ Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const; + virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override; virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + // world-space to avatar-space rigconversion functions + /**jsdoc + * @function MyAvatar.worldToJointPoint + * @param {Vec3} position + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.worldToJointDirection + * @param {Vec3} direction + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.worldToJointRotation + * @param {Quat} rotation + * @param {number} [jointIndex=-1] + * @returns {Quat} + */ + Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + + + /**jsdoc + * @function MyAvatar.jointToWorldPoint + * @param {vec3} position + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.jointToWorldDirection + * @param {Vec3} direction + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.jointToWorldRotation + * @param {Quat} rotation + * @param {number} [jointIndex=-1] + * @returns {Quat} + */ + Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; From 93516423bb9aeb87c887252f82967b7bd09b48fa Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 11 Jan 2019 21:32:18 +0100 Subject: [PATCH 030/104] zone visualizer prototype --- scripts/system/edit.js | 14 +- .../system/modules/entityShapeVisualizer.js | 209 ++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 scripts/system/modules/entityShapeVisualizer.js diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 84143f4c25..c6a30fe209 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -82,13 +82,18 @@ var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resourcesPath() + "icons/create-icons/23-zone-01.svg"; -var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; } else { return { url: PARTICLE_SYSTEM_URL, @@ -106,11 +111,15 @@ var gridTool = new GridTool({ }); gridTool.setVisible(false); +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.updateSelection(selectionManager.selections); }); var DEGREES_TO_RADIANS = Math.PI / 180.0; @@ -836,7 +845,7 @@ var toolBar = (function () { dialogWindow.fromQml.connect(fromQml); } }; - }; + } addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); @@ -1492,6 +1501,7 @@ Script.scriptEnding.connect(function () { cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); Entities.setLightsArePickable(originalLightsArePickable); Overlays.deleteOverlay(importingSVOImageOverlay); diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js new file mode 100644 index 0000000000..5c91b1311c --- /dev/null +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -0,0 +1,209 @@ +"use strict"; + +// entityShapeVisualizer.js +// +// Created by Thijs Wenker on 1/11/19 +// +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var SHAPETYPE_TO_SHAPE = { + "sphere": "Sphere", + "box": "Cube", + "ellipsoid": "Sphere", + "cylinder-y": "Cylinder", +}; + +function getEntityShapePropertiesForType(properties) { + switch (properties.type) { + case "Zone": + if (SHAPETYPE_TO_SHAPE[properties.shapeType]) { + return { + type: "Shape", + shape: SHAPETYPE_TO_SHAPE[properties.shapeType] + } + } else if (properties.shapeType === "compound") { + return { + type: "Model", + modelURL: properties.compoundShapeURL + } + } + break; + } + + // Default properties + return { + type: "Shape", + shape: "Cube" + } +} + +function getStringifiedEntityShapePropertiesForType(properties) { + return JSON.stringify(getEntityShapePropertiesForType(properties)); +} + +var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ + 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' +]; + +function EntityShape(entityID) { + this.entityID = entityID; + var properties = Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + + this.previousPropertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); + + this.initialize(properties, this.previousPropertiesForTypeStringified); +} + +EntityShape.prototype = { + initialize: function(properties, propertiesForTypeStringified) { + // Create new instance of JS object: + var overlayProperties = JSON.parse(propertiesForTypeStringified); + + overlayProperties.localPosition = Vec3.ZERO; + overlayProperties.localRotation = Quat.IDENTITY; + overlayProperties.localDimensions = properties.localDimensions; + overlayProperties.canCastShadows = false; + overlayProperties.parentID = this.entityID; + overlayProperties.collisionless = true; + this.entity = Entities.addEntity(overlayProperties, "local"); + + console.warn("created " + this.entity); + console.warn("SHAPETYPE = " + properties.shapeType); + console.warn("SHAPE = " + Entities.getEntityProperties(this.entity, "shape").shape); + + + this.materialEntity = Entities.addEntity({ + type: "Material", + localPosition: Vec3.ZERO, + localRotation: Quat.IDENTITY, + localDimensions: properties.localDimensions, + parentID: this.entity, + priority: 1, + materialURL: "materialData", + materialData: JSON.stringify({ + materialVersion: 1, + materials: { + albedo: [0.0, 0.0, 7.0], + unlit: true, + opacity: 0.4 + } + }), + }, "local"); + + }, + + update: function() { + var properties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + var propertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); + if (propertiesForTypeStringified !== this.previousPropertiesForTypeStringified) { + this.previousPropertiesForTypeStringified = propertiesForTypeStringified; + console.warn("Clearing old properties"); + this.clear(); + this.initialize(properties, propertiesForTypeStringified); + } else { + Entities.editEntity(this.entity, { + localDimensions: properties.localDimensions, + }); + } + + + + //this.previousProperties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + + + console.warn(JSON.stringify(this.previousProperties)); + }, + clear: function() { + Entities.deleteEntity(this.materialEntity); + Entities.deleteEntity(this.entity); + } +}; + +function EntityShapeVisualizer(visualizedTypes) { + this.acceptedEntities = []; + this.ignoredEntities = []; + this.entityShapes = {}; + + this.visualizedTypes = visualizedTypes; +} + +EntityShapeVisualizer.prototype = { + addEntity: function(entityID, properties) { + if (this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID] = new EntityShape(entityID); + + }, + updateEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].update(); + }, + removeEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].clear(); + delete this.entityShapes[entityID]; + }, + cleanup: function() { + Object.keys(this.entityShapes).forEach(function(entityID) { + this.entityShapes[entityID].clear(); + }, this); + this.entityShapes = {}; + }, + updateSelection: function(selection) { + var qualifiedSelection = selection.filter(function(entityID) { + if (this.acceptedEntities.indexOf(entityID) !== -1) { + return true; + } + if (this.ignoredEntities.indexOf(entityID) !== -1) { + return false; + } + if (this.visualizedTypes.indexOf(Entities.getEntityProperties(entityID, "type").type) !== -1) { + this.acceptedEntities.push(entityID); + return true; + } + this.ignoredEntities.push(entityID); + return false; + }, this); + + + var newEntries = []; + var updateEntries = []; + + var currentEntries = Object.keys(this.entityShapes); + qualifiedSelection.forEach(function(entityID) { + if (currentEntries.indexOf(entityID) !== -1) { + updateEntries.push(entityID); + } else { + newEntries.push(entityID); + } + }); + + var deleteEntries = currentEntries.filter(function(entityID) { + return updateEntries.indexOf(entityID) === -1; + }); + + deleteEntries.forEach(function(entityID) { + console.warn("removing " + entityID); + this.removeEntity(entityID); + }, this); + + updateEntries.forEach(function(entityID) { + this.updateEntity(entityID); + }, this); + + newEntries.forEach(function(entityID) { + this.addEntity(entityID); + }, this); + } +}; + +module.exports = EntityShapeVisualizer; From 6c431996c77be700aec6276ae832c5d1b15e9644 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Tue, 22 Jan 2019 12:21:06 -0300 Subject: [PATCH 031/104] Allow to set a display name and choose an avatar --- android/app/src/main/cpp/native.cpp | 28 ++++ .../hifiinterface/MainActivity.java | 31 ++++- .../fragment/ProfileFragment.java | 126 ++++++++++++++++++ .../provider/AvatarProvider.java | 70 ++++++++++ .../hifiinterface/view/AvatarAdapter.java | 111 +++++++++++++++ .../app/src/main/res/layout/avatar_item.xml | 22 +++ .../src/main/res/layout/fragment_profile.xml | 107 +++++++++++++++ .../app/src/main/res/menu/menu_navigation.xml | 4 + android/app/src/main/res/values/dimens.xml | 5 +- android/app/src/main/res/values/strings.xml | 4 + interface/src/AndroidHelper.cpp | 14 ++ interface/src/AndroidHelper.h | 3 + 12 files changed, 523 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java create mode 100644 android/app/src/main/res/layout/avatar_item.xml create mode 100644 android/app/src/main/res/layout/fragment_profile.xml diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index f9c7751a3e..01d805b077 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J } +JNIEXPORT jstring JNICALL +Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env, + jobject instance) { + + QString displayName = AndroidHelper::instance().getDisplayName(); + return env->NewStringUTF(displayName.toLatin1().data()); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env, + jobject instance, + jstring name_) { + const char *c_name = env->GetStringUTFChars(name_, 0); + const QString name = QString::fromUtf8(c_name); + env->ReleaseStringUTFChars(name_, c_name); + AndroidHelper::instance().setDisplayName(name); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env, + jobject instance, + jstring url_) { + const char *url = env->GetStringUTFChars(url_, 0); + QString avatarUrl = QString::fromUtf8(url); + AndroidHelper::instance().setMyAvatarUrl(avatarUrl); + env->ReleaseStringUTFChars(url_, url); +} + JNIEXPORT void JNICALL Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) { DependencyManager::get()->logout(); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index e17b530f1c..2e3ca8a5e5 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso; import io.highfidelity.hifiinterface.fragment.FriendsFragment; import io.highfidelity.hifiinterface.fragment.HomeFragment; import io.highfidelity.hifiinterface.fragment.PolicyFragment; +import io.highfidelity.hifiinterface.fragment.ProfileFragment; import io.highfidelity.hifiinterface.fragment.SettingsFragment; import io.highfidelity.hifiinterface.fragment.SignupFragment; import io.highfidelity.hifiinterface.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, HomeFragment.OnHomeInteractionListener, - FriendsFragment.OnHomeInteractionListener { + FriendsFragment.OnHomeInteractionListener, + ProfileFragment.OnProfileInteractionListener { private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar; public static final String DEFAULT_FRAGMENT = "Home"; @@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private View mProfilePanel; private TextView mLogoutOption; private MenuItem mPeopleMenuItem; + private MenuItem mProfileMenuItem; private boolean backToScene; private String backToUrl; @@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people); + mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile); + updateDebugMenu(mNavigationView.getMenu()); Toolbar toolbar = findViewById(R.id.toolbar); @@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true); } + private void loadProfileFragment() { + Fragment fragment = ProfileFragment.newInstance(); + + loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true); + } + private void loadSettingsFragment() { SettingsFragment fragment = SettingsFragment.newInstance(); @@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case R.id.action_people: loadPeopleFragment(); return true; + case R.id.action_profile: + loadProfileFragment(); + break; case R.id.action_debug_settings: loadSettingsFragment(); return true; @@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On goToUser(username); } + @Override + public void onCancelProfileEdit() { + getFragmentManager().popBackStack(); + } + + @Override + public void onCompleteProfileEdit() { + getFragmentManager().popBackStack(); + } + + @Override + public void onAvatarChosen() { + getFragmentManager().popBackStack(); + } + private class RoundProfilePictureCallback implements Callback { @Override public void onSuccess() { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java new file mode 100644 index 0000000000..e5aa793341 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java @@ -0,0 +1,126 @@ +package io.highfidelity.hifiinterface.fragment; + + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.TextView; + +import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.AvatarProvider; +import io.highfidelity.hifiinterface.view.AvatarAdapter; + +public class ProfileFragment extends Fragment { + + private TextView mDisplayName; + + private Button mOkButton; + private OnProfileInteractionListener mListener; + private AvatarProvider mAvatarsProvider; + + private native String getDisplayName(); + private native void setDisplayName(String name); + private native void setAvatarUrl(String url); + + public ProfileFragment() { + // Required empty public constructor + } + + public static ProfileFragment newInstance() { + ProfileFragment fragment = new ProfileFragment(); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_profile, container, false); + + mDisplayName = rootView.findViewById(R.id.displayName); + mDisplayName.setText(getDisplayName()); + mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent)); + + mOkButton = rootView.findViewById(R.id.okButton); + mOkButton.setOnClickListener(view -> onOkButtonClicked()); + + rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit()); + + RecyclerView avatarsView = rootView.findViewById(R.id.gridview); + int numberOfColumns = 1; + mAvatarsProvider = new AvatarProvider(getContext()); + GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns); + avatarsView.setLayoutManager(gridLayoutMgr); + AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider); + avatarsView.setAdapter(avatarAdapter); + avatarAdapter.loadAvatars(); + + avatarAdapter.setClickListener((view, position, avatar) -> { + setAvatarUrl(avatar.avatarUrl); + if (mListener != null) { + mListener.onAvatarChosen(); + } + }); + return rootView; + } + + private void onOkButtonClicked() { + setDisplayName(mDisplayName.getText().toString()); + View view = getActivity().getCurrentFocus(); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + if (mListener != null) { + mListener.onCompleteProfileEdit(); + } + } + + private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + mOkButton.performClick(); + return true; + } + return false; + } + + private void onCancelProfileEdit() { + View view = getActivity().getCurrentFocus(); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + if (mListener != null) { + mListener.onCancelProfileEdit(); + } + } + + /** + * Processes the back pressed event and returns true if it was managed by this Fragment + * @return + */ + public boolean onBackPressed() { + return false; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnProfileInteractionListener) { + mListener = (OnProfileInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnProfileInteractionListener"); + } + } + + public interface OnProfileInteractionListener { + void onCancelProfileEdit(); + void onCompleteProfileEdit(); + void onAvatarChosen(); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java new file mode 100644 index 0000000000..5bbb8ee666 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java @@ -0,0 +1,70 @@ +package io.highfidelity.hifiinterface.provider; + +import android.content.Context; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.view.AvatarAdapter; + +/** + * Created by gcalero on 1/21/19 + */ +public class AvatarProvider { + + private static final String AVATARS_JSON = "avatars.json"; + private static final String JSON_FIELD_NAME = "name"; + private static final String JSON_FIELD_URL = "url"; + private static final String JSON_FIELD_IMAGE = "preview_image"; + private static final String JSON_FIELD_AVATARS_ARRAY = "avatars"; + private final Context mContext; + + public interface AvatarsCallback { + void retrieveOk(List avatars); + void retrieveError(Exception e, String message); + } + + public AvatarProvider(Context context) { + mContext = context; + } + + public void retrieve(AvatarsCallback avatarsCallback) + { + try { + JSONObject obj = new JSONObject(loadJSONFromAssets()); + JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY); + ArrayList avatars = new ArrayList<>(); + + for (int i = 0; i < m_jArry.length(); i++) { + JSONObject jo_inside = m_jArry.getJSONObject(i); + AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar(); + anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME); + anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE); + anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL); + avatars.add(anAvatar); + } + avatarsCallback.retrieveOk(avatars); + } catch (IOException e) { + avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON"); + } catch (JSONException e) { + avatarsCallback.retrieveError(e, "Failed parsing avatar JSON"); + } + } + + private String loadJSONFromAssets() throws IOException { + String json = null; + InputStream is = mContext.getAssets().open(AVATARS_JSON); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + json = new String(buffer, "UTF-8"); + return json; + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java new file mode 100644 index 0000000000..d88083ff2a --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java @@ -0,0 +1,111 @@ +package io.highfidelity.hifiinterface.view; + +import android.content.Context; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; + +import java.util.ArrayList; +import java.util.List; + +import io.highfidelity.hifiinterface.R; +import io.highfidelity.hifiinterface.provider.AvatarProvider; + +/** + * Created by gcalero on 1/21/19 + */ +public class AvatarAdapter extends RecyclerView.Adapter { + + private static final String TAG = "Interface"; + private final Context mContext; + private final LayoutInflater mInflater; + private final AvatarProvider mProvider; + private List mAvatars = new ArrayList<>(); + private ItemClickListener mClickListener; + + public AvatarAdapter(Context context, AvatarProvider provider) { + mContext = context; + mInflater = LayoutInflater.from(mContext); + mProvider = provider; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.avatar_item, parent, false); + return new AvatarAdapter.ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + AvatarAdapter.Avatar anAvatar = mAvatars.get(position); + assert(holder.mName != null); + holder.mName.setText(anAvatar.avatarName); + Uri uri = Uri.parse(anAvatar.avatarPreviewUrl); + Picasso.get().load(uri).into(holder.mPreviewImage); + } + + @Override + public int getItemCount() { + return mAvatars.size(); + } + + public void loadAvatars() { + mProvider.retrieve(new AvatarProvider.AvatarsCallback() { + @Override + public void retrieveOk(List avatars) { + mAvatars = new ArrayList<>(avatars); + notifyDataSetChanged(); + } + + @Override + public void retrieveError(Exception e, String message) { + Log.e(TAG, message, e); + } + }); + } + + public void setClickListener(ItemClickListener clickListener) { + mClickListener = clickListener; + } + + public interface ItemClickListener { + void onItemClick(View view, int position, Avatar avatar); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + TextView mName; + ImageView mPreviewImage; + + public ViewHolder(View itemView) { + super(itemView); + mName = itemView.findViewById(R.id.avatarName); + assert (mName != null); + mPreviewImage = itemView.findViewById(R.id.avatarPreview); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + int position= getAdapterPosition(); + if (mClickListener != null) { + mClickListener.onItemClick(view, position, mAvatars.get(position)); + } + } + } + + public static class Avatar { + public String avatarName; + public String avatarUrl; + public String avatarPreviewUrl; + + public Avatar() { } + } +} diff --git a/android/app/src/main/res/layout/avatar_item.xml b/android/app/src/main/res/layout/avatar_item.xml new file mode 100644 index 0000000000..4da6ee1170 --- /dev/null +++ b/android/app/src/main/res/layout/avatar_item.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_profile.xml b/android/app/src/main/res/layout/fragment_profile.xml new file mode 100644 index 0000000000..8a5f925ad2 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_profile.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + +