Merge branch 'master' of https://github.com/highfidelity/hifi into orange

This commit is contained in:
samcake 2016-02-25 23:48:53 -08:00
commit 3ee6f9d6f2
62 changed files with 3422 additions and 1481 deletions

View file

@ -153,7 +153,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
newestViewFrustum.setPosition(getCameraPosition());
newestViewFrustum.setOrientation(getCameraOrientation());
newestViewFrustum.setKeyholeRadius(getKeyholeRadius());
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
// Also make sure it's got the correct lens details from the camera
float originalFOV = getCameraFov();

View file

@ -271,8 +271,9 @@ FunctionEnd
@CPACK_NSIS_PAGE_COMPONENTS@
Page custom PostInstallOptionsPage ReadPostInstallOptions
!insertmacro MUI_PAGE_INSTFILES
Page custom PostInstallOptionsPage HandlePostInstallOptions
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
@ -341,6 +342,227 @@ FunctionEnd
;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA)
ReserveFile "@POST_INSTALL_OPTIONS_PATH@"
; Make sure nsDialogs is included before we use it
!include "nsdialogs.nsh"
;--------------------------------
; Post Install Options
Var PostInstallDialog
Var DesktopClientCheckbox
Var DesktopServerCheckbox
Var ServerStartupCheckbox
Var LaunchNowCheckbox
Var CurrentOffset
Var OffsetUnits
Var CopyFromProductionCheckbox
!macro SetPostInstallOption Checkbox OptionName Default
; reads the value for the given post install option to the registry
ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}"
${If} $0 == "NO"
; the value in the registry says it should not be checked
${NSD_SetState} ${Checkbox} ${BST_UNCHECKED}
${ElseIf} $0 == "YES"
; the value in the registry says it should be checked
${NSD_SetState} ${Checkbox} ${BST_CHECKED}
${Else}
; the value in the registry was not in the expected format, use default
${NSD_SetState} ${Checkbox} ${Default}
${EndIf}
!macroend
Function PostInstallOptionsPage
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
nsDialogs::Create 1018
Pop $PostInstallDialog
${If} $PostInstallDialog == error
Abort
${EndIf}
StrCpy $CurrentOffset 0
StrCpy $OffsetUnits u
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@"
Pop $DesktopClientCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@"
Pop $DesktopServerCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
IntOp $CurrentOffset $CurrentOffset + 15
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup"
Pop $ServerStartupCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
IntOp $CurrentOffset $CurrentOffset + 15
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console after install"
${Else}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity after install"
${EndIf}
Pop $LaunchNowCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED}
${If} @PR_BUILD@ == 1
; a PR build defaults all install options expect LaunchNowCheckbox and the settings copy to unchecked
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED}
${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED}
${EndIf}
; push the offset
IntOp $CurrentOffset $CurrentOffset + 15
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install"
Pop $CopyFromProductionCheckbox
${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED}
${EndIf}
nsDialogs::Show
FunctionEnd
!macro WritePostInstallOption OptionName Option
; writes the value for the given post install option to the registry
WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option}
!macroend
Var DesktopClientState
Var DesktopServerState
Var ServerStartupState
Var LaunchNowState
Var CopyFromProductionState
Function ReadPostInstallOptions
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to High Fidelity
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to Server Console
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
; check if the user asked to have Server Console launched every startup
${NSD_GetState} $ServerStartupCheckbox $ServerStartupState
${EndIf}
${If} @PR_BUILD@ == 1
; check if we need to copy settings/content from production for this PR build
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
${EndIf}
; check if we need to launch an application post-install
${NSD_GetState} $LaunchNowCheckbox $LaunchNowState
FunctionEnd
Function HandlePostInstallOptions
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to High Fidelity
${If} $DesktopClientState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
!insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES
${Else}
!insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to Server Console
${If} $DesktopServerState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
${Else}
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
; check if the user asked to have Server Console launched every startup
${If} $ServerStartupState == ${BST_CHECKED}
; in case we added a shortcut in the global context, pull that now
SetShellVarContext all
Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk"
; make a startup shortcut in this user's current context
SetShellVarContext current
CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
; reset the shell var context back
SetShellVarContext all
!insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES
${Else}
!insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO
${EndIf}
${EndIf}
${If} @PR_BUILD@ == 1
; check if we need to copy settings/content from production for this PR build
${If} $CopyFromProductionState == ${BST_CHECKED}
SetShellVarContext current
StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@"
; we need to copy whatever is in the data folder for production build to the data folder for this build
CreateDirectory $0
ClearErrors
; copy the data from production build to this PR build
CopyFiles "$APPDATA\High Fidelity\*" $0
; handle an error in copying files
IfErrors 0 NoError
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \
"There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually."
NoError:
SetShellVarContext all
${EndIf}
${EndIf}
${If} $LaunchNowState == ${BST_CHECKED}
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES
; both launches use the explorer trick in case the user has elevated permissions for the installer
; it won't be possible to use this approach if either application should be launched with a command line param
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
${Else}
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"'
${EndIf}
${Else}
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO
${EndIf}
FunctionEnd
;--------------------------------
;Installer Sections
@ -469,233 +691,11 @@ Section "-Core installation"
@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
; Handle whichever post install options were set
Call HandlePostInstallOptions
SectionEnd
; Make sure nsDialogs is included before we use it
!include "nsdialogs.nsh"
Var PostInstallDialog
Var OptionsLabel
Var DesktopClientCheckbox
Var DesktopServerCheckbox
Var ServerStartupCheckbox
Var LaunchNowCheckbox
Var CurrentOffset
Var OffsetUnits
Var CopyFromProductionCheckbox
!macro SetPostInstallOption Checkbox OptionName Default
; reads the value for the given post install option to the registry
ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}"
${If} $0 == "NO"
; the value in the registry says it should not be checked
${NSD_SetState} ${Checkbox} ${BST_UNCHECKED}
${ElseIf} $0 == "YES"
; the value in the registry says it should be checked
${NSD_SetState} ${Checkbox} ${BST_CHECKED}
${Else}
; the value in the registry was not in the expected format, use default
${NSD_SetState} ${Checkbox} ${Default}
${EndIf}
!macroend
Function PostInstallOptionsPage
; Set the text on the dialog button to match finish, hide the back and cancel buttons
GetDlgItem $R1 $hwndparent 1
SendMessage $R1 ${WM_SETTEXT} 0 "STR:&Finish"
GetDlgItem $R3 $hwndparent 3
ShowWindow $R3 0
nsDialogs::Create 1018
Pop $PostInstallDialog
${If} $PostInstallDialog == error
Abort
${EndIf}
${NSD_CreateLabel} 0 0 100% 12u "Setup Options"
Pop $OptionsLabel
; Set label to bold
CreateFont $R2 "Arial" 10 700
SendMessage $OptionsLabel ${WM_SETFONT} $R2 0
; Force label redraw
ShowWindow $OptionsLabel ${SW_HIDE}
ShowWindow $OptionsLabel ${SW_SHOW}
StrCpy $CurrentOffset 15
StrCpy $OffsetUnits u
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@"
Pop $DesktopClientCheckbox
IntOp $CurrentOffset $CurrentOffset + 15
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@"
Pop $DesktopServerCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
IntOp $CurrentOffset $CurrentOffset + 15
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup"
Pop $ServerStartupCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
IntOp $CurrentOffset $CurrentOffset + 15
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console Now"
${Else}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Now"
${EndIf}
Pop $LaunchNowCheckbox
; set the checkbox state depending on what is present in the registry
!insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED}
${If} @PR_BUILD@ == 1
; a PR build defaults all install options expect LaunchNowCheckbox and the settings copy to unchecked
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED}
${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED}
${EndIf}
; push the offset
IntOp $CurrentOffset $CurrentOffset + 15
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install"
Pop $CopyFromProductionCheckbox
${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED}
${EndIf}
nsDialogs::Show
FunctionEnd
!macro WritePostInstallOption OptionName Option
; writes the value for the given post install option to the registry
WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option}
!macroend
Var DesktopClientState
Var DesktopServerState
Var ServerStartupState
Var LaunchNowState
Var CopyFromProductionState
Function HandlePostInstallOptions
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to High Fidelity
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
${If} $DesktopClientState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
!insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES
${Else}
!insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
${EndIf}
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
; check if the user asked for a desktop shortcut to Server Console
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
${If} $DesktopServerState == ${BST_CHECKED}
CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
${Else}
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
${EndIf}
; check if the user asked to have Server Console launched every startup
${NSD_GetState} $ServerStartupCheckbox $ServerStartupState
${If} $ServerStartupState == ${BST_CHECKED}
; in case we added a shortcut in the global context, pull that now
SetShellVarContext all
Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk"
; make a startup shortcut in this user's current context
SetShellVarContext current
CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
; reset the shell var context back
SetShellVarContext all
!insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES
${Else}
!insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO
${EndIf}
${EndIf}
${If} @PR_BUILD@ == 1
; check if we need to copy settings/content from production for this PR build
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
${If} $CopyFromProductionState == ${BST_CHECKED}
SetShellVarContext current
StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@"
; we need to copy whatever is in the data folder for production build to the data folder for this build
CreateDirectory $0
ClearErrors
; copy the data from production build to this PR build
CopyFiles "$APPDATA\High Fidelity\*" $0
; handle an error in copying files
IfErrors 0 NoError
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \
"There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually."
NoError:
SetShellVarContext all
${EndIf}
${EndIf}
; check if we need to launch an application post-install
${NSD_GetState} $LaunchNowCheckbox $LaunchNowState
${If} $LaunchNowState == ${BST_CHECKED}
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES
; both launches use the explorer trick in case the user has elevated permissions for the installer
; it won't be possible to use this approach if either application should be launched with a command line param
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
${Else}
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"'
${EndIf}
${Else}
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO
${EndIf}
FunctionEnd
!include nsProcess.nsh
!macro PromptForRunningApplication applicationName displayName action prompter

View file

@ -95,9 +95,9 @@ EntityViewer.setPosition({
y: 0,
z: 0
});
EntityViewer.setKeyholeRadius(60000);
EntityViewer.setCenterRadius(60000);
var octreeQueryInterval = Script.setInterval(function() {
EntityViewer.queryOctree();
}, 1000);
Script.update.connect(update);
Script.update.connect(update);

View file

