Merge branch 'master' into Sitting_emote_variants

This commit is contained in:
dooglifeSF 2019-09-05 16:45:24 -07:00
commit b5d2675a05
49 changed files with 1283 additions and 1041 deletions

View file

@ -33,7 +33,7 @@ MixerAvatar::MixerAvatar() {
_challengeTimer.setSingleShot(true);
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
_challengeTimer.callOnTimeout([this]() {
_challengeTimer.callOnTimeout(this, [this]() {
if (_verifyState == challengeClient) {
_pendingEvent = false;
_verifyState = verificationFailed;

View file

@ -15,9 +15,11 @@ macro(TARGET_WEBRTC)
# select_library_configurations(WEBRTC)
else()
set(WEBRTC_INCLUDE_DIRS "${VCPKG_INSTALL_ROOT}/include/webrtc")
find_library(WEBRTC_LIBRARY NAMES webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH)
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${WEBRTC_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARY})
find_library(WEBRTC_LIBRARY_RELEASE webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH)
find_library(WEBRTC_LIBRARY_DEBUG webrtc PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH)
select_library_configurations(WEBRTC)
target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARIES})
endif()

View file

@ -117,6 +117,7 @@ if (APPLE)
# configure CMake to use a custom Info.plist
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
set(MACOSX_BUNDLE_BUNDLE_NAME "High Fidelity")
if (PRODUCTION_BUILD)
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface)
else ()

View file

@ -136,17 +136,17 @@ Flickable {
Layout.preferredHeight: 18
Layout.preferredWidth: parent.width
labelTextOn: "Show Emote UI"
checked: Settings.getValue("simplifiedUI/emoteIndicatorVisible", true)
checked: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true)
onClicked: {
var currentSetting = Settings.getValue("simplifiedUI/emoteIndicatorVisible", true);
Settings.setValue("simplifiedUI/emoteIndicatorVisible", !currentSetting);
var currentSetting = Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true);
Settings.setValue("simplifiedUI/allowEmoteDrawerExpansion", !currentSetting);
}
Connections {
target: Settings
onValueChanged: {
if (setting === "simplifiedUI/emoteIndicatorVisible") {
if (setting === "simplifiedUI/allowEmoteDrawerExpansion") {
emoteSwitch.checked = value;
}
}

View file

@ -154,6 +154,7 @@ class MyAvatar : public Avatar {
*
* @property {Vec3} qmlPosition - A synonym for <code>position</code> for use by QML.
*
* @property {Vec3} feetPosition - The position of the avatar's feet.
* @property {boolean} shouldRenderLocally=true - If <code>true</code> then your avatar is rendered for you in Interface,
* otherwise it is not rendered for you (but it is still rendered for other users).
* @property {Vec3} motorVelocity=Vec3.ZERO - The target velocity of your avatar to be achieved by a scripted motor.
@ -340,6 +341,7 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); }
Q_PROPERTY(glm::vec3 feetPosition READ getWorldFeetPosition WRITE goToFeetLocation)
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
@ -1938,9 +1940,8 @@ public slots:
* @param {boolean} [shouldFaceLocation=false] - Set to <code>true</code> to position the avatar a short distance away from
* the new position and orientate the avatar to face the position.
*/
void goToFeetLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation);
void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false,
const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false);
/**jsdoc
* Moves the avatar to a new position and/or orientation in the domain.

View file

@ -337,10 +337,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
eyeParams.modelTranslation = getTranslation();
eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye");
eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye");
if (_owningAvatar->getHasProceduralEyeFaceMovement()) {
_rig.updateFromEyeParameters(eyeParams);
}
_rig.updateFromEyeParameters(eyeParams);
updateFingers();
}

View file

@ -1,6 +1,10 @@
#import "DownloadLauncher.h"
#import "Launcher.h"
#include <sys/stat.h>
static const __int32_t kMinLauncherSize = 250000; // 308kb is our smallest launcher
static const NSString *kIOError = @"IOError";
@implementation DownloadLauncher
@ -22,8 +26,38 @@
}
-(void)validateHQLauncherZipAt:(NSURL *)location {
// Does the file look like a valid HQLauncher ZIP?
struct stat lStat;
const char *cStrLocation = location.fileSystemRepresentation;
if (stat(cStrLocation, &lStat) != 0) {
NSLog(@"couldn't stat download file: %s", cStrLocation);
@throw [NSException exceptionWithName:(NSString *)kIOError
reason:@"couldn't stat download file"
userInfo:nil];
}
if (lStat.st_size <= kMinLauncherSize) {
NSLog(@"download is too small: %s is %lld bytes, should be at least %d bytes",
cStrLocation, lStat.st_size, kMinLauncherSize);
@throw [NSException exceptionWithName:(NSString *)kIOError
reason:@"download is too small"
userInfo:nil];
}
}
-(void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didFinishDownloadingToURL:(NSURL*)location {
NSLog(@"Did finish downloading to url");
@try {
[self validateHQLauncherZipAt:location];
}
@catch (NSException *exc) {
if ([exc.name isEqualToString:(NSString *)kIOError]) {
[[Launcher sharedLauncher] displayErrorPage];
return;
}
@throw;
}
NSError* error = nil;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSString* destinationFileName = downloadTask.originalRequest.URL.lastPathComponent;

View file

@ -21,15 +21,39 @@ int main(int argc, char const* argv[]) {
for (int index = 0; index < argc; index++) {
NSLog(@"argv at index %d = %s", index, argv[index]);
}
NSError *error = nil;
NSString* oldLauncher = [NSString stringWithUTF8String:argv[1]];
NSString* newLauncher = [NSString stringWithUTF8String:argv[2]];
NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newLauncher];
NSFileManager* fileManager = [NSFileManager defaultManager];
[fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:oldLauncher] withItemAtURL:[UpdaterHelper NSStringToNSURL:newLauncher] backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:nil];
[fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:oldLauncher]
withItemAtURL:[UpdaterHelper NSStringToNSURL:newLauncher]
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:&destinationUrl
error:&error];
if (error != nil) {
NSLog(@"couldn't update launcher: %@", error);
return 1;
}
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
NSURL* applicationURL = [UpdaterHelper NSStringToNSURL: [oldLauncher stringByAppendingString: @"/Contents/MacOS/HQ Launcher"]];
NSArray* arguments =@[];
NSLog(@"Launcher agruments: %@", arguments);
[workspace launchApplicationAtURL:applicationURL options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:nil];
NSDictionary *configuration = [NSDictionary dictionaryWithObject:arguments
forKey:NSWorkspaceLaunchConfigurationArguments];
[workspace launchApplicationAtURL:applicationURL
options:NSWorkspaceLaunchNewInstance
configuration:configuration
error:&error];
if (error != nil) {
NSLog(@"couldn't start launcher: %@", error);
return 1;
}
return 0;
}

View file

@ -13,6 +13,8 @@
#include "LauncherApp.h"
#include "LauncherDlg.h"
#include <propsys.h>
#include <propkey.h>
#include <d2d1.h>
#pragma comment(lib, "d2d1")
@ -84,7 +86,7 @@ END_MESSAGE_MAP()
BOOL CLauncherDlg::OnInitDialog() {
CDialog::OnInitDialog();
MarkWindowAsUnpinnable();
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
@ -129,6 +131,19 @@ BOOL CLauncherDlg::OnInitDialog() {
return TRUE;
}
void CLauncherDlg::MarkWindowAsUnpinnable() {
HWND hwnd = AfxGetMainWnd()->m_hWnd;
IPropertyStore* pps;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr)) {
PROPVARIANT var;
var.vt = VT_BOOL;
var.boolVal = VARIANT_TRUE;
hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
pps->Release();
}
}
POINT CLauncherDlg::getMouseCoords(MSG* pMsg) {
POINT pos;
pos.x = (int)(short)LOWORD(pMsg->lParam);

View file

@ -59,6 +59,7 @@ protected:
BOOL getTextFormat(int ResID, TextFormat& formatOut);
void showWindows(std::vector<CStatic*> windows, bool show);
POINT getMouseCoords(MSG* pMsg);
void MarkWindowAsUnpinnable();
bool _isConsoleRunning { false };

View file

@ -150,7 +150,7 @@ BOOL LauncherUtils::launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs)
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// start the program up
BOOL success = CreateProcess(
lpApplicationName, // the path

View file

@ -14,9 +14,10 @@
#include "AnimUtil.h"
#include "AnimClip.h"
AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) :
AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha, AnimBlendType blendType) :
AnimNode(AnimNode::Type::BlendLinear, id),
_alpha(alpha) {
_alpha(alpha),
_blendType(blendType) {
}
@ -36,6 +37,19 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con
} else if (_children.size() == 1) {
_poses = _children[0]->evaluate(animVars, context, dt, triggersOut);
context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType());
} else if (_children.size() == 2 && _blendType != AnimBlendType_Normal) {
// special case for additive blending
float alpha = glm::clamp(_alpha, 0.0f, 1.0f);
const size_t prevPoseIndex = 0;
const size_t nextPoseIndex = 1;
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
// for animation stack debugging
float weight2 = alpha;
float weight1 = 1.0f - weight2;
context.setDebugAlpha(_children[prevPoseIndex]->getID(), weight1 * parentDebugAlpha, _children[prevPoseIndex]->getType());
context.setDebugAlpha(_children[nextPoseIndex]->getID(), weight2 * parentDebugAlpha, _children[nextPoseIndex]->getType());
} else {
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
size_t prevPoseIndex = glm::floor(clampedAlpha);
@ -79,7 +93,35 @@ void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, c
if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) {
_poses.resize(prevPoses.size());
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
if (_blendType == AnimBlendType_Normal) {
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
} else if (_blendType == AnimBlendType_AddRelative) {
::blendAdd(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
} else if (_blendType == AnimBlendType_AddAbsolute) {
// convert prev from relative to absolute
AnimPoseVec absPrev = prevPoses;
_skeleton->convertRelativePosesToAbsolute(absPrev);
// rotate the offset rotations from next into the parent relative frame of each joint.
AnimPoseVec relOffsetPoses;
relOffsetPoses.reserve(nextPoses.size());
for (size_t i = 0; i < nextPoses.size(); ++i) {
// copy translation and scale from nextPoses
AnimPose pose = nextPoses[i];
int parentIndex = _skeleton->getParentIndex((int)i);
if (parentIndex >= 0) {
// but transform nextPoses rot into absPrev parent frame.
pose.rot() = glm::inverse(absPrev[parentIndex].rot()) * pose.rot() * absPrev[parentIndex].rot();
}
relOffsetPoses.push_back(pose);
}
// then blend
::blendAdd(_poses.size(), &prevPoses[0], &relOffsetPoses[0], alpha, &_poses[0]);
}
}
}
}

View file

@ -27,7 +27,7 @@ class AnimBlendLinear : public AnimNode {
public:
friend class AnimTests;
AnimBlendLinear(const QString& id, float alpha);
AnimBlendLinear(const QString& id, float alpha, AnimBlendType blendType);
virtual ~AnimBlendLinear() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
@ -44,6 +44,7 @@ protected:
AnimPoseVec _poses;
float _alpha;
AnimBlendType _blendType;
QString _alphaVar;

View file

@ -16,100 +16,6 @@
#include "AnimationLogging.h"
#include "AnimUtil.h"
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame),
_endFrame(endFrame),
_timeScale(timeScale),
_loopFlag(loopFlag),
_mirrorFlag(mirrorFlag),
_frame(startFrame)
{
loadURL(url);
}
AnimClip::~AnimClip() {
}
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
// lookup parameters from animVars, using current instance variables as defaults.
_startFrame = animVars.lookup(_startFrameVar, _startFrame);
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
_mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag);
float frame = animVars.lookup(_frameVar, _frame);
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut);
// poll network anim to see if it's finished loading yet.
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
// loading is complete, copy animation frames from network animation, then throw it away.
copyFromNetworkAnim();
_networkAnim.reset();
}
if (_anim.size()) {
// lazy creation of mirrored animation frames.
if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) {
buildMirrorAnim();
}
int prevIndex = (int)glm::floor(_frame);
int nextIndex;
if (_loopFlag && _frame >= _endFrame) {
nextIndex = (int)glm::ceil(_startFrame);
} else {
nextIndex = (int)glm::ceil(_frame);
}
// It can be quite possible for the user to set _startFrame and _endFrame to
// values before or past valid ranges. We clamp the frames here.
int frameCount = (int)_anim.size();
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex];
const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex];
float alpha = glm::fract(_frame);
::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
}
processOutputJoints(triggersOut);
return _poses;
}
void AnimClip::loadURL(const QString& url) {
auto animCache = DependencyManager::get<AnimationCache>();
_networkAnim = animCache->getAnimation(url);
_url = url;
}
void AnimClip::setCurrentFrameInternal(float frame) {
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
AnimVariantMap triggers;
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers);
}
static std::vector<int> buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) {
std::vector<int> jointIndexMap;
int srcJointCount = srcSkeleton.getNumJoints();
jointIndexMap.reserve(srcJointCount);
for (int srcJointIndex = 0; srcJointIndex < srcJointCount; srcJointIndex++) {
QString srcJointName = srcSkeleton.getJointName(srcJointIndex);
int dstJointIndex = dstSkeleton.nameToJointIndex(srcJointName);
jointIndexMap.push_back(dstJointIndex);
}
return jointIndexMap;
}
#ifdef USE_CUSTOM_ASSERT
#undef ASSERT
#define ASSERT(x) \
@ -123,12 +29,73 @@ static std::vector<int> buildJointIndexMap(const AnimSkeleton& dstSkeleton, cons
#define ASSERT assert
#endif
void AnimClip::copyFromNetworkAnim() {
assert(_networkAnim && _networkAnim->isLoaded() && _skeleton);
_anim.clear();
static std::vector<int> buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) {
std::vector<int> jointIndexMap;
int srcJointCount = srcSkeleton.getNumJoints();
jointIndexMap.reserve(srcJointCount);
for (int srcJointIndex = 0; srcJointIndex < srcJointCount; srcJointIndex++) {
QString srcJointName = srcSkeleton.getJointName(srcJointIndex);
int dstJointIndex = dstSkeleton.nameToJointIndex(srcJointName);
jointIndexMap.push_back(dstJointIndex);
}
return jointIndexMap;
}
auto avatarSkeleton = getSkeleton();
const HFMModel& animModel = _networkAnim->getHFMModel();
static void bakeRelativeDeltaAnim(std::vector<AnimPoseVec>& anim, const AnimPoseVec& basePoses) {
// invert all the basePoses
AnimPoseVec invBasePoses = basePoses;
for (auto&& invBasePose : invBasePoses) {
invBasePose = invBasePose.inverse();
}
// for each frame of the animation
for (auto&& animPoses : anim) {
ASSERT(animPoses.size() == basePoses.size());
// for each joint in animPoses
for (size_t i = 0; i < animPoses.size(); ++i) {
// convert this relative AnimPose into a delta animation.
animPoses[i] = animPoses[i] * invBasePoses[i];
}
}
}
void bakeAbsoluteDeltaAnim(std::vector<AnimPoseVec>& anim, const AnimPoseVec& basePoses, AnimSkeleton::ConstPointer skeleton) {
// invert all the basePoses
AnimPoseVec invBasePoses = basePoses;
for (auto&& invBasePose : invBasePoses) {
invBasePose = invBasePose.inverse();
}
AnimPoseVec absBasePoses = basePoses;
skeleton->convertRelativePosesToAbsolute(absBasePoses);
// for each frame of the animation
for (auto&& animPoses : anim) {
ASSERT(animPoses.size() == basePoses.size());
// for each joint in animPoses
for (size_t i = 0; i < animPoses.size(); ++i) {
// scale and translation are relative frame
animPoses[i] = animPoses[i] * invBasePoses[i];
// but transform the rotation delta into the absolute frame.
int parentIndex = skeleton->getParentIndex((int)i);
if (parentIndex >= 0) {
animPoses[i].rot() = absBasePoses[parentIndex].rot() * animPoses[i].rot() * glm::inverse(absBasePoses[parentIndex].rot());
}
}
}
}
static std::vector<AnimPoseVec> copyAndRetargetFromNetworkAnim(AnimationPointer networkAnim, AnimSkeleton::ConstPointer avatarSkeleton) {
ASSERT(networkAnim && networkAnim->isLoaded() && avatarSkeleton);
std::vector<AnimPoseVec> anim;
const HFMModel& animModel = networkAnim->getHFMModel();
AnimSkeleton animSkeleton(animModel);
const int animJointCount = animSkeleton.getNumJoints();
const int avatarJointCount = avatarSkeleton->getNumJoints();
@ -137,7 +104,7 @@ void AnimClip::copyFromNetworkAnim() {
std::vector<int> avatarToAnimJointIndexMap = buildJointIndexMap(animSkeleton, *avatarSkeleton);
const int animFrameCount = animModel.animationFrames.size();
_anim.resize(animFrameCount);
anim.resize(animFrameCount);
// find the size scale factor for translation in the animation.
float boneLengthScale = 1.0f;
@ -223,8 +190,8 @@ void AnimClip::copyFromNetworkAnim() {
// convert avatar rotations into relative frame
avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations);
ASSERT(frame >= 0 && frame < (int)_anim.size());
_anim[frame].reserve(avatarJointCount);
ASSERT(frame >= 0 && frame < (int)anim.size());
anim[frame].reserve(avatarJointCount);
for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) {
const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex);
@ -251,14 +218,129 @@ void AnimClip::copyFromNetworkAnim() {
// build the final pose
ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarRotations.size());
_anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation));
anim[frame].push_back(AnimPose(relativeScale, avatarRotations[avatarJointIndex], relativeTranslation));
}
}
// mirrorAnim will be re-built on demand, if needed.
_mirrorAnim.clear();
return anim;
}
_poses.resize(avatarJointCount);
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag,
AnimBlendType blendType, const QString& baseURL, float baseFrame) :
AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame),
_endFrame(endFrame),
_timeScale(timeScale),
_loopFlag(loopFlag),
_mirrorFlag(mirrorFlag),
_frame(startFrame),
_blendType(blendType),
_baseFrame(baseFrame)
{
loadURL(url);
if (blendType != AnimBlendType_Normal) {
auto animCache = DependencyManager::get<AnimationCache>();
_baseNetworkAnim = animCache->getAnimation(baseURL);
_baseURL = baseURL;
}
}
AnimClip::~AnimClip() {
}
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) {
// lookup parameters from animVars, using current instance variables as defaults.
_startFrame = animVars.lookup(_startFrameVar, _startFrame);
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
_mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag);
float frame = animVars.lookup(_frameVar, _frame);
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut);
// poll network anim to see if it's finished loading yet.
if (_blendType == AnimBlendType_Normal) {
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
// loading is complete, copy & retarget animation.
_anim = copyAndRetargetFromNetworkAnim(_networkAnim, _skeleton);
// we no longer need the actual animation resource anymore.
_networkAnim.reset();
// mirrorAnim will be re-built on demand, if needed.
_mirrorAnim.clear();
_poses.resize(_skeleton->getNumJoints());
}
} else {
// an additive blend type
if (_networkAnim && _networkAnim->isLoaded() && _baseNetworkAnim && _baseNetworkAnim->isLoaded() && _skeleton) {
// loading is complete, copy & retarget animation.
_anim = copyAndRetargetFromNetworkAnim(_networkAnim, _skeleton);
// we no longer need the actual animation resource anymore.
_networkAnim.reset();
// mirrorAnim will be re-built on demand, if needed.
// TODO: handle mirrored relative animations.
_mirrorAnim.clear();
_poses.resize(_skeleton->getNumJoints());
// copy & retarget baseAnim!
auto baseAnim = copyAndRetargetFromNetworkAnim(_baseNetworkAnim, _skeleton);
if (_blendType == AnimBlendType_AddAbsolute) {
bakeAbsoluteDeltaAnim(_anim, baseAnim[(int)_baseFrame], _skeleton);
} else {
// AnimBlendType_AddRelative
bakeRelativeDeltaAnim(_anim, baseAnim[(int)_baseFrame]);
}
}
}
if (_anim.size()) {
// lazy creation of mirrored animation frames.
if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) {
buildMirrorAnim();
}
int prevIndex = (int)glm::floor(_frame);
int nextIndex;
if (_loopFlag && _frame >= _endFrame) {
nextIndex = (int)glm::ceil(_startFrame);
} else {
nextIndex = (int)glm::ceil(_frame);
}
// It can be quite possible for the user to set _startFrame and _endFrame to
// values before or past valid ranges. We clamp the frames here.
int frameCount = (int)_anim.size();
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex];
const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex];
float alpha = glm::fract(_frame);
::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
}
processOutputJoints(triggersOut);
return _poses;
}
void AnimClip::setCurrentFrameInternal(float frame) {
// because dt is 0, we should not encounter any triggers
const float dt = 0.0f;
AnimVariantMap triggers;
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame + _startFrame, dt, _loopFlag, _id, triggers);
}
void AnimClip::buildMirrorAnim() {
@ -275,3 +357,9 @@ void AnimClip::buildMirrorAnim() {
const AnimPoseVec& AnimClip::getPosesInternal() const {
return _poses;
}
void AnimClip::loadURL(const QString& url) {
auto animCache = DependencyManager::get<AnimationCache>();
_networkAnim = animCache->getAnimation(url);
_url = url;
}

View file

@ -25,7 +25,8 @@ class AnimClip : public AnimNode {
public:
friend class AnimTests;
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag,
AnimBlendType blendType, const QString& baseURL, float baseFrame);
virtual ~AnimClip() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
@ -52,19 +53,20 @@ public:
void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
float getFrame() const { return _frame; }
void loadURL(const QString& url);
protected:
virtual void setCurrentFrameInternal(float frame) override;
void copyFromNetworkAnim();
void buildMirrorAnim();
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimationPointer _networkAnim;
AnimationPointer _baseNetworkAnim;
AnimPoseVec _poses;
// _anim[frame][joint]
@ -78,6 +80,9 @@ protected:
bool _loopFlag;
bool _mirrorFlag;
float _frame;
AnimBlendType _blendType;
QString _baseURL;
float _baseFrame;
QString _startFrameVar;
QString _endFrameVar;

View file

@ -34,6 +34,13 @@ enum class AnimNodeType {
NumTypes
};
enum AnimBlendType {
AnimBlendType_Normal,
AnimBlendType_AddRelative,
AnimBlendType_AddAbsolute,
AnimBlendType_NumTypes
};
class AnimContext {
public:
AnimContext() {}

View file

@ -161,6 +161,19 @@ static EasingType stringToEasingType(const QString& str) {
}
}
static AnimBlendType stringToAnimBlendType(const QString& str) {
if (str == "normal") {
return AnimBlendType_Normal;
} else if (str == "addRelative") {
return AnimBlendType_AddRelative;
} else if (str == "addAbsolute") {
return AnimBlendType_AddAbsolute;
} else {
return AnimBlendType_NumTypes;
}
}
static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
switch (type) {
case AnimManipulator::JointVar::Type::Absolute: return "absolute";
@ -374,6 +387,9 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false);
READ_OPTIONAL_STRING(blendType, jsonObj);
READ_OPTIONAL_STRING(baseURL, jsonObj);
READ_OPTIONAL_FLOAT(baseFrame, jsonObj, 0.0f);
READ_OPTIONAL_STRING(startFrameVar, jsonObj);
READ_OPTIONAL_STRING(endFrameVar, jsonObj);
@ -381,11 +397,22 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
READ_OPTIONAL_STRING(loopFlagVar, jsonObj);
READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj);
// animation urls can be relative to the containing url document.
auto tempUrl = QUrl(url);
tempUrl = jsonUrl.resolved(tempUrl);
auto node = std::make_shared<AnimClip>(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
// AJT:
AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value
if (!blendType.isEmpty()) {
blendTypeEnum = stringToAnimBlendType(blendType);
if (blendTypeEnum == AnimBlendType_NumTypes) {
qCCritical(animation) << "AnimNodeLoader, bad blendType on clip, id = " << id;
return nullptr;
}
}
auto node = std::make_shared<AnimClip>(id, tempUrl.toString(), startFrame, endFrame, timeScale, loopFlag, mirrorFlag, blendTypeEnum, baseURL, baseFrame);
if (!startFrameVar.isEmpty()) {
node->setStartFrameVar(startFrameVar);
@ -409,10 +436,19 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_STRING(blendType, jsonObj);
READ_OPTIONAL_STRING(alphaVar, jsonObj);
auto node = std::make_shared<AnimBlendLinear>(id, alpha);
AnimBlendType blendTypeEnum = AnimBlendType_Normal; // default value
if (!blendType.isEmpty()) {
blendTypeEnum = stringToAnimBlendType(blendType);
if (blendTypeEnum == AnimBlendType_NumTypes) {
qCCritical(animation) << "AnimNodeLoader, bad blendType on blendLinear, id = " << id;
return nullptr;
}
}
auto node = std::make_shared<AnimBlendLinear>(id, alpha, blendTypeEnum);
if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar);

View file

@ -15,7 +15,6 @@
// TODO: use restrict keyword
// TODO: excellent candidate for simd vectorization.
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
for (size_t i = 0; i < numPoses; i++) {
const AnimPose& aPose = a[i];
@ -27,6 +26,29 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A
}
}
// additive blend
void blendAdd(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
const glm::quat identity = glm::quat();
for (size_t i = 0; i < numPoses; i++) {
const AnimPose& aPose = a[i];
const AnimPose& bPose = b[i];
result[i].scale() = lerp(aPose.scale(), bPose.scale(), alpha);
// ensure that delta has the same "polarity" as the identity quat.
// we don't need to do a full dot product, just sign of w is sufficient.
glm::quat delta = bPose.rot();
if (delta.w < 0.0f) {
delta = -delta;
}
delta = glm::lerp(identity, delta, alpha);
result[i].rot() = glm::normalize(delta * aPose.rot());
result[i].trans() = aPose.trans() + (alpha * bPose.trans());
}
}
glm::quat averageQuats(size_t numQuats, const glm::quat* quats) {
if (numQuats == 0) {
return glm::quat();

View file

@ -16,6 +16,9 @@
// this is where the magic happens
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result);
// additive blending
void blendAdd(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result);
glm::quat averageQuats(size_t numQuats, const glm::quat* quats);
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag,

View file

@ -564,7 +564,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f
_origRoleAnimations[role] = node;
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
auto clipNode = std::make_shared<AnimClip>(role, url, firstFrame, lastFrame, timeScale, loop, false);
auto clipNode = std::make_shared<AnimClip>(role, url, firstFrame, lastFrame, timeScale, loop, false, AnimBlendType_Normal, "", 0.0f);
_roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame };
AnimNode::Pointer parent = node->getParent();
parent->replaceChild(node, clipNode);
@ -1973,7 +1973,7 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
// TODO: does not properly handle avatar scale.
if (isIndexValid(index)) {
if (isIndexValid(index) && !_internalPoseSet._overrideFlags[index]) {
const glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation);
const glm::mat4 worldToRig = glm::inverse(rigToWorld);
const glm::vec3 lookAtVector = glm::normalize(transformPoint(worldToRig, lookAtSpot) - _internalPoseSet._absolutePoses[index].trans());

View file

@ -72,7 +72,6 @@
#include "EntityTreeRenderer.h"
#include "RenderablePolyVoxEntityItem.h"
#include "EntityEditPacketSender.h"
#include "PhysicalEntitySimulation.h"
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
@ -83,30 +82,60 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
_voxelData -- compressed QByteArray representation of which voxels have which values
_volData -- datastructure from the PolyVox library which holds which voxels have which values
_mesh -- renderable representation of the voxels
_shape -- used for bullet collisions
_shape -- used for bullet (physics) collisions
Each one depends on the one before it, except that _voxelData is set from _volData if a script edits the voxels.
There are booleans to indicate that something has been updated and the dependents now need to be updated.
_meshReady -- do we have something to give scripts that ask for the mesh?
_voxelDataDirty -- do we need to uncompress data and expand it into _volData?
_volDataDirty -- does recomputeMesh need to be called?
_shapeReady -- are we ready to tell bullet our shape?
_voxelDataDirty
_volDataDirty
_meshDirty
In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain.
decompressVolumeData() is called to decompress _voxelData into _volData. recomputeMesh() is called to invoke the
polyVox surface extractor to create _mesh (as well as set Simulation _flags). Because Simulation::DIRTY_SHAPE
is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on
the surface style.
Here is a simplified diagram of the state machine implemented in RenderablePolyVoxEntityItem::update
When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to
send a packet to the entity-server.
+-------------------+
| |
| |
| volDataDirty | voxelDataDirty
| +--v--+
| +------+Ready+--------+
| | +-----+ |
| | |
| +-----v----+ +------v------+
| |BakingMesh| |Uncompressing|
| +-----+----+ +------+------+
| | |
| | |
| +---v-------+ +-------v------------+
| |Compressing| |BakingMeshNoCompress|
| +---------+-+ ++-------------------+
| | |
| | |
| +-v--------v+
| |BakingShape|
| +-----+-----+
| |
+-------------------+
decompressVolumeData, recomputeMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive
to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread
finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step.
polyvoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox,
The work for each step is done on temporary worker threads. The PolyVox entity will update _updateNeeded and
enable or disable update calls on the entity, depending on if there is more work to do.
From the 'Ready' state, if we receive an update from the network, _voxelDataDirty will be set true. We
uncompress the received data, bake the mesh (for the render-engine's benefit), and then compute the shape
(for the physics-engine's benefit). This is the right-hand side of the diagram.
From the 'Ready' state, if a script changes a voxel, _volDataDirty will be set true. We bake the mesh,
compress the voxels into a new _voxelData, and transmit the new _voxelData to the entity-server. We then
bake the shape. This is the left-hand side of the diagram.
The actual state machine is more complicated than the diagram, because it's possible for _volDataDirty or
_voxelDataDirty to be set true while worker threads are attempting to bake meshes or shapes. If this happens,
we jump back to a higher point in the diagram to avoid wasting effort.
PolyVoxes are designed to seemlessly fit up against neighbors. If voxels go right up to the edge of polyvox,
the resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the
mesh.
@ -114,8 +143,8 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
previously mentioned gaps along the edges.
Non-edged polyvox entities can be told about their neighbors in all 6 cardinal directions. On the positive
edges of the polyvox, the values are set from the (negative edge of) relevant neighbor so that their meshes
knit together. This is handled by bonkNeighbors and copyUpperEdgesFromNeighbors. In these functions, variable
edges of the polyvox, the values are set from the (negative edge of the) relevant neighbor so that their meshes
knit together. This is handled by tellNeighborsToRecopyEdges and copyUpperEdgesFromNeighbors. In these functions, variable
names have XP for x-positive, XN x-negative, etc.
*/
@ -140,7 +169,8 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit
return entity;
}
RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) { }
RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) {
}
RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() {
withWriteLock([&] {
@ -165,11 +195,12 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) {
}
void RenderablePolyVoxEntityItem::setVoxelData(const QByteArray& voxelData) {
// compressed voxel information from the entity-server
// accept compressed voxel information from the entity-server
withWriteLock([&] {
if (_voxelData != voxelData) {
_voxelData = voxelData;
_voxelDataDirty = true;
startUpdates();
}
});
}
@ -189,21 +220,22 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel
bool willBeEdged = isEdged(voxelSurfaceStyle);
if (wasEdged != willBeEdged) {
_volDataDirty = true;
_volData.reset();
_voxelSurfaceStyle = voxelSurfaceStyle;
_voxelDataDirty = true;
volSizeChanged = true;
} else {
_volDataDirty = true;
_voxelSurfaceStyle = voxelSurfaceStyle;
}
_voxelSurfaceStyle = voxelSurfaceStyle;
startUpdates();
});
if (volSizeChanged) {
// setVoxelVolumeSize will re-alloc _volData with the right size
setVoxelVolumeSize(_voxelVolumeSize);
}
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
tellNeighborsToRecopyEdges(true);
startUpdates();
}
glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const {
@ -262,9 +294,6 @@ bool RenderablePolyVoxEntityItem::setVoxel(const ivec3& v, uint8_t toValue) {
withWriteLock([&] {
result = setVoxelInternal(v, toValue);
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -311,9 +340,6 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
result |= setVoxelInternal(v, toValue);
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -336,9 +362,6 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const
result |= setVoxelInternal(v, toValue);
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -374,9 +397,6 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(const vec3& center, float ra
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -434,9 +454,6 @@ bool RenderablePolyVoxEntityItem::setSphere(const vec3& centerWorldCoords, float
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -482,9 +499,6 @@ bool RenderablePolyVoxEntityItem::setCapsule(const vec3& startWorldCoords, const
});
});
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result;
}
@ -710,8 +724,9 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) {
_meshDirty = true;
_shapeReady = false;
EntityItem::setRegistrationPoint(value);
startUpdates();
}
}
@ -721,15 +736,11 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() const {
return true;
}
// we determine if we are ready to compute the physics shape by actually doing so.
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their
// threads to finish before creating the collision shape.
if (_meshDirty && !_voxelDataDirty && !_volDataDirty) {
const_cast<RenderablePolyVoxEntityItem*>(this)->_meshDirty = false;
const_cast<RenderablePolyVoxEntityItem*>(this)->computeShapeInfoWorker();
return false;
}
return true;
bool result;
withReadLock([&] {
result = _shapeReady;
});
return result;
}
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
@ -739,33 +750,155 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
return;
}
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here.
withWriteLock([&] {
withReadLock([&] {
info = _shapeInfo;
});
}
bool RenderablePolyVoxEntityItem::updateDependents() {
bool voxelDataDirty;
bool volDataDirty;
void RenderablePolyVoxEntityItem::changeUpdates(bool value) {
if (_updateNeeded != value) {
EntityTreePointer entityTree = getTree();
if (entityTree) {
EntitySimulationPointer simulation = entityTree->getSimulation();
if (simulation) {
_updateNeeded = value;
_flags |= Simulation::DIRTY_UPDATEABLE;
simulation->changeEntity(getThisPointer());
}
}
}
}
void RenderablePolyVoxEntityItem::startUpdates() {
changeUpdates(true);
}
void RenderablePolyVoxEntityItem::stopUpdates() {
changeUpdates(false);
}
void RenderablePolyVoxEntityItem::update(const quint64& now) {
bool doRecomputeMesh { false };
bool doUncompress { false };
bool doCompress { false };
bool doRecomputeShape { false };
withWriteLock([&] {
voxelDataDirty = _voxelDataDirty;
volDataDirty = _volDataDirty;
if (_voxelDataDirty) {
_voxelDataDirty = false;
} else if (_volDataDirty) {
_volDataDirty = false;
} else {
_meshReady = true;
tellNeighborsToRecopyEdges(false);
switch (_state) {
case PolyVoxState::Ready: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
copyUpperEdgesFromNeighbors();
if (!_volDataDirty && !_voxelDataDirty) {
// nothing to do
stopUpdates();
}
}
break;
}
case PolyVoxState::Uncompressing: {
break; // wait
}
case PolyVoxState::UncompressingFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMeshNoCompress;
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// _voxelData changed while we were uncompressing the previous version, uncompress again
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::Ready;
}
break;
}
case PolyVoxState::BakingMesh: {
break; // wait
}
case PolyVoxState::BakingMeshFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
// a local edit happened while we were baking the mesh. rebake mesh...
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// we received a change from the wire while baking the mesh.
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::Compressing;
doCompress = true;
}
break;
}
case PolyVoxState::BakingMeshNoCompress: {
break; // wait
}
case PolyVoxState::BakingMeshNoCompressFinished: {
if (_volDataDirty) {
_volDataDirty = _voxelDataDirty = false;
_state = PolyVoxState::BakingMesh;
// a local edit happened while we were baking the mesh. rebake mesh...
doRecomputeMesh = true;
} else if (_voxelDataDirty) {
_voxelDataDirty = false;
// we received a change from the wire while baking the mesh.
_state = PolyVoxState::Uncompressing;
doUncompress = true;
} else {
_state = PolyVoxState::BakingShape;
doRecomputeShape = true;
}
break;
}
case PolyVoxState::Compressing: {
break; // wait
}
case PolyVoxState::CompressingFinished: {
_state = PolyVoxState::BakingShape;
doRecomputeShape = true;
break;
}
case PolyVoxState::BakingShape: {
break; // wait
}
case PolyVoxState::BakingShapeFinished: {
_state = PolyVoxState::Ready;
break;
}
}
});
if (voxelDataDirty) {
decompressVolumeData();
} else if (volDataDirty) {
if (doRecomputeMesh) {
recomputeMesh();
}
return !volDataDirty;
if (doUncompress) {
uncompressVolumeData();
}
if (doCompress) {
compressVolumeDataAndSendEditPacket();
}
if (doRecomputeShape) {
computeShapeInfoWorker();
}
}
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) {
@ -782,6 +915,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolum
_voxelVolumeSize = voxelVolumeSize;
_volData.reset();
_onCount = 0;
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
startUpdates();
static const PolyVox::Vector3DInt32 lowCorner(0, 0, 0);
PolyVox::Vector3DInt32 highCorner;
@ -806,6 +941,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolum
// having the "outside of voxel-space" value be 255 has helped me notice some problems.
_volData->setBorderValue(255);
});
tellNeighborsToRecopyEdges(true);
}
bool inUserBounds(const std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> vol,
@ -853,32 +990,38 @@ uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(const ivec3& v) const {
}
void RenderablePolyVoxEntityItem::setVoxelMarkNeighbors(int x, int y, int z, uint8_t toValue) {
_volData->setVoxelAt(x, y, z, toValue);
if (x == 0) {
_neighborXNeedsUpdate = true;
startUpdates();
}
if (y == 0) {
_neighborYNeedsUpdate = true;
startUpdates();
}
if (z == 0) {
_neighborZNeedsUpdate = true;
startUpdates();
}
}
bool RenderablePolyVoxEntityItem::setVoxelInternal(const ivec3& v, uint8_t toValue) {
// set a voxel without recompressing the voxel data. This assumes that the caller has
// write-locked the entity.
bool result = false;
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) {
return result;
// set a voxel without recompressing the voxel data. This assumes that the caller has write-locked the entity.
bool result = updateOnCount(v, toValue);
if (result) {
if (isEdged()) {
setVoxelMarkNeighbors(v.x + 1, v.y + 1, v.z + 1, toValue);
} else {
setVoxelMarkNeighbors(v.x, v.y, v.z, toValue);
}
_volDataDirty = true;
startUpdates();
}
result = updateOnCount(v, toValue);
if (isEdged()) {
_volData->setVoxelAt(v.x + 1, v.y + 1, v.z + 1, toValue);
} else {
_volData->setVoxelAt(v.x, v.y, v.z, toValue);
}
if (glm::any(glm::equal(ivec3(0), v))) {
_neighborsNeedUpdate = true;
}
_volDataDirty |= result;
return result;
}
bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue) {
// keep _onCount up to date
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) {
@ -902,7 +1045,7 @@ bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue)
return false;
}
void RenderablePolyVoxEntityItem::decompressVolumeData() {
void RenderablePolyVoxEntityItem::uncompressVolumeData() {
// take compressed data and expand it into _volData.
QByteArray voxelData;
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
@ -921,9 +1064,9 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) {
qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping decompressions."
<< voxelXSize << voxelYSize << voxelZSize << getName() << getID();
entity->setVoxelDataDirty(false);
qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping uncompressions."
<< voxelXSize << voxelYSize << voxelZSize << getName() << getID();
entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return;
}
int rawSize = voxelXSize * voxelYSize * voxelZSize;
@ -934,10 +1077,10 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
QByteArray uncompressedData = qUncompress(compressedData);
if (uncompressedData.size() != rawSize) {
qCDebug(entitiesrenderer) << "PolyVox decompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")"
qCDebug(entitiesrenderer) << "PolyVox uncompress -- size is (" << voxelXSize << voxelYSize << voxelZSize << ")"
<< "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size()
<< getName() << getID();
entity->setVoxelDataDirty(false);
<< getName() << getID();
entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return;
}
@ -947,13 +1090,14 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData,
quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) {
// this accepts the payload from decompressVolumeData
// this accepts the payload from uncompressVolumeData
withWriteLock([&] {
loop3(ivec3(0), ivec3(voxelXSize, voxelYSize, voxelZSize), [&](const ivec3& v) {
int uncompressedIndex = (v.z * voxelYSize * voxelXSize) + (v.y * voxelZSize) + v.x;
setVoxelInternal(v, uncompressedData[uncompressedIndex]);
});
_volDataDirty = true;
_state = PolyVoxState::UncompressingFinished;
});
}
@ -972,10 +1116,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
voxelZSize = _voxelVolumeSize.z;
});
EntityTreeElementPointer element = getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity] {
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize);
@ -992,17 +1133,37 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
// HACK -- until we have a way to allow for properties larger than MTU, don't update.
// revert the active voxel-space to the last version that fit.
qCDebug(entitiesrenderer) << "compressed voxel data is too large" << entity->getName() << entity->getID();
const auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
polyVoxEntity->compressVolumeDataFinished(QByteArray());
return;
}
auto now = usecTimestampNow();
entity->setLastEdited(now);
entity->setLastBroadcast(now);
std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->compressVolumeDataFinished(newVoxelData);
});
}
std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->setVoxelData(newVoxelData);
void RenderablePolyVoxEntityItem::compressVolumeDataFinished(const QByteArray& voxelData) {
// compressed voxel information from the entity-server
withWriteLock([&] {
if (voxelData.size() > 0 && _voxelData != voxelData) {
_voxelData = voxelData;
}
_state = PolyVoxState::CompressingFinished;
});
auto now = usecTimestampNow();
setLastEdited(now);
setLastBroadcast(now);
EntityTreeElementPointer element = getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
if (tree) {
tree->withReadLock([&] {
EntityItemProperties properties = entity->getProperties();
EntityPropertyFlags desiredProperties;
desiredProperties.setHasProperty(PROP_VOXEL_DATA);
EntityItemProperties properties = getProperties(desiredProperties, false);
properties.setVoxelDataDirty();
properties.setLastEdited(now);
@ -1010,12 +1171,13 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
if (packetSender) {
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, getID(), properties);
}
});
});
}
}
EntityItemPointer lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) {
EntityItemPointer current = currentWP.lock();
@ -1064,56 +1226,103 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
return;
}
auto currentXPNeighbor = getXPNeighbor();
auto currentYPNeighbor = getYPNeighbor();
auto currentZPNeighbor = getZPNeighbor();
if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int y = 0; y < _volData->getHeight(); y++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentXPNeighbor->getVoxel({ 0, y, z });
if ((y == 0 || z == 0) && _volData->getVoxelAt(_volData->getWidth() - 1, y, z) != neighborValue) {
bonkNeighbors();
}
_volData->setVoxelAt(_volData->getWidth() - 1, y, z, neighborValue);
}
}
});
if (!_updateFromNeighborXEdge && !_updateFromNeighborYEdge && !_updateFromNeighborZEdge) {
return;
}
cacheNeighbors();
if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int x = 0; x < _volData->getWidth(); x++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentYPNeighbor->getVoxel({ x, 0, z });
if ((x == 0 || z == 0) && _volData->getVoxelAt(x, _volData->getHeight() - 1, z) != neighborValue) {
bonkNeighbors();
}
_volData->setVoxelAt(x, _volData->getHeight() - 1, z, neighborValue);
}
}
});
}
if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int x = 0; x < _volData->getWidth(); x++) {
if (_updateFromNeighborXEdge) {
_updateFromNeighborXEdge = false;
auto currentXPNeighbor = getXPNeighbor();
if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int x = _volData->getWidth() - 1;
for (int y = 0; y < _volData->getHeight(); y++) {
uint8_t neighborValue = currentZPNeighbor->getVoxel({ x, y, 0 });
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue);
if ((x == 0 || y == 0) && _volData->getVoxelAt(x, y, _volData->getDepth() - 1) != neighborValue) {
bonkNeighbors();
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentXPNeighbor->getVoxel({ 0, y, z });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue);
}
}
});
});
}
}
if (_updateFromNeighborYEdge) {
_updateFromNeighborYEdge = false;
auto currentYPNeighbor = getYPNeighbor();
if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int y = _volData->getHeight() - 1;
for (int x = 0; x < _volData->getWidth(); x++) {
for (int z = 0; z < _volData->getDepth(); z++) {
uint8_t neighborValue = currentYPNeighbor->getVoxel({ x, 0, z });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
}
});
}
}
if (_updateFromNeighborZEdge) {
_updateFromNeighborZEdge = false;
auto currentZPNeighbor = getZPNeighbor();
if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
int z = _volData->getDepth() - 1;
for (int x = 0; x < _volData->getWidth(); x++) {
for (int y = 0; y < _volData->getHeight(); y++) {
uint8_t neighborValue = currentZPNeighbor->getVoxel({ x, y, 0 });
uint8_t prevValue = _volData->getVoxelAt(x, y, z);
if (prevValue != neighborValue) {
_volData->setVoxelAt(x, y, z, neighborValue);
_volDataDirty = true;
}
}
}
});
}
}
}
void RenderablePolyVoxEntityItem::tellNeighborsToRecopyEdges(bool force) {
// if this polyvox has changed any of its voxels with a zero coord (in x, y, or z) notify neighbors, if there are any
if (force || _neighborXNeedsUpdate || _neighborYNeedsUpdate || _neighborZNeedsUpdate) {
cacheNeighbors();
if (force || _neighborXNeedsUpdate) {
_neighborXNeedsUpdate = false;
auto currentXNNeighbor = getXNNeighbor();
if (currentXNNeighbor) {
currentXNNeighbor->neighborXEdgeChanged();
}
}
if (force || _neighborYNeedsUpdate) {
_neighborYNeedsUpdate = false;
auto currentYNNeighbor = getYNNeighbor();
if (currentYNNeighbor) {
currentYNNeighbor->neighborYEdgeChanged();
}
}
if (force || _neighborZNeedsUpdate) {
_neighborZNeedsUpdate = false;
auto currentZNNeighbor = getZNNeighbor();
if (currentZNNeighbor) {
currentZNNeighbor->neighborZEdgeChanged();
}
}
}
}
void RenderablePolyVoxEntityItem::recomputeMesh() {
// use _volData to make a renderable mesh
PolyVoxSurfaceStyle voxelSurfaceStyle;
@ -1121,9 +1330,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
voxelSurfaceStyle = _voxelSurfaceStyle;
});
cacheNeighbors();
copyUpperEdgesFromNeighbors();
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
QtConcurrent::run([entity, voxelSurfaceStyle] {
@ -1135,24 +1341,14 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
entity->withReadLock([&] {
PolyVox::SimpleVolume<uint8_t>* volData = entity->getVolData();
switch (voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: {
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
case PolyVoxEntityItem::SURFACE_CUBIC: {
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
@ -1180,7 +1376,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->setVertexBuffer(vertexBufferView);
// TODO -- use 3-byte normals rather than 3-float normals
mesh->addAttribute(gpu::Stream::NORMAL,
gpu::BufferView(vertexBufferPtr,
@ -1194,28 +1389,24 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
(graphics::Index)vecIndices.size(), // numIndices
(graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), (gpu::Byte*) parts.data()),
gpu::Element::PART_DRAWCALL));
entity->setMesh(mesh);
});
}
void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
// this catches the payload from recomputeMesh
bool neighborsNeedUpdate;
withWriteLock([&] {
if (!_collisionless) {
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
_shapeReady = false;
_mesh = mesh;
_meshDirty = true;
_state = PolyVoxState::BakingMeshFinished;
_meshReady = true;
neighborsNeedUpdate = _neighborsNeedUpdate;
_neighborsNeedUpdate = false;
startUpdates();
});
if (neighborsNeedUpdate) {
bonkNeighbors();
}
somethingChangedNotification();
}
@ -1223,9 +1414,6 @@ void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// this creates a collision-shape for the physics engine. The shape comes from
// _volData for cubic extractors and from _mesh for marching-cube extractors
if (!_meshReady) {
return;
}
EntityItemPointer entity = getThisPointer();
@ -1295,8 +1483,8 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
const auto& x = v.x;
const auto& y = v.y;
const auto& z = v.z;
if (glm::all(glm::greaterThan(v, ivec3(0))) &&
glm::all(glm::lessThan(v, ivec3(voxelVolumeSize) - 1)) &&
if (glm::all(glm::greaterThan(v, ivec3(0))) &&
glm::all(glm::lessThan(v, ivec3(voxelVolumeSize) - 1)) &&
(polyVoxEntity->getVoxelInternal({ x - 1, y, z }) > 0) &&
(polyVoxEntity->getVoxelInternal({ x, y - 1, z }) > 0) &&
(polyVoxEntity->getVoxelInternal({ x, y, z - 1 }) > 0) &&
@ -1360,6 +1548,10 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
// this catches the payload from computeShapeInfoWorker
if (pointCollection.isEmpty()) {
EntityItem::computeShapeInfo(_shapeInfo);
withWriteLock([&] {
_shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
});
return;
}
@ -1373,7 +1565,8 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
QString::number(_registrationPoint.z);
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
_shapeInfo.setPointCollection(pointCollection);
_meshDirty = false;
_shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
});
}
@ -1384,7 +1577,8 @@ void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighbor
if (xNNeighborID != _xNNeighborID) {
PolyVoxEntityItem::setXNNeighborID(xNNeighborID);
cacheNeighbors();
_neighborXNeedsUpdate = true;
startUpdates();
}
}
@ -1395,7 +1589,8 @@ void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighbor
if (yNNeighborID != _yNNeighborID) {
PolyVoxEntityItem::setYNNeighborID(yNNeighborID);
cacheNeighbors();
_neighborYNeedsUpdate = true;
startUpdates();
}
}
@ -1406,7 +1601,8 @@ void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighbor
if (zNNeighborID != _zNNeighborID) {
PolyVoxEntityItem::setZNNeighborID(zNNeighborID);
cacheNeighbors();
_neighborZNeedsUpdate = true;
startUpdates();
}
}
@ -1416,7 +1612,8 @@ void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighbor
}
if (xPNeighborID != _xPNeighborID) {
PolyVoxEntityItem::setXPNeighborID(xPNeighborID);
_volDataDirty = true;
_updateFromNeighborXEdge = true;
startUpdates();
}
}
@ -1426,7 +1623,8 @@ void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighbor
}
if (yPNeighborID != _yPNeighborID) {
PolyVoxEntityItem::setYPNeighborID(yPNeighborID);
_volDataDirty = true;
_updateFromNeighborYEdge = true;
startUpdates();
}
}
@ -1436,7 +1634,8 @@ void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighbor
}
if (zPNeighborID != _zPNeighborID) {
PolyVoxEntityItem::setZPNeighborID(zPNeighborID);
_volDataDirty = true;
_updateFromNeighborZEdge = true;
startUpdates();
}
}
@ -1464,31 +1663,8 @@ std::shared_ptr<RenderablePolyVoxEntityItem> RenderablePolyVoxEntityItem::getZPN
return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock());
}
void RenderablePolyVoxEntityItem::bonkNeighbors() {
// flag neighbors to the negative of this entity as needing to rebake their meshes.
cacheNeighbors();
auto currentXNNeighbor = getXNNeighbor();
auto currentYNNeighbor = getYNNeighbor();
auto currentZNNeighbor = getZNNeighbor();
if (currentXNNeighbor) {
currentXNNeighbor->setVolDataDirty();
}
if (currentYNNeighbor) {
currentYNNeighbor->setVolDataDirty();
}
if (currentZNNeighbor) {
currentZNNeighbor->setVolDataDirty();
}
}
// deprecated
bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
if (!updateDependents()) {
return false;
}
bool success = false;
if (_mesh) {
MeshProxy* meshProxy = nullptr;
@ -1517,7 +1693,7 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
}
scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() {
if (!updateDependents() || !_mesh) {
if (!_mesh) {
return scriptable::ScriptableModelBase();
}
@ -1628,9 +1804,6 @@ bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPo
}
void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
if (entity->_voxelDataDirty || entity->_volDataDirty) {
entity->updateDependents();
}
#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT
if (!_hasTransitioned) {
@ -1662,7 +1835,6 @@ void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoi
graphics::MeshPointer newMesh;
entity->withReadLock([&] {
newMesh = entity->_mesh;
});
if (newMesh && newMesh->getIndexBuffer()._buffer) {
@ -1705,3 +1877,41 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) {
batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0);
}
QDebug operator<<(QDebug debug, PolyVoxState state) {
switch (state) {
case PolyVoxState::Ready:
debug << "Ready";
break;
case PolyVoxState::Uncompressing:
debug << "Uncompressing";
break;
case PolyVoxState::UncompressingFinished:
debug << "UncompressingFinished";
break;
case PolyVoxState::BakingMesh:
debug << "BakingMesh";
break;
case PolyVoxState::BakingMeshFinished:
debug << "BakingMeshFinished";
break;
case PolyVoxState::BakingMeshNoCompress:
debug << "BakingMeshNoCompress";
break;
case PolyVoxState::BakingMeshNoCompressFinished:
debug << "BakingMeshNoCompressFinished";
break;
case PolyVoxState::Compressing:
debug << "Compressing";
break;
case PolyVoxState::CompressingFinished:
debug << "CompressingFinished";
break;
case PolyVoxState::BakingShape:
debug << "BakingShape";
break;
case PolyVoxState::BakingShapeFinished:
debug << "BakingShapeFinished";
break;
}
return debug;
}

View file

@ -30,7 +30,25 @@
namespace render { namespace entities {
class PolyVoxEntityRenderer;
} }
} }
enum class PolyVoxState {
Ready,
Uncompressing,
UncompressingFinished,
BakingMesh,
BakingMeshFinished,
BakingMeshNoCompress,
BakingMeshNoCompressFinished,
Compressing,
CompressingFinished,
BakingShape,
BakingShapeFinished
};
QDebug operator<<(QDebug debug, PolyVoxState state);
class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider {
friend class render::entities::PolyVoxEntityRenderer;
@ -113,41 +131,61 @@ public:
uint8_t getVoxelInternal(const ivec3& v) const;
bool setVoxelInternal(const ivec3& v, uint8_t toValue);
void setVoxelMarkNeighbors(int x, int y, int z, uint8_t toValue);
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; _meshReady = false; }); }
void compressVolumeDataFinished(const QByteArray& voxelData);
void neighborXEdgeChanged() { withWriteLock([&] { _updateFromNeighborXEdge = true; }); startUpdates(); }
void neighborYEdgeChanged() { withWriteLock([&] { _updateFromNeighborYEdge = true; }); startUpdates(); }
void neighborZEdgeChanged() { withWriteLock([&] { _updateFromNeighborZEdge = true; }); startUpdates(); }
bool getMeshes(MeshProxyList& result) override; // deprecated
virtual scriptable::ScriptableModelBase getScriptableModel() override;
virtual void update(const quint64& now) override;
bool needsToCallUpdate() const override { return _updateNeeded; }
private:
bool updateOnCount(const ivec3& v, uint8_t toValue);
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
void changeUpdates(bool value);
void startUpdates();
void stopUpdates();
void recomputeMesh();
void cacheNeighbors();
void copyUpperEdgesFromNeighbors();
void bonkNeighbors();
void tellNeighborsToRecopyEdges(bool force);
bool updateDependents();
// these are run off the main thread
void decompressVolumeData();
void uncompressVolumeData();
void compressVolumeDataAndSendEditPacket();
void computeShapeInfoWorker();
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize.
bool _meshDirty { true }; // does collision-shape need to be recomputed?
bool _meshReady{ false };
bool _meshReady { false }; // do we have something to give scripts that ask for the mesh?
bool _voxelDataDirty { false }; // do we need to uncompress data and expand it into _volData?
bool _volDataDirty { false }; // does recomputeMesh need to be called?
bool _shapeReady { false }; // are we ready to tell bullet our shape?
PolyVoxState _state { PolyVoxState::Ready };
bool _updateNeeded { true };
graphics::MeshPointer _mesh;
ShapeInfo _shapeInfo;
std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> _volData;
bool _voxelDataDirty{ true };
bool _volDataDirty { false }; // does recomputeMesh need to be called?
int _onCount; // how many non-zero voxels are in _volData
bool _neighborsNeedUpdate { false };
bool _neighborXNeedsUpdate { false };
bool _neighborYNeedsUpdate { false };
bool _neighborZNeedsUpdate { false };
bool _updateFromNeighborXEdge { false };
bool _updateFromNeighborYEdge { false };
bool _updateFromNeighborZEdge { false };
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis
@ -156,7 +194,6 @@ private:
EntityItemWeakPointer _xPNeighbor; // neighbor found by going along positive X axis
EntityItemWeakPointer _yPNeighbor;
EntityItemWeakPointer _zPNeighbor;
};
namespace render { namespace entities {
@ -170,7 +207,7 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override {
return asTypedEntity<RenderablePolyVoxEntityItem>()->getScriptableModel();
}
protected:
virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); }
virtual ShapeKey getShapeKey() override;
@ -192,7 +229,6 @@ private:
glm::mat4 _lastVoxelToWorldMatrix;
PolyVoxEntityItem::PolyVoxSurfaceStyle _lastSurfaceStyle { PolyVoxEntityItem::SURFACE_MARCHING_CUBES };
std::array<QString, 3> _xyzTextureUrls;
bool _neighborsNeedUpdate{ false };
};
} }

