Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/ghost')
-rw-r--r--intern/ghost/CMakeLists.txt14
-rw-r--r--intern/ghost/GHOST_C-api.h31
-rw-r--r--intern/ghost/GHOST_Types.h32
-rw-r--r--intern/ghost/intern/GHOST_C-api.cpp35
-rw-r--r--intern/ghost/intern/GHOST_DisplayManagerSDL.cpp3
-rw-r--r--intern/ghost/intern/GHOST_ImeWin32.cpp2
-rw-r--r--intern/ghost/intern/GHOST_SystemCocoa.mm9
-rw-r--r--intern/ghost/intern/GHOST_SystemPathsCocoa.mm166
-rw-r--r--intern/ghost/intern/GHOST_SystemPathsUnix.cpp21
-rw-r--r--intern/ghost/intern/GHOST_SystemPathsWin32.cpp3
-rw-r--r--intern/ghost/intern/GHOST_SystemWin32.cpp9
-rw-r--r--intern/ghost/intern/GHOST_SystemX11.cpp21
-rw-r--r--intern/ghost/intern/GHOST_Window.h7
-rw-r--r--intern/ghost/intern/GHOST_WindowViewCocoa.h8
-rw-r--r--intern/ghost/intern/GHOST_WindowX11.cpp8
-rw-r--r--intern/ghost/intern/GHOST_Wintab.cpp53
-rw-r--r--intern/ghost/intern/GHOST_Wintab.h16
-rw-r--r--intern/ghost/intern/GHOST_XrAction.cpp5
-rw-r--r--intern/ghost/intern/GHOST_XrContext.cpp5
-rw-r--r--intern/ghost/intern/GHOST_XrControllerModel.cpp617
-rw-r--r--intern/ghost/intern/GHOST_XrControllerModel.h59
-rw-r--r--intern/ghost/intern/GHOST_XrSession.cpp98
-rw-r--r--intern/ghost/intern/GHOST_XrSession.h8
-rw-r--r--intern/ghost/test/CMakeLists.txt2
24 files changed, 1030 insertions, 202 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 76cac1049fb..84f156949aa 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -245,10 +245,10 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
if(WITH_X11_XF86VMODE)
add_definitions(-DWITH_X11_XF86VMODE)
list(APPEND INC_SYS
- ${X11_xf86vmode_INCLUDE_PATH}
+ ${X11_Xxf86vmode_INCLUDE_PATH}
)
list(APPEND LIB
- ${X11_Xf86vmode_LIB}
+ ${X11_Xxf86vmode_LIB}
)
endif()
@@ -473,6 +473,7 @@ if(WITH_XR_OPENXR)
intern/GHOST_Xr.cpp
intern/GHOST_XrAction.cpp
intern/GHOST_XrContext.cpp
+ intern/GHOST_XrControllerModel.cpp
intern/GHOST_XrEvent.cpp
intern/GHOST_XrGraphicsBinding.cpp
intern/GHOST_XrSession.cpp
@@ -482,13 +483,22 @@ if(WITH_XR_OPENXR)
intern/GHOST_IXrGraphicsBinding.h
intern/GHOST_XrAction.h
intern/GHOST_XrContext.h
+ intern/GHOST_XrControllerModel.h
intern/GHOST_XrException.h
intern/GHOST_XrSession.h
intern/GHOST_XrSwapchain.h
intern/GHOST_Xr_intern.h
intern/GHOST_Xr_openxr_includes.h
+
+ # Header only library.
+ ../../extern/tinygltf/tiny_gltf.h
+ )
+ list(APPEND INC
+ ../../extern/json/include
+ ../../extern/tinygltf
)
list(APPEND INC_SYS
+ ${EIGEN3_INCLUDE_DIRS}
${XR_OPENXR_SDK_INCLUDE_DIR}
)
list(APPEND LIB
diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index b78aac6f5eb..98094cc0669 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -729,13 +729,6 @@ extern GHOST_TSuccess GHOST_ReleaseOpenGLContext(GHOST_ContextHandle contexthand
extern unsigned int GHOST_GetContextDefaultOpenGLFramebuffer(GHOST_ContextHandle contexthandle);
/**
- * Returns whether a context is rendered upside down compared to OpenGL. This only needs to be
- * called if there's a non-OpenGL context, which is really the exception.
- * So generally, this does not need to be called.
- */
-extern int GHOST_isUpsideDownContext(GHOST_ContextHandle contexthandle);
-
-/**
* Get the OpenGL frame-buffer handle that serves as a default frame-buffer.
*/
extern unsigned int GHOST_GetDefaultOpenGLFramebuffer(GHOST_WindowHandle windwHandle);
@@ -1140,6 +1133,30 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_context,
const char *action_set_name,
void **r_customdata_array);
+/* controller model */
+/**
+ * Load the OpenXR controller model.
+ */
+int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
+
+/**
+ * Unload the OpenXR controller model.
+ */
+void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
+
+/**
+ * Update component transforms for the OpenXR controller model.
+ */
+int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_context,
+ const char *subaction_path);
+
+/**
+ * Get vertex data for the OpenXR controller model.
+ */
+int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
+ const char *subaction_path,
+ GHOST_XrControllerModelData *r_data);
+
#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index 221fa140f70..7fe9300ec3f 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -496,8 +496,6 @@ typedef struct {
int target_start;
/** Represents the position of the end of the selection */
int target_end;
- /** custom temporal data */
- GHOST_TUserDataPtr tmp;
} GHOST_TEventImeData;
typedef struct {
@@ -569,6 +567,7 @@ typedef enum {
GHOST_kUserSpecialDirMusic,
GHOST_kUserSpecialDirPictures,
GHOST_kUserSpecialDirVideos,
+ GHOST_kUserSpecialDirCaches,
/* Can be extended as needed. */
} GHOST_TUserSpecialDirTypes;
@@ -653,8 +652,8 @@ enum {
GHOST_kXrContextDebug = (1 << 0),
GHOST_kXrContextDebugTime = (1 << 1),
# ifdef WIN32
- /* Needed to avoid issues with the SteamVR OpenGL graphics binding (use DirectX fallback
- instead). */
+ /* Needed to avoid issues with the SteamVR OpenGL graphics binding
+ * (use DirectX fallback instead). */
GHOST_kXrContextGpuNVIDIA = (1 << 2),
# endif
};
@@ -753,8 +752,31 @@ typedef struct GHOST_XrActionProfileInfo {
const char *profile_path;
uint32_t count_subaction_paths;
const char **subaction_paths;
- /* Bindings for each subaction path. */
+ /** Bindings for each subaction path. */
const GHOST_XrActionBindingInfo *bindings;
} GHOST_XrActionProfileInfo;
+typedef struct GHOST_XrControllerModelVertex {
+ float position[3];
+ float normal[3];
+} GHOST_XrControllerModelVertex;
+
+typedef struct GHOST_XrControllerModelComponent {
+ /** World space transform. */
+ float transform[4][4];
+ uint32_t vertex_offset;
+ uint32_t vertex_count;
+ uint32_t index_offset;
+ uint32_t index_count;
+} GHOST_XrControllerModelComponent;
+
+typedef struct GHOST_XrControllerModelData {
+ uint32_t count_vertices;
+ const GHOST_XrControllerModelVertex *vertices;
+ uint32_t count_indices;
+ const uint32_t *indices;
+ uint32_t count_components;
+ const GHOST_XrControllerModelComponent *components;
+} GHOST_XrControllerModelData;
+
#endif /* WITH_XR_OPENXR */
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp
index a2871b46222..a21c3a90c06 100644
--- a/intern/ghost/intern/GHOST_C-api.cpp
+++ b/intern/ghost/intern/GHOST_C-api.cpp
@@ -1069,4 +1069,39 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_contexthandle,
xr_context);
}
+int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_contexthandle, const char *subaction_path)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XrSession *xr_session = xr_context->getSession();
+ GHOST_XR_CAPI_CALL_RET(xr_session->loadControllerModel(subaction_path), xr_context);
+ return 0;
+}
+
+void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_contexthandle,
+ const char *subaction_path)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XrSession *xr_session = xr_context->getSession();
+ GHOST_XR_CAPI_CALL(xr_session->unloadControllerModel(subaction_path), xr_context);
+}
+
+int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_contexthandle,
+ const char *subaction_path)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XrSession *xr_session = xr_context->getSession();
+ GHOST_XR_CAPI_CALL_RET(xr_session->updateControllerModelComponents(subaction_path), xr_context);
+ return 0;
+}
+
+int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle,
+ const char *subaction_path,
+ GHOST_XrControllerModelData *r_data)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XrSession *xr_session = xr_context->getSession();
+ GHOST_XR_CAPI_CALL_RET(xr_session->getControllerModelData(subaction_path, *r_data), xr_context);
+ return 0;
+}
+
#endif /* WITH_XR_OPENXR */
diff --git a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp
index 5b026eb1632..09b2e4dfe2b 100644
--- a/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp
+++ b/intern/ghost/intern/GHOST_DisplayManagerSDL.cpp
@@ -101,8 +101,7 @@ GHOST_TSuccess GHOST_DisplayManagerSDL::setCurrentDisplaySetting(
uint8_t display, const GHOST_DisplaySetting &setting)
{
/*
- * Mode switching code ported from Quake 2 version 3.21 and bzflag version
- * 2.4.0:
+ * Mode switching code ported from Quake 2 version 3.21 and BZFLAG version 2.4.0:
* ftp://ftp.idsoftware.com/idstuff/source/q2source-3.21.zip
* See linux/gl_glx.c:GLimp_SetMode
* http://wiki.bzflag.org/BZFlag_Source
diff --git a/intern/ghost/intern/GHOST_ImeWin32.cpp b/intern/ghost/intern/GHOST_ImeWin32.cpp
index 47b5f5688df..d1fc80adf56 100644
--- a/intern/ghost/intern/GHOST_ImeWin32.cpp
+++ b/intern/ghost/intern/GHOST_ImeWin32.cpp
@@ -106,7 +106,7 @@ bool GHOST_ImeWin32::IsImeKeyEvent(char ascii)
if (IsLanguage(IMELANG_JAPANESE) && (ascii >= ' ' && ascii <= '~')) {
return true;
}
- else if (IsLanguage(IMELANG_CHINESE) && ascii && strchr("!\"$'(),.:;<>?[\\]^_`", ascii)) {
+ else if (IsLanguage(IMELANG_CHINESE) && ascii && strchr("!\"$'(),.:;<>?[\\]^_`/", ascii)) {
return true;
}
}
diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm
index 189e663f91a..b92c3e73a88 100644
--- a/intern/ghost/intern/GHOST_SystemCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemCocoa.mm
@@ -116,8 +116,6 @@ static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
case kVK_ANSI_Z: return GHOST_kKeyZ;
#endif
/* Numbers keys: mapped to handle some int'l keyboard (e.g. French). */
- case kVK_ISO_Section:
- return GHOST_kKeyUnknown;
case kVK_ANSI_1:
return GHOST_kKey1;
case kVK_ANSI_2:
@@ -257,6 +255,7 @@ static GHOST_TKey convertKey(int rawCode, unichar recvChar, UInt16 keyAction)
case kVK_ANSI_LeftBracket: return GHOST_kKeyLeftBracket;
case kVK_ANSI_RightBracket: return GHOST_kKeyRightBracket;
case kVK_ANSI_Grave: return GHOST_kKeyAccentGrave;
+ case kVK_ISO_Section: return GHOST_kKeyUnknown;
#endif
case kVK_VolumeUp:
case kVK_VolumeDown:
@@ -1246,7 +1245,7 @@ GHOST_TSuccess GHOST_SystemCocoa::handleDraggingEvent(GHOST_TEventType eventType
/* Convert the image in a RGBA 32bit format */
/* As Core Graphics does not support contexts with non premutliplied alpha,
- we need to get alpha key values in a separate batch */
+ * we need to get alpha key values in a separate batch */
/* First get RGB values w/o Alpha to avoid pre-multiplication,
* 32bit but last byte is unused */
@@ -1480,8 +1479,8 @@ GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr)
CocoaWindow *cocoawindow;
/* [event window] returns other windows if mouse-over, that's OSX input standard
- however, if mouse exits window(s), the windows become inactive, until you click.
- We then fall back to the active window from ghost */
+ * however, if mouse exits window(s), the windows become inactive, until you click.
+ * We then fall back to the active window from ghost. */
window = (GHOST_WindowCocoa *)m_windowManager->getWindowAssociatedWithOSWindow(
(void *)[event window]);
if (!window) {
diff --git a/intern/ghost/intern/GHOST_SystemPathsCocoa.mm b/intern/ghost/intern/GHOST_SystemPathsCocoa.mm
index 3b29d5106f6..4032c145ab4 100644
--- a/intern/ghost/intern/GHOST_SystemPathsCocoa.mm
+++ b/intern/ghost/intern/GHOST_SystemPathsCocoa.mm
@@ -36,127 +36,101 @@ GHOST_SystemPathsCocoa::~GHOST_SystemPathsCocoa()
#pragma mark Base directories retrieval
-const char *GHOST_SystemPathsCocoa::getSystemDir(int, const char *versionstr) const
+static const char *GetApplicationSupportDir(const char *versionstr,
+ const NSSearchPathDomainMask mask,
+ char *tempPath,
+ const std::size_t len_tempPath)
{
- static char tempPath[512] = "";
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSString *basePath;
- NSArray *paths;
-
- paths = NSSearchPathForDirectoriesInDomains(
- NSApplicationSupportDirectory, NSLocalDomainMask, YES);
-
- if ([paths count] > 0)
- basePath = [paths objectAtIndex:0];
- else {
- [pool drain];
- return NULL;
- }
+ @autoreleasepool {
+ const NSArray *const paths = NSSearchPathForDirectoriesInDomains(
+ NSApplicationSupportDirectory, mask, YES);
- snprintf(tempPath,
- sizeof(tempPath),
- "%s/Blender/%s",
- [basePath cStringUsingEncoding:NSASCIIStringEncoding],
- versionstr);
-
- [pool drain];
+ if ([paths count] == 0) {
+ return NULL;
+ }
+ const NSString *const basePath = [paths objectAtIndex:0];
+
+ snprintf(tempPath,
+ len_tempPath,
+ "%s/Blender/%s",
+ [basePath cStringUsingEncoding:NSASCIIStringEncoding],
+ versionstr);
+ }
return tempPath;
}
-const char *GHOST_SystemPathsCocoa::getUserDir(int, const char *versionstr) const
+const char *GHOST_SystemPathsCocoa::getSystemDir(int, const char *versionstr) const
{
static char tempPath[512] = "";
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSString *basePath;
- NSArray *paths;
-
- paths = NSSearchPathForDirectoriesInDomains(
- NSApplicationSupportDirectory, NSUserDomainMask, YES);
-
- if ([paths count] > 0)
- basePath = [paths objectAtIndex:0];
- else {
- [pool drain];
- return NULL;
- }
-
- snprintf(tempPath,
- sizeof(tempPath),
- "%s/Blender/%s",
- [basePath cStringUsingEncoding:NSASCIIStringEncoding],
- versionstr);
+ return GetApplicationSupportDir(versionstr, NSLocalDomainMask, tempPath, sizeof(tempPath));
+}
- [pool drain];
- return tempPath;
+const char *GHOST_SystemPathsCocoa::getUserDir(int, const char *versionstr) const
+{
+ static char tempPath[512] = "";
+ return GetApplicationSupportDir(versionstr, NSUserDomainMask, tempPath, sizeof(tempPath));
}
const char *GHOST_SystemPathsCocoa::getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const
{
static char tempPath[512] = "";
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSString *basePath;
- NSArray *paths;
- NSSearchPathDirectory ns_directory;
-
- switch (type) {
- case GHOST_kUserSpecialDirDesktop:
- ns_directory = NSDesktopDirectory;
- break;
- case GHOST_kUserSpecialDirDocuments:
- ns_directory = NSDocumentDirectory;
- break;
- case GHOST_kUserSpecialDirDownloads:
- ns_directory = NSDownloadsDirectory;
- break;
- case GHOST_kUserSpecialDirMusic:
- ns_directory = NSMusicDirectory;
- break;
- case GHOST_kUserSpecialDirPictures:
- ns_directory = NSPicturesDirectory;
- break;
- case GHOST_kUserSpecialDirVideos:
- ns_directory = NSMoviesDirectory;
- break;
- default:
- GHOST_ASSERT(
- false,
- "GHOST_SystemPathsCocoa::getUserSpecialDir(): Invalid enum value for type parameter");
- [pool drain];
+ @autoreleasepool {
+ NSSearchPathDirectory ns_directory;
+
+ switch (type) {
+ case GHOST_kUserSpecialDirDesktop:
+ ns_directory = NSDesktopDirectory;
+ break;
+ case GHOST_kUserSpecialDirDocuments:
+ ns_directory = NSDocumentDirectory;
+ break;
+ case GHOST_kUserSpecialDirDownloads:
+ ns_directory = NSDownloadsDirectory;
+ break;
+ case GHOST_kUserSpecialDirMusic:
+ ns_directory = NSMusicDirectory;
+ break;
+ case GHOST_kUserSpecialDirPictures:
+ ns_directory = NSPicturesDirectory;
+ break;
+ case GHOST_kUserSpecialDirVideos:
+ ns_directory = NSMoviesDirectory;
+ break;
+ case GHOST_kUserSpecialDirCaches:
+ ns_directory = NSCachesDirectory;
+ break;
+ default:
+ GHOST_ASSERT(
+ false,
+ "GHOST_SystemPathsCocoa::getUserSpecialDir(): Invalid enum value for type parameter");
+ return NULL;
+ }
+
+ const NSArray *const paths = NSSearchPathForDirectoriesInDomains(
+ ns_directory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
return NULL;
- }
-
- paths = NSSearchPathForDirectoriesInDomains(ns_directory, NSUserDomainMask, YES);
+ }
+ const NSString *const basePath = [paths objectAtIndex:0];
- if ([paths count] > 0)
- basePath = [paths objectAtIndex:0];
- else {
- [pool drain];
- return NULL;
+ strncpy(tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding], sizeof(tempPath));
}
-
- strncpy(
- (char *)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding], sizeof(tempPath));
-
- [pool drain];
return tempPath;
}
const char *GHOST_SystemPathsCocoa::getBinaryDir() const
{
static char tempPath[512] = "";
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSString *basePath;
-
- basePath = [[NSBundle mainBundle] bundlePath];
- if (basePath == nil) {
- [pool drain];
- return NULL;
- }
+ @autoreleasepool {
+ const NSString *const basePath = [[NSBundle mainBundle] bundlePath];
- strcpy((char *)tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
+ if (basePath == nil) {
+ return NULL;
+ }
- [pool drain];
+ strcpy(tempPath, [basePath cStringUsingEncoding:NSASCIIStringEncoding]);
+ }
return tempPath;
}
diff --git a/intern/ghost/intern/GHOST_SystemPathsUnix.cpp b/intern/ghost/intern/GHOST_SystemPathsUnix.cpp
index b58799e9c2a..72396782752 100644
--- a/intern/ghost/intern/GHOST_SystemPathsUnix.cpp
+++ b/intern/ghost/intern/GHOST_SystemPathsUnix.cpp
@@ -114,6 +114,7 @@ const char *GHOST_SystemPathsUnix::getUserDir(int version, const char *versionst
const char *GHOST_SystemPathsUnix::getUserSpecialDir(GHOST_TUserSpecialDirTypes type) const
{
const char *type_str;
+ std::string add_path = "";
switch (type) {
case GHOST_kUserSpecialDirDesktop:
@@ -134,6 +135,18 @@ const char *GHOST_SystemPathsUnix::getUserSpecialDir(GHOST_TUserSpecialDirTypes
case GHOST_kUserSpecialDirVideos:
type_str = "VIDEOS";
break;
+ case GHOST_kUserSpecialDirCaches: {
+ const char *cache_dir = getenv("XDG_CACHE_HOME");
+ if (cache_dir) {
+ return cache_dir;
+ }
+ /* Fallback to ~home/.cache/.
+ * When invoking `xdg-user-dir` without parameters the user folder
+ * will be read. `.cache` will be appended. */
+ type_str = "";
+ add_path = ".cache";
+ break;
+ }
default:
GHOST_ASSERT(
false,
@@ -142,7 +155,7 @@ const char *GHOST_SystemPathsUnix::getUserSpecialDir(GHOST_TUserSpecialDirTypes
}
static string path = "";
- /* Pipe stderr to /dev/null to avoid error prints. We will fail gracefully still. */
+ /* Pipe `stderr` to `/dev/null` to avoid error prints. We will fail gracefully still. */
string command = string("xdg-user-dir ") + type_str + " 2> /dev/null";
FILE *fstream = popen(command.c_str(), "r");
@@ -152,7 +165,7 @@ const char *GHOST_SystemPathsUnix::getUserSpecialDir(GHOST_TUserSpecialDirTypes
std::stringstream path_stream;
while (!feof(fstream)) {
char c = fgetc(fstream);
- /* xdg-user-dir ends the path with '\n'. */
+ /* `xdg-user-dir` ends the path with '\n'. */
if (c == '\n') {
break;
}
@@ -163,6 +176,10 @@ const char *GHOST_SystemPathsUnix::getUserSpecialDir(GHOST_TUserSpecialDirTypes
return NULL;
}
+ if (!add_path.empty()) {
+ path_stream << '/' << add_path;
+ }
+
path = path_stream.str();
return path[0] ? path.c_str() : NULL;
}
diff --git a/intern/ghost/intern/GHOST_SystemPathsWin32.cpp b/intern/ghost/intern/GHOST_SystemPathsWin32.cpp
index 580cfcac7ba..bced552921f 100644
--- a/intern/ghost/intern/GHOST_SystemPathsWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemPathsWin32.cpp
@@ -100,6 +100,9 @@ const char *GHOST_SystemPathsWin32::getUserSpecialDir(GHOST_TUserSpecialDirTypes
case GHOST_kUserSpecialDirVideos:
folderid = FOLDERID_Videos;
break;
+ case GHOST_kUserSpecialDirCaches:
+ folderid = FOLDERID_LocalAppData;
+ break;
default:
GHOST_ASSERT(
false,
diff --git a/intern/ghost/intern/GHOST_SystemWin32.cpp b/intern/ghost/intern/GHOST_SystemWin32.cpp
index 482f20f5cd1..5251dd01b29 100644
--- a/intern/ghost/intern/GHOST_SystemWin32.cpp
+++ b/intern/ghost/intern/GHOST_SystemWin32.cpp
@@ -1002,10 +1002,10 @@ void GHOST_SystemWin32::processWintabEvent(GHOST_WindowWin32 *window)
DWORD pos = GetMessagePos();
int x = GET_X_LPARAM(pos);
int y = GET_Y_LPARAM(pos);
+ GHOST_TabletData td = wt->getLastTabletData();
- /* TODO supply tablet data */
system->pushEvent(new GHOST_EventCursor(
- system->getMilliSeconds(), GHOST_kEventCursorMove, window, x, y, GHOST_TABLET_DATA_NONE));
+ system->getMilliSeconds(), GHOST_kEventCursorMove, window, x, y, td));
}
}
@@ -1472,6 +1472,7 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
case WM_IME_SETCONTEXT: {
GHOST_ImeWin32 *ime = window->getImeInput();
ime->UpdateInputLanguage();
+ ime->UpdateConversionStatus(hwnd);
ime->CreateImeWindow(hwnd);
ime->CleanupComposition(hwnd);
ime->CheckFirst(hwnd);
@@ -1551,8 +1552,8 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
* button is press for menu. To prevent this we must return preventing DefWindowProc.
*
* Note that the four low-order bits of the wParam parameter are used internally by the
- * OS. To obtain the correct result when testing the value of wParam, an application
- * must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator.
+ * OS. To obtain the correct result when testing the value of wParam, an application must
+ * combine the value 0xFFF0 with the wParam value by using the bit-wise AND operator.
*/
switch (wParam & 0xFFF0) {
case SC_KEYMENU:
diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp
index c87b745cf40..85504bd94fb 100644
--- a/intern/ghost/intern/GHOST_SystemX11.cpp
+++ b/intern/ghost/intern/GHOST_SystemX11.cpp
@@ -714,7 +714,7 @@ bool GHOST_SystemX11::processEvents(bool waitForEvent)
anyProcessed = true;
#ifdef USE_UNITY_WORKAROUND
- /* note: processEvent() can't include this code because
+ /* NOTE: processEvent() can't include this code because
* KeymapNotify event have no valid window information. */
/* the X server generates KeymapNotify event immediately after
@@ -1044,7 +1044,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
#ifdef USE_NON_LATIN_KB_WORKAROUND
/* XXX: Code below is kinda awfully convoluted... Issues are:
- * - In keyboards like latin ones, numbers need a 'Shift' to be accessed but key_sym
+ * - In keyboards like Latin ones, numbers need a 'Shift' to be accessed but key_sym
* is unmodified (or anyone swapping the keys with `xmodmap`).
* - #XLookupKeysym seems to always use first defined key-map (see T47228), which generates
* key-codes unusable by ghost_key_from_keysym for non-Latin-compatible key-maps.
@@ -1131,7 +1131,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
}
}
#else
- /* In keyboards like latin ones,
+ /* In keyboards like Latin ones,
* numbers needs a 'Shift' to be accessed but key_sym
* is unmodified (or anyone swapping the keys with xmodmap).
*
@@ -1514,7 +1514,7 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
* around tablet surface */
window->GetTabletData().Active = xtablet.mode;
- /* Note: This event might be generated with incomplete data-set
+ /* NOTE: This event might be generated with incomplete data-set
* (don't exactly know why, looks like in some cases, if the value does not change,
* it is not included in subsequent #XDeviceMotionEvent events).
* So we have to check which values this event actually contains!
@@ -1528,13 +1528,13 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
window->GetTabletData().Pressure = axis_value / ((float)xtablet.PressureLevels);
}
- /* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
- * but I got garbage data without it. Found it in the xidump.c source --matt
+ /* NOTE(@broken): the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
+ * but I got garbage data without it. Found it in the `xidump.c` source.
*
- * The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
- * some drivers do not properly set the whole int value? Since we convert to float
- * afterward, I don't think we need to cast to short here, but do not have a device to
- * check this. --mont29
+ * NOTE(@mont29): The '& 0xffff' just truncates the value to its two lowest bytes,
+ * this probably means some drivers do not properly set the whole int value?
+ * Since we convert to float afterward,
+ * I don't think we need to cast to short here, but do not have a device to check this.
*/
if (AXIS_VALUE_GET(3, axis_value)) {
window->GetTabletData().Xtilt = (short)(axis_value & 0xffff) /
@@ -2278,6 +2278,7 @@ void GHOST_SystemX11::putClipboard(const char *buffer, bool selection) const
/* -------------------------------------------------------------------- */
/** \name Message Box
* \{ */
+
class DialogData {
public:
/* Width of the dialog. */
diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h
index f061e07b3c8..a3eb116780f 100644
--- a/intern/ghost/intern/GHOST_Window.h
+++ b/intern/ghost/intern/GHOST_Window.h
@@ -41,11 +41,10 @@ class GHOST_Window : public GHOST_IWindow {
* Constructor.
* Creates a new window and opens it.
* To check if the window was created properly, use the getValid() method.
- * \param width: The width the window.
- * \param heigh: The height the window.
+ * \param width: The width of the window.
+ * \param height: The height of the window.
* \param state: The state the window is initially opened with.
- * \param type: The type of drawing context installed in this window.
- * \param stereoVisual: Stereo visual for quad buffered stereo.
+ * \param wantStereoVisual: Stereo visual for quad buffered stereo.
* \param exclusive: Use to show the window ontop and ignore others (used full-screen).
*/
GHOST_Window(uint32_t width,
diff --git a/intern/ghost/intern/GHOST_WindowViewCocoa.h b/intern/ghost/intern/GHOST_WindowViewCocoa.h
index fa629528809..1bda59c3505 100644
--- a/intern/ghost/intern/GHOST_WindowViewCocoa.h
+++ b/intern/ghost/intern/GHOST_WindowViewCocoa.h
@@ -510,6 +510,14 @@
- (void)checkKeyCodeIsControlChar:(NSEvent *)event
{
ime.state_flag &= ~GHOST_IME_KEY_CONTROL_CHAR;
+
+ /* Don't use IME for command and ctrl key combinations, these are shortcuts. */
+ if ([event modifierFlags] & (NSEventModifierFlagCommand | NSEventModifierFlagControl)) {
+ ime.state_flag |= GHOST_IME_KEY_CONTROL_CHAR;
+ return;
+ }
+
+ /* Don't use IME for these control keys. */
switch ([event keyCode]) {
case kVK_ANSI_KeypadEnter:
case kVK_ANSI_KeypadClear:
diff --git a/intern/ghost/intern/GHOST_WindowX11.cpp b/intern/ghost/intern/GHOST_WindowX11.cpp
index de389951613..c77ef162d9a 100644
--- a/intern/ghost/intern/GHOST_WindowX11.cpp
+++ b/intern/ghost/intern/GHOST_WindowX11.cpp
@@ -544,7 +544,7 @@ void GHOST_WindowX11::refreshXInputDevices()
std::vector<XEventClass> xevents;
for (GHOST_SystemX11::GHOST_TabletX11 &xtablet : m_system->GetXTablets()) {
- /* With modern XInput (xlib 1.6.2 at least and/or evdev 2.9.0) and some 'no-name' tablets
+ /* With modern XInput (XLIB 1.6.2 at least and/or EVDEV 2.9.0) and some 'no-name' tablets
* like 'UC-LOGIC Tablet WP5540U', we also need to 'select' ButtonPress for motion event,
* otherwise we do not get any tablet motion event once pen is pressed... See T43367.
*/
@@ -1092,9 +1092,9 @@ GHOST_TSuccess GHOST_WindowX11::setOrder(GHOST_TWindowOrder order)
XWindowAttributes attr;
Atom atom;
- /* We use both XRaiseWindow and _NET_ACTIVE_WINDOW, since some
- * window managers ignore the former (e.g. kwin from kde) and others
- * don't implement the latter (e.g. fluxbox pre 0.9.9) */
+ /* We use both #XRaiseWindow and #_NET_ACTIVE_WINDOW, since some
+ * window managers ignore the former (e.g. KWIN from KDE) and others
+ * don't implement the latter (e.g. FLUXBOX before 0.9.9). */
XRaiseWindow(m_display, m_window);
diff --git a/intern/ghost/intern/GHOST_Wintab.cpp b/intern/ghost/intern/GHOST_Wintab.cpp
index cf0309b1521..953fcb171e5 100644
--- a/intern/ghost/intern/GHOST_Wintab.cpp
+++ b/intern/ghost/intern/GHOST_Wintab.cpp
@@ -130,8 +130,7 @@ GHOST_Wintab *GHOST_Wintab::loadWintab(HWND hwnd)
}
}
- return new GHOST_Wintab(hwnd,
- std::move(handle),
+ return new GHOST_Wintab(std::move(handle),
info,
get,
set,
@@ -174,8 +173,7 @@ void GHOST_Wintab::extractCoordinates(LOGCONTEXT &lc, Coord &tablet, Coord &syst
system.y.ext = -lc.lcSysExtY;
}
-GHOST_Wintab::GHOST_Wintab(HWND hwnd,
- unique_hmodule handle,
+GHOST_Wintab::GHOST_Wintab(unique_hmodule handle,
GHOST_WIN32_WTInfo info,
GHOST_WIN32_WTGet get,
GHOST_WIN32_WTSet set,
@@ -298,14 +296,12 @@ GHOST_TabletData GHOST_Wintab::getLastTabletData()
void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
{
const int numPackets = m_fpPacketsGet(m_context.get(), m_pkts.size(), m_pkts.data());
- outWintabInfo.resize(numPackets);
- size_t outExtent = 0;
+ outWintabInfo.reserve(numPackets);
for (int i = 0; i < numPackets; i++) {
PACKET pkt = m_pkts[i];
- GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
+ GHOST_WintabInfoWin32 out;
- out.tabletData = GHOST_TABLET_DATA_NONE;
/* % 3 for multiple devices ("DualTrack"). */
switch (pkt.pkCursor % 3) {
case 0:
@@ -328,12 +324,7 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
}
if ((m_maxAzimuth > 0) && (m_maxAltitude > 0)) {
- ORIENTATION ort = pkt.pkOrientation;
- float vecLen;
- float altRad, azmRad; /* In radians. */
-
- /*
- * From the wintab spec:
+ /* From the wintab spec:
* orAzimuth: Specifies the clockwise rotation of the cursor about the z axis through a
* full circular range.
* orAltitude: Specifies the angle with the x-y plane through a signed, semicircular range.
@@ -346,12 +337,14 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
* value.
*/
+ ORIENTATION ort = pkt.pkOrientation;
+
/* Convert raw fixed point data to radians. */
- altRad = (float)((fabs((float)ort.orAltitude) / (float)m_maxAltitude) * M_PI / 2.0);
- azmRad = (float)(((float)ort.orAzimuth / (float)m_maxAzimuth) * M_PI * 2.0);
+ float altRad = (float)((fabs((float)ort.orAltitude) / (float)m_maxAltitude) * M_PI / 2.0);
+ float azmRad = (float)(((float)ort.orAzimuth / (float)m_maxAzimuth) * M_PI * 2.0);
/* Find length of the stylus' projected vector on the XY plane. */
- vecLen = cos(altRad);
+ float vecLen = cos(altRad);
/* From there calculate X and Y components based on azimuth. */
out.tabletData.Xtilt = sin(azmRad) * vecLen;
@@ -362,13 +355,8 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
/* Some Wintab libraries don't handle relative button input, so we track button presses
* manually. */
- out.button = GHOST_kButtonMaskNone;
- out.type = GHOST_kEventCursorMove;
-
DWORD buttonsChanged = m_buttons ^ pkt.pkButtons;
WORD buttonIndex = 0;
- GHOST_WintabInfoWin32 buttonRef = out;
- int buttons = 0;
while (buttonsChanged) {
if (buttonsChanged & 1) {
@@ -376,23 +364,14 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
GHOST_TButtonMask button = mapWintabToGhostButton(pkt.pkCursor, buttonIndex);
if (button != GHOST_kButtonMaskNone) {
- /* Extend output if multiple buttons are pressed. We don't extend input until we confirm
- * a Wintab buttons maps to a system button. */
- if (buttons > 0) {
- outWintabInfo.resize(outWintabInfo.size() + 1);
- outExtent++;
- GHOST_WintabInfoWin32 &out = outWintabInfo[i + outExtent];
- out = buttonRef;
+ /* If this is not the first button found, push info for the prior Wintab button. */
+ if (out.button != GHOST_kButtonMaskNone) {
+ outWintabInfo.push_back(out);
}
- buttons++;
out.button = button;
- if (buttonsChanged & pkt.pkButtons) {
- out.type = GHOST_kEventButtonDown;
- }
- else {
- out.type = GHOST_kEventButtonUp;
- }
+ out.type = buttonsChanged & pkt.pkButtons ? GHOST_kEventButtonDown :
+ GHOST_kEventButtonUp;
}
m_buttons ^= 1 << buttonIndex;
@@ -401,6 +380,8 @@ void GHOST_Wintab::getInput(std::vector<GHOST_WintabInfoWin32> &outWintabInfo)
buttonsChanged >>= 1;
buttonIndex++;
}
+
+ outWintabInfo.push_back(out);
}
if (!outWintabInfo.empty()) {
diff --git a/intern/ghost/intern/GHOST_Wintab.h b/intern/ghost/intern/GHOST_Wintab.h
index 443c726d270..1994f057db9 100644
--- a/intern/ghost/intern/GHOST_Wintab.h
+++ b/intern/ghost/intern/GHOST_Wintab.h
@@ -56,11 +56,12 @@ typedef std::unique_ptr<std::remove_pointer_t<HMODULE>, decltype(&::FreeLibrary)
typedef std::unique_ptr<std::remove_pointer_t<HCTX>, GHOST_WIN32_WTClose> unique_hctx;
struct GHOST_WintabInfoWin32 {
- int32_t x, y;
- GHOST_TEventType type;
- GHOST_TButtonMask button;
- uint64_t time;
- GHOST_TabletData tabletData;
+ int32_t x = 0;
+ int32_t y = 0;
+ GHOST_TEventType type = GHOST_kEventCursorMove;
+ GHOST_TButtonMask button = GHOST_kButtonMaskNone;
+ uint64_t time = 0;
+ GHOST_TabletData tabletData = GHOST_TABLET_DATA_NONE;
};
class GHOST_Wintab {
@@ -148,7 +149,7 @@ class GHOST_Wintab {
* \param wtY: Wintab cursor y position.
* \return True if Win32 and Wintab cursor positions match within tolerance.
*
- * Note: Only test coordinates on button press, not release. This prevents issues when async
+ * NOTE: Only test coordinates on button press, not release. This prevents issues when async
* mismatch causes mouse movement to replay and snap back, which is only an issue while drawing.
*/
bool testCoordinates(int sysX, int sysY, int wtX, int wtY);
@@ -213,8 +214,7 @@ class GHOST_Wintab {
/** Most recently received tablet data, or none if pen is not in range. */
GHOST_TabletData m_lastTabletData = GHOST_TABLET_DATA_NONE;
- GHOST_Wintab(HWND hwnd,
- unique_hmodule handle,
+ GHOST_Wintab(unique_hmodule handle,
GHOST_WIN32_WTInfo info,
GHOST_WIN32_WTGet get,
GHOST_WIN32_WTSet set,
diff --git a/intern/ghost/intern/GHOST_XrAction.cpp b/intern/ghost/intern/GHOST_XrAction.cpp
index 704b1ce9fac..f51f98c9b3d 100644
--- a/intern/ghost/intern/GHOST_XrAction.cpp
+++ b/intern/ghost/intern/GHOST_XrAction.cpp
@@ -216,8 +216,9 @@ GHOST_XrAction::GHOST_XrAction(XrInstance instance,
XrActionCreateInfo action_info{XR_TYPE_ACTION_CREATE_INFO};
strcpy(action_info.actionName, info.name);
- strcpy(action_info.localizedActionName, info.name); /* Just use same name for localized. This can
- be changed in the future if necessary. */
+
+ /* Just use same name for localized. This can be changed in the future if necessary. */
+ strcpy(action_info.localizedActionName, info.name);
switch (info.type) {
case GHOST_kXrActionTypeBooleanInput:
diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp
index fe8fec052fe..15b40690d83 100644
--- a/intern/ghost/intern/GHOST_XrContext.cpp
+++ b/intern/ghost/intern/GHOST_XrContext.cpp
@@ -412,11 +412,14 @@ void GHOST_XrContext::getExtensionsToEnable(
try_ext.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
- /* Try enabling interaction profile extensions. */
+ /* Interaction profile extensions. */
try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME);
try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME);
try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME);
+ /* Controller model extension. */
+ try_ext.push_back(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME);
+
/* Varjo quad view extension. */
try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME);
diff --git a/intern/ghost/intern/GHOST_XrControllerModel.cpp b/intern/ghost/intern/GHOST_XrControllerModel.cpp
new file mode 100644
index 00000000000..aa46aaaf89a
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrControllerModel.cpp
@@ -0,0 +1,617 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup GHOST
+ */
+
+#include <cassert>
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include "GHOST_Types.h"
+#include "GHOST_XrException.h"
+#include "GHOST_Xr_intern.h"
+
+#include "GHOST_XrControllerModel.h"
+
+#define TINYGLTF_IMPLEMENTATION
+#define TINYGLTF_NO_STB_IMAGE
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#define STBIWDEF static inline
+#include "tiny_gltf.h"
+
+struct GHOST_XrControllerModelNode {
+ int32_t parent_idx = -1;
+ int32_t component_idx = -1;
+ float local_transform[4][4];
+};
+
+/* -------------------------------------------------------------------- */
+/** \name glTF Utilities
+ *
+ * Adapted from Microsoft OpenXR-Mixed Reality Samples (MIT License):
+ * https://github.com/microsoft/OpenXR-MixedReality
+ * \{ */
+
+struct GHOST_XrPrimitive {
+ std::vector<GHOST_XrControllerModelVertex> vertices;
+ std::vector<uint32_t> indices;
+};
+
+/**
+ * Validate that an accessor does not go out of bounds of the buffer view that it references and
+ * that the buffer view does not exceed the bounds of the buffer that it references
+ */
+static void validate_accessor(const tinygltf::Accessor &accessor,
+ const tinygltf::BufferView &buffer_view,
+ const tinygltf::Buffer &buffer,
+ size_t byte_stride,
+ size_t element_size)
+{
+ /* Make sure the accessor does not go out of range of the buffer view. */
+ if (accessor.byteOffset + (accessor.count - 1) * byte_stride + element_size >
+ buffer_view.byteLength) {
+ throw GHOST_XrException("glTF: Accessor goes out of range of bufferview.");
+ }
+
+ /* Make sure the buffer view does not go out of range of the buffer. */
+ if (buffer_view.byteOffset + buffer_view.byteLength > buffer.data.size()) {
+ throw GHOST_XrException("glTF: BufferView goes out of range of buffer.");
+ }
+}
+
+template<float (GHOST_XrControllerModelVertex::*field)[3]>
+static void read_vertices(const tinygltf::Accessor &accessor,
+ const tinygltf::BufferView &buffer_view,
+ const tinygltf::Buffer &buffer,
+ GHOST_XrPrimitive &primitive)
+{
+ if (accessor.type != TINYGLTF_TYPE_VEC3) {
+ throw GHOST_XrException(
+ "glTF: Accessor for primitive attribute has incorrect type (VEC3 expected).");
+ }
+
+ if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
+ throw GHOST_XrException(
+ "glTF: Accessor for primitive attribute has incorrect component type (FLOAT expected).");
+ }
+
+ /* If stride is not specified, it is tightly packed. */
+ constexpr size_t packed_size = sizeof(float) * 3;
+ const size_t stride = buffer_view.byteStride == 0 ? packed_size : buffer_view.byteStride;
+ validate_accessor(accessor, buffer_view, buffer, stride, packed_size);
+
+ /* Resize the vertices vector, if necessary, to include room for the attribute data.
+ * If there are multiple attributes for a primitive, the first one will resize, and the
+ * subsequent will not need to. */
+ primitive.vertices.resize(accessor.count);
+
+ /* Copy the attribute value over from the glTF buffer into the appropriate vertex field. */
+ const uint8_t *buffer_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset;
+ for (size_t i = 0; i < accessor.count; i++, buffer_ptr += stride) {
+ memcpy(primitive.vertices[i].*field, buffer_ptr, stride);
+ }
+}
+
+static void load_attribute_accessor(const tinygltf::Model &gltf_model,
+ const std::string &attribute_name,
+ int accessor_id,
+ GHOST_XrPrimitive &primitive)
+{
+ const auto &accessor = gltf_model.accessors.at(accessor_id);
+
+ if (accessor.bufferView == -1) {
+ throw GHOST_XrException("glTF: Accessor for primitive attribute specifies no bufferview.");
+ }
+
+ const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
+ if (buffer_view.target != TINYGLTF_TARGET_ARRAY_BUFFER && buffer_view.target != 0) {
+ throw GHOST_XrException(
+ "glTF: Accessor for primitive attribute uses bufferview with invalid 'target' type.");
+ }
+
+ const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
+
+ if (attribute_name.compare("POSITION") == 0) {
+ read_vertices<&GHOST_XrControllerModelVertex::position>(
+ accessor, buffer_view, buffer, primitive);
+ }
+ else if (attribute_name.compare("NORMAL") == 0) {
+ read_vertices<&GHOST_XrControllerModelVertex::normal>(
+ accessor, buffer_view, buffer, primitive);
+ }
+}
+
+/**
+ * Reads index data from a glTF primitive into a GHOST_XrPrimitive. glTF indices may be 8bit, 16bit
+ * or 32bit integers. This will coalesce indices from the source type(s) into a 32bit integer.
+ */
+template<typename TSrcIndex>
+static void read_indices(const tinygltf::Accessor &accessor,
+ const tinygltf::BufferView &buffer_view,
+ const tinygltf::Buffer &buffer,
+ GHOST_XrPrimitive &primitive)
+{
+
+ /* Allow 0 (not specified) even though spec doesn't seem to allow this (BoomBox GLB fails). */
+ if (buffer_view.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER && buffer_view.target != 0) {
+ throw GHOST_XrException(
+ "glTF: Accessor for indices uses bufferview with invalid 'target' type.");
+ }
+
+ constexpr size_t component_size_bytes = sizeof(TSrcIndex);
+ if (buffer_view.byteStride != 0 &&
+ buffer_view.byteStride !=
+ component_size_bytes) { /* Index buffer must be packed per glTF spec. */
+ throw GHOST_XrException(
+ "glTF: Accessor for indices uses bufferview with invalid 'byteStride'.");
+ }
+
+ validate_accessor(accessor, buffer_view, buffer, component_size_bytes, component_size_bytes);
+
+ /* Since only triangles are supported, enforce that the number of indices is divisible by 3. */
+ if ((accessor.count % 3) != 0) {
+ throw GHOST_XrException("glTF: Unexpected number of indices for triangle primitive");
+ }
+
+ const TSrcIndex *index_buffer = reinterpret_cast<const TSrcIndex *>(
+ buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset);
+ for (uint32_t i = 0; i < accessor.count; i++) {
+ primitive.indices.push_back(*(index_buffer + i));
+ }
+}
+
+/**
+ * Reads index data from a glTF primitive into a GHOST_XrPrimitive.
+ */
+static void load_index_accessor(const tinygltf::Model &gltf_model,
+ const tinygltf::Accessor &accessor,
+ GHOST_XrPrimitive &primitive)
+{
+ if (accessor.type != TINYGLTF_TYPE_SCALAR) {
+ throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'type'.");
+ }
+
+ if (accessor.bufferView == -1) {
+ throw GHOST_XrException("glTF: Index accessor without bufferView is currently not supported.");
+ }
+
+ const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
+ const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
+
+ if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
+ read_indices<uint8_t>(accessor, buffer_view, buffer, primitive);
+ }
+ else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
+ read_indices<uint16_t>(accessor, buffer_view, buffer, primitive);
+ }
+ else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
+ read_indices<uint32_t>(accessor, buffer_view, buffer, primitive);
+ }
+ else {
+ throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'componentType'.");
+ }
+}
+
+static GHOST_XrPrimitive read_primitive(const tinygltf::Model &gltf_model,
+ const tinygltf::Primitive &gltf_primitive)
+{
+ if (gltf_primitive.mode != TINYGLTF_MODE_TRIANGLES) {
+ throw GHOST_XrException(
+ "glTF: Unsupported primitive mode. Only TINYGLTF_MODE_TRIANGLES is supported.");
+ }
+
+ GHOST_XrPrimitive primitive;
+
+ /* glTF vertex data is stored in an attribute dictionary.Loop through each attribute and insert
+ * it into the GHOST_XrPrimitive. */
+ for (const auto &[attr_name, accessor_idx] : gltf_primitive.attributes) {
+ load_attribute_accessor(gltf_model, attr_name, accessor_idx, primitive);
+ }
+
+ if (gltf_primitive.indices != -1) {
+ /* If indices are specified for the glTF primitive, read them into the GHOST_XrPrimitive. */
+ load_index_accessor(gltf_model, gltf_model.accessors.at(gltf_primitive.indices), primitive);
+ }
+
+ return primitive;
+}
+
+/**
+ * Calculate node local and world transforms.
+ */
+static void calc_node_transforms(const tinygltf::Node &gltf_node,
+ const float parent_transform[4][4],
+ float r_local_transform[4][4],
+ float r_world_transform[4][4])
+{
+ /* A node may specify either a 4x4 matrix or TRS (Translation - Rotation - Scale) values, but not
+ * both. */
+ if (gltf_node.matrix.size() == 16) {
+ const std::vector<double> &dm = gltf_node.matrix;
+ float m[4][4] = {{(float)dm[0], (float)dm[1], (float)dm[2], (float)dm[3]},
+ {(float)dm[4], (float)dm[5], (float)dm[6], (float)dm[7]},
+ {(float)dm[8], (float)dm[9], (float)dm[10], (float)dm[11]},
+ {(float)dm[12], (float)dm[13], (float)dm[14], (float)dm[15]}};
+ memcpy(r_local_transform, m, sizeof(float) * 16);
+ }
+ else {
+ /* No matrix is present, so construct a matrix from the TRS values (each one is optional). */
+ std::vector<double> translation = gltf_node.translation;
+ std::vector<double> rotation = gltf_node.rotation;
+ std::vector<double> scale = gltf_node.scale;
+ Eigen::Matrix4f &m = *(Eigen::Matrix4f *)r_local_transform;
+ Eigen::Quaternionf q;
+ Eigen::Matrix3f scalemat;
+
+ if (translation.size() != 3) {
+ translation.resize(3);
+ translation[0] = translation[1] = translation[2] = 0.0;
+ }
+ if (rotation.size() != 4) {
+ rotation.resize(4);
+ rotation[0] = rotation[1] = rotation[2] = 0.0;
+ rotation[3] = 1.0;
+ }
+ if (scale.size() != 3) {
+ scale.resize(3);
+ scale[0] = scale[1] = scale[2] = 1.0;
+ }
+
+ q.w() = (float)rotation[3];
+ q.x() = (float)rotation[0];
+ q.y() = (float)rotation[1];
+ q.z() = (float)rotation[2];
+ q.normalize();
+
+ scalemat.setIdentity();
+ scalemat(0, 0) = (float)scale[0];
+ scalemat(1, 1) = (float)scale[1];
+ scalemat(2, 2) = (float)scale[2];
+
+ m.setIdentity();
+ m.block<3, 3>(0, 0) = q.toRotationMatrix() * scalemat;
+ m.block<3, 1>(0, 3) = Eigen::Vector3f(
+ (float)translation[0], (float)translation[1], (float)translation[2]);
+ }
+
+ *(Eigen::Matrix4f *)r_world_transform = *(Eigen::Matrix4f *)parent_transform *
+ *(Eigen::Matrix4f *)r_local_transform;
+}
+
+static void load_node(const tinygltf::Model &gltf_model,
+ int gltf_node_id,
+ int32_t parent_idx,
+ const float parent_transform[4][4],
+ const std::string &parent_name,
+ const std::vector<XrControllerModelNodePropertiesMSFT> &node_properties,
+ std::vector<GHOST_XrControllerModelVertex> &vertices,
+ std::vector<uint32_t> &indices,
+ std::vector<GHOST_XrControllerModelComponent> &components,
+ std::vector<GHOST_XrControllerModelNode> &nodes,
+ std::vector<int32_t> &node_state_indices)
+{
+ const tinygltf::Node &gltf_node = gltf_model.nodes.at(gltf_node_id);
+ float world_transform[4][4];
+
+ GHOST_XrControllerModelNode &node = nodes.emplace_back();
+ const int32_t node_idx = (int32_t)(nodes.size() - 1);
+ node.parent_idx = parent_idx;
+ calc_node_transforms(gltf_node, parent_transform, node.local_transform, world_transform);
+
+ for (size_t i = 0; i < node_properties.size(); ++i) {
+ if ((node_state_indices[i] < 0) && (parent_name == node_properties[i].parentNodeName) &&
+ (gltf_node.name == node_properties[i].nodeName)) {
+ node_state_indices[i] = node_idx;
+ break;
+ }
+ }
+
+ if (gltf_node.mesh != -1) {
+ const tinygltf::Mesh &gltf_mesh = gltf_model.meshes.at(gltf_node.mesh);
+
+ GHOST_XrControllerModelComponent &component = components.emplace_back();
+ node.component_idx = components.size() - 1;
+ memcpy(component.transform, world_transform, sizeof(component.transform));
+ component.vertex_offset = vertices.size();
+ component.index_offset = indices.size();
+
+ for (const tinygltf::Primitive &gltf_primitive : gltf_mesh.primitives) {
+ /* Read the primitive data from the glTF buffers. */
+ const GHOST_XrPrimitive primitive = read_primitive(gltf_model, gltf_primitive);
+
+ const size_t start_vertex = vertices.size();
+ size_t offset = start_vertex;
+ size_t count = primitive.vertices.size();
+ vertices.resize(offset + count);
+ memcpy(vertices.data() + offset,
+ primitive.vertices.data(),
+ count * sizeof(decltype(primitive.vertices)::value_type));
+
+ offset = indices.size();
+ count = primitive.indices.size();
+ indices.resize(offset + count);
+ for (size_t i = 0; i < count; i += 3) {
+ indices[offset + i + 0] = start_vertex + primitive.indices[i + 0];
+ indices[offset + i + 1] = start_vertex + primitive.indices[i + 2];
+ indices[offset + i + 2] = start_vertex + primitive.indices[i + 1];
+ }
+ }
+
+ component.vertex_count = vertices.size() - component.vertex_offset;
+ component.index_count = indices.size() - component.index_offset;
+ }
+
+ /* Recursively load all children. */
+ for (const int child_node_id : gltf_node.children) {
+ load_node(gltf_model,
+ child_node_id,
+ node_idx,
+ world_transform,
+ gltf_node.name,
+ node_properties,
+ vertices,
+ indices,
+ components,
+ nodes,
+ node_state_indices);
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name OpenXR Extension Functions
+ *
+ * \{ */
+
+static PFN_xrGetControllerModelKeyMSFT g_xrGetControllerModelKeyMSFT = nullptr;
+static PFN_xrLoadControllerModelMSFT g_xrLoadControllerModelMSFT = nullptr;
+static PFN_xrGetControllerModelPropertiesMSFT g_xrGetControllerModelPropertiesMSFT = nullptr;
+static PFN_xrGetControllerModelStateMSFT g_xrGetControllerModelStateMSFT = nullptr;
+static XrInstance g_instance = XR_NULL_HANDLE;
+
+#define INIT_EXTENSION_FUNCTION(name) \
+ CHECK_XR( \
+ xrGetInstanceProcAddr(instance, #name, reinterpret_cast<PFN_xrVoidFunction *>(&g_##name)), \
+ "Failed to get pointer to extension function: " #name);
+
+static void init_controller_model_extension_functions(XrInstance instance)
+{
+ if (instance != g_instance) {
+ g_instance = instance;
+ g_xrGetControllerModelKeyMSFT = nullptr;
+ g_xrLoadControllerModelMSFT = nullptr;
+ g_xrGetControllerModelPropertiesMSFT = nullptr;
+ g_xrGetControllerModelStateMSFT = nullptr;
+ }
+
+ if (g_xrGetControllerModelKeyMSFT == nullptr) {
+ INIT_EXTENSION_FUNCTION(xrGetControllerModelKeyMSFT);
+ }
+ if (g_xrLoadControllerModelMSFT == nullptr) {
+ INIT_EXTENSION_FUNCTION(xrLoadControllerModelMSFT);
+ }
+ if (g_xrGetControllerModelPropertiesMSFT == nullptr) {
+ INIT_EXTENSION_FUNCTION(xrGetControllerModelPropertiesMSFT);
+ }
+ if (g_xrGetControllerModelStateMSFT == nullptr) {
+ INIT_EXTENSION_FUNCTION(xrGetControllerModelStateMSFT);
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name GHOST_XrControllerModel
+ *
+ * \{ */
+
+GHOST_XrControllerModel::GHOST_XrControllerModel(XrInstance instance,
+ const char *subaction_path_str)
+{
+ init_controller_model_extension_functions(instance);
+
+ CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_path),
+ (std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
+}
+
+GHOST_XrControllerModel::~GHOST_XrControllerModel()
+{
+ if (m_load_task.valid()) {
+ m_load_task.wait();
+ }
+}
+
+void GHOST_XrControllerModel::load(XrSession session)
+{
+ if (m_data_loaded || m_load_task.valid()) {
+ return;
+ }
+
+ /* Get model key. */
+ XrControllerModelKeyStateMSFT key_state{XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT};
+ CHECK_XR(g_xrGetControllerModelKeyMSFT(session, m_subaction_path, &key_state),
+ "Failed to get controller model key state.");
+
+ if (key_state.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) {
+ m_model_key = key_state.modelKey;
+ /* Load asynchronously. */
+ m_load_task = std::async(std::launch::async,
+ [&, session = session]() { return loadControllerModel(session); });
+ }
+}
+
+void GHOST_XrControllerModel::loadControllerModel(XrSession session)
+{
+ /* Load binary buffers. */
+ uint32_t buf_size = 0;
+ CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, 0, &buf_size, nullptr),
+ "Failed to get controller model buffer size.");
+
+ std::vector<uint8_t> buf((size_t)buf_size);
+ CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, buf_size, &buf_size, buf.data()),
+ "Failed to load controller model binary buffers.");
+
+ /* Convert to glTF model. */
+ tinygltf::TinyGLTF gltf_loader;
+ tinygltf::Model gltf_model;
+ std::string err_msg;
+ {
+ /* Workaround for TINYGLTF_NO_STB_IMAGE define. Set custom image loader to prevent failure when
+ * parsing image data. */
+ auto load_img_func = [](tinygltf::Image *img,
+ const int p0,
+ std::string *p1,
+ std::string *p2,
+ int p3,
+ int p4,
+ const unsigned char *p5,
+ int p6,
+ void *user_pointer) -> bool {
+ (void)img;
+ (void)p0;
+ (void)p1;
+ (void)p2;
+ (void)p3;
+ (void)p4;
+ (void)p5;
+ (void)p6;
+ (void)user_pointer;
+ return true;
+ };
+ gltf_loader.SetImageLoader(load_img_func, nullptr);
+ }
+
+ if (!gltf_loader.LoadBinaryFromMemory(&gltf_model, &err_msg, nullptr, buf.data(), buf_size)) {
+ throw GHOST_XrException(("Failed to load glTF controller model: " + err_msg).c_str());
+ }
+
+ /* Get node properties. */
+ XrControllerModelPropertiesMSFT model_properties{XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT};
+ model_properties.nodeCapacityInput = 0;
+ CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
+ "Failed to get controller model node properties count.");
+
+ std::vector<XrControllerModelNodePropertiesMSFT> node_properties(
+ model_properties.nodeCountOutput, {XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT});
+ model_properties.nodeCapacityInput = (uint32_t)node_properties.size();
+ model_properties.nodeProperties = node_properties.data();
+ CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
+ "Failed to get controller model node properties.");
+
+ m_node_state_indices.resize(node_properties.size(), -1);
+
+ /* Get mesh vertex data. */
+ const tinygltf::Scene &default_scene = gltf_model.scenes.at(
+ (gltf_model.defaultScene == -1) ? 0 : gltf_model.defaultScene);
+ const int32_t root_idx = -1;
+ const std::string root_name = "";
+ float root_transform[4][4] = {{0}};
+ root_transform[0][0] = root_transform[1][1] = root_transform[2][2] = root_transform[3][3] = 1.0f;
+
+ for (const int node_id : default_scene.nodes) {
+ load_node(gltf_model,
+ node_id,
+ root_idx,
+ root_transform,
+ root_name,
+ node_properties,
+ m_vertices,
+ m_indices,
+ m_components,
+ m_nodes,
+ m_node_state_indices);
+ }
+
+ m_data_loaded = true;
+}
+
+void GHOST_XrControllerModel::updateComponents(XrSession session)
+{
+ if (!m_data_loaded) {
+ return;
+ }
+
+ /* Get node states. */
+ XrControllerModelStateMSFT model_state{XR_TYPE_CONTROLLER_MODEL_STATE_MSFT};
+ model_state.nodeCapacityInput = 0;
+ CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
+ "Failed to get controller model node state count.");
+
+ const uint32_t count = model_state.nodeCountOutput;
+ std::vector<XrControllerModelNodeStateMSFT> node_states(
+ count, {XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT});
+ model_state.nodeCapacityInput = count;
+ model_state.nodeStates = node_states.data();
+ CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
+ "Failed to get controller model node states.");
+
+ /* Update node local transforms. */
+ assert(m_node_state_indices.size() == count);
+
+ for (uint32_t state_idx = 0; state_idx < count; ++state_idx) {
+ const int32_t &node_idx = m_node_state_indices[state_idx];
+ if (node_idx >= 0) {
+ const XrPosef &pose = node_states[state_idx].nodePose;
+ Eigen::Matrix4f &m = *(Eigen::Matrix4f *)m_nodes[node_idx].local_transform;
+ Eigen::Quaternionf q(
+ pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z);
+ m.setIdentity();
+ m.block<3, 3>(0, 0) = q.toRotationMatrix();
+ m.block<3, 1>(0, 3) = Eigen::Vector3f(pose.position.x, pose.position.y, pose.position.z);
+ }
+ }
+
+ /* Calculate component transforms (in world space). */
+ std::vector<Eigen::Matrix4f> world_transforms(m_nodes.size());
+ uint32_t i = 0;
+ for (const GHOST_XrControllerModelNode &node : m_nodes) {
+ world_transforms[i] = (node.parent_idx >= 0) ? world_transforms[node.parent_idx] *
+ *(Eigen::Matrix4f *)node.local_transform :
+ *(Eigen::Matrix4f *)node.local_transform;
+ if (node.component_idx >= 0) {
+ memcpy(m_components[node.component_idx].transform,
+ world_transforms[i].data(),
+ sizeof(m_components[node.component_idx].transform));
+ }
+ ++i;
+ }
+}
+
+void GHOST_XrControllerModel::getData(GHOST_XrControllerModelData &r_data)
+{
+ if (m_data_loaded) {
+ r_data.count_vertices = (uint32_t)m_vertices.size();
+ r_data.vertices = m_vertices.data();
+ r_data.count_indices = (uint32_t)m_indices.size();
+ r_data.indices = m_indices.data();
+ r_data.count_components = (uint32_t)m_components.size();
+ r_data.components = m_components.data();
+ }
+ else {
+ r_data.count_vertices = 0;
+ r_data.vertices = nullptr;
+ r_data.count_indices = 0;
+ r_data.indices = nullptr;
+ r_data.count_components = 0;
+ r_data.components = nullptr;
+ }
+}
+
+/** \} */
diff --git a/intern/ghost/intern/GHOST_XrControllerModel.h b/intern/ghost/intern/GHOST_XrControllerModel.h
new file mode 100644
index 00000000000..a9aed961713
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrControllerModel.h
@@ -0,0 +1,59 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup GHOST
+ */
+
+/* NOTE: Requires OpenXR headers to be included before this one for OpenXR types (XrInstance,
+ * XrSession, etc.). */
+
+#pragma once
+
+#include <atomic>
+#include <future>
+#include <vector>
+
+struct GHOST_XrControllerModelNode;
+
+/**
+ * OpenXR glTF controller model.
+ */
+class GHOST_XrControllerModel {
+ public:
+ GHOST_XrControllerModel(XrInstance instance, const char *subaction_path);
+ ~GHOST_XrControllerModel();
+
+ void load(XrSession session);
+ void updateComponents(XrSession session);
+ void getData(GHOST_XrControllerModelData &r_data);
+
+ private:
+ XrPath m_subaction_path = XR_NULL_PATH;
+ XrControllerModelKeyMSFT m_model_key = XR_NULL_CONTROLLER_MODEL_KEY_MSFT;
+
+ std::future<void> m_load_task;
+ std::atomic<bool> m_data_loaded = false;
+
+ std::vector<GHOST_XrControllerModelVertex> m_vertices;
+ std::vector<uint32_t> m_indices;
+ std::vector<GHOST_XrControllerModelComponent> m_components;
+ std::vector<GHOST_XrControllerModelNode> m_nodes;
+ /** Maps node states to nodes. */
+ std::vector<int32_t> m_node_state_indices;
+
+ void loadControllerModel(XrSession session);
+};
diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp
index cd930c8328b..c68cb5992e3 100644
--- a/intern/ghost/intern/GHOST_XrSession.cpp
+++ b/intern/ghost/intern/GHOST_XrSession.cpp
@@ -30,6 +30,7 @@
#include "GHOST_IXrGraphicsBinding.h"
#include "GHOST_XrAction.h"
#include "GHOST_XrContext.h"
+#include "GHOST_XrControllerModel.h"
#include "GHOST_XrException.h"
#include "GHOST_XrSwapchain.h"
#include "GHOST_Xr_intern.h"
@@ -52,6 +53,8 @@ struct OpenXRSessionData {
std::vector<GHOST_XrSwapchain> swapchains;
std::map<std::string, GHOST_XrActionSet> action_sets;
+ /* Controller models identified by subaction path. */
+ std::map<std::string, GHOST_XrControllerModel> controller_models;
};
struct GHOST_XrDrawInfo {
@@ -124,7 +127,9 @@ void GHOST_XrSession::initSystem()
/** \name State Management
* \{ */
-static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose)
+static void create_reference_spaces(OpenXRSessionData &oxr,
+ const GHOST_XrPose &base_pose,
+ bool isDebugMode)
{
XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
create_info.poseInReferenceSpace.orientation.w = 1.0f;
@@ -160,10 +165,11 @@ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &
* since runtimes are not required to support the stage reference space. If the runtime
* doesn't support it then just fall back to the local space. */
if (result == XR_ERROR_REFERENCE_SPACE_UNSUPPORTED) {
- printf(
- "Warning: XR runtime does not support stage reference space, falling back to local "
- "reference space.\n");
-
+ if (isDebugMode) {
+ printf(
+ "Warning: XR runtime does not support stage reference space, falling back to local "
+ "reference space.\n");
+ }
create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
"Failed to create local reference space.");
@@ -179,11 +185,12 @@ static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &
CHECK_XR(xrGetReferenceSpaceBoundsRect(oxr.session, XR_REFERENCE_SPACE_TYPE_STAGE, &extents),
"Failed to get stage reference space bounds.");
if (extents.width == 0.0f || extents.height == 0.0f) {
- printf(
- "Warning: Invalid stage reference space bounds, falling back to local reference space. "
- "To use the stage reference space, please define a tracking space via the XR "
- "runtime.\n");
-
+ if (isDebugMode) {
+ printf(
+ "Warning: Invalid stage reference space bounds, falling back to local reference "
+ "space. To use the stage reference space, please define a tracking space via the XR "
+ "runtime.\n");
+ }
/* Fallback to local space. */
if (oxr.reference_space != XR_NULL_HANDLE) {
CHECK_XR(xrDestroySpace(oxr.reference_space), "Failed to destroy stage reference space.");
@@ -252,7 +259,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
"detailed error information to the command line.");
prepareDrawing();
- create_reference_spaces(*m_oxr, begin_info->base_pose);
+ create_reference_spaces(*m_oxr, begin_info->base_pose, m_context->isDebugMode());
/* Create and bind actions here. */
m_context->getCustomFuncs().session_create_fn();
@@ -300,6 +307,7 @@ GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent(
return SESSION_KEEP_ALIVE;
}
+
/** \} */ /* State Management */
/* -------------------------------------------------------------------- */
@@ -916,3 +924,71 @@ void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
}
/** \} */ /* Actions */
+
+/* -------------------------------------------------------------------- */
+/** \name Controller Model
+ *
+ * \{ */
+
+bool GHOST_XrSession::loadControllerModel(const char *subaction_path)
+{
+ if (!m_context->isExtensionEnabled(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME)) {
+ return false;
+ }
+
+ XrSession session = m_oxr->session;
+ std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
+ std::map<std::string, GHOST_XrControllerModel>::iterator it = controller_models.find(
+ subaction_path);
+
+ if (it == controller_models.end()) {
+ XrInstance instance = m_context->getInstance();
+ it = controller_models
+ .emplace(std::piecewise_construct,
+ std::make_tuple(subaction_path),
+ std::make_tuple(instance, subaction_path))
+ .first;
+ }
+
+ it->second.load(session);
+
+ return true;
+}
+
+void GHOST_XrSession::unloadControllerModel(const char *subaction_path)
+{
+ std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
+ if (controller_models.find(subaction_path) != controller_models.end()) {
+ controller_models.erase(subaction_path);
+ }
+}
+
+bool GHOST_XrSession::updateControllerModelComponents(const char *subaction_path)
+{
+ XrSession session = m_oxr->session;
+ std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
+ subaction_path);
+ if (it == m_oxr->controller_models.end()) {
+ return false;
+ }
+
+ it->second.updateComponents(session);
+
+ return true;
+}
+
+bool GHOST_XrSession::getControllerModelData(const char *subaction_path,
+ GHOST_XrControllerModelData &r_data)
+{
+ std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
+ subaction_path);
+ if (it == m_oxr->controller_models.end()) {
+ return false;
+ }
+
+ it->second.getData(r_data);
+
+ return true;
+}
+
+/** \} */ /* Controller Model */
diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h
index a76e11aede1..f5c0c04e65d 100644
--- a/intern/ghost/intern/GHOST_XrSession.h
+++ b/intern/ghost/intern/GHOST_XrSession.h
@@ -53,7 +53,7 @@ class GHOST_XrSession {
void draw(void *draw_customdata);
/** Action functions to be called pre-session start.
- * Note: The "destroy" functions can also be called post-session start. */
+ * NOTE: The "destroy" functions can also be called post-session start. */
bool createActionSet(const GHOST_XrActionSetInfo &info);
void destroyActionSet(const char *action_set_name);
bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos);
@@ -90,6 +90,12 @@ class GHOST_XrSession {
uint32_t getActionCount(const char *action_set_name);
void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array);
+ /** Controller model functions. */
+ bool loadControllerModel(const char *subaction_path);
+ void unloadControllerModel(const char *subaction_path);
+ bool updateControllerModelComponents(const char *subaction_path);
+ bool getControllerModelData(const char *subaction_path, GHOST_XrControllerModelData &r_data);
+
private:
/** Pointer back to context managing this session. Would be nice to avoid, but needed to access
* custom callbacks set before session start. */
diff --git a/intern/ghost/test/CMakeLists.txt b/intern/ghost/test/CMakeLists.txt
index 37bb00332dd..c564085c774 100644
--- a/intern/ghost/test/CMakeLists.txt
+++ b/intern/ghost/test/CMakeLists.txt
@@ -292,7 +292,7 @@ target_link_libraries(multitest_c
guardedalloc_lib
wcwidth_lib
${OPENGL_gl_LIBRARY}
- ${FREETYPE_LIBRARY}
+ ${FREETYPE_LIBRARIES} ${BROTLI_LIBRARIES}
${ZLIB_LIBRARIES}
${CMAKE_DL_LIBS}
${PLATFORM_LINKLIBS}