#include "../src/driver_droiddeck.cpp" #include #include void droiddeck::Presenter::AddTextures(const uint64_t[3], const int[3], uint32_t, uint32_t, uint32_t, uint32_t) {} void droiddeck::Presenter::RemoveTexture(uint64_t) {} void droiddeck::Presenter::Submit(const EyeFrame &) {} void droiddeck::Presenter::Shutdown() {} struct Host : IVRServerDriverHost { std::vector poses; const std::map *inputs = nullptr; std::vector> inputsAtPose; bool TrackedDeviceAdded(const char *, ETrackedDeviceClass, ITrackedDeviceServerDriver *) override { return true; } void TrackedDevicePoseUpdated(uint32_t, const DriverPose_t &p, uint32_t) override { poses.push_back(p); inputsAtPose.push_back(*inputs); } void VsyncEvent(double) override {} void VendorSpecificEvent(uint32_t, EVREventType, const VREvent_Data_t &, double) override {} bool IsExiting() override { return false; } bool PollNextEvent(VREvent_t *, uint32_t) override { return false; } void GetRawTrackedDevicePoses(float, TrackedDevicePose_t *, uint32_t) override {} void RequestRestart(const char *, const char *, const char *, const char *) override {} uint32_t GetFrameTimings(Compositor_FrameTiming *, uint32_t) override { return 0; } void SetDisplayEyeToHead(uint32_t, const HmdMatrix34_t &, const HmdMatrix34_t &) override {} void SetDisplayProjectionRaw(uint32_t, const HmdRect2_t &, const HmdRect2_t &) override {} void SetRecommendedRenderTargetSize(uint32_t, uint32_t, uint32_t) override {} }; struct Properties : IVRProperties { ETrackedPropertyError ReadPropertyBatch(PropertyContainerHandle_t, PropertyRead_t *, uint32_t) override { return TrackedProp_Success; } ETrackedPropertyError WritePropertyBatch(PropertyContainerHandle_t, PropertyWrite_t *, uint32_t) override { return TrackedProp_Success; } const char *GetPropErrorNameFromEnum(ETrackedPropertyError) override { return "success"; } PropertyContainerHandle_t TrackedDeviceToPropertyContainer(TrackedDeviceIndex_t id) override { return id+1; } }; struct Input : IVRDriverInput { std::map values; std::map handles; std::map failures; EVRInputError CreateBooleanComponent(PropertyContainerHandle_t, const char *name, VRInputComponentHandle_t *h) override { *h = values.size()+1; values[*h]=0; handles[name]=*h; return VRInputError_None; } EVRInputError UpdateBooleanComponent(VRInputComponentHandle_t h, bool v, double) override { if (failures[h] != VRInputError_None) return failures[h]; values[h]=v; return VRInputError_None; } EVRInputError CreateScalarComponent(PropertyContainerHandle_t c, const char *n, VRInputComponentHandle_t *h, EVRScalarType, EVRScalarUnits) override { return CreateBooleanComponent(c,n,h); } EVRInputError UpdateScalarComponent(VRInputComponentHandle_t h, float v, double) override { if (failures[h] != VRInputError_None) return failures[h]; values[h]=v; return VRInputError_None; } EVRInputError CreateHapticComponent(PropertyContainerHandle_t c, const char *n, VRInputComponentHandle_t *h) override { return CreateBooleanComponent(c,n,h); } EVRInputError CreateSkeletonComponent(PropertyContainerHandle_t, const char *, const char *, const char *, EVRSkeletalTrackingLevel, const VRBoneTransform_t *, uint32_t, VRInputComponentHandle_t *) override { return VRInputError_None; } EVRInputError UpdateSkeletonComponent(VRInputComponentHandle_t, EVRSkeletalMotionRange, const VRBoneTransform_t *, uint32_t) override { return VRInputError_None; } EVRInputError CreatePoseComponent(PropertyContainerHandle_t, const char *, VRInputComponentHandle_t *) override { return VRInputError_None; } EVRInputError UpdatePoseComponent(VRInputComponentHandle_t, const HmdMatrix34_t *, double) override { return VRInputError_None; } EVRInputError CreateEyeTrackingComponent(PropertyContainerHandle_t, const char *, VRInputComponentHandle_t *) override { return VRInputError_None; } EVRInputError UpdateEyeTrackingComponent(VRInputComponentHandle_t, const VREyeTrackingData_t *, double) override { return VRInputError_None; } }; struct DriverLog : IVRDriverLog { std::vector lines; void Log(const char *message) override { lines.emplace_back(message); } }; struct Context : IVRDriverContext { Host host; Input input; Properties properties; DriverLog log; Context() { host.inputs = &input.values; } void *GetGenericInterface(const char *name, EVRInitError *) override { if (!strcmp(name, IVRServerDriverHost_Version)) return &host; if (!strcmp(name, IVRDriverInput_Version)) return &input; if (!strcmp(name, IVRProperties_Version)) return &properties; if (!strcmp(name, IVRDriverLog_Version)) return &log; return nullptr; } DriverHandle_t GetDriverHandle() override { return 1; } }; int main() { Context context; VRDriverContext() = &context; CardboardController controller(false); controller.SetProfile(PadProfile::Vive); assert(controller.Activate(1) == VRInitError_None); const auto gripHandle=context.input.handles.at("/input/grip/click"); const auto clickHandle=context.input.handles.at("/input/trigger/click"); const auto triggerHandle=context.input.handles.at("/input/trigger/value"); const auto assertReleasedAtPose = [&] { const auto &inputs=context.host.inputsAtPose.back(); assert(inputs.at(gripHandle)==0 && inputs.at(clickHandle)==0 && inputs.at(triggerHandle)==0); }; dd_spatial_hand hand{}; for (int i=0; i<500; ++i) controller.UpdateOptical(hand, false, nullptr, 1000000000+i, 1, 1); assert(context.host.poses.size() == 500); assert(context.log.lines.size()==2); for (const auto &pose : context.host.poses) { assert(pose.deviceIsConnected && !pose.poseIsValid); assert(pose.result == TrackingResult_Running_OutOfRange); assert(pose.qRotation.w == 1); } hand.pose = {1000000000, {1,2,3}, {0,0,0,1}, DD_SPATIAL_VALID | DD_SPATIAL_TRIGGER_VALID | DD_SPATIAL_GRIP_VALID}; int64_t now=1000000000; uint64_t connection=1,origin=1; uint16_t epochs=0; const auto observe = [&](float trigger,uint32_t grip) { now+=80000000;hand.pose.time_ns=now;hand.trigger=trigger;hand.grip=grip; controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); }; const auto missing = [&](bool headValid,int64_t elapsed) { now+=elapsed;controller.UpdateOptical({},headValid,nullptr,now,connection,origin,epochs); }; const auto assertPressed = [&] { assert(context.host.poses.back().poseIsValid); assert(context.input.values.at(gripHandle)==1 && context.input.values.at(clickHandle)==1 && context.input.values.at(triggerHandle)==1); }; observe(0,0);observe(1,1);assertPressed(); now+=200000000;controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); assertPressed(); ++now;controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); assert(context.host.poses.back().deviceIsConnected && context.host.poses.back().poseIsValid); assertReleasedAtPose(); now=hand.pose.time_ns+350000000;controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); assert(context.host.poses.back().poseIsValid); assert(std::abs(context.host.poses.back().poseTimeOffset+.35)<1e-9); assertReleasedAtPose(); ++now;controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); assert(context.host.poses.back().deviceIsConnected && !context.host.poses.back().poseIsValid); assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); missing(true,50000000); assert(context.host.poses.back().poseIsValid && context.host.poses.back().vecPosition[0]==1); assert(std::abs(context.host.poses.back().poseTimeOffset+.05)<1e-9); assertReleasedAtPose(); missing(true,150000000); assert(context.host.poses.back().poseIsValid); assert(std::abs(context.host.poses.back().poseTimeOffset+.2)<1e-9); missing(true,150000000); assert(context.host.poses.back().poseIsValid); assert(std::abs(context.host.poses.back().poseTimeOffset+.35)<1e-9); assertReleasedAtPose(); missing(true,1);assert(!context.host.poses.back().poseIsValid); observe(1,1);assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); missing(false,10000000); assert(!context.host.poses.back().poseIsValid);assertReleasedAtPose(); missing(true,10000000);assert(!context.host.poses.back().poseIsValid); observe(0,0);observe(1,1);assertPressed(); epochs=1;observe(1,1); assert(context.host.poses.back().poseIsValid); assert(context.input.values.at(gripHandle)==1 && context.input.values.at(clickHandle)==0 && context.input.values.at(triggerHandle)==0); epochs=0x101;observe(1,1);assertReleasedAtPose(); hand.trigger=0;hand.grip=0; controller.UpdateOptical(hand,true,nullptr,now+1,connection,origin,epochs); observe(1,1);assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); ++origin;missing(true,10000000); assert(!context.host.poses.back().poseIsValid);assertReleasedAtPose(); observe(1,1);assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); ++connection;missing(true,10000000); assert(!context.host.poses.back().poseIsValid);assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); controller.Disconnect(); assert(!context.host.poses.back().deviceIsConnected);assertReleasedAtPose(); missing(true,10000000);assert(!context.host.poses.back().poseIsValid); observe(1,1);assertReleasedAtPose(); controller.Update(controller.GetPose(),nullptr,false,0); missing(true,10000000);assert(!context.host.poses.back().poseIsValid); PadState pad{};pad.axes[0]=16384;pad.buttons=PAD_SELECT; controller.UpdateOptical({},true,&pad,++now,connection,origin,epochs); assert(!context.host.poses.back().poseIsValid); assert(std::any_of(context.input.values.begin(),context.input.values.end(),[](const auto &v){ return v.second!=0; })); observe(0,0);observe(1,1);assertPressed(); context.input.failures[gripHandle]=VRInputError_IPCError; missing(false,10000000); assert(context.input.values.at(gripHandle)==1 && context.input.values.at(triggerHandle)==0); assert(context.log.lines.back().find("error=7/0/0")!=std::string::npos); const auto failedLogCount=context.log.lines.size(); for(int i=0;i<10;++i) missing(false,1); assert(context.log.lines.size()==failedLogCount); context.input.failures[gripHandle]=VRInputError_None; missing(false,10000000); assertReleasedAtPose(); assert(context.log.lines.back().find("error=0/0/0")!=std::string::npos); assert(context.log.lines.size()==failedLogCount+1); observe(1,1);assertReleasedAtPose(); observe(0,0);observe(1,1);assertPressed(); const dd_spatial_pose aimHead{now, {0,1.6f,0}, {0,0,0,1}, DD_SPATIAL_VALID}; controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs,&aimHead); assertPressed(); const auto aimed = controller.GetPose(); const auto expectedAim = ShoulderAim({0,1.6f,0}, {}, {1,2,3}, false, {}); assert(std::abs(aimed.qRotation.w - expectedAim.w) < 1e-6); assert(std::abs(aimed.qRotation.y - expectedAim.y) < 1e-6); assert(aimed.poseTimeOffset == 0); controller.UpdateOptical(hand,true,nullptr,now,connection,origin,epochs); assertPressed(); assert(controller.GetPose().qRotation.w == 1); const auto palmRotation=Normalize(AboutX(.4)*AboutY(-.6)); hand.pose.rotation[0]=palmRotation.x;hand.pose.rotation[1]=palmRotation.y; hand.pose.rotation[2]=palmRotation.z;hand.pose.rotation[3]=palmRotation.w; controller.UpdateOptical(hand,true,nullptr,now,connection,++origin,epochs); const auto normal=controller.GetPose(); assert(std::abs(normal.qRotation.w-palmRotation.w)<1e-6); assert(std::abs(normal.qRotation.x-palmRotation.x)<1e-6); assert(std::abs(normal.qRotation.y-palmRotation.y)<1e-6); assert(std::abs(normal.qRotation.z-palmRotation.z)<1e-6); assert(normal.vecPosition[0]==hand.pose.position[0] && normal.poseTimeOffset==0); controller.UpdateOptical({},true,nullptr,now+1,connection,origin,epochs,&aimHead); assertReleasedAtPose(); assert(!controller.GetPose().poseIsValid); controller.Deactivate(); for(const auto &value:context.input.values) assert(value.second==0); const auto poseCount=context.host.poses.size(); controller.UpdateOptical(hand,true,nullptr,now+1,connection,origin,epochs); assert(context.host.poses.size()==poseCount); HandController joyCon(false); assert(joyCon.Activate(2) == VRInitError_None); const auto joyTrigger = context.input.handles.at("/input/trigger/click"); droiddeck_joycon_hand joy{}; joy.rotation[0] = 1; joy.time_ns = now; joy.buttons = DROIDDECK_JOYCON_TRIGGER; droiddeck_spatialpose spatial{}; spatial.flags = DD_SPATIAL_ACTIVE | DD_SPATIAL_JOYCONS; spatial.connection = spatial.origin = 1; spatial.head = {now, {2, 1.6f, 3}, {0,0,0,1}, DD_SPATIAL_VALID}; spatial.hands[0].pose = {now, {1.8f, 1.2f, 2.6f}, {0,0,0,1}, DD_SPATIAL_VALID}; assert(joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial)); auto pose = joyCon.GetPose(); assert(pose.poseIsValid && context.input.values.at(joyTrigger) == 1); Quat expectedRotation; Vec3 expectedPosition; IndexRawPose(false, {}, {1.8f, 1.2f, 2.6f}, &expectedRotation, &expectedPosition); assert(std::abs(pose.vecPosition[0] - expectedPosition.x) < 1e-5); assert(std::abs(pose.qRotation.w - expectedRotation.w) < 1e-5); spatial.hands[0].pose.flags = 0; joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial); assert(joyCon.GetPose().poseIsValid && context.input.values.at(joyTrigger) == 1); const Quat turned = AboutY(.7); joy.rotation[0] = turned.w; joy.rotation[2] = turned.y; joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial); const auto beforeLoss = joyCon.GetPose().qRotation; spatial.head.flags = 0; joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial); assert(!joyCon.GetPose().poseIsValid && context.input.values.at(joyTrigger) == 0); assert(context.host.inputsAtPose.back().at(joyTrigger) == 0); spatial.head.flags = DD_SPATIAL_VALID; joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial); assert(std::abs(joyCon.GetPose().qRotation.w - beforeLoss.w) < 1e-5); assert(std::abs(joyCon.GetPose().qRotation.y - beforeLoss.y) < 1e-5); spatial.origin++; spatial.head.position[0] = -2; joyCon.Update(&joy, false, {1,0,0,0}, now, &spatial); assert(joyCon.GetPose().poseIsValid && joyCon.GetPose().vecPosition[0] < -2); joyCon.Update(nullptr, false, {1,0,0,0}, now, &spatial); assert(!joyCon.GetPose().deviceIsConnected && context.input.values.at(joyTrigger) == 0); puts("PASS: optical controller recovery and Luma Joy-Con camera, fallback, head loss and origin changes"); for (const PadProfile profile : {PadProfile::Index, PadProfile::Touch}) { auto &padContext = context; padContext.input.handles.clear(); CardboardController pad(true); pad.SetProfile(profile); assert(pad.Activate(2) == VRInitError_None); const auto &h = padContext.input.handles; const std::string stick = profile == PadProfile::Touch ? "/input/joystick" : "/input/thumbstick"; assert(!h.count("/input/trackpad/x") && h.count("/input/b/click") && h.count("/input/grip/value")); assert(h.count("/input/application_menu/click") == (profile == PadProfile::Touch)); DriverPose_t head{}; head.qRotation.w = 1; PadState state{}; state.buttons = PAD_A | PAD_RB; state.axes[2] = 32767; state.axes[4] = 255; pad.Update(head, &state, false, 0); const auto value = [&](const std::string &name) { return padContext.input.values.at(h.at(name)); }; assert(value("/input/a/click") == 1 && value("/input/b/click") == 0); assert(value(stick + "/x") == 1 && value(stick + "/touch") == 1 && value(stick + "/click") == 0); assert(value("/input/grip/value") == 1 && value("/input/trigger/value") == 1); pad.Update(head, &state, true, 0); assert(value(stick + "/x") == 0); state = {}; state.buttons = PAD_R3; pad.Update(head, &state, false, 0); assert(value(stick + "/click") == 1); state.buttons = PAD_L3 | PAD_R3; pad.Update(head, &state, false, 0); assert(value(stick + "/click") == 0); dd_spatial_hand optical{}; state = {}; state.buttons = PAD_A; pad.UpdateOptical(optical, false, &state, 1000000000, 1, 1); assert(value("/input/a/click") == 1 && value(stick + "/click") == 0 && value(stick + "/touch") == 0); state.buttons = PAD_B | PAD_R3; pad.UpdateOptical(optical, false, &state, 1000000001, 1, 1); assert(value("/input/b/click") == 1 && value("/input/a/click") == 0 && value(stick + "/click") == 1); assert(!padContext.input.values.count(0)); } puts("PASS: game pad hands as Index and Quest Touch thumbstick controllers"); }