@ -1,218 +1,260 @@
"use strict";
/*jslint nomen: true, plusplus: true, vars: true*/
/*global AvatarList, Entities, EntityViewer, Script, SoundCache, Audio, print, randFloat*/
//
// ACAudioSearchAndInject.js
// audio
//
// Created by Eric Levin 2/1/2016
// Created by Eric Levin and Howard Stearns 2/1/2016
// Copyright 2016 High Fidelity, Inc.
// This AC script searches for special sound entities nearby avatars and plays those sounds based off information specified in the entity's
// user data field ( see acAudioSearchAndCompatibilityEntitySpawner.js for an example)
//
// Keeps track of all sounds within QUERY_RADIUS of an avatar, where a "sound" is specified in entity userData.
// Inject as many as practical into the audio mixer.
// See acAudioSearchAndCompatibilityEntitySpawner.js.
//
// This implementation takes some precautions to scale well:
// - It doesn't hastle the entity server because it issues at most one octree query every UPDATE_TIME period, regardless of the number of avatars.
// - It does not load itself because it only gathers entities once every UPDATE_TIME period, and only
// checks entity properties for those small number of entities that are currently playing (plus a RECHECK_TIME period examination of all entities).
// This implementation tries to use all the available injectors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
Script.include("https://rawgit.com/highfidelity/hifi/master/examples/libraries/utils.js");
var SOUND_DATA_KEY = "soundKey";
var QUERY_RADIUS = 50;
EntityViewer.setKeyholeRadius(QUERY_RADIUS);
Entities.setPacketsPerSecond(6000);
Agent.isAvatar = true;
var MSEC_PER_SEC = 1000;
var SOUND_DATA_KEY = "io.highfidelity.soundKey"; // Sound data is specified in userData under this key.
var old_sound_data_key = "soundKey"; // For backwards compatibility.
var QUERY_RADIUS = 50; // meters
var UPDATE_TIME = 100; // ms. We'll update just one thing on this period.
var EXPIRATION_TIME = 5 * MSEC_PER_SEC; // ms. Remove sounds that have been out of range for this time.
var RECHECK_TIME = 10 * MSEC_PER_SEC; // ms. Check for new userData properties this often when not currently playing.
// (By not checking most of the time when not playing, we can efficiently go through all entities without getEntityProperties.)
var UPDATES_PER_STATS_LOG = RECHECK_TIME / UPDATE_TIME; // (It's nice to smooth out the results by straddling a recheck.)
var DEFAULT_SOUND_DATA = {
volume: 0.5,
loop: false,
playbackGap: 1000, // in ms
volume: 0.5, // userData cannot specify zero volume with our current method of defaulting.
loop: false, // Default must be false with our current method of defaulting, else there's no way to get a false value.
playbackGap: MSEC_PER_SEC, // in ms
playbackGapRange: 0 // in ms
};
var MIN_PLAYBACK_GAP = 0;
var UPDATE_TIME = 100;
var EXPIRATION_TIME = 5000;
Script.include("../../libraries/utils.js");
Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList.
function ignore() {}
function debug() { // Display the arguments not just [Object object].
//print.apply(null, [].map.call(arguments, JSON.stringify));
}
var soundEntityMap = {};
var soundUrls = {};
EntityViewer.setCenterRadius(QUERY_RADIUS);
var avatarPositions = [];
function update() {
var avatars = AvatarList.getAvatarIdentifiers();
for (var i = 0; i < avatars.length; i++) {
var avatar = AvatarList.getAvatar(avatars[i]);
var avatarPosition = avatar.position;
if (!avatarPosition) {
continue;
// ENTITY DATA CACHE
//
var entityCache = {}; // A dictionary of unexpired EntityData objects.
var examinationCount = 0;
function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about.
// This data is only use for our sound injection. There is no need to store such info in the replicated entity on everyone's computer.
var that = this;
that.lastUserDataUpdate = 0; // new entity is in need of rechecking user data
// State Transitions:
// no data => no data | sound data | expired
// expired => stop => remove
// sound data => downloading
// downloading => downloading | waiting
// waiting => playing | waiting (if too many already playing)
// playing => update position etc | no data
that.stop = function stop() {
if (!that.sound) {
return;
}
EntityViewer.setPosition(avatarPosition);
EntityViewer.queryOctree();
avatarPositions.push(avatarPosition);
print("stopping sound", entityIdentifier, that.url);
delete that.sound;
delete that.url;
if (!that.injector) {
return;
}
that.injector.stop();
delete that.injector;
};
this.update = function stateTransitions(expirationCutoff, userDataCutoff, now) {
if (that.timestamp < expirationCutoff) { // EXPIRED => STOP => REMOVE
that.stop(); // Alternatively, we could fade out and then stop...
delete entityCache[entityIdentifier];
return;
}
var properties, soundData; // Latest data, pulled from local octree.
// getEntityProperties locks the tree, which competes with the asynchronous processing of queryOctree results.
// Most entity updates are fast and only a very few do getEntityProperties.
function ensureSoundData() { // We only getEntityProperities when we need to.
if (properties) {
return;
}
properties = Entities.getEntityProperties(entityIdentifier, ['userData', 'position']);
examinationCount++; // Collect statistics on how many getEntityProperties we do.
debug("updating", that, properties);
try {
var userData = properties.userData && JSON.parse(properties.userData);
soundData = userData && (userData[SOUND_DATA_KEY] || userData[old_sound_data_key]); // Don't store soundData yet. Let state changes compare.
that.lastUserDataUpdate = now; // But do update these ...
that.url = soundData && soundData.url;
that.playAfter = that.url && now;
} catch (err) {
print(err, properties.userData);
}
}
// Stumbling on big new pile of entities will do a lot of getEntityProperties. Once.
if (that.lastUserDataUpdate < userDataCutoff) { // NO DATA => SOUND DATA
ensureSoundData();
}
if (!that.url) { // NO DATA => NO DATA
return that.stop();
}
if (!that.sound) { // SOUND DATA => DOWNLOADING
that.sound = SoundCache.getSound(soundData.url); // SoundCache can manage duplicates better than we can.
}
if (!that.sound.downloaded) { // DOWNLOADING => DOWNLOADING
return;
}
if (that.playAfter > now) { // DOWNLOADING | WAITING => WAITING
return;
}
ensureSoundData(); // We'll try to play/setOptions and will need position, so we might as well get soundData, too.
if (soundData.url !== that.url) { // WAITING => NO DATA (update next time around)
return that.stop();
}
var options = {
position: properties.position,
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
volume: soundData.volume || DEFAULT_SOUND_DATA.volume
};
function repeat() {
return !options.loop && (soundData.playbackGap >= 0);
}
function randomizedNextPlay() { // time of next play or recheck, randomized to distribute the work
var range = soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange,
base = repeat() ? ((that.sound.duration * MSEC_PER_SEC) + (soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap)) : RECHECK_TIME;
return now + base + randFloat(-Math.min(base, range), range);
}
if (!that.injector) { // WAITING => PLAYING | WAITING
debug("starting", that, options);
that.injector = Audio.playSound(that.sound, options); // Might be null if at at injector limit. Will try again later.
if (that.injector) {
print("started", entityIdentifier, that.url);
} else { // Don't hammer ensureSoundData or injector manager.
that.playAfter = randomizedNextPlay();
}
return;
}
that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC
if (!that.injector.isPlaying) { // Subtle: a looping sound will not check playbackGap.
if (repeat()) { // WAITING => PLAYING
// Setup next play just once, now. Changes won't be looked at while we wait.
that.playAfter = randomizedNextPlay();
// Subtle: if the restart fails b/c we're at injector limit, we won't try again until next playAfter.
that.injector.restart();
} else { // PLAYING => NO DATA
that.playAfter = Infinity; // was one-shot and we're finished
}
}
};
}
function internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar) {
ignore(avatarPosition, avatar); // We could use avatars and/or avatarPositions to prioritize which ones to play.
var entitySound = entityCache[entityIdentifier];
if (!entitySound) {
entitySound = entityCache[entityIdentifier] = new EntityDatum(entityIdentifier);
}
Script.setTimeout(function() {
avatarPositions.forEach(function(avatarPosition) {
var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS);
handleFoundSoundEntities(entities);
entitySound.timestamp = timestamp; // Might be updated for multiple avatars. That's fine.
}
var nUpdates = UPDATES_PER_STATS_LOG, lastStats = Date.now();
function updateAllEntityData() { // A fast update of all entities we know about. A few make sounds.
var now = Date.now(),
expirationCutoff = now - EXPIRATION_TIME,
userDataRecheckCutoff = now - RECHECK_TIME;
Object.keys(entityCache).forEach(function (entityIdentifier) {
entityCache[entityIdentifier].update(expirationCutoff, userDataRecheckCutoff, now);
});
if (nUpdates-- <= 0) { // Report statistics.
// For example, with:
// injector-limit = 40 (in C++ code)
// N_SOUNDS = 1000 (from userData in, e.g., acAudioSearchCompatibleEntitySpawner.js)
// replay-period = 3 + 20 = 23 (seconds, ditto)
// stats-period = UPDATES_PER_STATS_LOG * UPDATE_TIME / MSEC_PER_SEC = 10 seconds
// The log should show between each stats report:
// "start" lines ~= injector-limit * P(finish) = injector-limit * stats-period/replay-period = 17 ?
// total attempts at starting ("start" lines + "could not thread" lines) ~= N_SOUNDS = 1000 ?
// entities > N_SOUNDS * (1+ N_SILENT_ENTITIES_PER_SOUND) = 11000 + whatever was in the scene before running spawner
// sounds = N_SOUNDS = 1000
// getEntityPropertiesPerUpdate ~= playing + failed-starts/UPDATES_PER_STATS_LOG + other-rechecks-each-update
// = injector-limit + (total attempts - "start" lines)/UPDATES_PER_STATS__LOG
// + (entities - playing - failed-starts/UPDATES_PER_STATS_LOG) * P(recheck-in-update)
// where failed-starts/UPDATES_PER_STATS_LOG = (1000-17)/100 = 10
// = 40 + 10 + (11000 - 40 - 10)*UPDATE_TIME/RECHECK_TIME
// = 40 + 10 + 10950*0.01 = 159 (mostly proportional to enties/RECHECK_TIME)
// millisecondsPerUpdate ~= UPDATE_TIME = 100 (+ some timer machinery time)
// this assignment client activity monitor < 100% cpu
var stats = {
entities: 0,
sounds: 0,
playing: 0,
getEntityPropertiesPerUpdate: examinationCount / UPDATES_PER_STATS_LOG,
millisecondsPerUpdate: (now - lastStats) / UPDATES_PER_STATS_LOG
};
nUpdates = UPDATES_PER_STATS_LOG;
lastStats = now;
examinationCount = 0;
Object.keys(entityCache).forEach(function (entityIdentifier) {
var datum = entityCache[entityIdentifier];
stats.entities++;
if (datum.url) {
stats.sounds++;
if (datum.injector && datum.injector.isPlaying) {
stats.playing++;
}
}
});
//Now wipe list for next query;
avatarPositions = [];
}, UPDATE_TIME);
handleActiveSoundEntities();
}
function handleActiveSoundEntities() {
// Go through all our sound entities, if they have passed expiration time, remove them from map
for (var potentialSoundEntity in soundEntityMap) {
if (!soundEntityMap.hasOwnProperty(potentialSoundEntity)) {
// The current property is not a direct property of soundEntityMap so ignore it
continue;
}
var soundEntity = potentialSoundEntity;
var soundProperties = soundEntityMap[soundEntity];
soundProperties.timeWithoutAvatarInRange += UPDATE_TIME;
if (soundProperties.timeWithoutAvatarInRange > EXPIRATION_TIME && soundProperties.soundInjector) {
// An avatar hasn't been within range of this sound entity recently, so remove it from map
soundProperties.soundInjector.stop();
delete soundEntityMap[soundEntity];
} else if (soundProperties.isDownloaded) {
// If this sound hasn't expired yet, we want to potentially play it!
if (soundProperties.readyToPlay) {
var newPosition = Entities.getEntityProperties(soundEntity, "position").position;
if (!soundProperties.soundInjector) {
soundProperties.soundInjector = Audio.playSound(soundProperties.sound, {
volume: soundProperties.volume,
position: newPosition,
loop: soundProperties.loop
});
} else {
soundProperties.soundInjector.restart();
}
soundProperties.readyToPlay = false;
} else if (soundProperties.sound && soundProperties.loop === false) {
// We need to check all of our entities that are not looping but have an interval associated with them
// to see if it's time for them to play again
soundProperties.timeSinceLastPlay += UPDATE_TIME;
if (soundProperties.timeSinceLastPlay > soundProperties.clipDuration + soundProperties.currentPlaybackGap) {
soundProperties.readyToPlay = true;
soundProperties.timeSinceLastPlay = 0;
// Now let's get our new current interval
soundProperties.currentPlaybackGap = soundProperties.playbackGap + randFloat(-soundProperties.playbackGapRange, soundProperties.playbackGapRange);
soundProperties.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, soundProperties.currentPlaybackGap);
}
}
}
print(JSON.stringify(stats));
}
}
function handleFoundSoundEntities(entities) {
entities.forEach(function(entity) {
var soundData = getEntityCustomData(SOUND_DATA_KEY, entity);
if (soundData && soundData.url) {
//check sound entities list- if it's not in, add it
if (!soundEntityMap[entity]) {
var soundProperties = {
url: soundData.url,
volume: soundData.volume || DEFAULT_SOUND_DATA.volume,
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
playbackGap: soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap,
playbackGapRange: soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange,
readyToPlay: false,
position: Entities.getEntityProperties(entity, "position").position,
timeSinceLastPlay: 0,
timeWithoutAvatarInRange: 0,
isDownloaded: false
};
soundProperties.currentPlaybackGap = soundProperties.playbackGap + randFloat(-soundProperties.playbackGapRange, soundProperties.playbackGapRange);
soundProperties.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, soundProperties.currentPlaybackGap);
soundEntityMap[entity] = soundProperties;
if (!soundUrls[soundData.url]) {
// We need to download sound before we add it to our map
var sound = SoundCache.getSound(soundData.url);
// Only add it to map once it's downloaded
soundUrls[soundData.url] = sound;
sound.ready.connect(function() {
soundProperties.sound = sound;
soundProperties.readyToPlay = true;
soundProperties.isDownloaded = true;
soundProperties.clipDuration = sound.duration * 1000;
soundEntityMap[entity] = soundProperties;
});
} else {
// We already have sound downloaded, so just add it to map right away
soundProperties.sound = soundUrls[soundData.url];
soundProperties.clipDuration = soundProperties.sound.duration * 1000;
soundProperties.readyToPlay = true;
soundProperties.isDownloaded = true;
soundEntityMap[entity] = soundProperties;
}
} else {
//If this sound is in our map already, we want to reset timeWithoutAvatarInRange
// Also we want to check to see if the entity has been updated with new sound data- if so we want to update!
soundEntityMap[entity].timeWithoutAvatarInRange = 0;
checkForSoundPropertyChanges(soundEntityMap[entity], soundData);
}
}
// Update the set of which EntityData we know about.
//
function updateEntiesForAvatar(avatarIdentifier) { // Just one piece of update work.
// This does at most:
// one queryOctree request of the entity server, and
// one findEntities geometry query of our own octree, and
// a quick internEntityDatum of each of what may be a large number of entityIdentifiers.
// The idea is that this is a nice bounded piece of work that should not be done too frequently.
// However, it means that we won't learn about new entities until, on average (nAvatars * UPDATE_TIME) + query round trip.
var avatar = AvatarList.getAvatar(avatarIdentifier), avatarPosition = avatar && avatar.position;
if (!avatarPosition) { // No longer here.
return;
}
var timestamp = Date.now();
EntityViewer.setPosition(avatarPosition);
EntityViewer.queryOctree(); // Requests an update, but there's no telling when we'll actually see different results.
var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS);
debug("found", entities.length, "entities near", avatar.name || "unknown", "at", avatarPosition);
entities.forEach(function (entityIdentifier) {
internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar);
});
}
function checkForSoundPropertyChanges(currentProps, newProps) {
var needsNewInjector = false;
if (currentProps.playbackGap !== newProps.playbackGap && !currentProps.loop) {
// playbackGap only applies to non looping sounds
currentProps.playbackGap = newProps.playbackGap;
currentProps.currentPlaybackGap = currentProps.playbackGap + randFloat(-currentProps.playbackGapRange, currentProps.playbackGapRange);
currentProps.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, currentProps.currentPlaybackGap);
currentProps.readyToPlay = true;
}
if (currentProps.playbackGapRange !== currentProps.playbackGapRange) {
currentProps.playbackGapRange = newProps.playbackGapRange;
currentProps.currentPlaybackGap = currentProps.playbackGap + randFloat(-currentProps.playbackGapRange, currentProps.playbackGapRange);
currentProps.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, currentProps.currentPlaybackGap);
currentProps.readyToPlay = true;
}
if (currentProps.volume !== newProps.volume) {
currentProps.volume = newProps.volume;
needsNewInjector = true;
}
if (currentProps.url !== newProps.url) {
currentProps.url = newProps.url;
currentProps.sound = null;
if (!soundUrls[currentProps.url]) {
var sound = SoundCache.getSound(currentProps.url);
currentProps.isDownloaded = false;
sound.ready.connect(function() {
currentProps.sound = sound;
currentProps.clipDuration = sound.duration * 1000;
currentProps.isDownloaded = true;
});
} else {
currentProps.sound = sound;
currentProps.clipDuration = sound.duration * 1000;
}
needsNewInjector = true;
}
if (currentProps.loop !== newProps.loop) {
currentProps.loop = newProps.loop;
needsNewInjector = true;
}
if (needsNewInjector) {
// If we were looping we need to stop that so new changes are applied
currentProps.soundInjector.stop();
currentProps.soundInjector = null;
currentProps.readyToPlay = true;
}
// Slowly update the set of data we have to work with.
//
var workQueue = [];
function updateWorkQueueForAvatarsPresent() { // when nothing else to do, fill queue with individual avatar updates
workQueue = AvatarList.getAvatarIdentifiers().map(function (avatarIdentifier) {
return function () {
updateEntiesForAvatar(avatarIdentifier);
};
});
}
Script.setInterval(update, UPDATE_TIME);
Script.setInterval(function () {
// There might be thousands of EntityData known to us, but only a few will require any work to update.
updateAllEntityData(); // i.e., this better be pretty fast.
// Each interval, we do no more than one updateEntitiesforAvatar.
if (!workQueue.length) {
workQueue = [updateWorkQueueForAvatarsPresent];
}
workQueue.pop()(); // There's always one
}, UPDATE_TIME);

View file

@ -1,4 +1,6 @@
//
"use strict";
/*jslint nomen: true, plusplus: true, vars: true*/
/*global Entities, Script, Quat, Vec3, Camera, MyAvatar, print, randFloat*/
// acAudioSearchCompatibleEntitySpawner.js
// audio/acAudioSearching
//
@ -13,6 +15,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var N_SOUNDS = 1000;
var N_SILENT_ENTITIES_PER_SOUND = 10;
var ADD_PERIOD = 50; // ms between adding 1 sound + N_SILENT_ENTITIES_PER_SOUND, to not overrun entity server.
var SPATIAL_DISTRIBUTION = 10; // meters spread over how far to randomly distribute enties.
Script.include("../../libraries/utils.js");
var orientation = Camera.getOrientation();
orientation = Quat.safeEulerAngles(orientation);
@ -20,20 +26,21 @@ orientation.x = 0;
orientation = Quat.fromVec3Degrees(orientation);
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
// http://hifi-public.s3.amazonaws.com/ryan/demo/0619_Fireplace__Tree_B.L.wav
var SOUND_DATA_KEY = "soundKey";
var SOUND_DATA_KEY = "io.highfidelity.soundKey";
var userData = {
soundKey: {
url: "http://hifi-content.s3.amazonaws.com/DomainContent/Junkyard/Sounds/ClothSail/cloth_sail3.L.wav",
volume: 0.3,
loop: false,
playbackGap: 2000, // In ms - time to wait in between clip plays
playbackGapRange: 500 // In ms - the range to wait in between clip plays
playbackGap: 20000, // In ms - time to wait in between clip plays
playbackGapRange: 5000 // In ms - the range to wait in between clip plays
}
}
};
var userDataString = JSON.stringify(userData);
var entityProps = {
type: "Box",
position: center,
name: 'audioSearchEntity',
color: {
red: 200,
green: 10,
@ -43,15 +50,41 @@ var entityProps = {
x: 0.1,
y: 0.1,
z: 0.1
},
userData: JSON.stringify(userData)
}
};
var entities = [], nSounds = 0;
Script.include("../../libraries/utils.js");
function addOneSet() {
function randomizeDimension(coordinate) {
return coordinate + randFloat(-SPATIAL_DISTRIBUTION / 2, SPATIAL_DISTRIBUTION / 2);
}
function randomize() {
return {x: randomizeDimension(center.x), y: randomizeDimension(center.y), z: randomizeDimension(center.z)};
}
function addOne() {
entityProps.position = randomize();
entities.push(Entities.addEntity(entityProps));
}
var i;
entityProps.userData = userDataString;
entityProps.color.red = 200;
addOne();
delete entityProps.userData;
entityProps.color.red = 10;
for (i = 0; i < N_SILENT_ENTITIES_PER_SOUND; i++) {
addOne();
}
if (++nSounds < N_SOUNDS) {
Script.setTimeout(addOneSet, ADD_PERIOD);
}
}
var soundEntity = Entities.addEntity(entityProps);
addOneSet();
function cleanup() {
Entities.deleteEntity(soundEntity);
entities.forEach(Entities.deleteEntity);
}
// In console:
// Entities.findEntities(MyAvatar.position, 100).forEach(function (id) { if (Entities.getEntityProperties(id).name === 'audioSearchEntity') Entities.deleteEntity(id); })
Script.scriptEnding.connect(cleanup);
Script.scriptEnding.connect(cleanup);

