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.setSingleShot(true);
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS); _challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
_challengeTimer.callOnTimeout([this]() { _challengeTimer.callOnTimeout(this, [this]() {
if (_verifyState == challengeClient) { if (_verifyState == challengeClient) {
_pendingEvent = false; _pendingEvent = false;
_verifyState = verificationFailed; _verifyState = verificationFailed;

View file

@ -15,9 +15,11 @@ macro(TARGET_WEBRTC)
# select_library_configurations(WEBRTC) # select_library_configurations(WEBRTC)
else() else()
set(WEBRTC_INCLUDE_DIRS "${VCPKG_INSTALL_ROOT}/include/webrtc") 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_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() endif()

View file

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

View file

@ -136,17 +136,17 @@ Flickable {
Layout.preferredHeight: 18 Layout.preferredHeight: 18
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
labelTextOn: "Show Emote UI" labelTextOn: "Show Emote UI"
checked: Settings.getValue("simplifiedUI/emoteIndicatorVisible", true) checked: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true)
onClicked: { onClicked: {
var currentSetting = Settings.getValue("simplifiedUI/emoteIndicatorVisible", true); var currentSetting = Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true);
Settings.setValue("simplifiedUI/emoteIndicatorVisible", !currentSetting); Settings.setValue("simplifiedUI/allowEmoteDrawerExpansion", !currentSetting);
} }
Connections { Connections {
target: Settings target: Settings
onValueChanged: { onValueChanged: {
if (setting === "simplifiedUI/emoteIndicatorVisible") { if (setting === "simplifiedUI/allowEmoteDrawerExpansion") {
emoteSwitch.checked = value; 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} 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, * @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). * 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. * @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) Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); } 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(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) 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 * @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. * the new position and orientate the avatar to face the position.
*/ */
void goToFeetLocation(const glm::vec3& newPosition, void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false,
bool hasOrientation, const glm::quat& newOrientation, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false);
bool shouldFaceLocation);
/**jsdoc /**jsdoc
* Moves the avatar to a new position and/or orientation in the domain. * 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.modelTranslation = getTranslation();
eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye"); eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye");
eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye"); eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye");
_rig.updateFromEyeParameters(eyeParams);
if (_owningAvatar->getHasProceduralEyeFaceMovement()) {
_rig.updateFromEyeParameters(eyeParams);
}
updateFingers(); updateFingers();
} }

View file

@ -1,6 +1,10 @@
#import "DownloadLauncher.h" #import "DownloadLauncher.h"
#import "Launcher.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 @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 { -(void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didFinishDownloadingToURL:(NSURL*)location {
NSLog(@"Did finish downloading to url"); 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; NSError* error = nil;
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
NSString* destinationFileName = downloadTask.originalRequest.URL.lastPathComponent; 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++) { for (int index = 0; index < argc; index++) {
NSLog(@"argv at index %d = %s", index, argv[index]); NSLog(@"argv at index %d = %s", index, argv[index]);
} }
NSError *error = nil;
NSString* oldLauncher = [NSString stringWithUTF8String:argv[1]]; NSString* oldLauncher = [NSString stringWithUTF8String:argv[1]];
NSString* newLauncher = [NSString stringWithUTF8String:argv[2]]; NSString* newLauncher = [NSString stringWithUTF8String:argv[2]];
NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newLauncher]; NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newLauncher];
NSFileManager* fileManager = [NSFileManager defaultManager]; 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]; NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
NSURL* applicationURL = [UpdaterHelper NSStringToNSURL: [oldLauncher stringByAppendingString: @"/Contents/MacOS/HQ Launcher"]]; NSURL* applicationURL = [UpdaterHelper NSStringToNSURL: [oldLauncher stringByAppendingString: @"/Contents/MacOS/HQ Launcher"]];
NSArray* arguments =@[]; NSArray* arguments =@[];
NSLog(@"Launcher agruments: %@", 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; return 0;
} }

View file

@ -13,6 +13,8 @@
#include "LauncherApp.h" #include "LauncherApp.h"
#include "LauncherDlg.h" #include "LauncherDlg.h"
#include <propsys.h>
#include <propkey.h>
#include <d2d1.h> #include <d2d1.h>
#pragma comment(lib, "d2d1") #pragma comment(lib, "d2d1")
@ -84,7 +86,7 @@ END_MESSAGE_MAP()
BOOL CLauncherDlg::OnInitDialog() { BOOL CLauncherDlg::OnInitDialog() {
CDialog::OnInitDialog(); CDialog::OnInitDialog();
MarkWindowAsUnpinnable();
SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon SetIcon(m_hIcon, FALSE); // Set small icon
@ -129,6 +131,19 @@ BOOL CLauncherDlg::OnInitDialog() {
return TRUE; 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 CLauncherDlg::getMouseCoords(MSG* pMsg) {
POINT pos; POINT pos;
pos.x = (int)(short)LOWORD(pMsg->lParam); pos.x = (int)(short)LOWORD(pMsg->lParam);

View file

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

View file

@ -14,9 +14,10 @@
#include "AnimUtil.h" #include "AnimUtil.h"
#include "AnimClip.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), 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) { } else if (_children.size() == 1) {
_poses = _children[0]->evaluate(animVars, context, dt, triggersOut); _poses = _children[0]->evaluate(animVars, context, dt, triggersOut);
context.setDebugAlpha(_children[0]->getID(), parentDebugAlpha, _children[0]->getType()); 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 { } else {
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
size_t prevPoseIndex = glm::floor(clampedAlpha); 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()) { if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) {
_poses.resize(prevPoses.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: public:
friend class AnimTests; friend class AnimTests;
AnimBlendLinear(const QString& id, float alpha); AnimBlendLinear(const QString& id, float alpha, AnimBlendType blendType);
virtual ~AnimBlendLinear() override; virtual ~AnimBlendLinear() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) override;
@ -44,6 +44,7 @@ protected:
AnimPoseVec _poses; AnimPoseVec _poses;
float _alpha; float _alpha;
AnimBlendType _blendType;
QString _alphaVar; QString _alphaVar;

View file

@ -16,100 +16,6 @@
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimUtil.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 #ifdef USE_CUSTOM_ASSERT
#undef ASSERT #undef ASSERT
#define ASSERT(x) \ #define ASSERT(x) \
@ -123,12 +29,73 @@ static std::vector<int> buildJointIndexMap(const AnimSkeleton& dstSkeleton, cons
#define ASSERT assert #define ASSERT assert
#endif #endif
void AnimClip::copyFromNetworkAnim() { static std::vector<int> buildJointIndexMap(const AnimSkeleton& dstSkeleton, const AnimSkeleton& srcSkeleton) {
assert(_networkAnim && _networkAnim->isLoaded() && _skeleton); std::vector<int> jointIndexMap;
_anim.clear(); 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(); static void bakeRelativeDeltaAnim(std::vector<AnimPoseVec>& anim, const AnimPoseVec& basePoses) {
const HFMModel& animModel = _networkAnim->getHFMModel();
// 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); AnimSkeleton animSkeleton(animModel);
const int animJointCount = animSkeleton.getNumJoints(); const int animJointCount = animSkeleton.getNumJoints();
const int avatarJointCount = avatarSkeleton->getNumJoints(); const int avatarJointCount = avatarSkeleton->getNumJoints();
@ -137,7 +104,7 @@ void AnimClip::copyFromNetworkAnim() {
std::vector<int> avatarToAnimJointIndexMap = buildJointIndexMap(animSkeleton, *avatarSkeleton); std::vector<int> avatarToAnimJointIndexMap = buildJointIndexMap(animSkeleton, *avatarSkeleton);
const int animFrameCount = animModel.animationFrames.size(); const int animFrameCount = animModel.animationFrames.size();
_anim.resize(animFrameCount); anim.resize(animFrameCount);
// find the size scale factor for translation in the animation. // find the size scale factor for translation in the animation.
float boneLengthScale = 1.0f; float boneLengthScale = 1.0f;
@ -223,8 +190,8 @@ void AnimClip::copyFromNetworkAnim() {
// convert avatar rotations into relative frame // convert avatar rotations into relative frame
avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations); avatarSkeleton->convertAbsoluteRotationsToRelative(avatarRotations);
ASSERT(frame >= 0 && frame < (int)_anim.size()); ASSERT(frame >= 0 && frame < (int)anim.size());
_anim[frame].reserve(avatarJointCount); anim[frame].reserve(avatarJointCount);
for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) { for (int avatarJointIndex = 0; avatarJointIndex < avatarJointCount; avatarJointIndex++) {
const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex); const AnimPose& avatarDefaultPose = avatarSkeleton->getRelativeDefaultPose(avatarJointIndex);
@ -251,14 +218,129 @@ void AnimClip::copyFromNetworkAnim() {
// build the final pose // build the final pose
ASSERT(avatarJointIndex >= 0 && avatarJointIndex < (int)avatarRotations.size()); 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. return anim;
_mirrorAnim.clear(); }
_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() { void AnimClip::buildMirrorAnim() {
@ -275,3 +357,9 @@ void AnimClip::buildMirrorAnim() {
const AnimPoseVec& AnimClip::getPosesInternal() const { const AnimPoseVec& AnimClip::getPosesInternal() const {
return _poses; 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: public:
friend class AnimTests; 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 ~AnimClip() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimVariantMap& triggersOut) 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; } void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
float getFrame() const { return _frame; } float getFrame() const { return _frame; }
void loadURL(const QString& url); void loadURL(const QString& url);
protected: protected:
virtual void setCurrentFrameInternal(float frame) override; virtual void setCurrentFrameInternal(float frame) override;
void copyFromNetworkAnim();
void buildMirrorAnim(); void buildMirrorAnim();
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override; virtual const AnimPoseVec& getPosesInternal() const override;
AnimationPointer _networkAnim; AnimationPointer _networkAnim;
AnimationPointer _baseNetworkAnim;
AnimPoseVec _poses; AnimPoseVec _poses;
// _anim[frame][joint] // _anim[frame][joint]
@ -78,6 +80,9 @@ protected:
bool _loopFlag; bool _loopFlag;
bool _mirrorFlag; bool _mirrorFlag;
float _frame; float _frame;
AnimBlendType _blendType;
QString _baseURL;
float _baseFrame;
QString _startFrameVar; QString _startFrameVar;
QString _endFrameVar; QString _endFrameVar;

View file

@ -34,6 +34,13 @@ enum class AnimNodeType {
NumTypes NumTypes
}; };
enum AnimBlendType {
AnimBlendType_Normal,
AnimBlendType_AddRelative,
AnimBlendType_AddAbsolute,
AnimBlendType_NumTypes
};
class AnimContext { class AnimContext {
public: public:
AnimContext() {} 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) { static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) {
switch (type) { switch (type) {
case AnimManipulator::JointVar::Type::Absolute: return "absolute"; 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_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false); 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(startFrameVar, jsonObj);
READ_OPTIONAL_STRING(endFrameVar, 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(loopFlagVar, jsonObj);
READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj); READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj);
// animation urls can be relative to the containing url document. // animation urls can be relative to the containing url document.
auto tempUrl = QUrl(url); auto tempUrl = QUrl(url);
tempUrl = jsonUrl.resolved(tempUrl); 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()) { if (!startFrameVar.isEmpty()) {
node->setStartFrameVar(startFrameVar); 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) { static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_STRING(blendType, jsonObj);
READ_OPTIONAL_STRING(alphaVar, 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()) { if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar); node->setAlphaVar(alphaVar);

View file

@ -15,7 +15,6 @@
// TODO: use restrict keyword // TODO: use restrict keyword
// TODO: excellent candidate for simd vectorization. // TODO: excellent candidate for simd vectorization.
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) { void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
for (size_t i = 0; i < numPoses; i++) { for (size_t i = 0; i < numPoses; i++) {
const AnimPose& aPose = a[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) { glm::quat averageQuats(size_t numQuats, const glm::quat* quats) {
if (numQuats == 0) { if (numQuats == 0) {
return glm::quat(); return glm::quat();

View file

@ -16,6 +16,9 @@
// this is where the magic happens // this is where the magic happens
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); 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); glm::quat averageQuats(size_t numQuats, const glm::quat* quats);
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, 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; _origRoleAnimations[role] = node;
const float REFERENCE_FRAMES_PER_SECOND = 30.0f; const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; 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 }; _roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame };
AnimNode::Pointer parent = node->getParent(); AnimNode::Pointer parent = node->getParent();
parent->replaceChild(node, clipNode); 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. // 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 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation);
const glm::mat4 worldToRig = glm::inverse(rigToWorld); const glm::mat4 worldToRig = glm::inverse(rigToWorld);
const glm::vec3 lookAtVector = glm::normalize(transformPoint(worldToRig, lookAtSpot) - _internalPoseSet._absolutePoses[index].trans()); const glm::vec3 lookAtVector = glm::normalize(transformPoint(worldToRig, lookAtSpot) - _internalPoseSet._absolutePoses[index].trans());

View file

@ -72,7 +72,6 @@
#include "EntityTreeRenderer.h" #include "EntityTreeRenderer.h"
#include "RenderablePolyVoxEntityItem.h" #include "RenderablePolyVoxEntityItem.h"
#include "EntityEditPacketSender.h"
#include "PhysicalEntitySimulation.h" #include "PhysicalEntitySimulation.h"
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5; 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 _voxelData -- compressed QByteArray representation of which voxels have which values
_volData -- datastructure from the PolyVox library which holds which voxels have which values _volData -- datastructure from the PolyVox library which holds which voxels have which values
_mesh -- renderable representation of the voxels _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. 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. 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. Here is a simplified diagram of the state machine implemented in RenderablePolyVoxEntityItem::update
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.
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 the resulting mesh wont be closed -- the library assumes you'll have another polyvox next to it to continue the
mesh. mesh.
@ -114,8 +143,8 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
previously mentioned gaps along the edges. previously mentioned gaps along the edges.
Non-edged polyvox entities can be told about their neighbors in all 6 cardinal directions. On the positive 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 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 bonkNeighbors and copyUpperEdgesFromNeighbors. In these functions, variable knit together. This is handled by tellNeighborsToRecopyEdges and copyUpperEdgesFromNeighbors. In these functions, variable
names have XP for x-positive, XN x-negative, etc. names have XP for x-positive, XN x-negative, etc.
*/ */
@ -140,7 +169,8 @@ EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entit
return entity; return entity;
} }
RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) { } RenderablePolyVoxEntityItem::RenderablePolyVoxEntityItem(const EntityItemID& entityItemID) : PolyVoxEntityItem(entityItemID) {
}
RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() {
withWriteLock([&] { withWriteLock([&] {
@ -165,11 +195,12 @@ bool isEdged(PolyVoxEntityItem::PolyVoxSurfaceStyle surfaceStyle) {
} }
void RenderablePolyVoxEntityItem::setVoxelData(const QByteArray& voxelData) { void RenderablePolyVoxEntityItem::setVoxelData(const QByteArray& voxelData) {
// compressed voxel information from the entity-server // accept compressed voxel information from the entity-server
withWriteLock([&] { withWriteLock([&] {
if (_voxelData != voxelData) { if (_voxelData != voxelData) {
_voxelData = voxelData; _voxelData = voxelData;
_voxelDataDirty = true; _voxelDataDirty = true;
startUpdates();
} }
}); });
} }
@ -189,21 +220,22 @@ void RenderablePolyVoxEntityItem::setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxel
bool willBeEdged = isEdged(voxelSurfaceStyle); bool willBeEdged = isEdged(voxelSurfaceStyle);
if (wasEdged != willBeEdged) { if (wasEdged != willBeEdged) {
_volDataDirty = true;
_volData.reset(); _volData.reset();
_voxelSurfaceStyle = voxelSurfaceStyle;
_voxelDataDirty = true; _voxelDataDirty = true;
volSizeChanged = true; volSizeChanged = true;
} else {
_volDataDirty = true;
_voxelSurfaceStyle = voxelSurfaceStyle;
} }
_voxelSurfaceStyle = voxelSurfaceStyle;
startUpdates();
}); });
if (volSizeChanged) { if (volSizeChanged) {
// setVoxelVolumeSize will re-alloc _volData with the right size // setVoxelVolumeSize will re-alloc _volData with the right size
setVoxelVolumeSize(_voxelVolumeSize); setVoxelVolumeSize(_voxelVolumeSize);
} }
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
tellNeighborsToRecopyEdges(true);
startUpdates();
} }
glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const {
@ -262,9 +294,6 @@ bool RenderablePolyVoxEntityItem::setVoxel(const ivec3& v, uint8_t toValue) {
withWriteLock([&] { withWriteLock([&] {
result = setVoxelInternal(v, toValue); result = setVoxelInternal(v, toValue);
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -311,9 +340,6 @@ bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
result |= setVoxelInternal(v, toValue); result |= setVoxelInternal(v, toValue);
}); });
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -336,9 +362,6 @@ bool RenderablePolyVoxEntityItem::setCuboid(const glm::vec3& lowPosition, const
result |= setVoxelInternal(v, toValue); result |= setVoxelInternal(v, toValue);
}); });
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -374,9 +397,6 @@ bool RenderablePolyVoxEntityItem::setSphereInVolume(const vec3& center, float ra
}); });
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -434,9 +454,6 @@ bool RenderablePolyVoxEntityItem::setSphere(const vec3& centerWorldCoords, float
}); });
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -482,9 +499,6 @@ bool RenderablePolyVoxEntityItem::setCapsule(const vec3& startWorldCoords, const
}); });
}); });
if (result) {
compressVolumeDataAndSendEditPacket();
}
return result; return result;
} }
@ -710,8 +724,9 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) { void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) {
if (value != _registrationPoint) { if (value != _registrationPoint) {
_meshDirty = true; _shapeReady = false;
EntityItem::setRegistrationPoint(value); EntityItem::setRegistrationPoint(value);
startUpdates();
} }
} }
@ -721,15 +736,11 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() const {
return true; return true;
} }
// we determine if we are ready to compute the physics shape by actually doing so. bool result;
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their withReadLock([&] {
// threads to finish before creating the collision shape. result = _shapeReady;
if (_meshDirty && !_voxelDataDirty && !_volDataDirty) { });
const_cast<RenderablePolyVoxEntityItem*>(this)->_meshDirty = false; return result;
const_cast<RenderablePolyVoxEntityItem*>(this)->computeShapeInfoWorker();
return false;
}
return true;
} }
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) { void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
@ -739,33 +750,155 @@ void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
return; return;
} }
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here. withReadLock([&] {
withWriteLock([&] {
info = _shapeInfo; info = _shapeInfo;
}); });
} }
bool RenderablePolyVoxEntityItem::updateDependents() { void RenderablePolyVoxEntityItem::changeUpdates(bool value) {
bool voxelDataDirty; if (_updateNeeded != value) {
bool volDataDirty; 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([&] { withWriteLock([&] {
voxelDataDirty = _voxelDataDirty; tellNeighborsToRecopyEdges(false);
volDataDirty = _volDataDirty;
if (_voxelDataDirty) { switch (_state) {
_voxelDataDirty = false;
} else if (_volDataDirty) { case PolyVoxState::Ready: {
_volDataDirty = false; if (_volDataDirty) {
} else { _volDataDirty = _voxelDataDirty = false;
_meshReady = true; _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(); if (doRecomputeMesh) {
} else if (volDataDirty) {
recomputeMesh(); recomputeMesh();
} }
if (doUncompress) {
return !volDataDirty; uncompressVolumeData();
}
if (doCompress) {
compressVolumeDataAndSendEditPacket();
}
if (doRecomputeShape) {
computeShapeInfoWorker();
}
} }
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) { void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) {
@ -782,6 +915,8 @@ void RenderablePolyVoxEntityItem::setVoxelVolumeSize(const glm::vec3& voxelVolum
_voxelVolumeSize = voxelVolumeSize; _voxelVolumeSize = voxelVolumeSize;
_volData.reset(); _volData.reset();
_onCount = 0; _onCount = 0;
_updateFromNeighborXEdge = _updateFromNeighborYEdge = _updateFromNeighborZEdge = true;
startUpdates();
static const PolyVox::Vector3DInt32 lowCorner(0, 0, 0); static const PolyVox::Vector3DInt32 lowCorner(0, 0, 0);
PolyVox::Vector3DInt32 highCorner; 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. // having the "outside of voxel-space" value be 255 has helped me notice some problems.
_volData->setBorderValue(255); _volData->setBorderValue(255);
}); });
tellNeighborsToRecopyEdges(true);
} }
bool inUserBounds(const std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> vol, 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) { bool RenderablePolyVoxEntityItem::setVoxelInternal(const ivec3& v, uint8_t toValue) {
// set a voxel without recompressing the voxel data. This assumes that the caller has // set a voxel without recompressing the voxel data. This assumes that the caller has write-locked the entity.
// write-locked the entity. bool result = updateOnCount(v, toValue);
bool result = false; if (result) {
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) { if (isEdged()) {
return result; 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; return result;
} }
bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue) { bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue) {
// keep _onCount up to date // keep _onCount up to date
if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) { if (!inUserBounds(_volData, _voxelSurfaceStyle, v)) {
@ -902,7 +1045,7 @@ bool RenderablePolyVoxEntityItem::updateOnCount(const ivec3& v, uint8_t toValue)
return false; return false;
} }
void RenderablePolyVoxEntityItem::decompressVolumeData() { void RenderablePolyVoxEntityItem::uncompressVolumeData() {
// take compressed data and expand it into _volData. // take compressed data and expand it into _volData.
QByteArray voxelData; QByteArray voxelData;
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer()); auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
@ -921,9 +1064,9 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || if (voxelXSize == 0 || voxelXSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION || voxelYSize == 0 || voxelYSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION ||
voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) { voxelZSize == 0 || voxelZSize > PolyVoxEntityItem::MAX_VOXEL_DIMENSION) {
qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping decompressions." qCDebug(entitiesrenderer) << "voxelSize is not reasonable, skipping uncompressions."
<< voxelXSize << voxelYSize << voxelZSize << getName() << getID(); << voxelXSize << voxelYSize << voxelZSize << getName() << getID();
entity->setVoxelDataDirty(false); entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return; return;
} }
int rawSize = voxelXSize * voxelYSize * voxelZSize; int rawSize = voxelXSize * voxelYSize * voxelZSize;
@ -934,10 +1077,10 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
QByteArray uncompressedData = qUncompress(compressedData); QByteArray uncompressedData = qUncompress(compressedData);
if (uncompressedData.size() != rawSize) { 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() << "so expected uncompressed length of" << rawSize << "but length is" << uncompressedData.size()
<< getName() << getID(); << getName() << getID();
entity->setVoxelDataDirty(false); entity->setVoxelsFromData(QByteArray(1, 0), 1, 1, 1);
return; return;
} }
@ -947,13 +1090,14 @@ void RenderablePolyVoxEntityItem::decompressVolumeData() {
void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData, void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData,
quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) { quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) {
// this accepts the payload from decompressVolumeData // this accepts the payload from uncompressVolumeData
withWriteLock([&] { withWriteLock([&] {
loop3(ivec3(0), ivec3(voxelXSize, voxelYSize, voxelZSize), [&](const ivec3& v) { loop3(ivec3(0), ivec3(voxelXSize, voxelYSize, voxelZSize), [&](const ivec3& v) {
int uncompressedIndex = (v.z * voxelYSize * voxelXSize) + (v.y * voxelZSize) + v.x; int uncompressedIndex = (v.z * voxelYSize * voxelXSize) + (v.y * voxelZSize) + v.x;
setVoxelInternal(v, uncompressedData[uncompressedIndex]); setVoxelInternal(v, uncompressedData[uncompressedIndex]);
}); });
_volDataDirty = true;
_state = PolyVoxState::UncompressingFinished;
}); });
} }
@ -972,10 +1116,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
voxelZSize = _voxelVolumeSize.z; voxelZSize = _voxelVolumeSize.z;
}); });
EntityTreeElementPointer element = getElement(); QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity] {
EntityTreePointer tree = element ? element->getTree() : nullptr;
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity); auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize); 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. // 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. // revert the active voxel-space to the last version that fit.
qCDebug(entitiesrenderer) << "compressed voxel data is too large" << entity->getName() << entity->getID(); 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; return;
} }
auto now = usecTimestampNow(); std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->compressVolumeDataFinished(newVoxelData);
entity->setLastEdited(now); });
entity->setLastBroadcast(now); }
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([&] { tree->withReadLock([&] {
EntityItemProperties properties = entity->getProperties(); EntityPropertyFlags desiredProperties;
desiredProperties.setHasProperty(PROP_VOXEL_DATA);
EntityItemProperties properties = getProperties(desiredProperties, false);
properties.setVoxelDataDirty(); properties.setVoxelDataDirty();
properties.setLastEdited(now); properties.setLastEdited(now);
@ -1010,12 +1171,13 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation); PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
if (packetSender) { 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 lookUpNeighbor(EntityTreePointer tree, EntityItemID neighborID, EntityItemWeakPointer& currentWP) {
EntityItemPointer current = currentWP.lock(); EntityItemPointer current = currentWP.lock();
@ -1064,56 +1226,103 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
return; return;
} }
auto currentXPNeighbor = getXPNeighbor(); if (!_updateFromNeighborXEdge && !_updateFromNeighborYEdge && !_updateFromNeighborZEdge) {
auto currentYPNeighbor = getYPNeighbor(); return;
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);
}
}
});
} }
cacheNeighbors();
if (currentYPNeighbor && currentYPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) { if (_updateFromNeighborXEdge) {
withWriteLock([&] { _updateFromNeighborXEdge = false;
for (int x = 0; x < _volData->getWidth(); x++) { auto currentXPNeighbor = getXPNeighbor();
for (int z = 0; z < _volData->getDepth(); z++) { if (currentXPNeighbor && currentXPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
uint8_t neighborValue = currentYPNeighbor->getVoxel({ x, 0, z }); withWriteLock([&] {
if ((x == 0 || z == 0) && _volData->getVoxelAt(x, _volData->getHeight() - 1, z) != neighborValue) { int x = _volData->getWidth() - 1;
bonkNeighbors();
}
_volData->setVoxelAt(x, _volData->getHeight() - 1, z, neighborValue);
}
}
});
}
if (currentZPNeighbor && currentZPNeighbor->getVoxelVolumeSize() == _voxelVolumeSize) {
withWriteLock([&] {
for (int x = 0; x < _volData->getWidth(); x++) {
for (int y = 0; y < _volData->getHeight(); y++) { for (int y = 0; y < _volData->getHeight(); y++) {
uint8_t neighborValue = currentZPNeighbor->getVoxel({ x, y, 0 }); for (int z = 0; z < _volData->getDepth(); z++) {
_volData->setVoxelAt(x, y, _volData->getDepth() - 1, neighborValue); uint8_t neighborValue = currentXPNeighbor->getVoxel({ 0, y, z });
if ((x == 0 || y == 0) && _volData->getVoxelAt(x, y, _volData->getDepth() - 1) != neighborValue) { uint8_t prevValue = _volData->getVoxelAt(x, y, z);
bonkNeighbors(); 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() { void RenderablePolyVoxEntityItem::recomputeMesh() {
// use _volData to make a renderable mesh // use _volData to make a renderable mesh
PolyVoxSurfaceStyle voxelSurfaceStyle; PolyVoxSurfaceStyle voxelSurfaceStyle;
@ -1121,9 +1330,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
voxelSurfaceStyle = _voxelSurfaceStyle; voxelSurfaceStyle = _voxelSurfaceStyle;
}); });
cacheNeighbors();
copyUpperEdgesFromNeighbors();
auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer()); auto entity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(getThisPointer());
QtConcurrent::run([entity, voxelSurfaceStyle] { QtConcurrent::run([entity, voxelSurfaceStyle] {
@ -1135,24 +1341,14 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
entity->withReadLock([&] { entity->withReadLock([&] {
PolyVox::SimpleVolume<uint8_t>* volData = entity->getVolData(); PolyVox::SimpleVolume<uint8_t>* volData = entity->getVolData();
switch (voxelSurfaceStyle) { switch (voxelSurfaceStyle) {
case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES: { case PolyVoxEntityItem::SURFACE_EDGED_MARCHING_CUBES:
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: {
PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor PolyVox::MarchingCubesSurfaceExtractor<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh); (volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute(); surfaceExtractor.execute();
break; break;
} }
case PolyVoxEntityItem::SURFACE_EDGED_CUBIC: { case PolyVoxEntityItem::SURFACE_EDGED_CUBIC:
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh);
surfaceExtractor.execute();
break;
}
case PolyVoxEntityItem::SURFACE_CUBIC: { case PolyVoxEntityItem::SURFACE_CUBIC: {
PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor PolyVox::CubicSurfaceExtractorWithNormals<PolyVox::SimpleVolume<uint8_t>> surfaceExtractor
(volData, volData->getEnclosingRegion(), &polyVoxMesh); (volData, volData->getEnclosingRegion(), &polyVoxMesh);
@ -1180,7 +1376,6 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
mesh->setVertexBuffer(vertexBufferView); mesh->setVertexBuffer(vertexBufferView);
// TODO -- use 3-byte normals rather than 3-float normals // TODO -- use 3-byte normals rather than 3-float normals
mesh->addAttribute(gpu::Stream::NORMAL, mesh->addAttribute(gpu::Stream::NORMAL,
gpu::BufferView(vertexBufferPtr, gpu::BufferView(vertexBufferPtr,
@ -1194,28 +1389,24 @@ void RenderablePolyVoxEntityItem::recomputeMesh() {
(graphics::Index)vecIndices.size(), // numIndices (graphics::Index)vecIndices.size(), // numIndices
(graphics::Index)0, // baseVertex (graphics::Index)0, // baseVertex
graphics::Mesh::TRIANGLES)); // topology graphics::Mesh::TRIANGLES)); // topology
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(graphics::Mesh::Part), (gpu::Byte*) parts.data()),
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); gpu::Element::PART_DRAWCALL));
entity->setMesh(mesh); entity->setMesh(mesh);
}); });
} }
void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) { void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
// this catches the payload from recomputeMesh // this catches the payload from recomputeMesh
bool neighborsNeedUpdate;
withWriteLock([&] { withWriteLock([&] {
if (!_collisionless) { if (!_collisionless) {
_flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS; _flags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
} }
_shapeReady = false;
_mesh = mesh; _mesh = mesh;
_meshDirty = true; _state = PolyVoxState::BakingMeshFinished;
_meshReady = true; _meshReady = true;
neighborsNeedUpdate = _neighborsNeedUpdate; startUpdates();
_neighborsNeedUpdate = false;
}); });
if (neighborsNeedUpdate) {
bonkNeighbors();
}
somethingChangedNotification(); somethingChangedNotification();
} }
@ -1223,9 +1414,6 @@ void RenderablePolyVoxEntityItem::setMesh(graphics::MeshPointer mesh) {
void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
// this creates a collision-shape for the physics engine. The shape comes from // this creates a collision-shape for the physics engine. The shape comes from
// _volData for cubic extractors and from _mesh for marching-cube extractors // _volData for cubic extractors and from _mesh for marching-cube extractors
if (!_meshReady) {
return;
}
EntityItemPointer entity = getThisPointer(); EntityItemPointer entity = getThisPointer();
@ -1360,6 +1548,10 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
// this catches the payload from computeShapeInfoWorker // this catches the payload from computeShapeInfoWorker
if (pointCollection.isEmpty()) { if (pointCollection.isEmpty()) {
EntityItem::computeShapeInfo(_shapeInfo); EntityItem::computeShapeInfo(_shapeInfo);
withWriteLock([&] {
_shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
});
return; return;
} }
@ -1373,7 +1565,8 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection
QString::number(_registrationPoint.z); QString::number(_registrationPoint.z);
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey); _shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
_shapeInfo.setPointCollection(pointCollection); _shapeInfo.setPointCollection(pointCollection);
_meshDirty = false; _shapeReady = true;
_state = PolyVoxState::BakingShapeFinished;
}); });
} }
@ -1384,7 +1577,8 @@ void RenderablePolyVoxEntityItem::setXNNeighborID(const EntityItemID& xNNeighbor
if (xNNeighborID != _xNNeighborID) { if (xNNeighborID != _xNNeighborID) {
PolyVoxEntityItem::setXNNeighborID(xNNeighborID); PolyVoxEntityItem::setXNNeighborID(xNNeighborID);
cacheNeighbors(); _neighborXNeedsUpdate = true;
startUpdates();
} }
} }
@ -1395,7 +1589,8 @@ void RenderablePolyVoxEntityItem::setYNNeighborID(const EntityItemID& yNNeighbor
if (yNNeighborID != _yNNeighborID) { if (yNNeighborID != _yNNeighborID) {
PolyVoxEntityItem::setYNNeighborID(yNNeighborID); PolyVoxEntityItem::setYNNeighborID(yNNeighborID);
cacheNeighbors(); _neighborYNeedsUpdate = true;
startUpdates();
} }
} }
@ -1406,7 +1601,8 @@ void RenderablePolyVoxEntityItem::setZNNeighborID(const EntityItemID& zNNeighbor
if (zNNeighborID != _zNNeighborID) { if (zNNeighborID != _zNNeighborID) {
PolyVoxEntityItem::setZNNeighborID(zNNeighborID); PolyVoxEntityItem::setZNNeighborID(zNNeighborID);
cacheNeighbors(); _neighborZNeedsUpdate = true;
startUpdates();
} }
} }
@ -1416,7 +1612,8 @@ void RenderablePolyVoxEntityItem::setXPNeighborID(const EntityItemID& xPNeighbor
} }
if (xPNeighborID != _xPNeighborID) { if (xPNeighborID != _xPNeighborID) {
PolyVoxEntityItem::setXPNeighborID(xPNeighborID); PolyVoxEntityItem::setXPNeighborID(xPNeighborID);
_volDataDirty = true; _updateFromNeighborXEdge = true;
startUpdates();
} }
} }
@ -1426,7 +1623,8 @@ void RenderablePolyVoxEntityItem::setYPNeighborID(const EntityItemID& yPNeighbor
} }
if (yPNeighborID != _yPNeighborID) { if (yPNeighborID != _yPNeighborID) {
PolyVoxEntityItem::setYPNeighborID(yPNeighborID); PolyVoxEntityItem::setYPNeighborID(yPNeighborID);
_volDataDirty = true; _updateFromNeighborYEdge = true;
startUpdates();
} }
} }
@ -1436,7 +1634,8 @@ void RenderablePolyVoxEntityItem::setZPNeighborID(const EntityItemID& zPNeighbor
} }
if (zPNeighborID != _zPNeighborID) { if (zPNeighborID != _zPNeighborID) {
PolyVoxEntityItem::setZPNeighborID(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()); 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 // deprecated
bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
if (!updateDependents()) {
return false;
}
bool success = false; bool success = false;
if (_mesh) { if (_mesh) {
MeshProxy* meshProxy = nullptr; MeshProxy* meshProxy = nullptr;
@ -1517,7 +1693,7 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) {
} }
scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() { scriptable::ScriptableModelBase RenderablePolyVoxEntityItem::getScriptableModel() {
if (!updateDependents() || !_mesh) { if (!_mesh) {
return scriptable::ScriptableModelBase(); return scriptable::ScriptableModelBase();
} }
@ -1628,9 +1804,6 @@ bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPo
} }
void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { void PolyVoxEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
if (entity->_voxelDataDirty || entity->_volDataDirty) {
entity->updateDependents();
}
#ifdef POLYVOX_ENTITY_USE_FADE_EFFECT #ifdef POLYVOX_ENTITY_USE_FADE_EFFECT
if (!_hasTransitioned) { if (!_hasTransitioned) {
@ -1662,7 +1835,6 @@ void PolyVoxEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoi
graphics::MeshPointer newMesh; graphics::MeshPointer newMesh;
entity->withReadLock([&] { entity->withReadLock([&] {
newMesh = entity->_mesh; newMesh = entity->_mesh;
}); });
if (newMesh && newMesh->getIndexBuffer()._buffer) { if (newMesh && newMesh->getIndexBuffer()._buffer) {
@ -1705,3 +1877,41 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) {
batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_mesh->getNumIndices(), 0); 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

@ -32,6 +32,24 @@ namespace render { namespace entities {
class PolyVoxEntityRenderer; 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 { class RenderablePolyVoxEntityItem : public PolyVoxEntityItem, public scriptable::ModelProvider {
friend class render::entities::PolyVoxEntityRenderer; friend class render::entities::PolyVoxEntityRenderer;
@ -113,41 +131,61 @@ public:
uint8_t getVoxelInternal(const ivec3& v) const; uint8_t getVoxelInternal(const ivec3& v) const;
bool setVoxelInternal(const ivec3& v, uint8_t toValue); 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 bool getMeshes(MeshProxyList& result) override; // deprecated
virtual scriptable::ScriptableModelBase getScriptableModel() override; virtual scriptable::ScriptableModelBase getScriptableModel() override;
virtual void update(const quint64& now) override;
bool needsToCallUpdate() const override { return _updateNeeded; }
private: private:
bool updateOnCount(const ivec3& v, uint8_t toValue); bool updateOnCount(const ivec3& v, uint8_t toValue);
PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const; PolyVox::RaycastResult doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, glm::vec4& result) const;
void changeUpdates(bool value);
void startUpdates();
void stopUpdates();
void recomputeMesh(); void recomputeMesh();
void cacheNeighbors(); void cacheNeighbors();
void copyUpperEdgesFromNeighbors(); void copyUpperEdgesFromNeighbors();
void bonkNeighbors(); void tellNeighborsToRecopyEdges(bool force);
bool updateDependents(); bool updateDependents();
// these are run off the main thread // these are run off the main thread
void decompressVolumeData(); void uncompressVolumeData();
void compressVolumeDataAndSendEditPacket(); void compressVolumeDataAndSendEditPacket();
void computeShapeInfoWorker(); void computeShapeInfoWorker();
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions // The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
// may not match _voxelVolumeSize. // may not match _voxelVolumeSize.
bool _meshDirty { true }; // does collision-shape need to be recomputed? bool _meshReady { false }; // do we have something to give scripts that ask for the mesh?
bool _meshReady{ false }; 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; graphics::MeshPointer _mesh;
ShapeInfo _shapeInfo; ShapeInfo _shapeInfo;
std::shared_ptr<PolyVox::SimpleVolume<uint8_t>> _volData; 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 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 // these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
EntityItemWeakPointer _xNNeighbor; // neighbor found by going along negative X axis 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 _xPNeighbor; // neighbor found by going along positive X axis
EntityItemWeakPointer _yPNeighbor; EntityItemWeakPointer _yPNeighbor;
EntityItemWeakPointer _zPNeighbor; EntityItemWeakPointer _zPNeighbor;
}; };
namespace render { namespace entities { namespace render { namespace entities {
@ -192,7 +229,6 @@ private:
glm::mat4 _lastVoxelToWorldMatrix; glm::mat4 _lastVoxelToWorldMatrix;
PolyVoxEntityItem::PolyVoxSurfaceStyle _lastSurfaceStyle { PolyVoxEntityItem::SURFACE_MARCHING_CUBES }; PolyVoxEntityItem::PolyVoxSurfaceStyle _lastSurfaceStyle { PolyVoxEntityItem::SURFACE_MARCHING_CUBES };
std::array<QString, 3> _xyzTextureUrls; std::array<QString, 3> _xyzTextureUrls;
bool _neighborsNeedUpdate{ false };
}; };
} } } }

View file

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

View file

@ -1294,7 +1294,7 @@ public slots:
Q_INVOKABLE int getJointParent(const QUuid& entityID, int index); Q_INVOKABLE int getJointParent(const QUuid& entityID, int index);
/**jsdoc /**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. * position and orientation.
* @function Entities.getAbsoluteJointRotationInObjectFrame * @function Entities.getAbsoluteJointRotationInObjectFrame
* @param {Uuid} entityID - The ID of the entity. * @param {Uuid} entityID - The ID of the entity.

View file

@ -139,6 +139,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
} }
#else #else
void AntialiasingConfig::setAAMode(int mode) {
_mode = std::min((int)AntialiasingConfig::MODE_COUNT, std::max(0, mode));
emit dirty();
}
Antialiasing::Antialiasing(bool isSharpenEnabled) : Antialiasing::Antialiasing(bool isSharpenEnabled) :
_isSharpenEnabled{ isSharpenEnabled } { _isSharpenEnabled{ isSharpenEnabled } {
} }
@ -189,6 +194,8 @@ const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() {
} }
void Antialiasing::configure(const Config& config) { void Antialiasing::configure(const Config& config) {
_mode = (AntialiasingConfig::Mode) config.getAAMode();
_sharpen = config.sharpen * 0.25f; _sharpen = config.sharpen * 0.25f;
if (!_isSharpenEnabled) { if (!_isSharpenEnabled) {
_sharpen = 0.0f; _sharpen = 0.0f;
@ -298,29 +305,33 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
}); });
} }
void JitterSampleConfig::setIndex(int current) { void JitterSampleConfig::setIndex(int current) {
_index = (current) % JitterSample::SEQUENCE_LENGTH; _index = (current) % JitterSample::SEQUENCE_LENGTH;
emit dirty(); emit dirty();
} }
int JitterSampleConfig::cycleStopPauseRun() { void JitterSampleConfig::setState(int state) {
_state = (_state + 1) % 3; _state = (state) % 3;
switch (_state) { switch (_state) {
case 0: { case 0: {
return none(); none();
break; break;
}
case 1: {
return pause();
break;
}
case 2:
default: {
return play();
break;
}
} }
case 1: {
pause();
break;
}
case 2:
default: {
play();
break;
}
}
emit dirty();
}
int JitterSampleConfig::cycleStopPauseRun() {
setState((_state + 1) % 3);
return _state; 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 freeze MEMBER freeze NOTIFY dirty)
Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty)
Q_PROPERTY(int index READ getIndex NOTIFY dirty) Q_PROPERTY(int index READ getIndex NOTIFY dirty)
Q_PROPERTY(int state READ getState WRITE setState NOTIFY dirty)
public: public:
JitterSampleConfig() : render::Job::Config(true) {} JitterSampleConfig() : render::Job::Config(true) {}
@ -33,6 +34,7 @@ public:
bool freeze{ false }; bool freeze{ false };
void setIndex(int current); void setIndex(int current);
void setState(int state);
public slots: public slots:
int cycleStopPauseRun(); int cycleStopPauseRun();
@ -86,6 +88,7 @@ private:
class AntialiasingConfig : public render::Job::Config { class AntialiasingConfig : public render::Job::Config {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int mode READ getAAMode WRITE setAAMode NOTIFY dirty)
Q_PROPERTY(float blend MEMBER blend NOTIFY dirty) Q_PROPERTY(float blend MEMBER blend NOTIFY dirty)
Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty) Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty)
Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty) Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty)
@ -106,9 +109,21 @@ class AntialiasingConfig : public render::Job::Config {
public: public:
AntialiasingConfig() : render::Job::Config(true) {} 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();} void setDebugFXAA(bool debug) { debugFXAAX = (debug ? 0.0f : 1.0f); emit dirty();}
bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); } bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); }
int _mode{ TAA };
float blend{ 0.25f }; float blend{ 0.25f };
float sharpen{ 0.05f }; float sharpen{ 0.05f };
@ -195,6 +210,7 @@ private:
gpu::PipelinePointer _debugBlendPipeline; gpu::PipelinePointer _debugBlendPipeline;
TAAParamsBuffer _params; TAAParamsBuffer _params;
AntialiasingConfig::Mode _mode{ AntialiasingConfig::TAA };
float _sharpen{ 0.15f }; float _sharpen{ 0.15f };
bool _isSharpenEnabled{ true }; bool _isSharpenEnabled{ true };
}; };

View file

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

View file

@ -17,6 +17,7 @@
#include "BloomStage.h" #include "BloomStage.h"
#include "DeferredFrameTransform.h" #include "DeferredFrameTransform.h"
#include "LightingModel.h"
class BloomConfig : public render::Task::Config { class BloomConfig : public render::Task::Config {
Q_OBJECT Q_OBJECT
@ -28,7 +29,7 @@ class BloomThresholdConfig : public render::Job::Config {
class BloomThreshold { class BloomThreshold {
public: 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 Outputs = render::VaryingSet3<gpu::FramebufferPointer, float, graphics::BloomPointer>;
using Config = BloomThresholdConfig; using Config = BloomThresholdConfig;
using JobModel = render::Job::ModelIO<BloomThreshold, Inputs, Outputs, Config>; using JobModel = render::Job::ModelIO<BloomThreshold, Inputs, Outputs, Config>;
@ -87,12 +88,13 @@ private:
class DebugBloomConfig : public render::Job::Config { class DebugBloomConfig : public render::Job::Config {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int mode MEMBER mode NOTIFY dirty) Q_PROPERTY(int mode READ getMode WRITE setMode NOTIFY dirty)
public: public:
enum Mode { enum Mode {
MODE_LEVEL0 = 0, OFF = 0,
MODE_LEVEL0,
MODE_LEVEL1, MODE_LEVEL1,
MODE_LEVEL2, MODE_LEVEL2,
MODE_ALL_LEVELS, MODE_ALL_LEVELS,
@ -102,7 +104,10 @@ public:
DebugBloomConfig() : render::Job::Config(false) {} 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: signals:
void dirty(); void dirty();
@ -127,14 +132,14 @@ private:
class BloomEffect { class BloomEffect {
public: public:
using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer>; using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, gpu::FramebufferPointer, BloomStage::FramePointer, LightingModelPointer>;
using Config = BloomConfig; 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 configure(const Config& config);
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); 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 // 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 deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform", jitter);
const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying(); const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying();
const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs); const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs);
const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0); 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(); const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying();
task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber); task.addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaqueInputs, shapePlumber);
task.addJob<EndGPURangeTimer>("OpaqueRangeTimer", opaqueRangeTimer);
// Opaque all rendered // Opaque all rendered
// Linear Depth Pass // 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(); const auto transparentsInputs = RenderTransparentDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, shadowFrame, jitter).asVarying();
task.addJob<RenderTransparentDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber); 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(); const auto outlineInputs = DrawHighlightTask::Inputs(items, deferredFramebuffer, lightingFramebuffer, deferredFrameTransform, jitter).asVarying();
task.addJob<DrawHighlightTask>("DrawHighlight", outlineInputs); task.addJob<DrawHighlightTask>("DrawHighlight", outlineInputs);
task.addJob<EndGPURangeTimer>("HighlightRangeTimer", outlineRangeTimer);
// Layered Over (in front) // Layered Over (in front)
const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, jitter).asVarying(); const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, jitter).asVarying();
const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, 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); task.addJob<Antialiasing>("Antialiasing", antialiasingInputs);
// Add bloom // 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); task.addJob<BloomEffect>("Bloom", bloomInputs);
const auto destFramebuffer = static_cast<gpu::FramebufferPointer>(nullptr); const auto destFramebuffer = static_cast<gpu::FramebufferPointer>(nullptr);

View file

@ -2210,7 +2210,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
/**jsdoc /**jsdoc
* Triggered when the script starts for a user. See also, {@link Script.entityScriptPreloadFinished}. * 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> * <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 * @function Entities.preload
* @param {Uuid} entityID - The ID of the entity that the script is running in. * @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal} * @returns {Signal}
@ -2416,7 +2416,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
/**jsdoc /**jsdoc
* Triggered when the script terminates for a user. * 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> * <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 * @function Entities.unload
* @param {Uuid} entityID - The ID of the entity that the script is running in. * @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal} * @returns {Signal}

View file

@ -416,8 +416,10 @@ public:
* Provides access to methods or objects provided in an external JavaScript or JSON file. * 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. * See {@link https://docs.highfidelity.com/script/js-tips.html} for further details.
* @function Script.require * @function Script.require
* @param {string} module - The module to use. May be a JavaScript file or the name of a system module such as * @param {string} module - The module to use. May be a JavaScript file, a JSON file, or the name of a system module such
* <code>"sppUi"</code>. * 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); Q_INVOKABLE QScriptValue require(const QString& moduleId);
@ -842,7 +844,7 @@ signals:
/**jsdoc /**jsdoc
* Triggered when the script starts for the user. See also, {@link Entities.preload}. * 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 * @function Script.entityScriptPreloadFinished
* @param {Uuid} entityID - The ID of the entity that the script is running in. * @param {Uuid} entityID - The ID of the entity that the script is running in.
* @returns {Signal} * @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>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>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</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>LSCenter</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>LSX</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>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</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>RSCenter</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>RSX</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>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>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><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> * <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 // Set up the qml ui
var qml = Script.resolvePath('bloom.qml'); var window = Desktop.createWindow(Script.resolvePath('./luci/Bloom.qml'), {
var window = new OverlayWindow({ title: "Bloom",
title: 'Bloom', presentationMode: Desktop.PresentationMode.NATIVE,
source: qml, size: {x: 285, y: 40}
width: 285,
height: 40,
}); });
window.closed.connect(function() { Script.stop(); });

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'); function openView() {
var Page = Script.require('./luci/Page.js'); //window.closed.connect(function() { Script.stop(); });
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 };
}
var pages = new Pages(); 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) { function fromQml(message) {
if (pages.open(message.method)) { if (pages.open(message.method)) {
return; return;
} }
} }
var ui; var luciWindow
function startup() { function openLuciWindow(window) {
ui = new AppUi({ if (luciWindow !== undefined) {
buttonName: "LUCI", activeWindow.fromQml.disconnect(fromQml);
home: Script.resolvePath("deferredLighting.qml"), }
additionalAppScreens : Script.resolvePath("engineInspector.qml"), if (window !== undefined) {
onMessage: fromQml, window.fromQml.connect(fromQml);
normalButton: Script.resolvePath("../../../system/assets/images/luci-i.svg"), }
activeButton: Script.resolvePath("../../../system/assets/images/luci-a.svg") 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.mousePressEvent.disconnect(onMousePressEvent);
Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent); Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent);
Controller.mouseMoveEvent.disconnect(onMouseMoveEvent); Controller.mouseMoveEvent.disconnect(onMouseMoveEvent);
pages.clear(); 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 {} Antialiasing {}
} }
} }
Prop.PropFolderPanel {
label: "Bloom"
panelFrameData: Component {
Bloom {}
}
}
Prop.PropFolderPanel { Prop.PropFolderPanel {
label: "Culling" label: "Culling"
panelFrameData: Component { panelFrameData: Component {

View file

@ -22,15 +22,12 @@ import "../../lib/prop" as Prop
Column{ Column{
HifiConstants { id: hifi; } id: antialiasing
id: antialiasing
padding: 10
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: 10 Prop.PropScalar {
Prop.PropScalar {
label: "MSAA" label: "MSAA"
object: Render.getConfig("RenderMainView.PreparePrimaryBufferForward") object: Render.getConfig("RenderMainView.PreparePrimaryBufferForward")
property: "numSamples" property: "numSamples"
@ -38,49 +35,44 @@ Column{
max: 32 max: 32
integral: true 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 {} 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 { Row {
visible: (Render.getConfig("RenderMainView.JitterCam").state == 2)
spacing: 10 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 { HifiControls.Button {
text: "<" text: "<"
onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); } onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); }
@ -91,95 +83,74 @@ Column{
} }
} }
Separator {} Separator {}
HifiControls.CheckBox { Prop.PropBool {
boxSize: 20 label: "Constrain color"
text: "Constrain color" object: Render.getConfig("RenderMainView.Antialiasing")
checked: Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] property: "constrainColor"
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["constrainColor"] = checked }
} }
ConfigSlider { Prop.PropScalar {
label: qsTr("Covariance gamma") label: "Covariance gamma"
integral: false object: Render.getConfig("RenderMainView.Antialiasing")
config: Render.getConfig("RenderMainView.Antialiasing")
property: "covarianceGamma" property: "covarianceGamma"
max: 1.5 max: 1.5
min: 0.5 min: 0.5
height: 38
} }
Separator {} Separator {}
HifiControls.CheckBox { Prop.PropBool {
boxSize: 20 label: "Feedback history color"
text: "Feedback history color" object: Render.getConfig("RenderMainView.Antialiasing")
checked: Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] property: "feedbackColor"
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["feedbackColor"] = checked }
} }
Prop.PropScalar {
ConfigSlider { label: "Source blend"
label: qsTr("Source blend") object: Render.getConfig("RenderMainView.Antialiasing")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
property: "blend" property: "blend"
max: 1.0 max: 1.0
min: 0.0 min: 0.0
height: 38
} }
Prop.PropScalar {
ConfigSlider { label: "Post sharpen"
label: qsTr("Post sharpen") object: Render.getConfig("RenderMainView.Antialiasing")
integral: false
config: Render.getConfig("RenderMainView.Antialiasing")
property: "sharpen" property: "sharpen"
max: 1.0 max: 1.0
min: 0.0 min: 0.0
} }
Separator {} Separator {}
Row { Prop.PropBool {
label: "Debug"
spacing: 10 object: Render.getConfig("RenderMainView.Antialiasing")
HifiControls.CheckBox { property: "debug"
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 { Prop.PropBool {
label: qsTr("Debug Region <") label: "Show Debug Cursor"
integral: false object: Render.getConfig("RenderMainView.Antialiasing")
config: Render.getConfig("RenderMainView.Antialiasing") property: "showCursorPixel"
}
Prop.PropScalar {
label: "Debug Region <"
object: Render.getConfig("RenderMainView.Antialiasing")
property: "debugX" property: "debugX"
max: 1.0 max: 1.0
min: 0.0 min: 0.0
} }
HifiControls.CheckBox { Prop.PropBool {
boxSize: 20 label: "Closest Fragment"
text: "Closest Fragment" object: Render.getConfig("RenderMainView.Antialiasing")
checked: Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] property: "showClosestFragment"
onCheckedChanged: { Render.getConfig("RenderMainView.Antialiasing")["showClosestFragment"] = checked }
} }
ConfigSlider { Prop.PropScalar {
label: qsTr("Debug Velocity Threshold [pix]") label: "Debug Velocity Threshold [pix]"
integral: false object: Render.getConfig("RenderMainView.Antialiasing")
config: Render.getConfig("RenderMainView.Antialiasing")
property: "debugShowVelocityThreshold" property: "debugShowVelocityThreshold"
max: 50 max: 50
min: 0.0 min: 0.0
height: 38
} }
ConfigSlider { Prop.PropScalar {
label: qsTr("Debug Orb Zoom") label: "Debug Orb Zoom"
integral: false object: Render.getConfig("RenderMainView.Antialiasing")
config: Render.getConfig("RenderMainView.Antialiasing")
property: "debugOrbZoom" property: "debugOrbZoom"
max: 32.0 max: 32.0
min: 1.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 { Prop.PropCheckBox {
text: "Zones" text: "Zones"
checked: root.mainViewTask.getConfig("DrawZones")["enabled"] 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 { Column {

View file

@ -36,6 +36,7 @@ Column {
"Lightmap:LightingModel:enableLightmap", "Lightmap:LightingModel:enableLightmap",
"Background:LightingModel:enableBackground", "Background:LightingModel:enableBackground",
"Haze:LightingModel:enableHaze", "Haze:LightingModel:enableHaze",
"Bloom:LightingModel:enableBloom",
"AO:LightingModel:enableAmbientOcclusion", "AO:LightingModel:enableAmbientOcclusion",
"Textures:LightingModel:enableMaterialTexturing" "Textures:LightingModel:enableMaterialTexturing"
] ]

View file

@ -5,6 +5,7 @@ BoundingBoxes 1.0 BoundingBoxes.qml
Framebuffer 1.0 Framebuffer.qml Framebuffer 1.0 Framebuffer.qml
Antialiasing 1.0 Antialiasing.qml Antialiasing 1.0 Antialiasing.qml
Culling 1.0 Culling.qml Culling 1.0 Culling.qml
Bloom 1.0 Bloom.qml
Platform 1.0 Platform.qml Platform 1.0 Platform.qml
RenderSettings 1.0 RenderSettings.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 emojiCodeMap;
var customEmojiCodeMap; var customEmojiCodeMap;
var signalsConnected = false; var signalsConnected = false;
function init() { var _this;
function startup() {
// make a map of just the utf codes to help with accesing // make a map of just the utf codes to help with accesing
emojiCodeMap = emojiList.reduce(function (codeMap, currentEmojiInList, index) { emojiCodeMap = emojiList.reduce(function (codeMap, currentEmojiInList, index) {
if ( if (
@ -414,55 +415,30 @@ function init() {
pruneOldAvimojis(); pruneOldAvimojis();
Script.scriptEnding.connect(unload);
Window.domainChanged.connect(onDomainChanged); Window.domainChanged.connect(onDomainChanged);
MyAvatar.scaleChanged.connect(onScaleChanged); MyAvatar.scaleChanged.connect(onScaleChanged);
Script.scriptEnding.connect(scriptEnding);
signalsConnected = true; signalsConnected = true;
}
function AviMoji() {
// #endregion _this = this;
// ************************************* this._avimojiQMLWindow = null;
// 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;
} }
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) { function registerAvimojiQMLWindow(avimojiQMLWindow) {
this._avimojiQMLWindow = avimojiQMLWindow; this._avimojiQMLWindow = avimojiQMLWindow;
} }
function addEmojiFromQML(code) { function addEmojiFromQML(code) {
var emojiObject = emojiList[emojiCodeMap[code]]; var emojiObject = emojiList[emojiCodeMap[code]];
var emojiFilename; var emojiFilename;
@ -475,25 +451,17 @@ function addEmojiFromQML(code) {
handleSelectedEmoji(emojiFilename); handleSelectedEmoji(emojiFilename);
} }
function unload() { function unload() {
scriptEnding(); resetEmojis();
if (signalsConnected) {
Window.domainChanged.disconnect(onDomainChanged);
MyAvatar.scaleChanged.disconnect(onScaleChanged);
signalsConnected = false;
}
} }
function startup() {
init();
}
AviMoji.prototype = { var aviMoji = startup();
startup: startup,
addEmoji: addEmojiFromQML,
unload: unload,
registerAvimojiQMLWindow: registerAvimojiQMLWindow
};
module.exports = aviMoji;
module.exports = AviMoji;
// #endregion
// *************************************
// END API
// *************************************

View file

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

View file

@ -21,20 +21,24 @@ Rectangle {
id: root id: root
color: simplifiedUI.colors.white color: simplifiedUI.colors.white
anchors.fill: parent anchors.fill: parent
property int originalWidth: 48 property int originalWidth: 48
property int expandedWidth: mainEmojiContainer.width + drawerContainer.width 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` // For the below to work, the Repeater's Item's second child must be the individual button's `MouseArea`
property int requestedWidth: (drawerContainer.keepDrawerExpanded || property int requestedWidth: (
emoteIndicatorMouseArea.containsMouse || root.allowEmoteDrawerExpansion && (
emoteButtonsRepeater.itemAt(0).hovered || drawerContainer.keepDrawerExpanded ||
emoteButtonsRepeater.itemAt(1).hovered || emoteIndicatorMouseArea.containsMouse ||
emoteButtonsRepeater.itemAt(2).hovered || emoteButtonsRepeater.itemAt(0).hovered ||
emoteButtonsRepeater.itemAt(3).hovered || emoteButtonsRepeater.itemAt(1).hovered ||
emoteButtonsRepeater.itemAt(4).hovered || emoteButtonsRepeater.itemAt(2).hovered ||
emoteButtonsRepeater.itemAt(5).hovered) ? expandedWidth : originalWidth; 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 int totalEmojiDurationMS: 7000 // Must match `TOTAL_EMOJI_DURATION_MS` in `simplifiedEmoji.js`
readonly property string emoteIconSource: "images/emote_Icon.svg" readonly property string emoteIconSource: "images/emote_Icon.svg"
property bool allowEmoteDrawerExpansion: Settings.getValue("simplifiedUI/allowEmoteDrawerExpansion", true)
onRequestedWidthChanged: { onRequestedWidthChanged: {
root.requestNewWidth(root.requestedWidth); root.requestNewWidth(root.requestedWidth);
@ -45,6 +49,16 @@ Rectangle {
SmoothedAnimation { duration: 220 } SmoothedAnimation { duration: 220 }
} }
Connections {
target: Settings
onValueChanged: {
if (setting === "simplifiedUI/allowEmoteDrawerExpansion") {
root.allowEmoteDrawerExpansion = value;
}
}
}
SimplifiedConstants.SimplifiedConstants { SimplifiedConstants.SimplifiedConstants {
id: simplifiedUI id: simplifiedUI
} }
@ -158,7 +172,7 @@ Rectangle {
anchors.fill: lockIcon anchors.fill: lockIcon
source: lockIcon source: lockIcon
color: "#ffffff" color: "#ffffff"
visible: drawerContainer.keepDrawerExpanded visible: root.allowEmoteDrawerExpansion && drawerContainer.keepDrawerExpanded
} }
MouseArea { 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; var avatarNametagMode;
function startup() { function startup() {
nameTagListManager.create(); nameTagListManager.create();
handleAvatarNametagMode(Settings.getValue("simplifiedNametag/avatarNametagMode", "on")); handleAvatarNametagMode(Settings.getValue("simplifiedNametag/avatarNametagMode", "on"));
Script.scriptEnding.connect(unload);
Window.domainChanged.connect(onDomainChange); Window.domainChanged.connect(onDomainChange);
AvatarManager.avatarRemovedEvent.connect(onAvatarRemoved); AvatarManager.avatarRemovedEvent.connect(onAvatarRemoved);
AvatarManager.avatarAddedEvent.connect(onAvatarAdded); AvatarManager.avatarAddedEvent.connect(onAvatarAdded);
function NameTag() {}
NameTag.prototype = {
handleAvatarNametagMode: handleAvatarNametagMode
};
return new NameTag();
} }
@ -77,23 +86,7 @@ function handleAvatarNametagMode(newAvatarNameTagMode) {
} }
// ************************************* var nameTag = startup();
// START api
// *************************************
// #region api
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 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
function simplifiedStatusIndicator(properties) { function SimplifiedStatusIndicator() {
var that = this; var that = this;
var DEBUG = false; var DEBUG = false;
@ -86,6 +86,7 @@ function simplifiedStatusIndicator(properties) {
}); });
} }
// Get status from database // Get status from database
function getStatus(callback) { function getStatus(callback) {
var queryParamString = "type=getStatus"; var queryParamString = "type=getStatus";
@ -125,6 +126,17 @@ function simplifiedStatusIndicator(properties) {
// #region SIGNALS // #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 var currentStatus = "available"; // Default is available
function toggleStatus() { function toggleStatus() {
if (currentStatus === "busy") { if (currentStatus === "busy") {
@ -207,6 +219,8 @@ function simplifiedStatusIndicator(properties) {
Window.domainChanged.connect(onDomainChanged); Window.domainChanged.connect(onDomainChanged);
getStatus(setStatus); getStatus(setStatus);
Script.scriptEnding.connect(unload);
} }
@ -224,20 +238,15 @@ function simplifiedStatusIndicator(properties) {
// #endregion APP LIFETIME // #endregion APP LIFETIME
that.startup = startup;
that.unload = unload;
that.toggleStatus = toggleStatus; that.toggleStatus = toggleStatus;
that.setStatus = setStatus; that.setStatus = setStatus;
that.getLocalStatus = getLocalStatus; that.getLocalStatus = getLocalStatus;
that.statusChanged = statusChanged; that.statusChanged = statusChanged;
that.updateProperties = updateProperties;
// Overwrite with the given properties startup();
var overwriteableKeys = ["statusChanged"];
Object.keys(properties).forEach(function (key) {
if (overwriteableKeys.indexOf(key) > -1) {
that[key] = properties[key];
}
});
} }
var simplifiedStatusIndicator = new SimplifiedStatusIndicator();
module.exports = simplifiedStatusIndicator; module.exports = simplifiedStatusIndicator;

View file

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