summaryrefslogtreecommitdiffstats
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main.zig401
1 files changed, 357 insertions, 44 deletions
diff --git a/src/main.zig b/src/main.zig
index 67ce8f0..6bf546d 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -2,9 +2,11 @@ usingnamespace @import("xrvk.zig");
const std = @import("std");
const glm = @import("glm");
const gfx = @import("graphics.zig");
+const math = @import("math.zig");
const scene = @import("scene.zig");
const renderdoc = @import("renderdoc.zig");
-const Allocator = std.mem.Allocator;
+const mem = std.mem;
+const Allocator = mem.Allocator;
pub fn main() !void {
try renderdoc.load_api();
@@ -13,29 +15,259 @@ pub fn main() !void {
session.deinit();
}
-const Paths = struct {
- user_hand_right: xr.Path,
- user_hand_left: xr.Path,
- user_hand_head: xr.Path,
+const HandState = struct {
+ subaction_path: xr.Path,
+ handedness: []const u8,
+ space: xr.Space,
+
+ active: bool,
+ grab: ?f32,
+ pose: ?glm.Mat4,
+ quit: ?bool,
+ vibrate: ?xr.HapticVibration,
+
+ pub fn init(path: xr.Path, handedness: []const u8, space: xr.Space) HandState {
+ return .{
+ .subaction_path = path,
+ .space = space,
+ .handedness = handedness,
+
+ .active = false,
+ .grab = null,
+ .pose = null,
+ .quit = null,
+ .vibrate = null,
+ };
+ }
+
+ pub fn sync(self: *HandState, actions: *const Actions) !void {
+ const xri = actions.xri;
+ const session = actions.session;
+
+ var grab = xr.ActionStateFloat.empty();
+ var pose = xr.ActionStatePose.empty();
+ var quit = xr.ActionStateBoolean.empty();
+
+ try xri.getActionStateFloat(session, .{
+ .action = actions.grab_object,
+ .subaction_path = self.subaction_path,
+ }, &grab);
+ self.grab = if (grab.is_active == xr.TRUE) grab.current_state else null;
+
+ try xri.getActionStatePose(session, .{
+ .action = actions.grip_pose,
+ .subaction_path = self.subaction_path,
+ }, &pose);
+ self.active = pose.is_active == xr.TRUE;
+
+ try xri.getActionStateBoolean(session, .{
+ .action = actions.quit_session,
+ .subaction_path = self.subaction_path,
+ }, &quit);
+ self.quit = if (quit.is_active == xr.TRUE) quit.current_state == xr.TRUE else null;
+ }
+
+ pub fn locate(
+ self: *HandState,
+ xri: xr.InstanceDispatch,
+ reference_space: xr.Space,
+ query_time: xr.Time,
+ ) !void {
+ if (!self.active) {
+ self.pose = null;
+ return;
+ }
+
+ var location = xr.SpaceLocation.empty();
+ try xri.locateSpace(self.space, reference_space, query_time, &location);
+
+ if (!location.location_flags.contains(.{
+ .position_valid_bit = true,
+ .orientation_valid_bit = true,
+ })) {
+ self.pose = null;
+ return;
+ }
+
+ self.pose = math.pose2mat(location.pose);
+ }
+
+ pub fn applyOutput(self: *const HandState, actions: *const Actions) !void {
+ if (self.vibrate) |vibration| {
+ try xri.applyHapticFeedback(
+ session,
+ .{
+ .action = self.actions.vibrate,
+ .subaction_path = self.subaction_path,
+ },
+ @ptrCast(*const xr.HapticBaseHeader, &vibration),
+ );
+ }
+ }
+};
- pub fn init(xri: xr.InstanceDispatch, inst: xr.Instance) !Paths {
- var self: Paths = undefined;
+const Actions = struct {
+ xri: xr.InstanceDispatch,
+ session: xr.Session,
+
+ set: xr.ActionSet,
+ grab_object: xr.Action,
+ grip_pose: xr.Action,
+ quit_session: xr.Action,
+ vibrate: xr.Action,
+
+ hands: [2]HandState,
+
+ pub fn init(
+ xri: xr.InstanceDispatch,
+ instance: xr.Instance,
+ session: xr.Session,
+ ) !Actions {
+ var self: Actions = undefined;
+ self.xri = xri;
+ self.session = session;
+
+ const subaction_strs = [_][:0]const u8{ "/user/hand/right", "/user/hand/left" };
+ const handedness_strs = [_][:0]const u8{ "right", "left" };
+ var subaction_paths: [subaction_strs.len]xr.Path = undefined;
+ for (subaction_strs) |str, i| {
+ subaction_paths[i] = try xri.stringToPath(instance, str);
+ }
+
+ var set_ci = xr.ActionSetCreateInfo{
+ .action_set_name = undefined,
+ .localized_action_set_name = undefined,
+ .priority = 0,
+ };
+ mem.copy(u8, set_ci.action_set_name[0..], "gameplay\x00");
+ mem.copy(u8, set_ci.localized_action_set_name[0..], "Gameplay\x00");
+ self.set = try xri.createActionSet(instance, set_ci);
+
+ {
+ // create actions
+ var action_ci = xr.ActionCreateInfo{
+ .action_type = undefined,
+ .action_name = undefined,
+ .localized_action_name = undefined,
+ .count_subaction_paths = subaction_paths.len,
+ .subaction_paths = &subaction_paths,
+ };
+
+ action_ci.action_type = .float_input;
+ mem.copy(u8, action_ci.action_name[0..], "grab_object\x00");
+ mem.copy(u8, action_ci.localized_action_name[0..], "Grab Object\x00");
+ self.grab_object = try xri.createAction(self.set, action_ci);
+
+ action_ci.action_type = .pose_input;
+ mem.copy(u8, action_ci.action_name[0..], "grip_pose\x00");
+ mem.copy(u8, action_ci.localized_action_name[0..], "Grip Pose\x00");
+ self.grip_pose = try xri.createAction(self.set, action_ci);
+
+ action_ci.action_type = .boolean_input;
+ mem.copy(u8, action_ci.action_name[0..], "quit_session\x00");
+ mem.copy(u8, action_ci.localized_action_name[0..], "Quit Session\x00");
+ self.quit_session = try xri.createAction(self.set, action_ci);
+
+ action_ci.action_type = .vibration_output;
+ mem.copy(u8, action_ci.action_name[0..], "vibrate_hand\x00");
+ mem.copy(u8, action_ci.localized_action_name[0..], "Vibrate Hand\x00");
+ self.vibrate = try xri.createAction(self.set, action_ci);
+ }
+
+ {
+ // suggested bindings
+ const PathStruct = struct {
+ squeeze_force: xr.Path,
+ pose: xr.Path,
+ b_click: xr.Path,
+ haptic: xr.Path,
+ };
+
+ var paths: [subaction_strs.len]PathStruct = undefined;
+ for (subaction_strs) |subaction, i| {
+ var path_buffer = [_]u8{0} ** 2048;
+ var string: [:0]const u8 = undefined;
+
+ string = try std.fmt.bufPrintZ(path_buffer[0..], "{}/input/squeeze/force", .{subaction});
+ const squeeze_force = try xri.stringToPath(instance, string.ptr);
+
+ string = try std.fmt.bufPrintZ(path_buffer[0..], "{}/input/grip/pose", .{subaction});
+ const pose = try xri.stringToPath(instance, string.ptr);
+
+ string = try std.fmt.bufPrintZ(path_buffer[0..], "{}/input/b/click", .{subaction});
+ const b_click = try xri.stringToPath(instance, string.ptr);
+
+ string = try std.fmt.bufPrintZ(path_buffer[0..], "{}/output/haptic", .{subaction});
+ const haptic = try xri.stringToPath(instance, string.ptr);
+
+ paths[i] = .{
+ .squeeze_force = squeeze_force,
+ .pose = pose,
+ .b_click = b_click,
+ .haptic = haptic,
+ };
+ }
+
+ const index_profile = try xri.stringToPath(instance, "/interaction_profiles/valve/index_controller");
+ const bindings = [_]xr.ActionSuggestedBinding{
+ .{ .action = self.grab_object, .binding = paths[0].squeeze_force },
+ .{ .action = self.grab_object, .binding = paths[1].squeeze_force },
+ .{ .action = self.grip_pose, .binding = paths[0].pose },
+ .{ .action = self.grip_pose, .binding = paths[1].pose },
+ .{ .action = self.quit_session, .binding = paths[0].b_click },
+ .{ .action = self.quit_session, .binding = paths[1].b_click },
+ .{ .action = self.vibrate, .binding = paths[0].haptic },
+ .{ .action = self.vibrate, .binding = paths[1].haptic },
+ };
+ try xri.suggestInteractionProfileBindings(instance, .{
+ .interaction_profile = index_profile,
+ .count_suggested_bindings = bindings.len,
+ .suggested_bindings = &bindings,
+ });
+ }
- inline for (@typeInfo(Paths).Struct.fields) |field| {
- var buffer: [field.name.len + 2:0]u8 = undefined;
- _ = std.mem.replace(u8, field.name, "_", "/", buffer[1..]);
- buffer[0] = '/';
- buffer[buffer.len - 1] = 0;
- @field(self, field.name) = try xri.stringToPath(inst, buffer[0..]);
+ for (subaction_paths) |path, i| {
+ const handedness = handedness_strs[i];
+ const space = try xri.createActionSpace(session, .{
+ .action = self.grip_pose,
+ .pose_in_action_space = .{
+ // "origin": [-0.005154, 0.013042, 0.107171],
+ // "rotate_xyz" : [93.782, 0.0, 0.0]
+ // .position = .{ .x = 0.005154, .y = -0.013042, .z = -0.107171 },
+ // .orientation = .{ .x = 0.7300549, .y = 0, .z = 0, .w = 0.6833885 },
+ },
+ .subaction_path = path,
+ });
+ self.hands[i] = HandState.init(path, handedness, space);
}
+ try xri.attachSessionActionSets(session, .{
+ .count_action_sets = 1,
+ .action_sets = @ptrCast([*]xr.ActionSet, &self.set),
+ });
+
return self;
}
+
+ pub fn sync(self: *Actions) !void {
+ const active_sets = [_]xr.ActiveActionSet{
+ .{ .action_set = self.set, .subaction_path = 0 },
+ };
+ _ = try self.xri.syncActions(self.session, .{
+ .count_active_action_sets = active_sets.len,
+ .active_action_sets = &active_sets,
+ });
+
+ for (self.hands) |*hand| {
+ try hand.sync(self);
+ }
+ }
};
const Session = struct {
xrb: xr.BaseDispatch,
xri: xr.InstanceDispatch,
+ xrcmi: ?xr.CMInstanceDispatch,
instance: xr.Instance,
session: xr.Session,
@@ -43,14 +275,14 @@ const Session = struct {
allocator: *Allocator,
+ actions: Actions,
state: xr.SessionState,
views: ?ViewInfo,
- paths: Paths,
graphics: gfx.Graphics,
layers: []*const xr.CompositionLayerBaseHeader,
scene_space: xr.Space,
- first_render: bool = true,
+ hand_models: [2]?*scene.Model,
const ViewInfo = struct {
configurations: []xr.ViewConfigurationView,
@@ -59,7 +291,23 @@ const Session = struct {
pub fn init(allocator: *Allocator) !Session {
var name: [128]u8 = undefined;
- std.mem.copy(u8, name[0..], "openxr-zig-test" ++ [_]u8{0});
+ mem.copy(u8, name[0..], "openxr-zig-test" ++ [_]u8{0});
+
+ const xrb = try xr.BaseDispatch.load(xr.getProcAddr);
+
+ const have_controller_model_ext = blk: {
+ var extensions = [_]xr.ExtensionProperties{ xr.ExtensionProperties.empty() } ** 512;
+ const extension_count = try xrb.enumerateInstanceExtensionProperties(null, extensions.len, &extensions);
+ for (extensions[0..extension_count]) |ext| {
+ const ext_name = @ptrCast([*:0]const u8, ext.extension_name[0..]);
+ if (mem.eql(u8, mem.spanZ(ext_name), "XR_MSFT_controller_model")) {
+ std.debug.print("found XR_MSFT_controller_model extension version {}\n", .{ ext.extension_version },);
+ break :blk true;
+ }
+ }
+
+ break :blk false;
+ };
const zero = [_:0]u8{};
const extensions = [_][*:0]const u8{
@@ -67,7 +315,6 @@ const Session = struct {
"XR_MSFT_controller_model",
};
- const xrb = try xr.BaseDispatch.load(xr.getProcAddr);
const instance = try xrb.createInstance(.{
.create_flags = .{},
.application_info = .{
@@ -79,14 +326,17 @@ const Session = struct {
},
.enabled_api_layer_count = 0,
.enabled_api_layer_names = @ptrCast([*]const [*:0]const u8, &zero),
- .enabled_extension_count = extensions.len,
+ .enabled_extension_count = if (have_controller_model_ext) 2 else 1,
.enabled_extension_names = &extensions,
});
const xri = try xr.InstanceDispatch.load(instance, xr.getProcAddr);
errdefer xri.destroyInstance(instance) catch unreachable;
- const paths = try Paths.init(xri, instance);
+ var xrcmi: ?xr.CMInstanceDispatch = null;
+ if (have_controller_model_ext) {
+ xrcmi = try xr.CMInstanceDispatch.load(instance, xr.getProcAddr);
+ }
const system = try xri.getSystem(instance, .{ .form_factor = .head_mounted_display });
@@ -126,6 +376,8 @@ const Session = struct {
});
errdefer xri.destroySession(session) catch unreachable;
+ const actions = try Actions.init(xri, instance, session);
+
const scene_space = try xri.createReferenceSpace(session, .{
.reference_space_type = .stage,
.pose_in_reference_space = .{
@@ -136,18 +388,21 @@ const Session = struct {
return Session{
.xrb = xrb,
.xri = xri,
+ .xrcmi = xrcmi,
.instance = instance,
.session = session,
.system = system,
.allocator = allocator,
+ .actions = actions,
.state = .idle,
.views = null,
- .paths = paths,
.graphics = graphics,
.scene_space = scene_space,
.layers = &[_]*const xr.CompositionLayerBaseHeader{},
+
+ .hand_models = [_]?*scene.Model{ null, null },
};
}
@@ -225,12 +480,12 @@ const Session = struct {
.projections = try self.allocator.alloc(xr.CompositionLayerProjectionView, view_count),
};
- std.mem.set(
+ mem.set(
xr.ViewConfigurationView,
views.configurations,
xr.ViewConfigurationView.empty(),
);
- std.mem.set(
+ mem.set(
xr.CompositionLayerProjectionView,
views.projections,
xr.CompositionLayerProjectionView.empty(),
@@ -251,30 +506,18 @@ const Session = struct {
try self.graphics.createSwapchain(self.session, views.configurations);
var gfx_scene = try scene.Scene.init(&self.graphics);
- var helmet = scene.Model.initFile(&gfx_scene, "assets/DamagedHelmet.gltf");
+ var helmet = scene.Model.initFile(self.allocator, &gfx_scene, "assets/DamagedHelmet.gltf");
helmet.setTransform(glm.translation(glm.Vec3.init([_]f32{ 0, 1.5, -2 })));
try gfx_scene.models.append(helmet);
- for ([_]xr.Path{ self.paths.user_hand_left, self.paths.user_hand_right }) |path, i| {
- var model_state = xr.ControllerModelKeyStateMSFT.empty();
- try self.xri.getControllerModelKeyMSFT(self.session, path, &model_state);
-
- if (model_state.model_key == 0)
- continue;
-
- const size = try self.xri.loadControllerModelMSFT(self.session, model_state.model_key, 0, null);
- const buffer = try self.allocator.alloc(u8, size);
- defer self.allocator.free(buffer);
-
- _ = try self.xri.loadControllerModelMSFT(self.session, model_state.model_key, size, buffer.ptr);
- var model = scene.Model.initMemory(&gfx_scene, buffer);
-
- var transform = glm.Mat4.IDENTITY;
- transform = transform.mul(glm.translation(glm.Vec3.init([_]f32{ 0.6 * (@intToFloat(f32, i) - 0.5), 1.5, -0.5 })));
- transform = transform.mul(glm.rotation(0.9, glm.Vec3.init([_]f32{ 1, 0, 0 })));
- model.setTransform(transform);
- try gfx_scene.models.append(model);
- }
+ helmet = scene.Model.initFile(self.allocator, &gfx_scene, "assets/controller_right.glb");
+ helmet.setTransform(
+ glm.translation(glm.Vec3.init([_]f32{ -0.07, 1.6, -0.2 }))
+ .mul(glm.rotation(0.8, glm.Vec3.init([_]f32{ 1, 0, 0 })))
+ );
+ try helmet.loadNodeTransform("r_button_b");
+ try helmet.loadNodeTransform("r_trackpad_touch");
+ try gfx_scene.models.append(helmet);
const composition_layer = try self.allocator.create(xr.CompositionLayerProjection);
composition_layer.* = .{
@@ -291,8 +534,12 @@ const Session = struct {
while (true) {
try self.pollEvents();
if (self.state == .stopping) break;
+ try self.actions.sync();
- if (!try self.renderFrame(&gfx_scene)) {
+ const did_render = try self.renderFrame(&gfx_scene);
+ if (!did_render) {
+ // no frame submitted, so compositor won't throttle by waiting for GPU
+ // sleep to prevent busy-loop
std.os.nanosleep(0, 250_000_000);
}
}
@@ -313,6 +560,38 @@ const Session = struct {
transform = transform.mul(glm.rotation(predicted_seconds, glm.Vec3.init([_]f32{ 0, 1, 0 })));
gfx_scene.models.items[0].setTransform(transform);
+ {
+ const model = &gfx_scene.models.items[1];
+ var txm = &model.node_transforms.items[0];
+ // motion.type == translate:l
+ // apply translation axis * lerp(in, value_mapping)
+ txm.position = glm.Vec3.init([_]f32{0, -0.927, 0.0375}).mulScalar(
+ (std.math.sin(predicted_seconds * 3) / 2 + 0.5) * 0.002
+ );
+
+ var tp_mot_rot = glm.Mat4.IDENTITY;
+ // motion.type == trackpad: only apply negative component_local transform
+ // apply translation axis * lerp(in, value_mapping)
+ tp_mot_rot.mulAssign(glm.rotation(1.18682389, glm.Vec3.init([_]f32{ 1, 0, 0 })));
+ tp_mot_rot.mulAssign(math.quat2mat(xr.Quaternionf{ .x=-0.17349398, .y=-0.79472193, .z=0.32537197, .w=-0.48213067 }));
+ tp_mot_rot.mulAssign(glm.rotation(-1.18682389, glm.Vec3.init([_]f32{ 1, 0, 0 })));
+
+ txm = &model.node_transforms.items[1];
+ txm.position =
+ tp_mot_rot.apply(
+ glm.Vec4.init([_]f32{
+ std.math.clamp(std.math.sin(predicted_seconds * 3) * 2, -1, 1) * 0.009,
+ std.math.clamp(std.math.cos(predicted_seconds * 3) * 2, -1, 1) * 0.015,
+ 0,
+ 1
+ })
+ ).shrink(3);
+ // txm.rotation = glm.Vec4.init([_]f32{ -0.17349398, -0.79472193, 0.32537197, -0.48213067 });
+
+ model.flushTransforms();
+ std.debug.print("transform pos: {}\n", .{ model.node_transforms.items[0].position });
+ }
+
var layer_count: u32 = 0;
if (frame_state.should_render > 0) {
@@ -331,6 +610,40 @@ const Session = struct {
return error.InvalidPositionOrOrientation;
}
+ for (self.actions.hands) |*hand, i| {
+ try hand.locate(self.xri, self.scene_space, frame_state.predicted_display_time);
+ if (hand.active and self.hand_models[i] == null) {
+ var model = if (self.xrcmi) |xri| model: {
+ var model_state = xr.ControllerModelKeyStateMSFT.empty();
+ try xri.getControllerModelKeyMSFT(self.session, hand.subaction_path, &model_state);
+
+ if (model_state.model_key == 0)
+ continue;
+
+ const size = try xri.loadControllerModelMSFT(self.session, model_state.model_key, 0, null);
+ const buffer = try self.allocator.alloc(u8, size);
+ defer self.allocator.free(buffer);
+
+ _ = try xri.loadControllerModelMSFT(self.session, model_state.model_key, size, buffer.ptr);
+ break :model scene.Model.initMemory(self.allocator, gfx_scene, buffer);
+ } else model: {
+ var path_buffer = [_]u8{0} ** 128;
+ const path = try std.fmt.bufPrintZ(path_buffer[0..], "assets/controller_{}.glb", .{hand.handedness});
+ break :model scene.Model.initFile(self.allocator, gfx_scene, path);
+ };
+
+ const ptr = try gfx_scene.models.addOne();
+ ptr.* = model;
+ self.hand_models[i] = ptr;
+ }
+
+ if (hand.pose) |pose| {
+ if (self.hand_models[i]) |model| {
+ model.setTransform(pose);
+ }
+ }
+ }
+
const swapchain = self.graphics.swapchain;
try swapchain.acquireImage();