View file

@ -171,9 +171,8 @@ var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar";
var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic";
var HEART_BEAT_INTERVAL = 5; // seconds
var HEART_BEAT_TIMEOUT = 15;
var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC;
var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC;
function stateToName(state) {
switch (state) {

View file

@ -11,6 +11,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var leftTriggerValue = 0;
var rightTriggerValue = 0;
var LEAP_TRIGGER_START_ANGLE = 15.0;
var LEAP_TRIGGER_END_ANGLE = 40.0;
function getLeapMotionLeftTrigger() {
//print("left trigger = " + leftTriggerValue);
return leftTriggerValue;
}
function getLeapMotionRightTrigger() {
//print("right trigger = " + rightTriggerValue);
return rightTriggerValue;
}
var leapHands = (function () {
var isOnHMD,
@ -61,78 +76,6 @@ var leapHands = (function () {
print(i + ": " + jointNames[i]);
}
print("... skeleton joint names");
/*
http://public.highfidelity.io/models/skeletons/ron_standing.fst
Skeleton joint names ...
0: Hips
1: RightUpLeg
2: RightLeg
3: RightFoot
4: RightToeBase
5: RightToe_End
6: LeftUpLeg
7: LeftLeg
8: LeftFoot
9: LeftToeBase
10: LeftToe_End
11: Spine
12: Spine1
13: Spine2
14: RightShoulder
15: RightArm
16: RightForeArm
17: RightHand
18: RightHandPinky1
19: RightHandPinky2
20: RightHandPinky3
21: RightHandPinky4
22: RightHandRing1
23: RightHandRing2
24: RightHandRing3
25: RightHandRing4
26: RightHandMiddle1
27: RightHandMiddle2
28: RightHandMiddle3
29: RightHandMiddle4
30: RightHandIndex1
31: RightHandIndex2
32: RightHandIndex3
33: RightHandIndex4
34: RightHandThumb1
35: RightHandThumb2
36: RightHandThumb3
37: RightHandThumb4
38: LeftShoulder
39: LeftArm
40: LeftForeArm
41: LeftHand
42: LeftHandPinky1
43: LeftHandPinky2
44: LeftHandPinky3
45: LeftHandPinky4
46: LeftHandRing1
47: LeftHandRing2
48: LeftHandRing3
49: LeftHandRing4
50: LeftHandMiddle1
51: LeftHandMiddle2
52: LeftHandMiddle3
53: LeftHandMiddle4
54: LeftHandIndex1
55: LeftHandIndex2
56: LeftHandIndex3
57: LeftHandIndex4
58: LeftHandThumb1
59: LeftHandThumb2
60: LeftHandThumb3
61: LeftHandThumb4
62: Neck
63: Head
64: HeadTop_End
65: body
... skeleton joint names
*/
}
function animateLeftHand() {
@ -357,6 +300,13 @@ var leapHands = (function () {
settingsTimer = Script.setInterval(checkSettings, 2000);
calibrationStatus = UNCALIBRATED;
{
var mapping = Controller.newMapping("LeapmotionTrigger");
mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT);
mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT);
mapping.enable();
}
}
function moveHands() {
@ -469,10 +419,17 @@ var leapHands = (function () {
hands[h].rotation = handRotation;
// Set finger joints ...
var summed = 0;
var closeAngle = 0;
for (i = 0; i < NUM_FINGERS; i += 1) {
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
if (fingers[h][i][j].controller !== null) {
locRotation = fingers[h][i][j].controller.getLocRotation();
var eulers = Quat.safeEulerAngles(locRotation);
closeAngle += eulers.x;
summed++;
if (i === THUMB) {
locRotation = {
x: side * locRotation.y,
@ -496,8 +453,21 @@ var leapHands = (function () {
}
}
}
hands[h].inactiveCount = 0;
if (summed > 0) {
closeAngle /= summed;
}
var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE);
triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0));
if (h == 0) {
leftTriggerValue = triggerValue;
} else {
rightTriggerValue = triggerValue;
}
} else {
@ -509,6 +479,8 @@ var leapHands = (function () {
if (handAnimationStateHandlers[h] !== null) {
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
handAnimationStateHandlers[h] = null;
leftTriggerValue = 0.0;
rightTriggerValue = 0.0;
}
}
}

View file

@ -72,10 +72,13 @@
};
}
function createEmitNumberPropertyUpdateFunction(propertyName) {
function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
decimals = decimals == undefined ? 4 : decimals;
return function() {
var value = parseFloat(this.value).toFixed(decimals);
EventBridge.emitWebEvent(
'{ "type":"update", "properties":{"' + propertyName + '":' + parseFloat(this.value).toFixed(4) + '}}'
'{ "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
);
};
}
@ -323,6 +326,7 @@
var elLightColorBlue = document.getElementById("property-light-color-blue");
var elLightIntensity = document.getElementById("property-light-intensity");
var elLightFalloffRadius = document.getElementById("property-light-falloff-radius");
var elLightExponent = document.getElementById("property-light-exponent");
var elLightCutoff = document.getElementById("property-light-cutoff");
@ -604,9 +608,10 @@
elLightColorGreen.value = properties.color.green;
elLightColorBlue.value = properties.color.blue;
elLightIntensity.value = properties.intensity;
elLightExponent.value = properties.exponent;
elLightCutoff.value = properties.cutoff;
elLightIntensity.value = properties.intensity.toFixed(1);
elLightFalloffRadius.value = properties.falloffRadius.toFixed(1);
elLightExponent.value = properties.exponent.toFixed(2);
elLightCutoff.value = properties.cutoff.toFixed(2);
} else if (properties.type == "Zone") {
for (var i = 0; i < elZoneSections.length; i++) {
elZoneSections[i].style.display = 'block';
@ -795,9 +800,10 @@
}
})
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity'));
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1));
elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1));
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2));
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2));
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
@ -1043,25 +1049,25 @@
<div class="text-section property">
<div class="label">Line Height</div>
<div class="value">
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
<input class="coord" type="number" id="property-text-line-height" min="0" step="0.005">
</div>
</div>
<div class="text-section property">
<div class="label">Text Color</div>
<div class="value">
<div class='color-picker' id="property-text-text-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-text-text-color-blue"></div>
</div>
</div>
<div class="text-section property">
<div class="label">Background Color</div>
<div class="value">
<div class='color-picker' id="property-text-background-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-text-background-color-blue"></div>
</div>
</div>
@ -1085,32 +1091,32 @@
<div class="label">Light Color</div>
<div class="value">
<div class='color-picker' id="property-zone-key-light-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
<div class="input-area">R <input class="coord" type="number" id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
<div class="input-area">G <input class="coord" type="number" id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
<div class="input-area">B <input class="coord" type="number" id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Light Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1">
<input class="coord" type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1">
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Light Direction</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-zone-key-light-direction-x"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-zone-key-light-direction-y"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-zone-key-light-direction-z"></div>
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Ambient Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
<input class="coord" type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
</div>
</div>
@ -1129,19 +1135,19 @@
<div class="zone-section stage-section property">
<div class="label">Stage Latitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1">
<input class="coord" type="number" id="property-zone-stage-latitude" min="-90" max="90" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Longitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1">
<input class="coord" type="number" id="property-zone-stage-longitude" min="-180" max="180" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Altitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-altitude" step="1">
<input class="coord" type="number" id="property-zone-stage-altitude" step="1">
</div>
</div>
@ -1155,13 +1161,13 @@
<div class="zone-section stage-section property">
<div class="label">Stage Day</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1">
<input class="coord" type="number" id="property-zone-stage-day" min="0" max="365" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Hour</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5">
<input class="coord" type="number" id="property-zone-stage-hour" min="0" max="24" step="0.5">
</div>
</div>
@ -1186,9 +1192,9 @@
<div class="label">Skybox Color</div>
<div class="value">
<div class='color-picker' id="property-zone-skybox-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-zone-skybox-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-zone-skybox-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-zone-skybox-color-blue"></div>
</div>
</div>
@ -1264,23 +1270,23 @@
<div class="property">
<div class="label">Registration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-reg-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-reg-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-reg-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Dimensions</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
<div>
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
</div>
<div class="input-area">
<input class="" type='number' id="dimension-rescale-pct" value=100>%
<input class="" type="number" id="dimension-rescale-pct" value=100>%
</div>
<span>
<input type="button" id="dimension-rescale-button" value="Rescale">
@ -1291,9 +1297,9 @@
<div class="poly-vox-section property">
<div class="label">Voxel Volume Size</div>
<div class="value">
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></div>
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></div>
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></div>
<div class="input-area">X <br> <input class="coord" type="number" id="property-voxel-volume-size-x"></div>
<div class="input-area">Y <br><input class="coord" type="number" id="property-voxel-volume-size-y"></div>
<div class="input-area">Z <br><input class="coord" type="number" id="property-voxel-volume-size-z"></div>
</div>
<div class="label">Surface Extractor</div>
@ -1325,9 +1331,9 @@
<div class="property">
<div class="label">Rotation</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x" step="0.1"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y" step="0.1"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"step="0.1"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-rot-x" step="0.1"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-rot-y" step="0.1"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-rot-z"step="0.1"></div>
</div>
</div>
@ -1339,66 +1345,66 @@
<div class="property">
<div class="label">Linear Velocity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-lvel-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-lvel-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-lvel-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Linear Damping</div>
<div class="value">
<input class="coord" type='number' id="property-ldamping">
<input class="coord" type="number" id="property-ldamping">
</div>
</div>
<div class="property">
<div class="label">Angular Velocity</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-avel-x"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-avel-y"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-avel-z"></div>
</div>
</div>
<div class="property">
<div class="label">Angular Damping</div>
<div class="value">
<input class="coord" type='number' id="property-adamping">
<input class="coord" type="number" id="property-adamping">
</div>
</div>
<div class="property">
<div class="label">Restitution</div>
<div class="value">
<input class="coord" type='number' id="property-restitution">
<input class="coord" type="number" id="property-restitution">
</div>
</div>
<div class="property">
<div class="label">Friction</div>
<div class="value">
<input class="coord" type='number' id="property-friction">
<input class="coord" type="number" id="property-friction">
</div>
</div>
<div class="property">
<div class="label">Gravity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-grav-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-grav-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-grav-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Acceleration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-lacc-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-lacc-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-lacc-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Density</div>
<div>
<input type='number' id="property-density">
<input type="number" id="property-density">
</div>
</div>
@ -1406,9 +1412,9 @@
<div class="label">Color</div>
<div class="value">
<div id="property-color" class='color-picker'></div>
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-color-blue"></div>
</div>
</div>
@ -1483,7 +1489,7 @@
<div class="property">
<div class="label">Lifetime</div>
<div class="value">
<input type='number' id="property-lifetime">
<input type="number" id="property-lifetime">
</div>
</div>
@ -1541,25 +1547,25 @@
<div class="model-section property">
<div class="label">Animation FPS</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-fps">
<input class="coord" type="number" id="property-model-animation-fps">
</div>
</div>
<div class="model-section property">
<div class="label">Animation Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-frame">
<input class="coord" type="number" id="property-model-animation-frame">
</div>
</div>
<div class="model-section property">
<div class="label">Animation First Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-first-frame">
<input class="coord" type="number" id="property-model-animation-first-frame">
</div>
</div>
<div class="model-section property">
<div class="label">Animation Last Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-last-frame">
<input class="coord" type="number" id="property-model-animation-last-frame">
</div>
</div>
<div class="model-section property">
@ -1591,37 +1597,43 @@
<label>Light</label>
</div>
<div class="light-section property">
<div class="label">Color</div>
<div class="value">
<div class='color-picker' id="property-light-color"></div>
<div class="input-area">R <input class="coord" type="number" id="property-light-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-light-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-light-color-blue"></div>
</div>
</div>
<div class="light-section property">
<div class="label">Intensity</div>
<div class="value">
<input class="coord" type="number" id="property-light-intensity" min="0" step="0.1">
</div>
</div>
<div class="light-section property">
<div class="label">Falloff Radius</div>
<div class="value">
<input class="coord" type="number" id="property-light-falloff-radius" min="0" step="0.1">
</div>
</div>
<div class="light-section property">
<span class="label">Spot Light</span>
<span class="value">
<input type='checkbox' id="property-light-spot-light">
</span>
</div>
<div class="light-section property">
<div class="label">Color</div>
<div class="value">
<div class='color-picker' id="property-light-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></div>
</div>
</div>
<div class="light-section property">
<div class="label">Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-light-intensity">
</div>
</div>
<div class="light-section property">
<div class="label">Spot Light Exponent</div>
<div class="value">
<input class="coord" type='number' id="property-light-exponent">
<input class="coord" type="number" id="property-light-exponent" step="0.01">
</div>
</div>
<div class="light-section property">
<div class="label">Spot Light Cutoff (degrees)</div>
<div class="value">
<input class="coord" type='number' id="property-light-cutoff">
<input class="coord" type="number" id="property-light-cutoff" step="0.01">
</div>
</div>
</div>

View file