View file

@ -965,7 +965,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* // Value overrides entity's "color" property.
* albedo: [1.0, 1.0, 0] // Yellow
* }
* }),
* })
* });
*/

View file

@ -1294,7 +1294,7 @@ public slots:
Q_INVOKABLE int getJointParent(const QUuid& entityID, int index);
/**jsdoc
* Gets the translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's
* Gets the rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's
* position and orientation.
* @function Entities.getAbsoluteJointRotationInObjectFrame
* @param {Uuid} entityID - The ID of the entity.

View file

@ -139,6 +139,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
}
#else
void AntialiasingConfig::setAAMode(int mode) {
_mode = std::min((int)AntialiasingConfig::MODE_COUNT, std::max(0, mode));
emit dirty();
}
Antialiasing::Antialiasing(bool isSharpenEnabled) :
_isSharpenEnabled{ isSharpenEnabled } {
}
@ -189,6 +194,8 @@ const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() {
}
void Antialiasing::configure(const Config& config) {
_mode = (AntialiasingConfig::Mode) config.getAAMode();
_sharpen = config.sharpen * 0.25f;
if (!_isSharpenEnabled) {
_sharpen = 0.0f;
@ -298,29 +305,33 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
});
}
void JitterSampleConfig::setIndex(int current) {
_index = (current) % JitterSample::SEQUENCE_LENGTH;
emit dirty();
}
int JitterSampleConfig::cycleStopPauseRun() {
_state = (_state + 1) % 3;
void JitterSampleConfig::setState(int state) {
_state = (state) % 3;
switch (_state) {
case 0: {
return none();
break;
}
case 1: {
return pause();
break;
}
case 2:
default: {
return play();
break;
}
case 0: {
none();
break;
}
case 1: {
pause();
break;
}
case 2:
default: {
play();
break;
}
}
emit dirty();
}
int JitterSampleConfig::cycleStopPauseRun() {
setState((_state + 1) % 3);
return _state;
}

View file

@ -25,6 +25,7 @@ class JitterSampleConfig : public render::Job::Config {
Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty)
Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty)
Q_PROPERTY(int index READ getIndex NOTIFY dirty)
Q_PROPERTY(int state READ getState WRITE setState NOTIFY dirty)
public:
JitterSampleConfig() : render::Job::Config(true) {}
@ -33,6 +34,7 @@ public:
bool freeze{ false };
void setIndex(int current);
void setState(int state);
public slots:
int cycleStopPauseRun();
@ -86,6 +88,7 @@ private:
class AntialiasingConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int mode READ getAAMode WRITE setAAMode NOTIFY dirty)
Q_PROPERTY(float blend MEMBER blend NOTIFY dirty)
Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty)
Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty)
@ -106,9 +109,21 @@ class AntialiasingConfig : public render::Job::Config {
public:
AntialiasingConfig() : render::Job::Config(true) {}
enum Mode {
OFF = 0,
TAA,
FXAA,
MODE_COUNT
};
void setAAMode(int mode);
int getAAMode() const { return _mode; }
void setDebugFXAA(bool debug) { debugFXAAX = (debug ? 0.0f : 1.0f); emit dirty();}
bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); }
int _mode{ TAA };
float blend{ 0.25f };
float sharpen{ 0.05f };
@ -195,6 +210,7 @@ private:
gpu::PipelinePointer _debugBlendPipeline;
TAAParamsBuffer _params;
AntialiasingConfig::Mode _mode{ AntialiasingConfig::TAA };
float _sharpen{ 0.15f };
bool _isSharpenEnabled{ true };
};

View file

@ -35,12 +35,13 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons
const auto frameTransform = inputs.get0();
const auto inputFrameBuffer = inputs.get1();
const auto bloomFrame = inputs.get2();
const auto lightingModel = inputs.get3();
const auto& bloomStage = renderContext->_scene->getStage<BloomStage>();
graphics::BloomPointer bloom;
if (bloomStage && bloomFrame->_blooms.size()) {
bloom = bloomStage->getBloom(bloomFrame->_blooms.front());
}
if (!bloom) {
if (!bloom || (lightingModel && !lightingModel->isBloomEnabled())) {
renderContext->taskFlow.abortTask();
return;
}
@ -187,12 +188,17 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp
}
}
void DebugBloomConfig::setMode(int mode) {
_mode = std::min((int)DebugBloomConfig::MODE_COUNT, std::max(0, mode));
emit dirty();
}
DebugBloom::DebugBloom() {
_params = std::make_shared<gpu::Buffer>(sizeof(glm::vec4), nullptr);
}
void DebugBloom::configure(const Config& config) {
_mode = static_cast<DebugBloomConfig::Mode>(config.mode);
_mode = (DebugBloomConfig::Mode) config.getMode();
assert(_mode < DebugBloomConfig::MODE_COUNT);
}
@ -201,6 +207,10 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In
assert(renderContext->args->hasViewFrustum());
RenderArgs* args = renderContext->args;
if (_mode == DebugBloomConfig::OFF) {
return;
}
const auto frameBuffer = inputs.get0();
const auto combinedBlurBuffer = inputs.get4();
const auto framebufferSize = frameBuffer->getSize();

View file

@ -17,6 +17,7 @@
#include "BloomStage.h"
#include "DeferredFrameTransform.h"
#include "LightingModel.h"
class BloomConfig : public render::Task::Config {
Q_OBJECT
@ -28,7 +29,7 @@ class BloomThresholdConfig : public render::Job::Config {
class BloomThreshold {
public:
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer>;
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer, LightingModelPointer>;
using Outputs = render::VaryingSet3<gpu::FramebufferPointer, float, graphics::BloomPointer>;
using Config = BloomThresholdConfig;
using JobModel = render::Job::ModelIO<BloomThreshold, Inputs, Outputs, Config>;
@ -87,12 +88,13 @@ private:
class DebugBloomConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int mode MEMBER mode NOTIFY dirty)
Q_PROPERTY(int mode READ getMode WRITE setMode NOTIFY dirty)
public:
enum Mode {
MODE_LEVEL0 = 0,
OFF = 0,
MODE_LEVEL0,
MODE_LEVEL1,
MODE_LEVEL2,
MODE_ALL_LEVELS,
@ -102,7 +104,10 @@ public:
DebugBloomConfig() : render::Job::Config(false) {}
int mode{ MODE_ALL_LEVELS };
void setMode(int mode);
int getMode() const { return _mode; }
int _mode{ MODE_ALL_LEVELS };
signals:
void dirty();
@ -127,14 +132,14 @@ private:
class BloomEffect {
public:
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer>;
using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer, LightingModelPointer>;
using Config = BloomConfig;
using JobModel = render::Task::ModelI<BloomEffect, Inputs, Config>;
using JobModel = render::Task::ModelI<BloomEffect, Inputs, Config>;
BloomEffect();
BloomEffect();
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
void configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
};

View file

@ -150,8 +150,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
// Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer
const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform", jitter);
const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying();
const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs);
const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0);
@ -164,8 +162,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying();
task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber);
task.addJob<EndGPURangeTimer>("OpaqueRangeTimer", opaqueRangeTimer);
// Opaque all rendered
// Linear Depth Pass
@ -216,13 +212,10 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
const auto transparentsInputs = RenderTransparentDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, shadowFrame, jitter).asVarying();
task.addJob<RenderTransparentDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber);
const auto outlineRangeTimer = task.addJob<BeginGPURangeTimer>("BeginHighlightRangeTimer", "Highlight");
// Highlight
const auto outlineInputs = DrawHighlightTask::Inputs(items, deferredFramebuffer, lightingFramebuffer, deferredFrameTransform, jitter).asVarying();
task.addJob<DrawHighlightTask>("DrawHighlight", outlineInputs);
task.addJob<EndGPURangeTimer>("HighlightRangeTimer", outlineRangeTimer);
// Layered Over (in front)
const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, jitter).asVarying();
const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, hazeFrame, jitter).asVarying();
@ -234,7 +227,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
task.addJob<Antialiasing>("Antialiasing", antialiasingInputs);
// Add bloom
const auto bloomInputs = BloomEffect::Inputs(deferredFrameTransform, lightingFramebuffer, bloomFrame).asVarying();
const auto bloomInputs = BloomEffect::Inputs(deferredFrameTransform, lightingFramebuffer, bloomFrame, lightingModel).asVarying();
task.addJob<BloomEffect>("Bloom", bloomInputs);
const auto destFramebuffer = static_cast<gpu::FramebufferPointer>(nullptr);

View file

@ -2210,7 +2210,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
/**jsdoc
* Triggered when the script starts for a user. See also, {@link Script.entityScriptPreloadFinished}.
* <p>Note: Can only be connected to via <code>this.preload = function (...) { ... }</code> in the entity script.</p>
* <table><tr><th>Available in:</th><td>Client Entity Scripts</td><td>Server Entity Scripts</td></tr></table>
* <p class="availableIn"><strong>Supported Script Types:</strong> Client Entity Scripts &bull; Server Entity Scripts</p>
* @function Entities.preload
* @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal}
@ -2416,7 +2416,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
/**jsdoc
* Triggered when the script terminates for a user.
* <p>Note: Can only be connected to via <code>this.unoad = function () { ... }</code> in the entity script.</p>
* <table><tr><th>Available in:</th><td>Client Entity Scripts</td><td>Server Entity Scripts</td></tr></table>
* <p class="availableIn"><strong>Supported Script Types:</strong> Client Entity Scripts &bull; Server Entity Scripts</p>
* @function Entities.unload
* @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal}

View file

@ -416,8 +416,10 @@ public:
* Provides access to methods or objects provided in an external JavaScript or JSON file.
* See {@link https://docs.highfidelity.com/script/js-tips.html} for further details.
* @function Script.require
* @param {string} module - The module to use. May be a JavaScript file or the name of a system module such as
* <code>"sppUi"</code>.
* @param {string} module - The module to use. May be a JavaScript file, a JSON file, or the name of a system module such
* as <code>"appUi"</code> (i.e., the "appUi.js" system module JavaScript file).
* @returns {object|array} The value assigned to <code>module.exports</code> in the JavaScript file, or the value defined
* in the JSON file.
*/
Q_INVOKABLE QScriptValue require(const QString& moduleId);
@ -842,7 +844,7 @@ signals:
/**jsdoc
* Triggered when the script starts for the user. See also, {@link Entities.preload}.
* <table><tr><th>Available in:</th><td>Client Entity Scripts</td><td>Server Entity Scripts</td></tr></table>
* <p class="availableIn"><strong>Supported Script Types:</strong> Client Entity Scripts &bull; Server Entity Scripts</p>
* @function Script.entityScriptPreloadFinished
* @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal}

View file

@ -1319,13 +1319,13 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
* <tr><td><code>RX</code></td><td>number</td><td>number</td><td>Right stick x-axis scale.</td></tr>
* <tr><td><code>RY</code></td><td>number</td><td>number</td><td>Right stick y-axis scale.</td></tr>
* <tr><td><code>LS</code></td><td>number</td><td>number</td><td>Left touch pad pressed.</td></tr>
* <tr><td><code>LS_CENTER</code></td><td>number</td><td>number</td><td>Left touch pad center pressed.</td></tr>
* <tr><td><code>LS_X</code></td><td>number</td><td>number</td><td>Left touch pad pressed x-coordinate.</td></tr>
* <tr><td><code>LS_Y</code></td><td>number</td><td>number</td><td>Left touch pad pressed y-coordinate.</td></tr>
* <tr><td><code>LSCenter</code></td><td>number</td><td>number</td><td>Left touch pad center pressed.</td></tr>
* <tr><td><code>LSX</code></td><td>number</td><td>number</td><td>Left touch pad pressed x-coordinate.</td></tr>
* <tr><td><code>LSY</code></td><td>number</td><td>number</td><td>Left touch pad pressed y-coordinate.</td></tr>
* <tr><td><code>RS</code></td><td>number</td><td>number</td><td>Right touch pad pressed.</td></tr>
* <tr><td><code>RS_CENTER</code></td><td>number</td><td>number</td><td>Right touch pad center pressed.</td></tr>
* <tr><td><code>RS_X</code></td><td>number</td><td>number</td><td>Right touch pad pressed x-coordinate.</td></tr>
* <tr><td><code>RS_Y</code></td><td>number</td><td>number</td><td>Right touch pad pressed y-coordinate.</td></tr>
* <tr><td><code>RSCenter</code></td><td>number</td><td>number</td><td>Right touch pad center pressed.</td></tr>
* <tr><td><code>RSX</code></td><td>number</td><td>number</td><td>Right touch pad pressed x-coordinate.</td></tr>
* <tr><td><code>RSY</code></td><td>number</td><td>number</td><td>Right touch pad pressed y-coordinate.</td></tr>
* <tr><td><code>LSTouch</code></td><td>number</td><td>number</td><td>Left touch pad is touched.</td></tr>
* <tr><td><code>RSTouch</code></td><td>number</td><td>number</td><td>Right touch pad is touched.</td></tr>
* <tr><td colspan="4"><strong>Triggers</strong></td></tr>

View file

@ -1,83 +0,0 @@
//
// bloom.qml
// developer/utilities/render
//
// Olivier Prat, created on 09/25/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "configSlider"
Item {
id: root
property var configDebug: Render.getConfig("RenderMainView.DebugBloom")
Column {
spacing: 8
GroupBox {
title: "Debug"
Row {
ExclusiveGroup { id: debugGroup }
RadioButton {
text : "Off"
checked : !root.configDebug["enabled"]
onCheckedChanged: {
if (checked) {
root.configDebug["enabled"] = false
}
}
exclusiveGroup : debugGroup
}
RadioButton {
text : "Lvl 0"
checked :root.configDebug["enabled"] && root.configDebug["mode"]==0
onCheckedChanged: {
if (checked) {
root.configDebug["enabled"] = true
root.configDebug["mode"] = 0
}
}
exclusiveGroup : debugGroup
}
RadioButton {
text : "Lvl 1"
checked : root.configDebug["enabled"] && root.configDebug["mode"]==1
onCheckedChanged: {
if (checked) {
root.configDebug["enabled"] = true
root.configDebug["mode"] = 1
}
}
exclusiveGroup : debugGroup
}
RadioButton {
text : "Lvl 2"
checked : root.configDebug["enabled"] && root.configDebug["mode"]==2
onCheckedChanged: {
if (checked) {
root.configDebug["enabled"] = true
root.configDebug["mode"] = 2
}
}
exclusiveGroup : debugGroup
}
RadioButton {
text : "All"
checked : root.configDebug["enabled"] && root.configDebug["mode"]==3
onCheckedChanged: {
if (checked) {
root.configDebug["enabled"] = true
root.configDebug["mode"] = 3
}
}
exclusiveGroup : debugGroup
}
}
}
}
}

View file

@ -10,11 +10,8 @@
//
// Set up the qml ui
var qml = Script.resolvePath('bloom.qml');
var window = new OverlayWindow({
title: 'Bloom',
source: qml,
width: 285,
height: 40,
});
window.closed.connect(function() { Script.stop(); });
var window = Desktop.createWindow(Script.resolvePath('./luci/Bloom.qml'), {
title: "Bloom",
presentationMode: Desktop.PresentationMode.NATIVE,
size: {x: 285, y: 40}
});

View file

@ -1,103 +0,0 @@
//
// deferredLighting.qml
//
// Created by Sam Gateau on 6/6/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import "configSlider"
import "luci"
Rectangle {
HifiConstants { id: hifi;}
id: render;
anchors.margins: hifi.dimensions.contentMargin.x
color: hifi.colors.baseGray;
property var mainViewTask: Render.getConfig("RenderMainView")
Column {
spacing: 5
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: hifi.dimensions.contentMargin.x
HifiControls.Label {
text: "Shading"
}
ShadingModel {}
Separator {}
ToneMapping {}
Separator {}
Column {
anchors.left: parent.left
anchors.right: parent.right
spacing: 5
Repeater {
model: [ "MSAA:PreparePrimaryBufferForward:numSamples:4:1"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: true
config: render.mainViewTask.getConfig(modelData.split(":")[1])
property: modelData.split(":")[2]
max: modelData.split(":")[3]
min: modelData.split(":")[4]
anchors.left: parent.left
anchors.right: parent.right
}
}
}
Separator {}
Framebuffer {}
Separator {}
BoundingBoxes {
}
Separator {}
Row {
HifiControls.Button {
text: "Engine"
// activeFocusOnPress: false
onClicked: {
sendToScript({method: "openEngineView"});
}
}
HifiControls.Button {
text: "LOD"
// activeFocusOnPress: false
onClicked: {
sendToScript({method: "openEngineLODView"});
}
}
HifiControls.Button {
text: "Cull"
// activeFocusOnPress: false
onClicked: {
sendToScript({method: "openCullInspectorView"});
}
}
}
Row {
HifiControls.Button {
text: "Material"
onClicked: {
sendToScript({method: "openMaterialInspectorView"});
}
}
}
}
}

View file

@ -1,80 +1,84 @@
"use strict";
//
// Luci.js
// tablet-engine app
//
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var MaterialInspector = Script.require('./materialInspector.js');
var Page = Script.require('./luci/Page.js');
(function() {
var AppUi = Script.require('appUi');
var MaterialInspector = Script.require('./materialInspector.js');
var Page = Script.require('./luci/Page.js');
var moveDebugCursor = false;
var onMousePressEvent = function (e) {
if (e.isMiddleButton) {
moveDebugCursor = true;
setDebugCursor(e.x, e.y);
}
};
Controller.mousePressEvent.connect(onMousePressEvent);
var onMouseReleaseEvent = function () {
moveDebugCursor = false;
};
Controller.mouseReleaseEvent.connect(onMouseReleaseEvent);
var onMouseMoveEvent = function (e) {
if (moveDebugCursor) {
setDebugCursor(e.x, e.y);
}
};
Controller.mouseMoveEvent.connect(onMouseMoveEvent);
function setDebugCursor(x, y) {
var nx = 2.0 * (x / Window.innerWidth) - 1.0;
var ny = 1.0 - 2.0 * ((y) / (Window.innerHeight));
Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 };
}
function openView() {
//window.closed.connect(function() { Script.stop(); });
var pages = new Pages();
pages.addPage('openEngineLODView', 'Render LOD', '../lod.qml', 300, 400);
pages.addPage('openCullInspectorView', 'Cull Inspector', '../luci/Culling.qml', 300, 400);
pages.addPage('openMaterialInspectorView', 'Material Inspector', '../materialInspector.qml', 300, 400, MaterialInspector.setWindow);
function fromQml(message) {
if (pages.open(message.method)) {
return;
}
}
var ui;
function startup() {
ui = new AppUi({
buttonName: "LUCI",
home: Script.resolvePath("deferredLighting.qml"),
additionalAppScreens : Script.resolvePath("engineInspector.qml"),
onMessage: fromQml,
normalButton: Script.resolvePath("../../../system/assets/images/luci-i.svg"),
activeButton: Script.resolvePath("../../../system/assets/images/luci-a.svg")
});
var luciWindow
function openLuciWindow(window) {
if (luciWindow !== undefined) {
activeWindow.fromQml.disconnect(fromQml);
}
if (window !== undefined) {
window.fromQml.connect(fromQml);
}
luciWindow = window;
var moveDebugCursor = false;
var onMousePressEvent = function (e) {
if (e.isMiddleButton) {
moveDebugCursor = true;
setDebugCursor(e.x, e.y);
}
};
Controller.mousePressEvent.connect(onMousePressEvent);
var onMouseReleaseEvent = function () {
moveDebugCursor = false;
};
Controller.mouseReleaseEvent.connect(onMouseReleaseEvent);
var onMouseMoveEvent = function (e) {
if (moveDebugCursor) {
setDebugCursor(e.x, e.y);
}
};
Controller.mouseMoveEvent.connect(onMouseMoveEvent);
function setDebugCursor(x, y) {
var nx = 2.0 * (x / Window.innerWidth) - 1.0;
var ny = 1.0 - 2.0 * ((y) / (Window.innerHeight));
Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 };
}
}
startup();
Script.scriptEnding.connect(function () {
function closeLuciWindow() {
if (luciWindow !== undefined) {
activeWindow.fromQml.disconnect(fromQml);
}
luciWindow = {};
Controller.mousePressEvent.disconnect(onMousePressEvent);
Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent);
Controller.mouseMoveEvent.disconnect(onMouseMoveEvent);
pages.clear();
});
}());
}
pages.addPage('Luci', 'Luci', '../luci.qml', 300, 420, openLuciWindow, closeLuciWindow);
pages.addPage('openEngineInspectorView', 'Render Engine Inspector', '../engineInspector.qml', 300, 400);
pages.addPage('openEngineLODView', 'Render LOD', '../lod.qml', 300, 400);
pages.addPage('openMaterialInspectorView', 'Material Inspector', '../materialInspector.qml', 300, 400, MaterialInspector.setWindow, MaterialInspector.setWindow);
pages.open('Luci');
return pages;
}
openView();

View file

@ -72,6 +72,12 @@ Rectangle {
Antialiasing {}
}
}
Prop.PropFolderPanel {
label: "Bloom"
panelFrameData: Component {
Bloom {}
}
}
Prop.PropFolderPanel {
label: "Culling"
panelFrameData: Component {

View file

@ -22,15 +22,12 @@ import "../../lib/prop" as Prop
Column{
HifiConstants { id: hifi; }
id: antialiasing
id: antialiasing
padding: 10
anchors.left: parent.left
anchors.right: parent.right
spacing: 10
Prop.PropScalar {
Prop.PropScalar {
label: "MSAA"
object: Render.getConfig("RenderMainView.PreparePrimaryBufferForward")
property: "numSamples"
@ -38,49 +35,44 @@ Column{
max: 32
integral: true
}
Row {
spacing: 10
id: fxaaOnOff
property bool debugFXAA: false
HifiControls.Button {
function getTheText() {
if (Render.getConfig("RenderMainView.Antialiasing").fxaaOnOff) {
return "FXAA"
} else {
return "TAA"
}
}
text: getTheText()
onClicked: {
var onOff = !Render.getConfig("RenderMainView.Antialiasing").fxaaOnOff;
if (onOff) {
Render.getConfig("RenderMainView.JitterCam").none();
Render.getConfig("RenderMainView.Antialiasing").fxaaOnOff = true;
} else {
Render.getConfig("RenderMainView.JitterCam").play();
Render.getConfig("RenderMainView.Antialiasing").fxaaOnOff = false;
}
}
}
Prop.PropEnum {
label: "Deferred AA Method"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "mode"
enums: [
"Off",
"TAA",
"FXAA",
]
}
Prop.PropEnum {
id: jitter
label: "Jitter"
object: Render.getConfig("RenderMainView.JitterCam")
property: "state"
enums: [
"Off",
"On",
"Paused",
]
}
Separator {}
Prop.PropScalar {
visible: (Render.getConfig("RenderMainView.JitterCam").state == 2)
label: "Sample Index"
object: Render.getConfig("RenderMainView.JitterCam")
property: "index"
// min: -1
// max: 32
readOnly: true
integral: true
}
Row {
visible: (Render.getConfig("RenderMainView.JitterCam").state == 2)
spacing: 10
HifiControls.Button {
text: {
var state = 2 - (Render.getConfig("RenderMainView.JitterCam").freeze * 1 - Render.getConfig("RenderMainView.JitterCam").stop * 2);
if (state === 2) {
return "Jitter"
} else if (state === 1) {
return "Paused at " + Render.getConfig("RenderMainView.JitterCam").index + ""
} else {
return "No Jitter"
}
}
onClicked: { Render.getConfig("RenderMainView.JitterCam").cycleStopPauseRun(); }
}
HifiControls.Button {
text: "<"
onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); }
@ -90,96 +82,75 @@ Column{
onClicked: { Render.getConfig("RenderMainView.JitterCam").next(); }
}
}
Separator {}
HifiControls.CheckBox {
boxSize: 20
text: "Constrain color"
checked: Render.getConfig("RenderMainView.Antialiasing")["constrainColor"]
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] = checked }
}
ConfigSlider {
label: qsTr("Covariance gamma")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
Separator {}
Prop.PropBool {
label: "Constrain color"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "constrainColor"
}
Prop.PropScalar {
label: "Covariance gamma"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "covarianceGamma"
max: 1.5
min: 0.5
height: 38
}
Separator {}
HifiControls.CheckBox {
boxSize: 20
text: "Feedback history color"
checked: Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"]
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] = checked }
}
ConfigSlider {
label: qsTr("Source blend")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
}
Separator {}
Prop.PropBool {
label: "Feedback history color"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "feedbackColor"
}
Prop.PropScalar {
label: "Source blend"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "blend"
max: 1.0
min: 0.0
height: 38
}
ConfigSlider {
label: qsTr("Post sharpen")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
Prop.PropScalar {
label: "Post sharpen"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "sharpen"
max: 1.0
min: 0.0
}
Separator {}
Row {
spacing: 10
HifiControls.CheckBox {
boxSize: 20
text: "Debug"
checked: Render.getConfig("RenderMainView.Antialiasing")["debug"]
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["debug"] = checked }
}
HifiControls.CheckBox {
boxSize: 20
text: "Show Debug Cursor"
checked: Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"]
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showCursorPixel"] = checked }
}
}
ConfigSlider {
label: qsTr("Debug Region <")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
Separator {}
Prop.PropBool {
label: "Debug"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "debug"
}
Prop.PropBool {
label: "Show Debug Cursor"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "showCursorPixel"
}
Prop.PropScalar {
label: "Debug Region <"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "debugX"
max: 1.0
min: 0.0
}
HifiControls.CheckBox {
boxSize: 20
text: "Closest Fragment"
checked: Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"]
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] = checked }
}
ConfigSlider {
label: qsTr("Debug Velocity Threshold [pix]")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
Prop.PropBool {
label: "Closest Fragment"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "showClosestFragment"
}
Prop.PropScalar {
label: "Debug Velocity Threshold [pix]"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "debugShowVelocityThreshold"
max: 50
min: 0.0
height: 38
}
ConfigSlider {
label: qsTr("Debug Orb Zoom")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
Prop.PropScalar {
label: "Debug Orb Zoom"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "debugOrbZoom"
max: 32.0
min: 1.0
height: 38
}
}
}

View file

@ -0,0 +1,48 @@
//
// bloom.qml
//
// Olivier Prat, created on 09/25/2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import "../../lib/prop" as Prop
Column {
anchors.left: parent.left
anchors.right: parent.right
id: bloom
property var config: Render.getConfig("RenderMainView.DebugBloom")
Prop.PropBool {
label: "Apply Bloom"
object: Render.getConfig("RenderMainView.LightingModel")
property: "enableBloom"
}
function setDebugMode(mode) {
console.log("Bloom mode is " + mode)
bloom.config.enabled = (mode != 0);
bloom.config.mode = mode;
}
Prop.PropEnum {
label: "Debug Bloom Buffer"
// object: config
// property: "mode"
enums: [
"Off",
"Lvl 0",
"Lvl 1",
"Lvl 2",
"All",
]
valueVarSetter: function (mode) { bloom.setDebugMode(mode) }
}
}

View file

@ -54,7 +54,7 @@ Column {
Prop.PropCheckBox {
text: "Zones"
checked: root.mainViewTask.getConfig("DrawZones")["enabled"]
onCheckedChanged: { root.mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; root.mainViewTask.getConfig("DrawZones")["enabled"] = checked; }
onCheckedChanged: { root.mainViewTask.getConfig("DrawZones")["enabled"] = checked; }
}
}
Column {

View file

@ -35,7 +35,8 @@ Column {
"Emissive:LightingModel:enableEmissive",
"Lightmap:LightingModel:enableLightmap",
"Background:LightingModel:enableBackground",
"Haze:LightingModel:enableHaze",
"Haze:LightingModel:enableHaze",
"Bloom:LightingModel:enableBloom",
"AO:LightingModel:enableAmbientOcclusion",
"Textures:LightingModel:enableMaterialTexturing"
]

View file

@ -5,6 +5,7 @@ BoundingBoxes 1.0 BoundingBoxes.qml
Framebuffer 1.0 Framebuffer.qml
Antialiasing 1.0 Antialiasing.qml
Culling 1.0 Culling.qml
Bloom 1.0 Bloom.qml
Platform 1.0 Platform.qml
RenderSettings 1.0 RenderSettings.qml

View file

@ -1,84 +0,0 @@
var MaterialInspector = Script.require('./materialInspector.js');
var Page = Script.require('./luci/Page.js');
function openView() {
//window.closed.connect(function() { Script.stop(); });
var pages = new Pages();
function fromQml(message) {
if (pages.open(message.method)) {
return;
}
}
var luciWindow
function openLuciWindow(window) {
if (luciWindow !== undefined) {
activeWindow.fromQml.disconnect(fromQml);
}
if (window !== undefined) {
window.fromQml.connect(fromQml);
}
luciWindow = window;
var moveDebugCursor = false;
var onMousePressEvent = function (e) {
if (e.isMiddleButton) {
moveDebugCursor = true;
setDebugCursor(e.x, e.y);
}
};
Controller.mousePressEvent.connect(onMousePressEvent);
var onMouseReleaseEvent = function () {
moveDebugCursor = false;
};
Controller.mouseReleaseEvent.connect(onMouseReleaseEvent);
var onMouseMoveEvent = function (e) {
if (moveDebugCursor) {
setDebugCursor(e.x, e.y);
}
};
Controller.mouseMoveEvent.connect(onMouseMoveEvent);
function setDebugCursor(x, y) {
var nx = 2.0 * (x / Window.innerWidth) - 1.0;
var ny = 1.0 - 2.0 * ((y) / (Window.innerHeight));
Render.getConfig("RenderMainView").getConfig("DebugDeferredBuffer").size = { x: nx, y: ny, z: 1.0, w: 1.0 };
}
}
function closeLuciWindow() {
if (luciWindow !== undefined) {
activeWindow.fromQml.disconnect(fromQml);
}
luciWindow = {};
Controller.mousePressEvent.disconnect(onMousePressEvent);
Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent);
Controller.mouseMoveEvent.disconnect(onMouseMoveEvent);
pages.clear();
}
pages.addPage('Luci', 'Luci', '../luci.qml', 300, 420, openLuciWindow, closeLuciWindow);
pages.addPage('openEngineInspectorView', 'Render Engine Inspector', '../engineInspector.qml', 300, 400);
pages.addPage('openEngineLODView', 'Render LOD', '../lod.qml', 300, 400);
pages.addPage('openMaterialInspectorView', 'Material Inspector', '../materialInspector.qml', 300, 400, MaterialInspector.setWindow, MaterialInspector.setWindow);
pages.open('Luci');
return pages;
}
openView();

View file

@ -388,7 +388,8 @@ function playPopAnimation() {
var emojiCodeMap;
var customEmojiCodeMap;
var signalsConnected = false;
function init() {
var _this;
function startup() {
// make a map of just the utf codes to help with accesing
emojiCodeMap = emojiList.reduce(function (codeMap, currentEmojiInList, index) {
if (
@ -414,55 +415,30 @@ function init() {
pruneOldAvimojis();
Script.scriptEnding.connect(unload);
Window.domainChanged.connect(onDomainChanged);
MyAvatar.scaleChanged.connect(onScaleChanged);
Script.scriptEnding.connect(scriptEnding);
signalsConnected = true;
}
// #endregion
// *************************************
// END main
// *************************************
// *************************************
// START cleanup
// *************************************
// #region cleanup
function scriptEnding() {
resetEmojis();
if (signalsConnected) {
Script.scriptEnding.disconnect(scriptEnding);
Window.domainChanged.disconnect(onDomainChanged);
MyAvatar.scaleChanged.disconnect(onScaleChanged);
signalsConnected = false;
function AviMoji() {
_this = this;
this._avimojiQMLWindow = null;
}
AviMoji.prototype = {
addEmoji: addEmojiFromQML,
registerAvimojiQMLWindow: registerAvimojiQMLWindow
};
return new AviMoji();
}
// #endregion
// *************************************
// END cleanup
// *************************************
// *************************************
// START API
// *************************************
// #region API
var _this;
function AviMoji() {
_this = this;
this._avimojiQMLWindow;
}
function registerAvimojiQMLWindow(avimojiQMLWindow) {
this._avimojiQMLWindow = avimojiQMLWindow;
}
function addEmojiFromQML(code) {
var emojiObject = emojiList[emojiCodeMap[code]];
var emojiFilename;
@ -475,25 +451,17 @@ function addEmojiFromQML(code) {
handleSelectedEmoji(emojiFilename);
}
function unload() {
scriptEnding();
resetEmojis();
if (signalsConnected) {
Window.domainChanged.disconnect(onDomainChanged);
MyAvatar.scaleChanged.disconnect(onScaleChanged);
signalsConnected = false;
}
}
function startup() {
init();
}
AviMoji.prototype = {
startup: startup,
addEmoji: addEmojiFromQML,
unload: unload,
registerAvimojiQMLWindow: registerAvimojiQMLWindow
};
var aviMoji = startup();
module.exports = AviMoji;
// #endregion
// *************************************
// END API
// *************************************
module.exports = aviMoji;

View file

@ -38,7 +38,6 @@ var customEmojiList = Script.require("./emojiApp/resources/modules/customEmojiLi
// #region EMOTE_UTILITY
function updateEmoteAppBarPosition() {
if (!emoteAppBarWindow) {
return;
@ -425,7 +424,7 @@ function onGeometryChanged(rect) {
function onWindowMinimizedChanged(isMinimized) {
if (isMinimized) {
handleEmoteIndicatorVisibleChanged(false);
} else if (!HMD.active && Settings.getValue("simplifiedUI/emoteIndicatorVisible", true)) {
} else if (!HMD.active) {
handleEmoteIndicatorVisibleChanged(true);
}
}
@ -520,8 +519,8 @@ function showEmoteAppBar() {
}
function handleEmoteIndicatorVisibleChanged(newValue) {
if (newValue && !emoteAppBarWindow) {
function handleEmoteIndicatorVisibleChanged(shouldBeVisible) {
if (shouldBeVisible && !emoteAppBarWindow) {
showEmoteAppBar();
} else if (emoteAppBarWindow) {
emoteAppBarWindow.fromQml.disconnect(onMessageFromEmoteAppBar);
@ -531,13 +530,6 @@ function handleEmoteIndicatorVisibleChanged(newValue) {
}
function onSettingsValueChanged(settingName, newValue) {
if (settingName === "simplifiedUI/emoteIndicatorVisible") {
handleEmoteIndicatorVisibleChanged(newValue);
}
}
function onDisplayModeChanged(isHMDMode) {
reactionsBegun.forEach(function(react) {
endReactionWrapper(react);
@ -545,18 +537,17 @@ function onDisplayModeChanged(isHMDMode) {
if (isHMDMode) {
handleEmoteIndicatorVisibleChanged(false);
} else if (Settings.getValue("simplifiedUI/emoteIndicatorVisible", true)) {
} else {
handleEmoteIndicatorVisibleChanged(true);
}
}
var EmojiAPI = Script.require("./emojiApp/simplifiedEmoji.js");
var emojiAPI = new EmojiAPI();
var emojiAPI = Script.require("./emojiApp/simplifiedEmoji.js");
var keyPressSignalsConnected = false;
var emojiCodeMap;
var customEmojiCodeMap;
function init() {
function setup() {
deleteOldReticles();
// make a map of just the utf codes to help with accesing
@ -584,22 +575,19 @@ function init() {
Window.minimizedChanged.connect(onWindowMinimizedChanged);
Window.geometryChanged.connect(onGeometryChanged);
Settings.valueChanged.connect(onSettingsValueChanged);
HMD.displayModeChanged.connect(onDisplayModeChanged);
emojiAPI.startup();
getSounds();
handleEmoteIndicatorVisibleChanged(Settings.getValue("simplifiedUI/emoteIndicatorVisible", true));
handleEmoteIndicatorVisibleChanged(true);
Controller.keyPressEvent.connect(keyPressHandler);
Controller.keyReleaseEvent.connect(keyReleaseHandler);
keyPressSignalsConnected = true;
Script.scriptEnding.connect(shutdown);
Script.scriptEnding.connect(unload);
}
function shutdown() {
function unload() {
if (emoteAppBarWindow) {
emoteAppBarWindow.fromQml.disconnect(onMessageFromEmoteAppBar);
emoteAppBarWindow.close();
@ -614,14 +602,12 @@ function shutdown() {
endReactionWrapper(react);
});
emojiAPI.unload();
maybeClearClapSoundInterval();
maybeClearReticleUpdateLimiterTimeout();
maybeDeleteRemoteIndicatorTimeout();
Window.minimizedChanged.disconnect(onWindowMinimizedChanged);
Window.geometryChanged.disconnect(onGeometryChanged);
Settings.valueChanged.disconnect(onSettingsValueChanged);
HMD.displayModeChanged.disconnect(onDisplayModeChanged);
if (keyPressSignalsConnected) {
@ -768,37 +754,4 @@ function toggleEmojiApp() {
// END EMOJI
// *************************************
// *************************************
// START API
// *************************************
// #region API
function startup() {
init();
}
function unload() {
shutdown();
}
var _this;
function EmoteBar() {
_this = this;
}
EmoteBar.prototype = {
startup: startup,
unload: unload
};
module.exports = EmoteBar;
// #endregion
// *************************************
// END API
// *************************************
setup();

View file

@ -21,20 +21,24 @@ Rectangle {
id: root
color: simplifiedUI.colors.white
anchors.fill: parent
property int originalWidth: 48
property int expandedWidth: mainEmojiContainer.width + drawerContainer.width
// For the below to work, the Repeater's Item's second child must be the individual button's `MouseArea`
property int requestedWidth: (drawerContainer.keepDrawerExpanded ||
emoteIndicatorMouseArea.containsMouse ||
emoteButtonsRepeater.itemAt(0).hovered ||
emoteButtonsRepeater.itemAt(1).hovered ||
emoteButtonsRepeater.itemAt(2).hovered ||
emoteButtonsRepeater.itemAt(3).hovered ||
emoteButtonsRepeater.itemAt(4).hovered ||
emoteButtonsRepeater.itemAt(5).hovered) ? expandedWidth : originalWidth;
property int requestedWidth: (
root.allowEmoteDrawerExpansion && (
drawerContainer.keepDrawerExpanded ||
emoteIndicatorMouseArea.containsMouse ||
emoteButtonsRepeater.itemAt(0).hovered ||
emoteButtonsRepeater.itemAt(1).hovered ||
emoteButtonsRepeater.itemAt(2).hovered ||
emoteButtonsRepeater.itemAt(3).hovered ||
emoteButtonsRepeater.itemAt(4).hovered ||
emoteButtonsRepeater.itemAt(5).hovered)
) ? expandedWidth : originalWidth;
readonly property int totalEmojiDurationMS: 7000 // Must match `TOTAL_EMOJI_DURATION_MS` in `simplifiedEmoji.js`
readonly property string emoteIconSource: "images/emote_Icon.svg"
property bool allowEmoteDrawerExpansion: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true)
onRequestedWidthChanged: {
root.requestNewWidth(root.requestedWidth);
@ -45,6 +49,16 @@ Rectangle {
SmoothedAnimation { duration: 220 }
}
Connections {
target: Settings
onValueChanged: {
if (setting === "simplifiedUI/allowEmoteDrawerExpansion") {
root.allowEmoteDrawerExpansion = value;
}
}
}
SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI
}
@ -158,7 +172,7 @@ Rectangle {
anchors.fill: lockIcon
source: lockIcon
color: "#ffffff"
visible: drawerContainer.keepDrawerExpanded
visible: root.allowEmoteDrawerExpansion && drawerContainer.keepDrawerExpanded
}
MouseArea {

View file

@ -47,15 +47,24 @@ function onAvatarAdded(uuid) {
}
// Called on init
// Create a new nametag list manager, connect signals, and return back a new Nametag object.
var avatarNametagMode;
function startup() {
nameTagListManager.create();
handleAvatarNametagMode(Settings.getValue("simplifiedNametag/avatarNametagMode", "on"));
Script.scriptEnding.connect(unload);
Window.domainChanged.connect(onDomainChange);
AvatarManager.avatarRemovedEvent.connect(onAvatarRemoved);
AvatarManager.avatarAddedEvent.connect(onAvatarAdded);
function NameTag() {}
NameTag.prototype = {
handleAvatarNametagMode: handleAvatarNametagMode
};
return new NameTag();
}
@ -77,23 +86,7 @@ function handleAvatarNametagMode(newAvatarNameTagMode) {
}
// *************************************
// START api
// *************************************
// #region api
var nameTag = startup();
function NameTag() {}
module.exports = nameTag;
NameTag.prototype = {
startup: startup,
unload: unload,
handleAvatarNametagMode: handleAvatarNametagMode
};
module.exports = NameTag;
// #endregion
// *************************************
// END api
// *************************************

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
function simplifiedStatusIndicator(properties) {
function SimplifiedStatusIndicator() {
var that = this;
var DEBUG = false;
@ -86,6 +86,7 @@ function simplifiedStatusIndicator(properties) {
});
}
// Get status from database
function getStatus(callback) {
var queryParamString = "type=getStatus";
@ -125,6 +126,17 @@ function simplifiedStatusIndicator(properties) {
// #region SIGNALS
function updateProperties(properties) {
// Overwrite with the given properties
var overwriteableKeys = ["statusChanged"];
Object.keys(properties).forEach(function (key) {
if (overwriteableKeys.indexOf(key) > -1) {
that[key] = properties[key];
}
});
}
var currentStatus = "available"; // Default is available
function toggleStatus() {
if (currentStatus === "busy") {
@ -207,6 +219,8 @@ function simplifiedStatusIndicator(properties) {
Window.domainChanged.connect(onDomainChanged);
getStatus(setStatus);
Script.scriptEnding.connect(unload);
}
@ -224,20 +238,15 @@ function simplifiedStatusIndicator(properties) {
// #endregion APP LIFETIME
that.startup = startup;
that.unload = unload;
that.toggleStatus = toggleStatus;
that.setStatus = setStatus;
that.getLocalStatus = getLocalStatus;
that.statusChanged = statusChanged;
// Overwrite with the given properties
var overwriteableKeys = ["statusChanged"];
Object.keys(properties).forEach(function (key) {
if (overwriteableKeys.indexOf(key) > -1) {
that[key] = properties[key];
}
});
that.updateProperties = updateProperties;
startup();
}
var simplifiedStatusIndicator = new SimplifiedStatusIndicator();
module.exports = simplifiedStatusIndicator;

View file

@ -583,12 +583,9 @@ function restoreLODSettings() {
}
var SimplifiedNametag = Script.require("./simplifiedNametag/simplifiedNametag.js?" + Date.now());
var SimplifiedStatusIndicator = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js?" + Date.now());
var SimplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js?" + Date.now());
var si;
var nametag;
var emote;
var nametag = Script.require("./simplifiedNametag/simplifiedNametag.js?" + Date.now());
var si = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js?" + Date.now())
var emote = Script.require("../simplifiedEmote/simplifiedEmote.js?" + Date.now());
var oldShowAudioTools;
var oldShowBubbleTools;
var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false);
@ -607,16 +604,8 @@ function startup() {
loadSimplifiedTopBar();
si = new SimplifiedStatusIndicator({
statusChanged: onStatusChanged
});
si.startup();
nametag = new SimplifiedNametag();
nametag.startup();
emote = new SimplifiedEmote();
emote.startup();
si.updateProperties({ statusChanged: onStatusChanged });
updateInputDeviceMutedOverlay(Audio.muted);
updateOutputDeviceMutedOverlay(isOutputMuted());
@ -665,10 +654,6 @@ function shutdown() {
maybeDeleteInputDeviceMutedOverlay();
maybeDeleteOutputDeviceMutedOverlay();
nametag.unload();
si.unload();
emote.unload();
Audio.mutedDesktopChanged.disconnect(onDesktopInputDeviceMutedChanged);
Audio.mutedHMDChanged.disconnect(onHMDInputDeviceMutedChanged);
Window.geometryChanged.disconnect(onGeometryChanged);