XR Controller Support Step 4: Controller Drawing
Addresses T77127 (Controller Drawing). Adds VR controller visualization and custom drawing via draw handlers. Add-ons can draw to the XR surface (headset display) and mirror window by adding a View3D draw handler of region type 'XR' and draw type 'POST_VIEW'. Controller drawing and custom overlays can be toggled individually as XR session options, which will be added in a future update to the VR Scene Inspection add-on. For the actual drawing, the OpenXR XR_MSFT_controller_model extension is used to load a glTF model provided by the XR runtime. The model's vertex data is then used to create a GPUBatch in the XR session state. Finally, this batch is drawn via the XR surface draw handler mentioned above. For runtimes that do not support the controller model extension, a a simple fallback shape (sphere) is drawn instead. Reviewed By: Severin, fclem Differential Revision: https://developer.blender.org/D10948
This commit is contained in:
parent
cfa59b3fab
commit
9dda65455b
|
@ -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,19 @@ 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
|
||||
)
|
||||
list(APPEND INC
|
||||
../../extern/json/include
|
||||
../../extern/tinygltf
|
||||
)
|
||||
list(APPEND INC_SYS
|
||||
${EIGEN3_INCLUDE_DIRS}
|
||||
${XR_OPENXR_SDK_INCLUDE_DIR}
|
||||
)
|
||||
list(APPEND LIB
|
||||
|
|
|
@ -1140,6 +1140,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
|
||||
|
|
|
@ -754,8 +754,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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -0,0 +1,629 @@
|
|||
/*
|
||||
* 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)
|
||||
{
|
||||
if (buffer_view.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER &&
|
||||
buffer_view.target != 0) { /* Allow 0 (not specified) even though spec doesn't seem to allow
|
||||
this (BoomBox GLB fails). */
|
||||
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);
|
||||
|
||||
if ((accessor.count % 3) != 0) { /* Since only triangles are supported, enforce that the number
|
||||
of indices is divisible by 3. */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
|
@ -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);
|
||||
};
|
|
@ -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 {
|
||||
|
@ -916,3 +919,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 */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#include "BKE_pbvh.h"
|
||||
#include "BKE_pointcache.h"
|
||||
#include "BKE_pointcloud.h"
|
||||
#include "BKE_screen.h"
|
||||
#include "BKE_volume.h"
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
|
@ -1418,6 +1419,27 @@ void DRW_draw_callbacks_post_scene(void)
|
|||
|
||||
ED_region_draw_cb_draw(DST.draw_ctx.evil_C, DST.draw_ctx.region, REGION_DRAW_POST_VIEW);
|
||||
|
||||
#ifdef WITH_XR_OPENXR
|
||||
/* XR callbacks (controllers, custom draw functions) for session mirror. */
|
||||
if ((v3d->flag & V3D_XR_SESSION_MIRROR) != 0) {
|
||||
if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) {
|
||||
ARegionType *art = WM_xr_surface_controller_region_type_get();
|
||||
if (art) {
|
||||
ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW);
|
||||
}
|
||||
}
|
||||
if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) {
|
||||
SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D);
|
||||
if (st) {
|
||||
ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR);
|
||||
if (art) {
|
||||
ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Callback can be nasty and do whatever they want with the state.
|
||||
* Don't trust them! */
|
||||
DRW_state_reset();
|
||||
|
@ -1464,6 +1486,46 @@ void DRW_draw_callbacks_post_scene(void)
|
|||
ED_annotation_draw_view3d(DEG_get_input_scene(depsgraph), depsgraph, v3d, region, true);
|
||||
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
|
||||
}
|
||||
|
||||
#ifdef WITH_XR_OPENXR
|
||||
if ((v3d->flag & V3D_XR_SESSION_SURFACE) != 0) {
|
||||
DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
|
||||
|
||||
DRW_state_reset();
|
||||
|
||||
GPU_framebuffer_bind(dfbl->overlay_fb);
|
||||
|
||||
GPU_matrix_projection_set(rv3d->winmat);
|
||||
GPU_matrix_set(rv3d->viewmat);
|
||||
|
||||
/* XR callbacks (controllers, custom draw functions) for session surface. */
|
||||
if (((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) ||
|
||||
((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0)) {
|
||||
GPU_depth_test(GPU_DEPTH_NONE);
|
||||
GPU_apply_state();
|
||||
|
||||
if ((v3d->flag2 & V3D_XR_SHOW_CONTROLLERS) != 0) {
|
||||
ARegionType *art = WM_xr_surface_controller_region_type_get();
|
||||
if (art) {
|
||||
ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW);
|
||||
}
|
||||
}
|
||||
if ((v3d->flag2 & V3D_XR_SHOW_CUSTOM_OVERLAYS) != 0) {
|
||||
SpaceType *st = BKE_spacetype_from_id(SPACE_VIEW3D);
|
||||
if (st) {
|
||||
ARegionType *art = BKE_regiontype_from_id(st, RGN_TYPE_XR);
|
||||
if (art) {
|
||||
ED_region_surface_draw_cb_draw(art, REGION_DRAW_POST_VIEW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DRW_state_reset();
|
||||
}
|
||||
|
||||
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,8 +72,9 @@ void *ED_region_draw_cb_activate(struct ARegionType *art,
|
|||
void (*draw)(const struct bContext *, struct ARegion *, void *),
|
||||
void *customdata,
|
||||
int type);
|
||||
void ED_region_draw_cb_draw(const struct bContext *, struct ARegion *, int);
|
||||
void ED_region_draw_cb_exit(struct ARegionType *, void *);
|
||||
void ED_region_draw_cb_draw(const struct bContext *C, struct ARegion *region, int type);
|
||||
void ED_region_surface_draw_cb_draw(struct ARegionType *art, int type);
|
||||
void ED_region_draw_cb_exit(struct ARegionType *art, void *handle);
|
||||
void ED_region_draw_cb_remove_by_type(struct ARegionType *art,
|
||||
void *draw_fn,
|
||||
void (*free)(void *));
|
||||
|
|
|
@ -60,7 +60,7 @@ void ED_view3d_draw_offscreen(struct Depsgraph *depsgraph,
|
|||
void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph,
|
||||
struct Scene *scene,
|
||||
struct View3DShading *shading_override,
|
||||
int drawtype,
|
||||
eDrawType drawtype,
|
||||
int winx,
|
||||
int winy,
|
||||
unsigned int draw_flags,
|
||||
|
@ -68,6 +68,7 @@ void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph,
|
|||
const float winmat[4][4],
|
||||
float clip_start,
|
||||
float clip_end,
|
||||
bool is_xr_surface,
|
||||
bool is_image_render,
|
||||
bool draw_background,
|
||||
const char *viewname,
|
||||
|
|
|
@ -264,9 +264,9 @@ void ED_region_draw_cb_exit(ARegionType *art, void *handle)
|
|||
}
|
||||
}
|
||||
|
||||
void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type)
|
||||
static void ed_region_draw_cb_draw(const bContext *C, ARegion *region, ARegionType *art, int type)
|
||||
{
|
||||
LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, ®ion->type->drawcalls) {
|
||||
LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) {
|
||||
if (rdc->type == type) {
|
||||
rdc->draw(C, region, rdc->customdata);
|
||||
|
||||
|
@ -276,6 +276,16 @@ void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type)
|
|||
}
|
||||
}
|
||||
|
||||
void ED_region_draw_cb_draw(const bContext *C, ARegion *region, int type)
|
||||
{
|
||||
ed_region_draw_cb_draw(C, region, region->type, type);
|
||||
}
|
||||
|
||||
void ED_region_surface_draw_cb_draw(ARegionType *art, int type)
|
||||
{
|
||||
ed_region_draw_cb_draw(NULL, NULL, art, type);
|
||||
}
|
||||
|
||||
void ED_region_draw_cb_remove_by_type(ARegionType *art, void *draw_fn, void (*free)(void *))
|
||||
{
|
||||
LISTBASE_FOREACH_MUTABLE (RegionDrawCB *, rdc, &art->drawcalls) {
|
||||
|
|
|
@ -1798,5 +1798,10 @@ void ED_spacetype_view3d(void)
|
|||
art = ED_area_type_hud(st->spaceid);
|
||||
BLI_addhead(&st->regiontypes, art);
|
||||
|
||||
/* regions: xr */
|
||||
art = MEM_callocN(sizeof(ARegionType), "spacetype view3d xr region");
|
||||
art->regionid = RGN_TYPE_XR;
|
||||
BLI_addhead(&st->regiontypes, art);
|
||||
|
||||
BKE_spacetype_register(st);
|
||||
}
|
||||
|
|
|
@ -339,7 +339,16 @@ static void view3d_xr_mirror_setup(const wmWindowManager *wm,
|
|||
}
|
||||
view3d_main_region_setup_view(depsgraph, scene, v3d, region, viewmat, NULL, rect);
|
||||
|
||||
/* Reset overridden View3D data */
|
||||
/* Set draw flags. */
|
||||
SET_FLAG_FROM_TEST(v3d->flag2,
|
||||
(wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) != 0,
|
||||
V3D_XR_SHOW_CONTROLLERS);
|
||||
SET_FLAG_FROM_TEST(v3d->flag2,
|
||||
(wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) !=
|
||||
0,
|
||||
V3D_XR_SHOW_CUSTOM_OVERLAYS);
|
||||
|
||||
/* Reset overridden View3D data. */
|
||||
v3d->lens = lens_old;
|
||||
}
|
||||
#endif /* WITH_XR_OPENXR */
|
||||
|
@ -1757,6 +1766,7 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
|
|||
const float winmat[4][4],
|
||||
float clip_start,
|
||||
float clip_end,
|
||||
bool is_xr_surface,
|
||||
bool is_image_render,
|
||||
bool draw_background,
|
||||
const char *viewname,
|
||||
|
@ -1786,23 +1796,37 @@ void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
|
|||
v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS;
|
||||
}
|
||||
|
||||
if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) {
|
||||
v3d.flag2 |= V3D_SHOW_ANNOTATION;
|
||||
if ((draw_flags & ~V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS) == V3D_OFSDRAW_NONE) {
|
||||
v3d.flag2 = V3D_HIDE_OVERLAYS;
|
||||
}
|
||||
if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) {
|
||||
v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y;
|
||||
v3d.grid = 1.0f;
|
||||
v3d.gridlines = 16;
|
||||
v3d.gridsubdiv = 10;
|
||||
|
||||
/* Show grid, disable other overlays (set all available _HIDE_ flags). */
|
||||
else {
|
||||
if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) {
|
||||
v3d.flag2 |= V3D_SHOW_ANNOTATION;
|
||||
}
|
||||
if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) {
|
||||
v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y;
|
||||
v3d.grid = 1.0f;
|
||||
v3d.gridlines = 16;
|
||||
v3d.gridsubdiv = 10;
|
||||
}
|
||||
if (draw_flags & V3D_OFSDRAW_SHOW_SELECTION) {
|
||||
v3d.flag |= V3D_SELECT_OUTLINE;
|
||||
}
|
||||
if (draw_flags & V3D_OFSDRAW_XR_SHOW_CONTROLLERS) {
|
||||
v3d.flag2 |= V3D_XR_SHOW_CONTROLLERS;
|
||||
}
|
||||
if (draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) {
|
||||
v3d.flag2 |= V3D_XR_SHOW_CUSTOM_OVERLAYS;
|
||||
}
|
||||
/* Disable other overlays (set all available _HIDE_ flags). */
|
||||
v3d.overlay.flag |= V3D_OVERLAY_HIDE_CURSOR | V3D_OVERLAY_HIDE_TEXT |
|
||||
V3D_OVERLAY_HIDE_MOTION_PATHS | V3D_OVERLAY_HIDE_BONES |
|
||||
V3D_OVERLAY_HIDE_OBJECT_XTRAS | V3D_OVERLAY_HIDE_OBJECT_ORIGINS;
|
||||
v3d.flag |= V3D_HIDE_HELPLINES;
|
||||
}
|
||||
else {
|
||||
v3d.flag2 = V3D_HIDE_OVERLAYS;
|
||||
|
||||
if (is_xr_surface) {
|
||||
v3d.flag |= V3D_XR_SESSION_SURFACE;
|
||||
}
|
||||
|
||||
rv3d.persp = RV3D_PERSP;
|
||||
|
|
|
@ -666,8 +666,11 @@ typedef enum eRegion_Type {
|
|||
RGN_TYPE_EXECUTE = 10,
|
||||
RGN_TYPE_FOOTER = 11,
|
||||
RGN_TYPE_TOOL_HEADER = 12,
|
||||
/* Region type used exclusively by internal code and add-ons to register draw callbacks to the XR
|
||||
context (surface, mirror view). Does not represent any real region. */
|
||||
RGN_TYPE_XR = 13,
|
||||
|
||||
#define RGN_TYPE_LEN (RGN_TYPE_TOOL_HEADER + 1)
|
||||
#define RGN_TYPE_LEN (RGN_TYPE_XR + 1)
|
||||
} eRegion_Type;
|
||||
|
||||
/* use for function args */
|
||||
|
|
|
@ -30,6 +30,9 @@ typedef enum eV3DOffscreenDrawFlag {
|
|||
V3D_OFSDRAW_SHOW_ANNOTATION = (1 << 0),
|
||||
V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS = (1 << 1),
|
||||
V3D_OFSDRAW_SHOW_GRIDFLOOR = (1 << 2),
|
||||
V3D_OFSDRAW_SHOW_SELECTION = (1 << 3),
|
||||
V3D_OFSDRAW_XR_SHOW_CONTROLLERS = (1 << 4),
|
||||
V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS = (1 << 5),
|
||||
} eV3DOffscreenDrawFlag;
|
||||
|
||||
/** #View3DShading.light */
|
||||
|
|
|
@ -373,6 +373,7 @@ typedef struct View3D {
|
|||
#define V3D_HIDE_HELPLINES (1 << 2)
|
||||
#define V3D_FLAG_UNUSED_2 (1 << 3) /* cleared */
|
||||
#define V3D_XR_SESSION_MIRROR (1 << 4)
|
||||
#define V3D_XR_SESSION_SURFACE (1 << 5)
|
||||
|
||||
#define V3D_FLAG_UNUSED_10 (1 << 10) /* cleared */
|
||||
#define V3D_SELECT_OUTLINE (1 << 11)
|
||||
|
@ -465,6 +466,8 @@ enum {
|
|||
#define V3D_FLAG2_UNUSED_13 (1 << 13) /* cleared */
|
||||
#define V3D_FLAG2_UNUSED_14 (1 << 14) /* cleared */
|
||||
#define V3D_FLAG2_UNUSED_15 (1 << 15) /* cleared */
|
||||
#define V3D_XR_SHOW_CONTROLLERS (1 << 16)
|
||||
#define V3D_XR_SHOW_CUSTOM_OVERLAYS (1 << 17)
|
||||
|
||||
/** #View3D.gp_flag (short) */
|
||||
#define V3D_GP_FADE_OBJECTS (1 << 0) /* Fade all non GP objects */
|
||||
|
|
|
@ -42,7 +42,9 @@ typedef struct XrSessionSettings {
|
|||
|
||||
/** View3D draw flags (V3D_OFSDRAW_NONE, V3D_OFSDRAW_SHOW_ANNOTATION, ...). */
|
||||
char draw_flags;
|
||||
char _pad2[3];
|
||||
/** Draw style for controller visualization. */
|
||||
char controller_draw_style;
|
||||
char _pad2[2];
|
||||
|
||||
/** Clipping distance. */
|
||||
float clip_start, clip_end;
|
||||
|
@ -61,6 +63,13 @@ typedef enum eXRSessionBasePoseType {
|
|||
XR_BASE_POSE_CUSTOM = 2,
|
||||
} eXRSessionBasePoseType;
|
||||
|
||||
typedef enum eXrSessionControllerDrawStyle {
|
||||
XR_CONTROLLER_DRAW_DARK = 0,
|
||||
XR_CONTROLLER_DRAW_LIGHT = 1,
|
||||
XR_CONTROLLER_DRAW_DARK_RAY = 2,
|
||||
XR_CONTROLLER_DRAW_LIGHT_RAY = 3,
|
||||
} eXrSessionControllerDrawStyle;
|
||||
|
||||
/** XR action type. Enum values match those in GHOST_XrActionType enum for consistency. */
|
||||
typedef enum eXrActionType {
|
||||
XR_BOOLEAN_INPUT = 1,
|
||||
|
|
|
@ -46,6 +46,7 @@ const EnumPropertyItem rna_enum_region_type_items[] = {
|
|||
{RGN_TYPE_EXECUTE, "EXECUTE", 0, "Execute Buttons", ""},
|
||||
{RGN_TYPE_FOOTER, "FOOTER", 0, "Footer", ""},
|
||||
{RGN_TYPE_TOOL_HEADER, "TOOL_HEADER", 0, "Tool Header", ""},
|
||||
{RGN_TYPE_XR, "XR", 0, "XR", ""},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
|
|
|
@ -1559,6 +1559,22 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
|
|||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem controller_draw_styles[] = {
|
||||
{XR_CONTROLLER_DRAW_DARK, "DARK", 0, "Dark", "Draw dark controller"},
|
||||
{XR_CONTROLLER_DRAW_LIGHT, "LIGHT", 0, "Light", "Draw light controller"},
|
||||
{XR_CONTROLLER_DRAW_DARK_RAY,
|
||||
"DARK_RAY",
|
||||
0,
|
||||
"Dark + Ray",
|
||||
"Draw dark controller with aiming axis ray"},
|
||||
{XR_CONTROLLER_DRAW_LIGHT_RAY,
|
||||
"LIGHT_RAY",
|
||||
0,
|
||||
"Light + Ray",
|
||||
"Draw light controller with aiming axis ray"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
srna = RNA_def_struct(brna, "XrSessionSettings", NULL);
|
||||
RNA_def_struct_ui_text(srna, "XR Session Settings", "");
|
||||
|
||||
|
@ -1609,6 +1625,29 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
|
|||
RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "show_selection", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_SELECTION);
|
||||
RNA_def_property_ui_text(prop, "Show Selection", "Show selection outlines");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "show_controllers", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CONTROLLERS);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Show Controllers", "Show VR controllers (requires VR actions for controller poses)");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "show_custom_overlays", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS);
|
||||
RNA_def_property_ui_text(prop, "Show Custom Overlays", "Show custom VR overlays");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "controller_draw_style", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_enum_items(prop, controller_draw_styles);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Controller Draw Style", "Style to use when drawing VR controllers");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "clip_start", PROP_FLOAT, PROP_DISTANCE);
|
||||
RNA_def_property_range(prop, 1e-6f, FLT_MAX);
|
||||
RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
|
||||
|
|
|
@ -1013,6 +1013,8 @@ bool WM_xr_session_state_controller_aim_rotation_get(const wmXrData *xr,
|
|||
unsigned int subaction_idx,
|
||||
float r_rotation[4]);
|
||||
|
||||
struct ARegionType *WM_xr_surface_controller_region_type_get(void);
|
||||
|
||||
/* wm_xr_actions.c */
|
||||
/* XR action functions to be called pre-XR session start.
|
||||
* NOTE: The "destroy" functions can also be called post-session start. */
|
||||
|
|
|
@ -24,12 +24,17 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include "BKE_context.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
|
||||
#include "ED_view3d_offscreen.h"
|
||||
|
||||
#include "GHOST_C-api.h"
|
||||
#include "GPU_batch_presets.h"
|
||||
#include "GPU_immediate.h"
|
||||
#include "GPU_matrix.h"
|
||||
|
||||
#include "GPU_viewport.h"
|
||||
|
||||
|
@ -153,6 +158,7 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata)
|
|||
winmat,
|
||||
settings->clip_start,
|
||||
settings->clip_end,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
NULL,
|
||||
|
@ -172,3 +178,200 @@ void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata)
|
|||
|
||||
wm_xr_draw_viewport_buffers_to_active_framebuffer(xr_data->runtime, surface_data, draw_view);
|
||||
}
|
||||
|
||||
static GPUBatch *wm_xr_controller_model_batch_create(GHOST_XrContextHandle xr_context,
|
||||
const char *subaction_path)
|
||||
{
|
||||
GHOST_XrControllerModelData model_data;
|
||||
|
||||
if (!GHOST_XrGetControllerModelData(xr_context, subaction_path, &model_data) ||
|
||||
model_data.count_vertices < 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GPUVertFormat format = {0};
|
||||
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
|
||||
GPU_vertformat_attr_add(&format, "nor", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
|
||||
|
||||
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
|
||||
GPU_vertbuf_data_alloc(vbo, model_data.count_vertices);
|
||||
void *vbo_data = GPU_vertbuf_get_data(vbo);
|
||||
memcpy(
|
||||
vbo_data, model_data.vertices, model_data.count_vertices * sizeof(model_data.vertices[0]));
|
||||
|
||||
GPUIndexBuf *ibo = NULL;
|
||||
if (model_data.count_indices > 0 && ((model_data.count_indices % 3) == 0)) {
|
||||
GPUIndexBufBuilder ibo_builder;
|
||||
const unsigned int prim_len = model_data.count_indices / 3;
|
||||
GPU_indexbuf_init(&ibo_builder, GPU_PRIM_TRIS, prim_len, model_data.count_vertices);
|
||||
for (unsigned int i = 0; i < prim_len; ++i) {
|
||||
const uint32_t *idx = &model_data.indices[i * 3];
|
||||
GPU_indexbuf_add_tri_verts(&ibo_builder, idx[0], idx[1], idx[2]);
|
||||
}
|
||||
ibo = GPU_indexbuf_build(&ibo_builder);
|
||||
}
|
||||
|
||||
return GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, ibo, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
|
||||
}
|
||||
|
||||
static void wm_xr_controller_model_draw(const XrSessionSettings *settings,
|
||||
GHOST_XrContextHandle xr_context,
|
||||
wmXrSessionState *state)
|
||||
{
|
||||
GHOST_XrControllerModelData model_data;
|
||||
|
||||
float color[4];
|
||||
switch (settings->controller_draw_style) {
|
||||
case XR_CONTROLLER_DRAW_DARK:
|
||||
case XR_CONTROLLER_DRAW_DARK_RAY:
|
||||
color[0] = color[1] = color[2] = 0.0f, color[3] = 0.4f;
|
||||
break;
|
||||
case XR_CONTROLLER_DRAW_LIGHT:
|
||||
case XR_CONTROLLER_DRAW_LIGHT_RAY:
|
||||
color[0] = 0.422f, color[1] = 0.438f, color[2] = 0.446f, color[3] = 0.4f;
|
||||
break;
|
||||
}
|
||||
|
||||
GPU_depth_test(GPU_DEPTH_NONE);
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
|
||||
LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) {
|
||||
GPUBatch *model = controller->model;
|
||||
if (!model) {
|
||||
model = controller->model = wm_xr_controller_model_batch_create(xr_context,
|
||||
controller->subaction_path);
|
||||
}
|
||||
|
||||
if (model &&
|
||||
GHOST_XrGetControllerModelData(xr_context, controller->subaction_path, &model_data) &&
|
||||
model_data.count_components > 0) {
|
||||
GPU_batch_program_set_builtin(model, GPU_SHADER_3D_UNIFORM_COLOR);
|
||||
GPU_batch_uniform_4fv(model, "color", color);
|
||||
|
||||
GPU_matrix_push();
|
||||
GPU_matrix_mul(controller->grip_mat);
|
||||
for (unsigned int component_idx = 0; component_idx < model_data.count_components;
|
||||
++component_idx) {
|
||||
const GHOST_XrControllerModelComponent *component = &model_data.components[component_idx];
|
||||
GPU_matrix_push();
|
||||
GPU_matrix_mul(component->transform);
|
||||
GPU_batch_draw_range(model,
|
||||
model->elem ? component->index_offset : component->vertex_offset,
|
||||
model->elem ? component->index_count : component->vertex_count);
|
||||
GPU_matrix_pop();
|
||||
}
|
||||
GPU_matrix_pop();
|
||||
}
|
||||
else {
|
||||
/* Fallback. */
|
||||
const float scale = 0.05f;
|
||||
GPUBatch *sphere = GPU_batch_preset_sphere(2);
|
||||
GPU_batch_program_set_builtin(sphere, GPU_SHADER_3D_UNIFORM_COLOR);
|
||||
GPU_batch_uniform_4fv(sphere, "color", color);
|
||||
|
||||
GPU_matrix_push();
|
||||
GPU_matrix_mul(controller->grip_mat);
|
||||
GPU_matrix_scale_1f(scale);
|
||||
GPU_batch_draw(sphere);
|
||||
GPU_matrix_pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void wm_xr_controller_aim_draw(const XrSessionSettings *settings, wmXrSessionState *state)
|
||||
{
|
||||
bool draw_ray;
|
||||
switch (settings->controller_draw_style) {
|
||||
case XR_CONTROLLER_DRAW_DARK:
|
||||
case XR_CONTROLLER_DRAW_LIGHT:
|
||||
draw_ray = false;
|
||||
break;
|
||||
case XR_CONTROLLER_DRAW_DARK_RAY:
|
||||
case XR_CONTROLLER_DRAW_LIGHT_RAY:
|
||||
draw_ray = true;
|
||||
break;
|
||||
}
|
||||
|
||||
GPUVertFormat *format = immVertexFormat();
|
||||
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
|
||||
uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
|
||||
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_FLAT_COLOR);
|
||||
|
||||
float viewport[4];
|
||||
GPU_viewport_size_get_f(viewport);
|
||||
immUniform2fv("viewportSize", &viewport[2]);
|
||||
|
||||
immUniform1f("lineWidth", 3.0f * U.pixelsize);
|
||||
|
||||
if (draw_ray) {
|
||||
const uchar color[4] = {89, 89, 255, 127};
|
||||
const float scale = settings->clip_end;
|
||||
float ray[3];
|
||||
|
||||
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
|
||||
immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 2);
|
||||
|
||||
LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) {
|
||||
const float(*mat)[4] = controller->aim_mat;
|
||||
madd_v3_v3v3fl(ray, mat[3], mat[2], -scale);
|
||||
|
||||
immAttrSkip(col);
|
||||
immVertex3fv(pos, mat[3]);
|
||||
immAttr4ubv(col, color);
|
||||
immVertex3fv(pos, ray);
|
||||
}
|
||||
|
||||
immEnd();
|
||||
}
|
||||
else {
|
||||
const uchar r[4] = {255, 51, 82, 255};
|
||||
const uchar g[4] = {139, 220, 0, 255};
|
||||
const uchar b[4] = {40, 144, 255, 255};
|
||||
const float scale = 0.01f;
|
||||
float x_axis[3], y_axis[3], z_axis[3];
|
||||
|
||||
GPU_depth_test(GPU_DEPTH_NONE);
|
||||
GPU_blend(GPU_BLEND_NONE);
|
||||
|
||||
immBegin(GPU_PRIM_LINES, (uint)BLI_listbase_count(&state->controllers) * 6);
|
||||
|
||||
LISTBASE_FOREACH (wmXrController *, controller, &state->controllers) {
|
||||
const float(*mat)[4] = controller->aim_mat;
|
||||
madd_v3_v3v3fl(x_axis, mat[3], mat[0], scale);
|
||||
madd_v3_v3v3fl(y_axis, mat[3], mat[1], scale);
|
||||
madd_v3_v3v3fl(z_axis, mat[3], mat[2], scale);
|
||||
|
||||
immAttrSkip(col);
|
||||
immVertex3fv(pos, mat[3]);
|
||||
immAttr4ubv(col, r);
|
||||
immVertex3fv(pos, x_axis);
|
||||
|
||||
immAttrSkip(col);
|
||||
immVertex3fv(pos, mat[3]);
|
||||
immAttr4ubv(col, g);
|
||||
immVertex3fv(pos, y_axis);
|
||||
|
||||
immAttrSkip(col);
|
||||
immVertex3fv(pos, mat[3]);
|
||||
immAttr4ubv(col, b);
|
||||
immVertex3fv(pos, z_axis);
|
||||
}
|
||||
|
||||
immEnd();
|
||||
}
|
||||
|
||||
immUnbindProgram();
|
||||
}
|
||||
|
||||
void wm_xr_draw_controllers(const bContext *UNUSED(C), ARegion *UNUSED(region), void *customdata)
|
||||
{
|
||||
wmXrData *xr = customdata;
|
||||
const XrSessionSettings *settings = &xr->session_settings;
|
||||
GHOST_XrContextHandle xr_context = xr->runtime->context;
|
||||
wmXrSessionState *state = &xr->runtime->session_state;
|
||||
|
||||
wm_xr_controller_model_draw(settings, xr_context, state);
|
||||
wm_xr_controller_aim_draw(settings, state);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,11 @@ typedef struct wmXrViewportPair {
|
|||
typedef struct {
|
||||
/** Off-screen buffers/viewports for each view. */
|
||||
ListBase viewports; /* #wmXrViewportPair */
|
||||
|
||||
/** Dummy region type for controller draw callback. */
|
||||
struct ARegionType *controller_art;
|
||||
/** Controller draw callback handle. */
|
||||
void *controller_draw_handle;
|
||||
} wmXrSurfaceData;
|
||||
|
||||
typedef struct wmXrDrawData {
|
||||
|
@ -114,12 +119,16 @@ typedef struct wmXrController {
|
|||
/input/trigger/value, interaction_path = /user/hand/left/input/trigger/value).
|
||||
*/
|
||||
char subaction_path[64];
|
||||
/* Pose (in world space) that represents the user's hand when holding the controller.*/
|
||||
|
||||
/** Pose (in world space) that represents the user's hand when holding the controller. */
|
||||
GHOST_XrPose grip_pose;
|
||||
float grip_mat[4][4];
|
||||
/* Pose (in world space) that represents the controller's aiming source. */
|
||||
/** Pose (in world space) that represents the controller's aiming source. */
|
||||
GHOST_XrPose aim_pose;
|
||||
float aim_mat[4][4];
|
||||
|
||||
/** Controller model. */
|
||||
struct GPUBatch *model;
|
||||
} wmXrController;
|
||||
|
||||
typedef struct wmXrAction {
|
||||
|
@ -207,3 +216,4 @@ void wm_xr_session_controller_data_clear(wmXrSessionState *state);
|
|||
void wm_xr_pose_to_mat(const GHOST_XrPose *pose, float r_mat[4][4]);
|
||||
void wm_xr_pose_to_imat(const GHOST_XrPose *pose, float r_imat[4][4]);
|
||||
void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata);
|
||||
void wm_xr_draw_controllers(const struct bContext *C, struct ARegion *region, void *customdata);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "BKE_idprop.h"
|
||||
#include "BKE_main.h"
|
||||
#include "BKE_scene.h"
|
||||
#include "BKE_screen.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math.h"
|
||||
|
@ -36,9 +37,11 @@
|
|||
#include "DRW_engine.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
#include "ED_space_api.h"
|
||||
|
||||
#include "GHOST_C-api.h"
|
||||
|
||||
#include "GPU_batch.h"
|
||||
#include "GPU_viewport.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
@ -72,7 +75,15 @@ static void wm_xr_session_create_cb(void)
|
|||
|
||||
static void wm_xr_session_controller_data_free(wmXrSessionState *state)
|
||||
{
|
||||
BLI_freelistN(&state->controllers);
|
||||
ListBase *lb = &state->controllers;
|
||||
wmXrController *c;
|
||||
|
||||
while ((c = BLI_pophead(lb))) {
|
||||
if (c->model) {
|
||||
GPU_batch_discard(c->model);
|
||||
}
|
||||
BLI_freelinkN(lb, c);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_xr_session_data_free(wmXrSessionState *state)
|
||||
|
@ -529,6 +540,7 @@ static void wm_xr_session_controller_pose_calc(const GHOST_XrPose *raw_pose,
|
|||
static void wm_xr_session_controller_data_update(const XrSessionSettings *settings,
|
||||
const wmXrAction *grip_action,
|
||||
const wmXrAction *aim_action,
|
||||
GHOST_XrContextHandle xr_context,
|
||||
wmXrSessionState *state)
|
||||
{
|
||||
BLI_assert(grip_action->count_subaction_paths == aim_action->count_subaction_paths);
|
||||
|
@ -560,6 +572,16 @@ static void wm_xr_session_controller_data_update(const XrSessionSettings *settin
|
|||
base_mat,
|
||||
&controller->aim_pose,
|
||||
controller->aim_mat);
|
||||
|
||||
if (!controller->model) {
|
||||
/* Notify GHOST to load/continue loading the controller model data. This can be called more
|
||||
* than once since the model may not be available from the runtime yet. The batch itself will
|
||||
* be created in wm_xr_draw_controllers(). */
|
||||
GHOST_XrLoadControllerModel(xr_context, controller->subaction_path);
|
||||
}
|
||||
else {
|
||||
GHOST_XrUpdateControllerModelComponents(xr_context, controller->subaction_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1089,6 +1111,7 @@ void wm_xr_session_actions_update(wmWindowManager *wm)
|
|||
wm_xr_session_controller_data_update(&xr->session_settings,
|
||||
active_action_set->controller_grip_action,
|
||||
active_action_set->controller_aim_action,
|
||||
xr_context,
|
||||
state);
|
||||
}
|
||||
|
||||
|
@ -1125,11 +1148,33 @@ void wm_xr_session_controller_data_populate(const wmXrAction *grip_action,
|
|||
|
||||
BLI_addtail(controllers, controller);
|
||||
}
|
||||
|
||||
/* Activate draw callback. */
|
||||
if (g_xr_surface) {
|
||||
wmXrSurfaceData *surface_data = g_xr_surface->customdata;
|
||||
if (surface_data && !surface_data->controller_draw_handle) {
|
||||
if (surface_data->controller_art) {
|
||||
surface_data->controller_draw_handle = ED_region_draw_cb_activate(
|
||||
surface_data->controller_art, wm_xr_draw_controllers, xr, REGION_DRAW_POST_VIEW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wm_xr_session_controller_data_clear(wmXrSessionState *state)
|
||||
{
|
||||
wm_xr_session_controller_data_free(state);
|
||||
|
||||
/* Deactivate draw callback. */
|
||||
if (g_xr_surface) {
|
||||
wmXrSurfaceData *surface_data = g_xr_surface->customdata;
|
||||
if (surface_data && surface_data->controller_draw_handle) {
|
||||
if (surface_data->controller_art) {
|
||||
ED_region_draw_cb_exit(surface_data->controller_art, surface_data->controller_draw_handle);
|
||||
}
|
||||
surface_data->controller_draw_handle = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */ /* XR-Session Actions */
|
||||
|
@ -1255,6 +1300,11 @@ static void wm_xr_session_surface_free_data(wmSurface *surface)
|
|||
BLI_freelinkN(lb, vp);
|
||||
}
|
||||
|
||||
if (data->controller_art) {
|
||||
BLI_freelistN(&data->controller_art->drawcalls);
|
||||
MEM_freeN(data->controller_art);
|
||||
}
|
||||
|
||||
MEM_freeN(surface->customdata);
|
||||
|
||||
g_xr_surface = NULL;
|
||||
|
@ -1269,6 +1319,7 @@ static wmSurface *wm_xr_session_surface_create(void)
|
|||
|
||||
wmSurface *surface = MEM_callocN(sizeof(*surface), __func__);
|
||||
wmXrSurfaceData *data = MEM_callocN(sizeof(*data), "XrSurfaceData");
|
||||
data->controller_art = MEM_callocN(sizeof(*(data->controller_art)), "XrControllerRegionType");
|
||||
|
||||
surface->draw = wm_xr_session_surface_draw;
|
||||
surface->free_data = wm_xr_session_surface_free_data;
|
||||
|
@ -1278,6 +1329,7 @@ static wmSurface *wm_xr_session_surface_create(void)
|
|||
surface->ghost_ctx = DRW_xr_opengl_context_get();
|
||||
surface->gpu_ctx = DRW_xr_gpu_context_get();
|
||||
|
||||
data->controller_art->regionid = RGN_TYPE_XR;
|
||||
surface->customdata = data;
|
||||
|
||||
g_xr_surface = surface;
|
||||
|
@ -1311,4 +1363,14 @@ void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle UNUSED(contex
|
|||
WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
}
|
||||
|
||||
ARegionType *WM_xr_surface_controller_region_type_get(void)
|
||||
{
|
||||
if (g_xr_surface) {
|
||||
wmXrSurfaceData *data = g_xr_surface->customdata;
|
||||
return data->controller_art;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** \} */ /* XR-Session Surface */
|
||||
|
|
Loading…
Reference in New Issue