@ -425,11 +425,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
_lastFaceTrackerUpdate(0)
{
// FIXME this may be excessivly conservative. On the other hand
// FIXME this may be excessivly conservative. On the other hand
// maybe I'm used to having an 8-core machine
// Perhaps find the ideal thread count and subtract 2 or 3
// Perhaps find the ideal thread count and subtract 2 or 3
// (main thread, present thread, random OS load)
// More threads == faster concurrent loads, but also more concurrent
// More threads == faster concurrent loads, but also more concurrent
// load on the GPU until we can serialize GPU transfers (off the main thread)
QThreadPool::globalInstance()->setMaxThreadCount(2);
thread()->setPriority(QThread::HighPriority);
@ -560,7 +560,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
connect(&locationUpdateTimer, &QTimer::timeout,
connect(&locationUpdateTimer, &QTimer::timeout,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
@ -604,7 +604,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
// Save avatar location immediately after a teleport.
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
@ -625,7 +625,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
getEntities()->reloadEntityScripts();
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptLoadError,
connect(scriptEngines, &ScriptEngines::scriptLoadError,
scriptEngines, [](const QString& filename, const QString& error){
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
@ -975,7 +975,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
disconnect(_idleTimer);
});
// Setting the interval to zero forces this to get called whenever there are no messages
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// of logic to abort early if it's being called too often.
_idleTimer->start(0);
}
@ -1023,7 +1023,7 @@ void Application::cleanupBeforeQuit() {
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
DependencyManager::get<ScriptEngines>()->saveScripts();
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ScriptEngines>();
// first stop all timers directly or by invokeMethod
// depending on what thread they run in
@ -1212,10 +1212,10 @@ void Application::initializeUi() {
setupPreferences();
// For some reason there is already an "Application" object in the QML context,
// For some reason there is already an "Application" object in the QML context,
// though I can't find it. Hence, "ApplicationInterface"
rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader());
rootContext->setContextProperty("ApplicationInterface", this);
rootContext->setContextProperty("ApplicationInterface", this);
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
@ -2435,7 +2435,7 @@ void Application::idle(uint64_t now) {
if (_aboutToQuit) {
return; // bail early, nothing to do here.
}
auto displayPlugin = getActiveDisplayPlugin();
// depending on whether we're throttling or not.
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
@ -2459,7 +2459,7 @@ void Application::idle(uint64_t now) {
// Nested ifs are for clarity in the logic. Don't collapse them into a giant single if.
// Don't saturate the main thread with rendering, no paint calls until the last one is complete
if (!_pendingPaint) {
// Also no paint calls until the display plugin has increased by at least one frame
// Also no paint calls until the display plugin has increased by at least one frame
// (don't render at 90fps if the display plugin only goes at 60)
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
// Record what present frame we're on
@ -2469,14 +2469,14 @@ void Application::idle(uint64_t now) {
// But when we DO request a paint, get to it as soon as possible: high priority
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
// For the rest of idle, we want to cap at the max sim rate, so we might not call
}
// For the rest of idle, we want to cap at the max sim rate, so we might not call
// the remaining idle work every paint frame, or vice versa
// In theory this means we could call idle processing more often than painting,
// but in practice, when the paintGL calls aren't keeping up, there's no room left
// in the main thread to call idle more often than paint.
// This check is mostly to keep idle from burning up CPU cycles by running at
// This check is mostly to keep idle from burning up CPU cycles by running at
// hundreds of idles per second when the rendering is that fast
if ((timeSinceLastUpdateUs / USECS_PER_MSEC) < CAPPED_SIM_FRAME_PERIOD_MS) {
// No paint this round, but might be time for a new idle, otherwise return
@ -3387,7 +3387,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@ -3423,9 +3423,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.y * TREE_SCALE,
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
rootDetails.s * TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) {
inViewServers++;
}
}
@ -3491,12 +3489,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.s * TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds);
} else {
if (wantExtraDebugging) {
qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
@ -4752,7 +4745,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
groupingMenu = "Developer";
break;
default:
groupingMenu = "Standard";
groupingMenu = "Standard";
break;
}

View file

@ -136,7 +136,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
AABox Avatar::getBounds() const {
// Our skeleton models are rigged, and this method call safely produces the static bounds of the model.
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
// and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the
// real model bounds, if this is unrealistically small.
if (!_skeletonModel.isRenderable()) {
@ -188,15 +188,14 @@ void Avatar::simulate(float deltaTime) {
// simple frustum check
float boundingRadius = getBoundingRadius();
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
ViewFrustum::OUTSIDE;
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
{
PerformanceTimer perfTimer("hand");
getHand()->simulate(deltaTime, false);
}
if (_shouldAnimate && !_shouldSkipRender && inViewFrustum) {
if (_shouldAnimate && !_shouldSkipRender && inView) {
{
PerformanceTimer perfTimer("skeleton");
_skeletonModel.getRig()->copyJointsFromJointData(_jointData);
@ -401,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
frustum = qApp->getDisplayViewFrustum();
}
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
if (frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
endRender();
return;
}
@ -430,6 +429,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
// add local lights
const float BASE_LIGHT_DISTANCE = 2.0f;
const float LIGHT_FALLOFF_RADIUS = 0.01f;
const float LIGHT_EXPONENT = 1.0f;
const float LIGHT_CUTOFF = glm::radians(80.0f);
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
@ -438,7 +438,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
glm::vec3 direction = orientation * light.direction;
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
}
}
@ -516,7 +516,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
auto& frustum = *renderArgs->_viewFrustum;
auto textPosition = getDisplayNamePosition();
if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) {
if (frustum.pointIntersectsFrustum(textPosition)) {
renderDisplayName(batch, frustum, textPosition);
}
}
@ -669,10 +669,10 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
return namePosition;
}
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const {
Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE,
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const {
Q_ASSERT_X(view.pointIntersectsFrustum(textPosition),
"Avatar::calculateDisplayNameTransform", "Text not in viewfrustum.");
glm::vec3 toFrustum = frustum.getPosition() - textPosition;
glm::vec3 toFrustum = view.getPosition() - textPosition;
// Compute orientation
// If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees
@ -694,7 +694,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons
return result;
}
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const {
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
@ -702,7 +702,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
static const float CLIP_DISTANCE = 0.2f;
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|| (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) {
return;
}
auto renderer = textRenderer(DISPLAYNAME);
@ -743,7 +743,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
(_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA);
// Compute display name transform
auto textTransform = calculateDisplayNameTransform(frustum, textPosition);
auto textTransform = calculateDisplayNameTransform(view, textPosition);
// Test on extent above insures abs(height) > 0.0f
textTransform.postScale(1.0f / height);
batch.setModelTransform(textTransform);

View file

@ -231,8 +231,8 @@ protected:
float getPelvisFloatingHeight() const;
glm::vec3 getDisplayNamePosition() const;
Transform calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const;
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const;
Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const;
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const;
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f);
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
virtual void fixupModelsInScene();

View file

@ -656,7 +656,7 @@ void MyAvatar::saveData() {
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("snapTurn", _useSnapTurn);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.endGroup();
}
@ -750,7 +750,7 @@ void MyAvatar::loadData() {
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("snapTurn").toBool());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
settings.endGroup();

View file

@ -403,6 +403,22 @@ glm::vec2 ApplicationCompositor::getReticlePosition() const {
return toGlm(QCursor::pos());
}
bool ApplicationCompositor::getReticleOverDesktop() const {
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
// but... if we're outside of the overlay area, we also want to call ourselves
// as being over the desktop.
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);
glm::vec2 maxOverlayPosition = qApp->getUiSize();
if (_reticlePositionInHMD.x < 0 || _reticlePositionInHMD.y < 0 ||
_reticlePositionInHMD.x > maxOverlayPosition.x || _reticlePositionInHMD.y > maxOverlayPosition.y) {
return true; // we are outside the overlay area, consider ourselves over the desktop
}
}
return _isOverDesktop;
}
void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFakeEvent) {
if (qApp->isHMDMode()) {
QMutexLocker locker(&_reticleLock);

View file

@ -107,7 +107,7 @@ public:
bool shouldCaptureMouse() const;
/// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false
bool getReticleOverDesktop() const { return _isOverDesktop; }
bool getReticleOverDesktop() const;
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
private:

View file

@ -36,15 +36,16 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
glm::vec3 color = toGlm(getXColor());
float intensity = getIntensity();
float falloffRadius = getFalloffRadius();
float exponent = getExponent();
float cutoff = glm::radians(getCutoff());
if (_isSpotlight) {
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position, largestDiameter / 2.0f,
color, intensity, rotation, exponent, cutoff);
color, intensity, falloffRadius, rotation, exponent, cutoff);
} else {
DependencyManager::get<DeferredLightingEffect>()->addPointLight(position, largestDiameter / 2.0f,
color, intensity);
color, intensity, falloffRadius);
}
#ifdef WANT_DEBUG

View file

@ -245,6 +245,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic);
CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight);
CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity);
CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius);
CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent);
CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff);
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
@ -445,6 +446,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Light) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff);
}
@ -597,6 +599,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic);
COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight);
COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius);
COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff);
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
@ -762,6 +765,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused);
ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool);
ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float);
ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float);
ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float);
ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float);
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
@ -1043,6 +1047,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight());
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity());
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius());
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent());
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff());
}
@ -1332,6 +1337,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
}
@ -1477,6 +1483,7 @@ void EntityItemProperties::markAllChanged() {
_dynamicChanged = true;
_intensityChanged = true;
_falloffRadiusChanged = true;
_exponentChanged = true;
_cutoffChanged = true;
_lockedChanged = true;
@ -1719,6 +1726,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (intensityChanged()) {
out += "intensity";
}
if (falloffRadiusChanged()) {
out += "falloffRadius";
}
if (exponentChanged()) {
out += "exponent";
}

View file

@ -35,6 +35,7 @@
#include "EntityItemPropertiesMacros.h"
#include "EntityTypes.h"
#include "EntityPropertyFlags.h"
#include "LightEntityItem.h"
#include "LineEntityItem.h"
#include "ParticleEffectEntityItem.h"
#include "PolyVoxEntityItem.h"
@ -129,10 +130,11 @@ public:
DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS);
DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t, ENTITY_COLLISION_MASK_DEFAULT);
DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC);
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, false);
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, 1.0f);
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, 0.0f);
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, ENTITY_ITEM_DEFAULT_CUTOFF);
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT);
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY);
DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS);
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT);
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF);
DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED);
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, "");
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA);
@ -359,6 +361,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Dynamic, dynamic, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, IsSpotlight, isSpotlight, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Intensity, intensity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FalloffRadius, falloffRadius, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Exponent, exponent, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cutoff, cutoff, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");

View file

@ -72,8 +72,6 @@ const bool ENTITY_ITEM_DEFAULT_COLLISIONLESS = false;
const bool ENTITY_ITEM_DEFAULT_DYNAMIC = false;
const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
const float ENTITY_ITEM_DEFAULT_CUTOFF = PI / 2;
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -167,6 +167,8 @@ enum EntityPropertyList {
PROP_COLLISION_MASK, // one byte of collision group flags
PROP_FALLOFF_RADIUS, // for Light entity
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties to end of list just ABOVE this line
PROP_AFTER_LAST_ITEM,

View file

@ -85,7 +85,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
forEachEntity([&](EntityItemPointer entity) {
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
});
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
@ -96,39 +96,39 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
// If we haven't completely sent the child yet, then we should include it
return !childCompleted;
}
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
// the child data for this element
assert(false);
return false;
}
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
EntityTreeElementPointer childElement = getChildAtIndex(childIndex);
if (childElement->alreadyFullyEncoded(params)) {
return false;
}
return true; // if we don't know otherwise than recurse!
}
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
// If we know that ALL subtrees below us have already been recursed, then we don't
// If we know that ALL subtrees below us have already been recursed, then we don't
// need to recurse this child.
return entityTreeElementExtraEncodeData->subtreeCompleted;
}
@ -139,7 +139,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
if (childAppendState == OctreeElement::COMPLETED) {
@ -155,7 +155,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const {
const bool wantDebug = false;
if (wantDebug) {
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
}
@ -188,7 +188,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
// If we've encoding this element before... but we're coming back a second time in an attempt to
// encoud our parent... this might happen.
if (extraEncodeData->contains(childElement.get())) {
EntityTreeElementExtraEncodeData* childExtraEncodeData
EntityTreeElementExtraEncodeData* childExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement.get()));
if (wantDebug) {
@ -197,7 +197,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
}
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
if (wantDebug) {
qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
@ -217,24 +217,24 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
if (wantDebug) {
qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
if (thisExtraEncodeData->subtreeCompleted) {
qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
}
}
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
EncodeBitstreamParams& params) const {
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL;
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
QVector<uint16_t> indexesOfEntitiesToInclude;
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
for (uint16_t i = 0; i < _entityItems.size(); i++) {
@ -304,15 +304,13 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// frustum culling on rendering.
bool success;
AACube entityCube = entity->getQueryAACube(success);
if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) {
includeThisEntity = false; // out of view, don't include it
}
// Now check the size of the entity, it's possible that a "too small to see" entity is included in a
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
// before we consider including it.
if (includeThisEntity) {
} else {
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
// before we consider including it.
success = true;
// we can't cull a parent-entity by its dimensions because the child may be larger. we need to
// avoid sending details about a child but not the parent. the parent's queryAACube should have
@ -397,7 +395,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// this octree element.
if (extraEncodeData && entityTreeElementExtraEncodeData) {
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
// Only our parent can remove our extra data in these cases and only after it knows that all of its
// children have been encoded.
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
@ -412,7 +410,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
} else {
// If we weren't previously completed, check to see if we are
if (!entityTreeElementExtraEncodeData->elementCompleted) {
// If all of our items have been encoded, then we are complete as an element.
@ -426,9 +424,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
}
}
// Determine if no entities at all were able to fit
// Determine if no entities at all were able to fit
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
// If we wrote fewer entities than we expected, update the number of entities in our packet
bool successUpdateEntityCount = true;
if (numberOfEntities != actualNumberOfEntities) {
@ -504,7 +502,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
// If our child would be smaller than our smallest reasonable element, then we are the best fit.
float childScale = _cube.getScale() / 2.0f;
if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
@ -524,7 +522,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, void** intersectedObject, bool precisionPicking) {
keepSearching = true; // assume that we will continue searching after this.
@ -607,7 +605,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
localFace, localSurfaceNormal)) {
if (localDistance < distance) {
// now ask the entity if we actually intersect
@ -862,12 +860,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
return 0;
}
const unsigned char* dataAt = data;
int bytesRead = 0;
uint16_t numberOfEntities = 0;
int expectedBytesPerEntity = EntityItem::expectedBytes();
args.elementsPerPacket++;
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
@ -947,7 +945,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItem->recordCreationTime();
}
} else {
qDebug() << "Recieved packet for previously deleted entity [" <<
qDebug() << "Recieved packet for previously deleted entity [" <<
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
}
}
@ -959,7 +957,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
}
}
}
return bytesRead;
}
@ -990,7 +988,7 @@ bool EntityTreeElement::pruneChildren() {
bool somethingPruned = false;
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
EntityTreeElementPointer child = getChildAtIndex(childIndex);
// if my child is a leaf, but has no entities, then it's safe to delete my child
if (child && child->isLeaf() && !child->hasEntities()) {
deleteChildAtIndex(childIndex);
@ -1040,4 +1038,4 @@ void EntityTreeElement::debugDump() {
}
});
}

View file

@ -21,6 +21,12 @@
#include "EntityTreeElement.h"
#include "LightEntityItem.h"
const bool LightEntityItem::DEFAULT_IS_SPOTLIGHT = false;
const float LightEntityItem::DEFAULT_INTENSITY = 1.0f;
const float LightEntityItem::DEFAULT_FALLOFF_RADIUS = 0.1f;
const float LightEntityItem::DEFAULT_EXPONENT = 0.0f;
const float LightEntityItem::DEFAULT_CUTOFF = PI / 2.0f;
bool LightEntityItem::_lightsArePickable = false;
EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
@ -32,12 +38,7 @@ EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const E
// our non-pure virtual subclass for now...
LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
_type = EntityTypes::Light;
// default property values
_color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0;
_intensity = 1.0f;
_exponent = 0.0f;
_cutoff = PI;
}
void LightEntityItem::setDimensions(const glm::vec3& value) {
@ -62,10 +63,15 @@ EntityItemProperties LightEntityItem::getProperties(EntityPropertyFlags desiredP
COPY_ENTITY_PROPERTY_TO_PROPERTIES(intensity, getIntensity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(falloffRadius, getFalloffRadius);
return properties;
}
void LightEntityItem::setFalloffRadius(float value) {
_falloffRadius = glm::max(value, 0.0f);
}
void LightEntityItem::setIsSpotlight(bool value) {
if (value != _isSpotlight) {
_isSpotlight = value;
@ -101,6 +107,7 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(intensity, setIntensity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(falloffRadius, setFalloffRadius);
if (somethingChanged) {
bool wantDebug = false;
@ -150,6 +157,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_INTENSITY, float, setIntensity);
READ_ENTITY_PROPERTY(PROP_EXPONENT, float, setExponent);
READ_ENTITY_PROPERTY(PROP_CUTOFF, float, setCutoff);
READ_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
}
return bytesRead;
@ -164,6 +172,7 @@ EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_INTENSITY;
requestedProperties += PROP_EXPONENT;
requestedProperties += PROP_CUTOFF;
requestedProperties += PROP_FALLOFF_RADIUS;
return requestedProperties;
}
@ -181,4 +190,5 @@ void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, getIntensity());
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, getExponent());
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, getCutoff());
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, getFalloffRadius());
}

View file

@ -16,6 +16,12 @@
class LightEntityItem : public EntityItem {
public:
static const bool DEFAULT_IS_SPOTLIGHT;
static const float DEFAULT_INTENSITY;
static const float DEFAULT_FALLOFF_RADIUS;
static const float DEFAULT_EXPONENT;
static const float DEFAULT_CUTOFF;
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
LightEntityItem(const EntityItemID& entityItemID);
@ -65,6 +71,9 @@ public:
float getIntensity() const { return _intensity; }
void setIntensity(float value) { _intensity = value; }
float getFalloffRadius() const { return _falloffRadius; }
void setFalloffRadius(float value);
float getExponent() const { return _exponent; }
void setExponent(float value) { _exponent = value; }
@ -78,10 +87,11 @@ protected:
// properties of a light
rgbColor _color;
bool _isSpotlight;
float _intensity;
float _exponent;
float _cutoff;
bool _isSpotlight { DEFAULT_IS_SPOTLIGHT };
float _intensity { DEFAULT_INTENSITY };
float _falloffRadius { DEFAULT_FALLOFF_RADIUS };
float _exponent { DEFAULT_EXPONENT };
float _cutoff { DEFAULT_CUTOFF };
static bool _lightsArePickable;
};

View file

@ -21,6 +21,7 @@ Framebuffer::~Framebuffer() {
Framebuffer* Framebuffer::create() {
auto framebuffer = new Framebuffer();
framebuffer->_renderBuffers.resize(MAX_NUM_RENDER_BUFFERS);
framebuffer->_colorStamps.resize(MAX_NUM_RENDER_BUFFERS, 0);
return framebuffer;
}
@ -174,6 +175,8 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
}
}
++_colorStamps[slot];
updateSize(texture);
// assign the new one
@ -190,6 +193,7 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
}
void Framebuffer::removeRenderBuffers() {
if (isSwapchain()) {
return;
}
@ -230,6 +234,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const {
}
bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
++_depthStamp;
if (isSwapchain()) {
return false;
}

View file

@ -135,10 +135,15 @@ public:
static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; }
const GPUObjectPointer gpuObject {};
Stamp getDepthStamp() const { return _depthStamp; }
const std::vector<Stamp>& getColorStamps() const { return _colorStamps; }
protected:
SwapchainPointer _swapchain;
Stamp _depthStamp { 0 };
std::vector<Stamp> _colorStamps;
TextureViews _renderBuffers;
TextureView _depthStencilBuffer;

View file

@ -173,6 +173,9 @@ public:
public:
GLuint _fbo = 0;
std::vector<GLenum> _colorBuffers;
Stamp _depthStamp { 0 };
std::vector<Stamp> _colorStamps;
GLFramebuffer();
~GLFramebuffer();

View file

@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() {
GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) {
GLFramebuffer* object = Backend::getGPUObject<GLBackend::GLFramebuffer>(framebuffer);
bool needsUpate { false };
if (!object ||
framebuffer.getDepthStamp() != object->_depthStamp ||
framebuffer.getColorStamps() != object->_colorStamps) {
needsUpate = true;
}
// If GPU object already created and in sync
if (object) {
if (!needsUpate) {
return object;
} else if (framebuffer.isEmpty()) {
// NO framebuffer definition yet so let's avoid thinking
@ -37,94 +44,112 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
// need to have a gpu object?
if (!object) {
GLint currentFBO;
// All is green, assign the gpuobject to the Framebuffer
object = new GLFramebuffer();
Backend::setGPUObject(framebuffer, object);
glGenFramebuffers(1, &object->_fbo);
(void)CHECK_GL_ERROR();
}
if (needsUpate) {
GLint currentFBO = -1;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentFBO);
GLuint fbo;
glGenFramebuffers(1, &fbo);
(void) CHECK_GL_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLTexture* gltexture = nullptr;
TexturePointer surface;
if (framebuffer.getColorStamps() != object->_colorStamps) {
if (framebuffer.hasColor()) {
object->_colorBuffers.clear();
static const GLenum colorAttachments[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4,
GL_COLOR_ATTACHMENT5,
GL_COLOR_ATTACHMENT6,
GL_COLOR_ATTACHMENT7,
GL_COLOR_ATTACHMENT8,
GL_COLOR_ATTACHMENT9,
GL_COLOR_ATTACHMENT10,
GL_COLOR_ATTACHMENT11,
GL_COLOR_ATTACHMENT12,
GL_COLOR_ATTACHMENT13,
GL_COLOR_ATTACHMENT14,
GL_COLOR_ATTACHMENT15 };
std::vector<GLenum> colorBuffers;
if (framebuffer.hasColor()) {
static const GLenum colorAttachments[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4,
GL_COLOR_ATTACHMENT5,
GL_COLOR_ATTACHMENT6,
GL_COLOR_ATTACHMENT7,
GL_COLOR_ATTACHMENT8,
GL_COLOR_ATTACHMENT9,
GL_COLOR_ATTACHMENT10,
GL_COLOR_ATTACHMENT11,
GL_COLOR_ATTACHMENT12,
GL_COLOR_ATTACHMENT13,
GL_COLOR_ATTACHMENT14,
GL_COLOR_ATTACHMENT15 };
int unit = 0;
for (auto& b : framebuffer.getRenderBuffers()) {
surface = b._texture;
if (surface) {
gltexture = GLBackend::syncGPUObject(*surface);
} else {
gltexture = nullptr;
}
int unit = 0;
for (auto& b : framebuffer.getRenderBuffers()) {
auto surface = b._texture;
if (surface) {
auto gltexture = GLBackend::syncGPUObject(*surface);
if (gltexture) {
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0);
object->_colorBuffers.push_back(colorAttachments[unit]);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0);
}
colorBuffers.push_back(colorAttachments[unit]);
unit++;
}
}
}
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
else {
GLuint renderBuffer = 0;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
(void) CHECK_GL_ERROR();
}
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
#endif
if (framebuffer.hasDepthStencil()) {
auto surface = framebuffer.getDepthStencilBuffer();
if (surface) {
auto gltexture = GLBackend::syncGPUObject(*surface);
if (gltexture) {
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
if (!framebuffer.hasStencil()) {
attachement = GL_DEPTH_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else if (!framebuffer.hasDepth()) {
attachement = GL_STENCIL_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else {
attachement = GL_DEPTH_STENCIL_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
}
(void) CHECK_GL_ERROR();
}
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
else {
GLuint renderBuffer = 0;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
(void) CHECK_GL_ERROR();
}
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
#endif
object->_colorStamps = framebuffer.getColorStamps();
}
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
if (!framebuffer.hasStencil()) {
attachement = GL_DEPTH_ATTACHMENT;
} else if (!framebuffer.hasDepth()) {
attachement = GL_STENCIL_ATTACHMENT;
}
if (framebuffer.getDepthStamp() != object->_depthStamp) {
auto surface = framebuffer.getDepthStencilBuffer();
if (framebuffer.hasDepthStencil() && surface) {
gltexture = GLBackend::syncGPUObject(*surface);
}
if (gltexture) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0);
}
object->_depthStamp = framebuffer.getDepthStamp();
}
// Last but not least, define where we draw
if (!colorBuffers.empty()) {
glDrawBuffers((GLsizei)colorBuffers.size(), colorBuffers.data());
if (!object->_colorBuffers.empty()) {
glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data());
} else {
glDrawBuffer( GL_NONE );
}
// Now check for completness
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
// restore the current framebuffer
if (currentFBO != -1) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
}
bool result = false;
switch (status) {
case GL_FRAMEBUFFER_COMPLETE :
@ -147,20 +172,10 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
break;
}
if (!result && fbo) {
glDeleteFramebuffers( 1, &fbo );
if (!result && object->_fbo) {
glDeleteFramebuffers(1, &object->_fbo);
return nullptr;
}
// All is green, assign the gpuobject to the Framebuffer
object = new GLFramebuffer();
object->_fbo = fbo;
object->_colorBuffers = colorBuffers;
Backend::setGPUObject(framebuffer, object);
// restore the current framebuffer
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
}
return object;

View file

@ -12,19 +12,18 @@
using namespace model;
Light::Light() :
_flags(0),
_schemaBuffer(),
_transform() {
Light::Light() {
// only if created from nothing shall we create the Buffer to store the properties
Schema schema;
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
updateLightRadius();
}
Light::Light(const Light& light) :
_flags(light._flags),
_schemaBuffer(light._schemaBuffer),
_transform(light._transform) {
_transform(light._transform)
{
}
Light& Light::operator= (const Light& light) {
@ -70,18 +69,36 @@ void Light::setAmbientIntensity(float intensity) {
editSchema()._ambientIntensity = intensity;
}
void Light::setFalloffRadius(float radius) {
if (radius <= 0.0f) {
radius = 0.1f;
}
editSchema()._attenuation.x = radius;
updateLightRadius();
}
void Light::setMaximumRadius(float radius) {
if (radius <= 0.f) {
radius = 1.0f;
}
editSchema()._attenuation.w = radius;
editSchema()._attenuation.y = radius;
updateLightRadius();
}
void Light::updateLightRadius() {
float CutOffIntensityRatio = 0.05f;
float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f);
editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius());
// This function relies on the attenuation equation:
// I = Li / (1 + (d + Lr)/Lr)^2
// where I = calculated intensity, Li = light intensity, Lr = light falloff radius, d = distance from surface
// see: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
// note that falloff radius replaces surface radius in linked example
// This equation is biased back by Lr so that all lights act as true points, regardless of surface radii
const float MIN_CUTOFF_INTENSITY = 0.001f;
// Get cutoff radius at minimum intensity
float intensity = getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z);
float cutoffRadius = getFalloffRadius() * ((glm::sqrt(intensity / MIN_CUTOFF_INTENSITY) - 1) - 1);
// If it is less than max radius, store it to buffer to avoid extra shading
editSchema()._attenuation.z = std::min(getMaximumRadius(), cutoffRadius);
}
#include <math.h>

View file

@ -74,8 +74,17 @@ public:
bool isRanged() const { return (getType() == POINT) || (getType() == SPOT ); }
// FalloffRradius is the physical radius of the light sphere through which energy shines,
// expressed in meters. It is used only to calculate the falloff curve of the light.
// Actual rendered lights will all have surface radii approaching 0.
void setFalloffRadius(float radius);
float getFalloffRadius() const { return getSchema()._attenuation.x; }
// Maximum radius is the cutoff radius of the light energy, expressed in meters.
// It is used to bound light entities, and *will not* affect the falloff curve of the light.
// Setting it low will result in a noticeable cutoff.
void setMaximumRadius(float radius);
float getMaximumRadius() const { return getSchema()._attenuation.w; }
float getMaximumRadius() const { return getSchema()._attenuation.y; }
// Spot properties
bool isSpot() const { return getType() == SPOT; }
@ -107,7 +116,7 @@ public:
float _ambientIntensity{0.0f};
Color _color{1.0f};
float _intensity{1.0f};
Vec4 _attenuation{1.0f};
Vec4 _attenuation{0.1f, 1.0f, 0.0f, 0.0f};
Vec4 _spot{0.0f, 0.0f, 0.0f, 0.0f};
Vec4 _shadow{0.0f};
@ -120,7 +129,7 @@ public:
protected:
Flags _flags;
Flags _flags{ 0 };
UniformBufferView _schemaBuffer;
Transform _transform;

View file

@ -66,14 +66,6 @@ vec3 getLightColor(Light l) { return l._color.rgb; }
float getLightIntensity(Light l) { return l._color.w; }
float getLightAmbientIntensity(Light l) { return l._direction.w; }
float evalLightAttenuation(Light l, float r) {
float d = max(r - l._attenuation.x, 0.0);
float denom = d * l._attenuation.y + 1.0;
float attenuation = 1.0 / (denom * denom);
return max((attenuation - l._attenuation.z)/(1.0 - l._attenuation.z), 0.0);
// return clamp(1.0/(l._attenuation.x + l._attenuation.y * r + l._attenuation.z * r * r), 0.0, 1.0);
}
float getLightSpotAngleCos(Light l) {
return l._spot.x;
}
@ -86,22 +78,33 @@ float evalLightSpotAttenuation(Light l, float cosA) {
return pow(cosA, l._spot.w);
}
float getLightSquareRadius(Light l) {
return l._attenuation.w * l._attenuation.w;
}
float getLightRadius(Light l) {
return l._attenuation.w;
return l._attenuation.x;
}
float getLightAttenuationCutoff(Light l) {
float getLightSquareRadius(Light l) {
return getLightRadius(l) * getLightRadius(l);
}
float getLightCutoffRadius(Light l) {
return l._attenuation.z;
}
float getLightCutoffSquareRadius(Light l) {
return getLightCutoffRadius(l) * getLightCutoffRadius(l);
}
float getLightShowContour(Light l) {
return l._control.w;
}
float evalLightAttenuation(Light l, float d) {
float radius = getLightRadius(l);
float denom = d / radius + 1.0;
float attenuation = min(1.0, 1.0 / (denom * denom));
return attenuation;
}
SphericalHarmonics getLightAmbientSphere(Light l) {
return l._ambientSphere;
}

View file

@ -41,7 +41,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ATMOSPHERE_REMOVED;
return VERSION_LIGHT_HAS_FALLOFF_RADIUS;
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);

View file

@ -166,6 +166,7 @@ const PacketVersion VERSION_MODEL_ENTITIES_JOINTS_ON_WIRE = 53;
const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54;
const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55;
const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56;
const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,

View file

@ -937,7 +937,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
params.stats->traversed(element);
}
ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
currentEncodeLevel, parentLocationThisView);
@ -974,7 +974,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
OctreePacketData* packetData, OctreeElementBag& bag,
EncodeBitstreamParams& params, int& currentEncodeLevel,
const ViewFrustum::location& parentLocationThisView) const {
const ViewFrustum::intersection& parentLocationThisView) const {
const bool wantDebug = false;
@ -1013,7 +1013,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
}
}
ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
// caller can pass NULL as viewFrustum if they want everything
if (params.viewFrustum) {
@ -1034,7 +1034,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
// if we are INSIDE, INTERSECT, or OUTSIDE
if (parentLocationThisView != ViewFrustum::INSIDE) {
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
nodeLocationThisView = element->inFrustum(*params.viewFrustum);
nodeLocationThisView = element->computeViewIntersection(*params.viewFrustum);
}
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
@ -1053,7 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bool wasInView = false;
if (params.deltaViewFrustum && params.lastViewFrustum) {
ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum);
ViewFrustum::intersection location = element->computeViewIntersection(*params.lastViewFrustum);
// If we're a leaf, then either intersect or inside is considered "formerly in view"
if (element->isLeaf()) {
@ -1237,7 +1237,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
bool childWasInView = false;
if (childElement && params.deltaViewFrustum && params.lastViewFrustum) {
ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum);
ViewFrustum::intersection location = childElement->computeViewIntersection(*params.lastViewFrustum);
// If we're a leaf, then either intersect or inside is considered "formerly in view"
if (childElement->isLeaf()) {

View file

@ -367,7 +367,7 @@ protected:
int encodeTreeBitstreamRecursion(OctreeElementPointer element,
OctreePacketData* packetData, OctreeElementBag& bag,
EncodeBitstreamParams& params, int& currentEncodeLevel,
const ViewFrustum::location& parentLocationThisView) const;
const ViewFrustum::intersection& parentLocationThisView) const;
static bool countOctreeElementsOperation(OctreeElementPointer element, void* extraData);

View file

@ -458,8 +458,8 @@ float OctreeElement::getEnclosingRadius() const {
return getScale() * sqrtf(3.0f) / 2.0f;
}
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
return viewFrustum.cubeInFrustum(_cube);
ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrustum& viewFrustum) const {
return viewFrustum.calculateCubeKeyholeIntersection(_cube);
}
// There are two types of nodes for which we want to "render"

View file

@ -49,20 +49,20 @@ protected:
OctreeElement();
virtual OctreeElementPointer createNewElement(unsigned char * octalCode = NULL) = 0;
public:
virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction.
virtual ~OctreeElement();
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual OctreeElementPointer addChildAtIndex(int childIndex);
/// Override this to implement LOD averaging on changes to the tree.
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren() { }
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren() { return false; }
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
@ -72,12 +72,12 @@ public:
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
@ -88,17 +88,17 @@ public:
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { }
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; }
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; }
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { }
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
{ return COMPLETED; }
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
{ return 0; }
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
@ -106,7 +106,7 @@ public:
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return true; }
virtual bool canRayIntersect() const { return isLeaf(); }
@ -114,7 +114,7 @@ public:
/// \param radius radius of sphere in meters
/// \param[out] penetration pointing into cube from sphere
/// \param penetratedObject unused
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
// Base class methods you don't need to implement
@ -125,7 +125,7 @@ public:
bool isParentOf(OctreeElementPointer possibleChild) const;
/// handles deletion of all descendants, returns false if delete not approved
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
const AACube& getAACube() const { return _cube; }
@ -134,8 +134,8 @@ public:
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
float getEnclosingRadius() const;
bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; }
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
bool isInView(const ViewFrustum& viewFrustum) const { return computeViewIntersection(viewFrustum) != ViewFrustum::OUTSIDE; }
ViewFrustum::intersection computeViewIntersection(const ViewFrustum& viewFrustum) const;
float distanceToCamera(const ViewFrustum& viewFrustum) const;
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
@ -257,7 +257,7 @@ protected:
static std::map<QString, uint16_t> _mapSourceUUIDsToKeys;
static std::map<uint16_t, QString> _mapKeysToSourceUUIDs;
unsigned char _childBitmask; // 1 byte
unsigned char _childBitmask; // 1 byte
bool _falseColored : 1, /// Client only, is this voxel false colored, 1 bit
_isDirty : 1, /// Client only, has this voxel changed since being rendered, 1 bit

View file

@ -27,9 +27,9 @@ void OctreeHeadlessViewer::init() {
void OctreeHeadlessViewer::queryOctree() {
char serverType = getMyNodeType();
PacketType packetType = getMyQueryMessageType();
NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions();
bool wantExtraDebugging = false;
if (wantExtraDebugging) {
@ -52,7 +52,7 @@ void OctreeHeadlessViewer::queryOctree() {
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownJurisdictionServers++;
return;
}
}
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
@ -91,9 +91,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) {
inViewServers++;
}
}
@ -164,13 +162,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds));
}
if (inView) {
@ -208,7 +200,7 @@ void OctreeHeadlessViewer::queryOctree() {
// setup the query packet
auto queryPacket = NLPacket::create(packetType);
// read the data to our packet and set the payload size to fit the query
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(querySize);

View file

@ -46,7 +46,8 @@ public slots:
// setters for camera attributes
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
void setKeyholeRadius(float keyholdRadius) { _viewFrustum.setKeyholeRadius(keyholdRadius); }
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
// setters for LOD and PPS
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }

View file

@ -64,8 +64,8 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
destinationBuffer += sizeof(_boundaryLevelAdjust);
memcpy(destinationBuffer, &_keyholeRadius, sizeof(_keyholeRadius));
destinationBuffer += sizeof(_keyholeRadius);
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
destinationBuffer += sizeof(_cameraCenterRadius);
return destinationBuffer - bufferStart;
}
@ -109,9 +109,9 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
auto bytesRead = sourceBuffer - startPosition;
auto bytesLeft = message.getSize() - bytesRead;
if (bytesLeft >= (int)sizeof(_keyholeRadius)) {
memcpy(&_keyholeRadius, sourceBuffer, sizeof(_keyholeRadius));
sourceBuffer += sizeof(_keyholeRadius);
if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) {
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
sourceBuffer += sizeof(_cameraCenterRadius);
}
return sourceBuffer - startPosition;
}

View file

@ -58,7 +58,7 @@ public:
float getCameraNearClip() const { return _cameraNearClip; }
float getCameraFarClip() const { return _cameraFarClip; }
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
float getKeyholeRadius() const { return _keyholeRadius; }
float getCameraCenterRadius() const { return _cameraCenterRadius; }
glm::vec3 calculateCameraDirection() const;
@ -70,7 +70,7 @@ public:
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
void setKeyholeRadius(float keyholeRadius) { _keyholeRadius = keyholeRadius; }
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
// related to Octree Sending strategies
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
@ -90,7 +90,7 @@ protected:
float _cameraAspectRatio = 1.0f;
float _cameraNearClip = 0.0f;
float _cameraFarClip = 0.0f;
float _keyholeRadius { 0.0f };
float _cameraCenterRadius { 0.0f };
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
// octree server sending items

View file

@ -115,10 +115,6 @@ void ViewFrustum::calculate() {
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
_ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around
// Set up our keyhole bounding box...
glm::vec3 corner = _position - _keyholeRadius;
_keyholeBoundingCube = AACube(corner,(_keyholeRadius * 2.0f));
}
//enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
@ -134,231 +130,129 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
return "Unknown";
}
ViewFrustum::location ViewFrustum::pointInKeyhole(const glm::vec3& point) const {
ViewFrustum::location result = INTERSECT;
float distance = glm::distance(point, _position);
if (distance > _keyholeRadius) {
result = OUTSIDE;
} else if (distance < _keyholeRadius) {
result = INSIDE;
}
return result;
}
// To determine if two spheres intersect, simply calculate the distance between the centers of the two spheres.
// If the distance is greater than the sum of the two sphere radii, they dont intersect. Otherwise they intersect.
// If the distance plus the radius of sphere A is less than the radius of sphere B then, sphere A is inside of sphere B
ViewFrustum::location ViewFrustum::sphereInKeyhole(const glm::vec3& center, float radius) const {
ViewFrustum::location result = INTERSECT;
float distance = glm::distance(center, _position);
if (distance > (radius + _keyholeRadius)) {
result = OUTSIDE;
} else if ((distance + radius) < _keyholeRadius) {
result = INSIDE;
}
return result;
}
// A box is inside a sphere if all of its corners are inside the sphere
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
ViewFrustum::location ViewFrustum::cubeInKeyhole(const AACube& cube) const {
// First check to see if the cube is in the bounding cube for the sphere, if it's not, then we can short circuit
// this and not check with sphere penetration which is more expensive
if (!_keyholeBoundingCube.contains(cube)) {
return OUTSIDE;
}
glm::vec3 penetration;
bool intersects = cube.findSpherePenetration(_position, _keyholeRadius, penetration);
ViewFrustum::location result = OUTSIDE;
// if the cube intersects the sphere, then it may also be inside... calculate further
if (intersects) {
result = INTERSECT;
// test all the corners, if they are all inside the sphere, the entire cube is in the sphere
bool allPointsInside = true; // assume the best
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
glm::vec3 vertex = cube.getVertex((BoxVertex)v);
if (!pointInKeyhole(vertex)) {
allPointsInside = false;
break;
ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const {
// only check against frustum
ViewFrustum::intersection result = INSIDE;
for(int i=0; i < 6; i++) {
const glm::vec3& normal = _planes[i].getNormal();
// check distance to farthest cube point
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
return OUTSIDE;
} else {
// check distance to nearest cube point
if (_planes[i].distance(cube.getNearestVertex(normal)) < 0.0f) {
// cube straddles the plane
result = INTERSECT;
}
}
if (allPointsInside) {
result = INSIDE;
}
}
return result;
}
// A box is inside a sphere if all of its corners are inside the sphere
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const {
const float HALF_SQRT_THREE = 0.8660254f;
// First check to see if the box is in the bounding box for the sphere, if it's not, then we can short circuit
// this and not check with sphere penetration which is more expensive
if (!_keyholeBoundingCube.contains(box)) {
return OUTSIDE;
ViewFrustum::intersection ViewFrustum::calculateCubeKeyholeIntersection(const AACube& cube) const {
// check against centeral sphere
ViewFrustum::intersection sphereResult = INTERSECT;
glm::vec3 cubeOffset = cube.calcCenter() - _position;
float distance = glm::length(cubeOffset);
if (distance > EPSILON) {
glm::vec3 vertex = cube.getFarthestVertex(cubeOffset) - _position;
if (glm::dot(vertex, cubeOffset) < _centerSphereRadius * distance) {
// the most outward cube vertex is inside central sphere
return INSIDE;
}
if (!cube.touchesSphere(_position, _centerSphereRadius)) {
sphereResult = OUTSIDE;
}
} else if (_centerSphereRadius > HALF_SQRT_THREE * cube.getScale()) {
// the cube is in center of sphere and its bounding radius is inside
return INSIDE;
}
glm::vec3 penetration;
bool intersects = box.findSpherePenetration(_position, _keyholeRadius, penetration);
// check against frustum
ViewFrustum::intersection frustumResult = calculateCubeFrustumIntersection(cube);
ViewFrustum::location result = OUTSIDE;
// if the box intersects the sphere, then it may also be inside... calculate further
if (intersects) {
result = INTERSECT;
// test all the corners, if they are all inside the sphere, the entire box is in the sphere
bool allPointsInside = true; // assume the best
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
glm::vec3 vertex = box.getVertex((BoxVertex)v);
if (!pointInKeyhole(vertex)) {
allPointsInside = false;
break;
}
}
if (allPointsInside) {
result = INSIDE;
}
}
return result;
return (frustumResult == OUTSIDE) ? sphereResult : frustumResult;
}
ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point, bool ignoreKeyhole) const {
ViewFrustum::location regularResult = INSIDE;
ViewFrustum::location keyholeResult = OUTSIDE;
// If we have a keyholeRadius, check that first, since it's cheaper
if (!ignoreKeyhole && _keyholeRadius >= 0.0f) {
keyholeResult = pointInKeyhole(point);
if (keyholeResult == INSIDE) {
return keyholeResult;
}
}
// If we're not known to be INSIDE the keyhole, then check the regular frustum
bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const {
// only check against frustum
for(int i = 0; i < 6; ++i) {
float distance = _planes[i].distance(point);
if (distance < 0) {
return keyholeResult; // escape early will be the value from checking the keyhole
if (distance < 0.0f) {
return false;
}
}
return regularResult;
return true;
}
ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, float radius) const {
ViewFrustum::location regularResult = INSIDE;
ViewFrustum::location keyholeResult = OUTSIDE;
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = sphereInKeyhole(center, radius);
}
if (keyholeResult == INSIDE) {
return keyholeResult;
}
float distance;
bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) const {
// only check against frustum
for(int i=0; i < 6; i++) {
distance = _planes[i].distance(center);
float distance = _planes[i].distance(center);
if (distance < -radius) {
// This is outside the regular frustum, so just return the value from checking the keyhole
return keyholeResult;
} else if (distance < radius) {
regularResult = INTERSECT;
return false;
}
}
return regularResult;
return true;
}
ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const {
ViewFrustum::location regularResult = INSIDE;
ViewFrustum::location keyholeResult = OUTSIDE;
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = cubeInKeyhole(cube);
}
if (keyholeResult == INSIDE) {
return keyholeResult;
}
// TODO: These calculations are expensive, taking up 80% of our time in this function.
// This appears to be expensive because we have to test the distance to each plane.
// One suggested optimization is to first check against the approximated cone. We might
// also be able to test against the cone to the bounding sphere of the box.
bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const {
// only check against frustum
for(int i=0; i < 6; i++) {
const glm::vec3& normal = _planes[i].getNormal();
const glm::vec3& boxVertexP = cube.getVertexP(normal);
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
const glm::vec3& boxVertexN = cube.getVertexN(normal);
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
if (planeToBoxVertexPDistance < 0) {
// This is outside the regular frustum, so just return the value from checking the keyhole
return keyholeResult;
} else if (planeToBoxVertexNDistance < 0) {
regularResult = INTERSECT;
// check distance to farthest box point
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
return false;
}
}
return regularResult;
return true;
}
ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
ViewFrustum::location regularResult = INSIDE;
ViewFrustum::location keyholeResult = OUTSIDE;
// If we have a keyholeRadius, check that first, since it's cheaper
if (_keyholeRadius >= 0.0f) {
keyholeResult = boxInKeyhole(box);
bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const {
// check positive touch against central sphere
if (glm::length(center - _position) <= (radius + _centerSphereRadius)) {
return true;
}
if (keyholeResult == INSIDE) {
return keyholeResult;
}
// TODO: These calculations are expensive, taking up 80% of our time in this function.
// This appears to be expensive because we have to test the distance to each plane.
// One suggested optimization is to first check against the approximated cone. We might
// also be able to test against the cone to the bounding sphere of the box.
// check negative touches against frustum planes
for(int i=0; i < 6; i++) {
const glm::vec3& normal = _planes[i].getNormal();
const glm::vec3& boxVertexP = box.getVertexP(normal);
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
const glm::vec3& boxVertexN = box.getVertexN(normal);
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
if (planeToBoxVertexPDistance < 0) {
// This is outside the regular frustum, so just return the value from checking the keyhole
return keyholeResult;
} else if (planeToBoxVertexNDistance < 0) {
regularResult = INTERSECT;
if ( _planes[i].distance(center) < -radius) {
return false;
}
}
return regularResult;
return true;
}
bool ViewFrustum::cubeIntersectsKeyhole(const AACube& cube) const {
// check positive touch against central sphere
if (cube.touchesSphere(_position, _centerSphereRadius)) {
return true;
}
// check negative touches against frustum planes
for(int i=0; i < 6; i++) {
const glm::vec3& normal = _planes[i].getNormal();
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
return false;
}
}
return true;
}
bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
// check positive touch against central sphere
if (box.touchesSphere(_position, _centerSphereRadius)) {
return true;
}
// check negative touches against frustum planes
for(int i=0; i < 6; i++) {
const glm::vec3& normal = _planes[i].getNormal();
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
return false;
}
}
return true;
}
bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) {
@ -490,7 +384,7 @@ PickRay ViewFrustum::computePickRay(float x, float y) {
}
void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const {
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
y * (_cornersWorld[BOTTOM_LEFT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]);
direction = glm::normalize(origin - _position);
}
@ -540,7 +434,7 @@ void ViewFrustum::printDebugDetails() const {
qCDebug(octree, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z );
qCDebug(octree, "_fieldOfView=%f", (double)_fieldOfView);
qCDebug(octree, "_aspectRatio=%f", (double)_aspectRatio);
qCDebug(octree, "_keyHoleRadius=%f", (double)_keyholeRadius);
qCDebug(octree, "_centerSphereRadius=%f", (double)_centerSphereRadius);
qCDebug(octree, "_nearClip=%f", (double)_nearClip);
qCDebug(octree, "_farClip=%f", (double)_farClip);
qCDebug(octree, "_focalLength=%f", (double)_focalLength);
@ -804,7 +698,7 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize
// FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return
// a floating point for objects that have small angular size to indicate that they may be rendered
// with lower preciscion
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
}
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {

View file

@ -27,12 +27,15 @@
#include "OctreeConstants.h"
#include "OctreeProjectedPolygon.h"
const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f;
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
const float DEFAULT_NEAR_CLIP = 0.08f;
const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE;
// the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with
// a central sphere for stuff that is nearby (for physics simulation).
class ViewFrustum {
public:
// setters for camera attributes
@ -83,18 +86,26 @@ public:
const glm::vec3& getNearBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_NEAR]; }
const glm::vec3& getNearBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_NEAR]; }
// get/set for keyhole attribute
void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; }
float getKeyholeRadius() const { return _keyholeRadius; }
// get/set for central spherek attribute
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
float getCenterRadius() const { return _centerSphereRadius; }
void calculate();
typedef enum {OUTSIDE, INTERSECT, INSIDE} location;
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
ViewFrustum::location pointInFrustum(const glm::vec3& point, bool ignoreKeyhole = false) const;
ViewFrustum::location sphereInFrustum(const glm::vec3& center, float radius) const;
ViewFrustum::location cubeInFrustum(const AACube& cube) const;
ViewFrustum::location boxInFrustum(const AABox& box) const;
/// @return INSIDE, INTERSECT, or OUTSIDE depending on how cube intersects the keyhole shape
ViewFrustum::intersection calculateCubeFrustumIntersection(const AACube& cube) const;
ViewFrustum::intersection calculateCubeKeyholeIntersection(const AACube& cube) const;
bool pointIntersectsFrustum(const glm::vec3& point) const;
bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const;
bool cubeIntersectsFrustum(const AACube& box) const;
bool boxIntersectsFrustum(const AABox& box) const;
bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const;
bool cubeIntersectsKeyhole(const AACube& cube) const;
bool boxIntersectsKeyhole(const AABox& box) const;
// some frustum comparisons
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
@ -114,15 +125,15 @@ public:
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const;
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
float distanceToCamera(const glm::vec3& point) const;
void evalProjectionMatrix(glm::mat4& proj) const;
void evalViewTransform(Transform& view) const;
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple
/// level it returns 0.0f for things that are so small for the current settings that they could not be visible.
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
int boundaryLevelAdjust = 0) const;
float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
@ -131,12 +142,6 @@ public:
const ::Plane* getPlanes() const { return _planes; }
private:
// Used for keyhole calculations
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const;
ViewFrustum::location cubeInKeyhole(const AACube& cube) const;
ViewFrustum::location boxInKeyhole(const AABox& box) const;
// camera location/orientation attributes
glm::vec3 _position; // the position in world-frame
glm::quat _orientation;
@ -150,9 +155,7 @@ private:
glm::vec3 _up = IDENTITY_UP;
glm::vec3 _right = IDENTITY_RIGHT;
// keyhole attributes
float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS;
AACube _keyholeBoundingCube;
float _centerSphereRadius = DEFAULT_CENTER_SPHERE_RADIUS;
// Calculated values
glm::mat4 _inverseProjection;
@ -165,7 +168,7 @@ private:
float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
glm::vec4 _corners[8];
glm::vec3 _cornersWorld[8];
::Plane _planes[6]; // How will this be used?
::Plane _planes[6]; // plane normals point inside frustum
const char* debugPlaneName (int plane) const;

View file

@ -98,12 +98,12 @@ void DeferredLightingEffect::init() {
}
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
float intensity) {
addSpotLight(position, radius, color, intensity);
float intensity, float falloffRadius) {
addSpotLight(position, radius, color, intensity, falloffRadius);
}
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
if (lightID >= _allocatedLights.size()) {
@ -115,7 +115,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu
lp->setMaximumRadius(radius);
lp->setColor(color);
lp->setIntensity(intensity);
//lp->setShowContour(quadraticAttenuation);
lp->setFalloffRadius(falloffRadius);
if (exponent == 0.0f && cutoff == PI) {
lp->setType(model::Light::POINT);

View file

@ -36,11 +36,12 @@ public:
/// Adds a point light to render for the current frame.
void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f),
float intensity = 0.5f);
float intensity = 0.5f, float falloffRadius = 0.01f);
/// Adds a spot light to render for the current frame.
void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f),
float intensity = 0.5f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
float intensity = 0.5f, float falloffRadius = 0.01f,
const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
void prepare(RenderArgs* args);
void render(const render::RenderContextPointer& renderContext);

View file

@ -450,9 +450,9 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
if (!_isBlendShaped) {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
batch.setInputFormat((_drawMesh->getVertexFormat()));
batch.setInputStream(0, _drawMesh->getVertexStream());
} else {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
@ -463,7 +463,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
}
// TODO: Get rid of that extra call
if (!_hasColorAttrib) {
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
@ -511,17 +511,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
#ifdef DEBUG_BOUNDING_PARTS
{
AABox partBounds = getPartBounds(_meshIndex, partIndex);
bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE;
glm::vec4 cubeColor;
glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
if (isSkinned) {
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
} else if (inView) {
} else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
} else {
cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
}
Transform transform;
transform.setTranslation(partBounds.calcCenter());
transform.setScale(partBounds.getDimensions());
@ -529,7 +526,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif //def DEBUG_BOUNDING_PARTS
auto locations = args->_pipeline->locations;
assert(locations);
@ -537,23 +534,23 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
_model->updateClusterMatrices(_transform.getTranslation(), _transform.getRotation());
bindTransform(batch, locations, canCauterize);
//Bind the index buffer and vertex buffer and Blend shapes if needed
bindMesh(batch);
// apply material properties
bindMaterial(batch, locations);
if (args) {
args->_details._materialSwitches++;
}
// Draw!
{
PerformanceTimer perfTimer("batch.drawIndexed()");
drawCall(batch);
}
if (args) {
const int INDICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;

View file

@ -48,7 +48,7 @@ void main(void) {
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
// Kill if too far from the light center
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
discard;
}

View file

@ -47,7 +47,7 @@ void main(void) {
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
// Kill if too far from the light center
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
discard;
}

View file

@ -29,7 +29,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
ViewFrustum* frustum = args->_viewFrustum;
details._considered += (int)inItems.size();
// Culling / LOD
for (auto item : inItems) {
if (item.bound.isNull()) {
@ -39,12 +39,12 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
bool outOfView;
bool inView;
{
PerformanceTimer perfTimer("boxInFrustum");
outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE;
PerformanceTimer perfTimer("boxIntersectsFrustum");
inView = frustum->boxIntersectsFrustum(item.bound);
}
if (!outOfView) {
if (inView) {
bool bigEnoughToRender;
{
PerformanceTimer perfTimer("shouldRender");
@ -88,10 +88,10 @@ struct BackToFrontSort {
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
// Allocate and simply copy
outItems.clear();
@ -238,7 +238,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
}
bool frustumTest(const AABox& bound) {
if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) {
if (!_args->_viewFrustum->boxIntersectsFrustum(bound)) {
_renderDetails._outOfView++;
return false;
}

View file

@ -75,32 +75,32 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) {
_scale = scale;
}
glm::vec3 AABox::getVertexP(const glm::vec3& normal) const {
glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x > 0) {
if (normal.x > 0.0f) {
result.x += _scale.x;
}
if (normal.y > 0) {
if (normal.y > 0.0f) {
result.y += _scale.y;
}
if (normal.z > 0) {
if (normal.z > 0.0f) {
result.z += _scale.z;
}
return result;
}
glm::vec3 AABox::getVertexN(const glm::vec3& normal) const {
glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x < 0) {
if (normal.x < 0.0f) {
result.x += _scale.x;
}
if (normal.y < 0) {
if (normal.y < 0.0f) {
result.y += _scale.y;
}
if (normal.z < 0) {
if (normal.z < 0.0f) {
result.z += _scale.z;
}
@ -217,7 +217,7 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
}
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const {
// handle the trivial case where the box contains the origin
if (contains(origin)) {
@ -281,6 +281,12 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false;
}
bool AABox::touchesSphere(const glm::vec3& center, float radius) const {
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO);
return glm::length2(e) <= radius * radius;
}
bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
glm::vec4 center4 = glm::vec4(center, 1.0f);
@ -537,4 +543,4 @@ void AABox::transform(const Transform& transform) {
scale(transform.getScale());
rotate(transform.getRotation());
translate(transform.getTranslation());
}
}

View file

@ -35,12 +35,12 @@ public:
AABox(const glm::vec3& corner, const glm::vec3& dimensions);
AABox();
~AABox() {};
void setBox(const glm::vec3& corner, const glm::vec3& scale);
void setBox(const glm::vec3& corner, float scale);
glm::vec3 getVertexP(const glm::vec3& normal) const;
glm::vec3 getVertexN(const glm::vec3& normal) const;
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
const glm::vec3& getCorner() const { return _corner; }
const glm::vec3& getScale() const { return _scale; }
@ -68,11 +68,12 @@ public:
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const;
bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
bool isNull() const { return _scale == glm::vec3(0.0f, 0.0f, 0.0f); }
AABox clamp(const glm::vec3& min, const glm::vec3& max) const;
@ -113,7 +114,7 @@ inline bool operator==(const AABox& a, const AABox& b) {
}
inline QDebug operator<<(QDebug debug, const AABox& box) {
debug << "AABox[ ("
debug << "AABox[ ("
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"

View file

@ -79,32 +79,32 @@ void AACube::setBox(const glm::vec3& corner, float scale) {
_scale = scale;
}
glm::vec3 AACube::getVertexP(const glm::vec3& normal) const {
glm::vec3 AACube::getFarthestVertex(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x > 0) {
if (normal.x > 0.0f) {
result.x += _scale;
}
if (normal.y > 0) {
if (normal.y > 0.0f) {
result.y += _scale;
}
if (normal.z > 0) {
if (normal.z > 0.0f) {
result.z += _scale;
}
return result;
}
glm::vec3 AACube::getVertexN(const glm::vec3& normal) const {
glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const {
glm::vec3 result = _corner;
if (normal.x < 0) {
if (normal.x < 0.0f) {
result.x += _scale;
}
if (normal.y < 0) {
if (normal.y < 0.0f) {
result.y += _scale;
}
if (normal.z < 0) {
if (normal.z < 0.0f) {
result.z += _scale;
}
@ -284,6 +284,12 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
return false;
}
bool AACube::touchesSphere(const glm::vec3& center, float radius) const {
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - glm::vec3(_scale), Vectors::ZERO);
return glm::length2(e) <= radius * radius;
}
bool AACube::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
glm::vec4 center4 = glm::vec4(center, 1.0f);

View file

@ -34,8 +34,8 @@ public:
~AACube() {};
void setBox(const glm::vec3& corner, float scale);
glm::vec3 getVertexP(const glm::vec3& normal) const;
glm::vec3 getVertexN(const glm::vec3& normal) const;
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
void scale(float scale);
const glm::vec3& getCorner() const { return _corner; }
float getScale() const { return _scale; }
@ -56,8 +56,9 @@ public:
bool touches(const AABox& otherBox) const;
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const;
bool touchesSphere(const glm::vec3& center, float radius) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
@ -88,7 +89,7 @@ inline bool operator!=(const AACube& a, const AACube& b) {
}
inline QDebug operator<<(QDebug debug, const AACube& cube) {
debug << "AACube[ ("
debug << "AACube[ ("
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"

View file

@ -670,9 +670,8 @@ void OctreeTests::byteCountCodingTests() {
}
void OctreeTests::modelItemTests() {
bool verbose = true;
#if 0 // TODO - repair/replace these
bool verbose = true;
//verbose = true;
EntityTreeElementExtraEncodeData modelTreeElementExtraEncodeData;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
//
// ViewFrustumTests.h
// tests/octree/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ViewFruxtumTests_h
#define hifi_ViewFruxtumTests_h
#include <QtTest/QtTest>
class ViewFrustumTests : public QObject {
Q_OBJECT
private slots:
void testInit();
void testCubeFrustumIntersection();
void testCubeKeyholeIntersection();
void testPointIntersectsFrustum();
void testSphereIntersectsFrustum();
void testBoxIntersectsFrustum();
void testSphereIntersectsKeyhole();
void testCubeIntersectsKeyhole();
void testBoxIntersectsKeyhole();
};
#endif // hifi_ViewFruxtumTests_h

View file

@ -0,0 +1,153 @@
//
// AABoxTests.cpp
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include "AABoxTests.h"
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <../GLMTestUtils.h>
#include <../QTestExtensions.h>
QTEST_MAIN(AABoxTests)
void AABoxTests::testCtorsAndSetters() {
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
// test ctor
AABox box(corner, scale);
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(box.getScale(), scale, EPSILON);
// test copy ctor
AABox copyBox(box);
QCOMPARE_WITH_ABS_ERROR(copyBox.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(copyBox.getScale(), scale, EPSILON);
// test setBox()
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
const glm::vec3 newScale = glm::vec3(4.32f, 8.95f, 10.31f);
box.setBox(newCorner, newScale);
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), newCorner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(box.getScale(), newScale, EPSILON);
// test misc
QCOMPARE_WITH_ABS_ERROR(newCorner, box.getMinimumPoint(), EPSILON);
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
QCOMPARE_WITH_ABS_ERROR(expectedMaxCorner, box.getMaximumPoint(), EPSILON);
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
QCOMPARE_WITH_ABS_ERROR(expectedCenter, box.calcCenter(), EPSILON);
}
void AABoxTests::testContainsPoint() {
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
AABox box(corner, scale);
float delta = 0.00001f;
glm::vec3 center = box.calcCenter();
QCOMPARE(box.contains(center), true);
for (int i = 0; i < 3; ++i) {
glm::vec3 halfScale = Vectors::ZERO;
halfScale[i] = 0.5f * scale[i];
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
QCOMPARE(box.contains(center + halfScale + deltaOffset), false); // outside +face
QCOMPARE(box.contains(center + halfScale - deltaOffset), true); // inside +face
QCOMPARE(box.contains(center - halfScale + deltaOffset), true); // inside -face
QCOMPARE(box.contains(center - halfScale - deltaOffset), false); // outside -face
}
}
void AABoxTests::testTouchesSphere() {
glm::vec3 corner(-4.56f, 7.89f, -1.35f);
float scale = 1.23f;
AABox box(corner, scale);
float delta = 0.00001f;
glm::vec3 cubeCenter = box.calcCenter();
float sphereRadius = 0.468f;
for (int i = 0; i < 3; ++i) {
int j = (i + 1) % 3;
int k = (j + 1) % 3;
{ // faces
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale + sphereRadius;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
// outside +face
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
// inside +face
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// inside -face
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside -face
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
}
{ // edges
glm::vec3 edgeOffset = Vectors::ZERO;
edgeOffset[i] = 0.5f * scale;
edgeOffset[j] = 0.5f * scale;
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
glm::vec3 sphereCenter;
// inside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
edgeOffset[j] = 0.0f;
edgeOffset[k] = 0.5f * scale;
edgeDirection = glm::normalize(edgeOffset);
// inside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
}
}
}

View file

@ -0,0 +1,28 @@
//
// AABoxTests.h
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AABoxTests_h
#define hifi_AABoxTests_h
#include <QtTest/QtTest>
#include <glm/glm.hpp>
#include <AABox.h>
class AABoxTests : public QObject {
Q_OBJECT
private slots:
void testCtorsAndSetters();
void testContainsPoint();
void testTouchesSphere();
};
#endif // hifi_AABoxTests_h

View file

@ -0,0 +1,154 @@
//
// AACubeTests.cpp
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include "AACubeTests.h"
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <../GLMTestUtils.h>
#include <../QTestExtensions.h>
QTEST_MAIN(AACubeTests)
void AACubeTests::ctorsAndSetters() {
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
const float scale = 2.34f;
// test ctor
AACube cube(corner, scale);
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), scale, EPSILON);
// test copy ctor
AACube copyCube(cube);
QCOMPARE_WITH_ABS_ERROR(copyCube.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(copyCube.getScale(), scale, EPSILON);
// test setBox()
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
const float newScale = 4.32f;
cube.setBox(newCorner, newScale);
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), newCorner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), newScale, EPSILON);
// test misc
QCOMPARE_WITH_ABS_ERROR(cube.getMinimumPoint(), newCorner, EPSILON);
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
QCOMPARE_WITH_ABS_ERROR(cube.getMaximumPoint(), expectedMaxCorner, EPSILON);
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
QCOMPARE_WITH_ABS_ERROR(cube.calcCenter(), expectedCenter, EPSILON);
}
void AACubeTests::containsPoint() {
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
const float scale = 1.23f;
AACube cube(corner, scale);
const float delta = scale / 1000.0f;
const glm::vec3 center = cube.calcCenter();
QCOMPARE(cube.contains(center), true);
for (int i = 0; i < 3; ++i) {
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
QCOMPARE(cube.contains(center + scaleOffset + deltaOffset), false); // outside +face
QCOMPARE(cube.contains(center + scaleOffset - deltaOffset), true); // inside +face
QCOMPARE(cube.contains(center - scaleOffset + deltaOffset), true); // inside -face
QCOMPARE(cube.contains(center - scaleOffset - deltaOffset), false); // outside -face
}
}
void AACubeTests::touchesSphere() {
const glm::vec3 corner(-4.56f, 7.89f, -1.35f);
const float scale = 1.23f;
AACube cube(corner, scale);
const float delta = scale / 1000.0f;
const glm::vec3 cubeCenter = cube.calcCenter();
const float sphereRadius = 0.468f;
for (int i = 0; i < 3; ++i) {
int j = (i + 1) % 3;
int k = (j + 1) % 3;
{ // faces
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale + sphereRadius;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
// outside +face
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
// inside +face
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// inside -face
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside -face
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
}
{ // edges
glm::vec3 edgeOffset = Vectors::ZERO;
edgeOffset[i] = 0.5f * scale;
edgeOffset[j] = 0.5f * scale;
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
glm::vec3 sphereCenter;
// inside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
edgeOffset[j] = 0.0f;
edgeOffset[k] = 0.5f * scale;
edgeDirection = glm::normalize(edgeOffset);
// inside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
}
}
}

View file

@ -0,0 +1,28 @@
//
// AACubeTests.h
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AACubeTests_h
#define hifi_AACubeTests_h
#include <QtTest/QtTest>
#include <glm/glm.hpp>
#include <AACube.h>
class AACubeTests : public QObject {
Q_OBJECT
private slots:
void ctorsAndSetters();
void containsPoint();
void touchesSphere();
};
#endif // hifi_AACubeTests_h

View file

@ -1,316 +0,0 @@
//
// AngularConstraintTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AngularConstraintTests.h"
#include <iostream>
#include <AngularConstraint.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include "../QTestExtensions.h"
QTEST_MAIN(AngularConstraintTests)
void AngularConstraintTests::testHingeConstraint() {
float minAngle = -PI;
float maxAngle = 0.0f;
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 minAngles(0.0f, -PI, 0.0f);
glm::vec3 maxAngles(0.0f, 0.0f, 0.0f);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
QVERIFY2(c != nullptr, "newAngularConstraint should make a constraint");
{ // test in middle of constraint
float angle = 0.5f * (minAngle + maxAngle);
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained == false, "HingeConstraint should not clamp()");
QVERIFY2(rotation == newRotation, "HingeConstraint should not change rotation");
}
{ // test just inside min edge of constraint
float angle = minAngle + 10.0f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
}
{ // test just inside max edge of constraint
float angle = maxAngle - 10.0f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
}
{ // test just outside min edge of constraint
float angle = minAngle - 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test just outside max edge of constraint
float angle = maxAngle + 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, rotation, EPSILON);
}
{ // test far outside min edge of constraint (wraps around to max)
float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test far outside max edge of constraint (wraps around to min)
float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
float ACCEPTABLE_ERROR = 1.0e-4f;
{ // test nearby but off-axis rotation
float offAngle = 0.1f;
glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = 0.5f * (maxAngle + minAngle);
glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(angle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, ACCEPTABLE_ERROR);
}
{ // test way off rotation > maxAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation < minAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation > maxAngle with wrap over to minAngle
float offAngle = -0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation < minAngle with wrap over to maxAngle
float offAngle = -0.6f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
delete c;
}
void AngularConstraintTests::testConeRollerConstraint() {
float minAngleX = -PI / 5.0f;
float minAngleY = -PI / 5.0f;
float minAngleZ = -PI / 8.0f;
float maxAngleX = PI / 4.0f;
float maxAngleY = PI / 3.0f;
float maxAngleZ = PI / 4.0f;
glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ);
glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
float expectedConeAngle = 0.25f * (maxAngleX - minAngleX + maxAngleY - minAngleY);
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis);
QVERIFY2(c != nullptr, "newAngularConstraint() should make a constraint");
{ // test in middle of constraint
glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f);
glm::quat rotation(angles);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
float deltaAngle = 0.001f;
{ // test just inside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just outside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
}
{ // test just inside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just inside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just outside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test just outside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
deltaAngle = 0.25f * expectedConeAngle;
{ // test far outside cone and min roll
glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test far outside cone and max roll
glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
delete c;
}

View file

@ -1,27 +0,0 @@
//
// AngularConstraintTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AngularConstraintTests_h
#define hifi_AngularConstraintTests_h
#include <glm/glm.hpp>
#include <QtTest/QtTest>
class AngularConstraintTests : public QObject {
Q_OBJECT
private slots:
void testHingeConstraint();
void testConeRollerConstraint();
};
float getErrorDifference(const glm::quat& a, const glm::quat& b);
#endif // hifi_AngularConstraintTests_h

View file

@ -0,0 +1,272 @@
//
// createTiltMaze.js
//
// Created by James B. Pollack @imgntn on 2/15/2016
// Copyright 2016 High Fidelity, Inc.
//
// This script creates a maze with a ball that you can tilt to try to get to the end.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var ball, ballSpawningAnchor, ballDetector, tiltMaze, lightAtTheEnd;
var MAZE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-4.fbx";
var MAZE_COLLISION_HULL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-3.obj";
var MAZE_SCRIPT = Script.resolvePath('maze.js?' + Math.random());
var SCALE = 0.5;
var MAZE_DIMENSIONS = Vec3.multiply(SCALE, {
x: 1,
y: 0.15,
z: 1
});
var BALL_DIMENSIONS = Vec3.multiply(SCALE, {
x: 0.035,
y: 0.035,
z: 0.035
});
var BALL_SPAWNER_DIMENSIONS = Vec3.multiply(SCALE, {
x: 0.05,
y: 0.05,
z: 0.05
});
var BALL_DETECTOR_DIMENSIONS = Vec3.multiply(SCALE, {
x: 0.1,
y: 0.1,
z: 0.1
});
var BALL_COLOR = {
red: 255,
green: 0,
blue: 0
};
var DEBUG_COLOR = {
red: 0,
green: 255,
blue: 0
};
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
x: 0,
y: 0.5,
z: 0
}), Vec3.multiply(1.5, Quat.getFront(Camera.getOrientation())));
var CLEANUP = true;
var BALL_FORWARD_OFFSET = -0.2 * SCALE;
var BALL_RIGHT_OFFSET = -0.4 * SCALE;
var BALL_VERTICAL_OFFSET = 0.02 * SCALE;
var BALL_FRICTION = 0.7;
var BALL_RESTITUTION = 0.1;
var BALL_DAMPING = 0.6;
var BALL_ANGULAR_DAMPING = 0.2;
var BALL_DENSITY = 1000;
var BALL_GRAVITY = {
x: 0,
y: -9.8,
z: 0
};
var MAZE_DENSITY = 1000;
var MAZE_RESTITUTION = 0.1;
var MAZE_DAMPING = 0.6;
var MAZE_ANGULAR_DAMPING = 0.6;
var DETECTOR_VERTICAL_OFFSET = 0.0 * SCALE;
var DETECTOR_FORWARD_OFFSET = 0.35 * SCALE;
var DETECTOR_RIGHT_OFFSET = 0.35 * SCALE;
var END_LIGHT_COLOR = {
red: 255,
green: 0,
blue: 0
};
var END_LIGHT_DIMENSIONS = {
x: 0.2,
y: 0.2,
z: 0.8
};
var END_LIGHT_INTENSITY = 0.035;
var END_LIGHT_CUTOFF = 30;
var END_LIGHT_EXPONENT = 1;
var getBallStartLocation = function() {
var mazeProps = Entities.getEntityProperties(tiltMaze);
var right = Quat.getRight(mazeProps.rotation);
var front = Quat.getFront(mazeProps.rotation);
var vertical = {
x: 0,
y: BALL_VERTICAL_OFFSET,
z: 0
};
var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET));
finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET));
var location = Vec3.sum(mazeProps.position, finalOffset);
return location;
};
var getBallFinishLocation = function() {
var mazeProps = Entities.getEntityProperties(tiltMaze);
var right = Quat.getRight(mazeProps.rotation);
var forward = Quat.getFront(mazeProps.rotation);
var up = Quat.getUp(mazeProps.rotation);
var position = Vec3.sum(mazeProps.position, Vec3.multiply(up, DETECTOR_VERTICAL_OFFSET));
position = Vec3.sum(position, Vec3.multiply(right, DETECTOR_RIGHT_OFFSET));
position = Vec3.sum(position, Vec3.multiply(forward, DETECTOR_FORWARD_OFFSET));
return position;
};
var createBall = function(position) {
var properties = {
name: 'Hifi Tilt Maze Ball',
type: 'Sphere',
position: getBallStartLocation(),
dynamic: true,
collisionless: false,
friction: BALL_FRICTION,
restitution: BALL_RESTITUTION,
angularDamping: BALL_ANGULAR_DAMPING,
damping: BALL_DAMPING,
gravity: BALL_GRAVITY,
density: BALL_DENSITY,
color: BALL_COLOR,
dimensions: BALL_DIMENSIONS,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
})
};
ball = Entities.addEntity(properties);
};
var createBallSpawningAnchor = function() {
var properties = {
name: 'Hifi Tilt Maze Ball Detector',
parentID: tiltMaze,
type: 'Box',
color: DEBUG_COLOR,
dimensions: BALL_SPAWNER_DIMENSIONS,
position: getBallStartLocation(),
collisionless: true,
visible: false,
};
ballSpawningAnchor = Entities.addEntity(properties);
};
var createBallDetector = function() {
var properties = {
name: 'Hifi Tilt Maze Ball Detector',
parentID: tiltMaze,
type: 'Box',
color: DEBUG_COLOR,
shapeType: 'none',
dimensions: BALL_DETECTOR_DIMENSIONS,
position: getBallFinishLocation(),
collisionless: true,
dynamic: false,
visible: false,
};
ballDetector = Entities.addEntity(properties);
};
var createTiltMaze = function(position) {
var properties = {
name: 'Hifi Tilt Maze',
type: 'Model',
modelURL: MAZE_MODEL_URL,
compoundShapeURL: MAZE_COLLISION_HULL,
dimensions: MAZE_DIMENSIONS,
position: position,
restitution: MAZE_RESTITUTION,
damping: MAZE_DAMPING,
angularDamping: MAZE_ANGULAR_DAMPING,
dynamic: true,
density: MAZE_DENSITY,
script: MAZE_SCRIPT
}
tiltMaze = Entities.addEntity(properties);
};
var createLightAtTheEnd = function() {
var mazeProps = Entities.getEntityProperties(tiltMaze, 'position');
var up = Quat.getUp(mazeProps.rotation);
var down = Vec3.multiply(-1, up);
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, down);
var position = getBallFinishLocation();
var lightProperties = {
parentID: tiltMaze,
name: 'Hifi Tilt Maze End Light',
type: "Light",
isSpotlight: true,
dimensions: END_LIGHT_DIMENSIONS,
color: END_LIGHT_COLOR,
intensity: END_LIGHT_INTENSITY,
exponent: END_LIGHT_EXPONENT,
cutoff: END_LIGHT_CUTOFF,
lifetime: -1,
position: position,
rotation: emitOrientation
};
lightAtTheEnd = Entities.addEntity(lightProperties);
};
var createAll = function() {
createTiltMaze(center);
createBallSpawningAnchor();
createBallDetector(center);
createBall(center);
createLightAtTheEnd();
Entities.editEntity(tiltMaze, {
userData: JSON.stringify({
tiltMaze: {
firstBall: ball,
ballSpawner: ballSpawningAnchor,
detector: ballDetector,
lightAtTheEnd: lightAtTheEnd
}
})
});
};
createAll();
if (CLEANUP === true) {
Script.scriptEnding.connect(function() {
Entities.deleteEntity(tiltMaze);
Entities.deleteEntity(ball);
Entities.deleteEntity(ballSpawningAnchor);
Entities.deleteEntity(lightAtTheEnd);
});
};

View file

@ -0,0 +1,212 @@
//
// maze.js
//
//
// Created by James B. Pollack @imgntn on 2/15/2016
// Copyright 2016 High Fidelity, Inc.
//
// This script resets a ball to its original position when the ball enters it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
Script.include('../../../../libraries/utils.js');
var SCALE = 0.5;
var VICTORY_SOUND;
var BALL_DISTANCE_THRESHOLD = 1 * SCALE;
var BALL_DETECTOR_THRESHOLD = 0.075 * SCALE;
var BALL_FORWARD_OFFSET = -0.2 * SCALE;
var BALL_RIGHT_OFFSET = -0.4 * SCALE;
var BALL_VERTICAL_OFFSET = 0.02 * SCALE;
var BALL_FRICTION = 0.7;
var BALL_RESTITUTION = 0.1;
var BALL_DAMPING = 0.6;
var BALL_ANGULAR_DAMPING = 0.2;
var BALL_DENSITY = 1000;
var BALL_GRAVITY = {
x: 0,
y: -9.8,
z: 0
};
var BALL_DIMENSIONS = Vec3.multiply(SCALE, {
x: 0.05,
y: 0.05,
z: 0.05
});
var BALL_COLOR = {
red: 255,
green: 0,
blue: 0
};
var _this;
function Maze() {
_this = this;
return;
}
Maze.prototype = {
ball: null,
ballLocked: false,
preload: function(entityID) {
this.entityID = entityID;
VICTORY_SOUND = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/levelUp.wav");
},
startNearGrab: function() {
//check to make sure a ball is in range, otherwise create one
this.testBallDistance();
},
continueNearGrab: function() {
this.testWinDistance();
this.testBallDistance();
},
continueDistantGrab: function() {
this.testBallDistance();
this.testWinDistance();
},
releaseGrab: function() {
this.testBallDistance();
},
getBallStartLocation: function() {
var mazeProps = Entities.getEntityProperties(this.entityID);
var right = Quat.getRight(mazeProps.rotation);
var front = Quat.getFront(mazeProps.rotation);
var vertical = {
x: 0,
y: BALL_VERTICAL_OFFSET,
z: 0
};
var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET));
finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET));
var location = Vec3.sum(mazeProps.position, finalOffset);
return location;
},
createBall: function() {
if (this.ballLocked === true) {
return;
}
var properties = {
name: 'Hifi Tilt Maze Ball',
type: 'Sphere',
position: this.getBallStartLocation(),
dynamic: true,
collisionless: false,
friction: BALL_FRICTION,
restitution: BALL_RESTITUTION,
angularDamping: BALL_ANGULAR_DAMPING,
damping: BALL_DAMPING,
gravity: BALL_GRAVITY,
density: BALL_DENSITY,
color: BALL_COLOR,
dimensions: BALL_DIMENSIONS
};
this.ball = Entities.addEntity(properties);
},
destroyBall: function() {
var results = Entities.findEntities(MyAvatar.position, 10);
results.forEach(function(result) {
var props = Entities.getEntityProperties(result, ['name']);
var isAMazeBall = props.name.indexOf('Maze Ball');
if (isAMazeBall > -1 && result === _this.ball) {
Entities.deleteEntity(result);
}
})
},
testBallDistance: function() {
if (this.ballLocked === true) {
return;
}
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = null;
try {
data = JSON.parse(userData);
} catch (e) {
// print('error parsing json in maze userdata')
}
if (data === null) {
// print('data is null in userData')
return;
}
var ballPosition;
if (this.ball === null) {
this.ball = data.tiltMaze.firstBall;
ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position;
} else {
ballPosition = Entities.getEntityProperties(this.ball, 'position').position;
}
var ballSpawnerPosition = Entities.getEntityProperties(data.tiltMaze.ballSpawner, 'position').position;
var separation = Vec3.distance(ballPosition, ballSpawnerPosition);
if (separation > BALL_DISTANCE_THRESHOLD) {
this.destroyBall();
this.createBall();
}
},
testWinDistance: function() {
if (this.ballLocked === true) {
return;
}
// print('testing win distance')
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
var data = null;
try {
data = JSON.parse(userData);
} catch (e) {
// print('error parsing json in maze userdata')
}
if (data === null) {
// print('data is null in userData')
return;
}
var ballPosition;
if (this.ball === null) {
this.ball = data.tiltMaze.firstBall;
ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position;
} else {
ballPosition = Entities.getEntityProperties(this.ball, 'position').position;
}
var ballDetectorPosition = Entities.getEntityProperties(data.tiltMaze.detector, 'position').position;
var separation = Vec3.distance(ballPosition, ballDetectorPosition);
if (separation < BALL_DETECTOR_THRESHOLD) {
this.ballLocked = true;
this.destroyBall();
this.playVictorySound();
Script.setTimeout(function() {
_this.ballLocked = false;
_this.createBall();
}, 1500)
}
},
playVictorySound: function() {
var position = Entities.getEntityProperties(this.entityID, "position").position;
var audioProperties = {
volume: 0.25,
position: position
};
Audio.playSound(VICTORY_SOUND, audioProperties);
},
};
return new Maze();
});