Skip to content

Commit

Permalink
Initial commit of the files of the new version of rotatoexpress
Browse files Browse the repository at this point in the history
This differs from the one that was distributed on discord in the
following ways:

 - Replaced OpenFrameworks with SDL2 and ImGui
 - UI is a smaller windows, themed with LIV color scheme (green and
   purple)
 - The program will restore your chaperone settings upon exit
 - The program can stay on top
 - The program is a win32 application, not a console application

Create LICENSE

Create README.md

add copyright info to everything

Add icon

install readme
  • Loading branch information
Ybalrid committed Apr 4, 2023
0 parents commit ed26fe4
Show file tree
Hide file tree
Showing 17 changed files with 674 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build/
*.aps
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[submodule "third-party/glm"]
path = third-party/glm
url = https://github.com/g-truc/glm.git
[submodule "third-party/imgui"]
path = third-party/imgui
url = https://github.com/ocornut/imgui.git
[submodule "third-party/openvr"]
path = third-party/openvr
url = https://github.com/ValveSoftware/openvr.git
[submodule "third-party/SDL"]
path = third-party/SDL
url = https://github.com/libsdl-org/SDL.git
42 changes: 42 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright LIV Inc. 2019-2023

cmake_minimum_required(VERSION 3.20)

project(rotatoexpress VERSION 0.3)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/INSTALLOUT CACHE PATH "Force installation location" FORCE)

add_subdirectory(${CMAKE_SOURCE_DIR}/third-party/SDL)

file(GLOB rotatoexpress_src CONFIGURE_DEPENDS src/*)

set(TPDIR "${CMAKE_SOURCE_DIR}/third-party/")
set(IMDIR "${TPDIR}/imgui/")

file(GLOB ImGui
${IMDIR}/imgui*
${IMDIR}/imstb*
${IMDIR}/backends/imgui_impl_sdlrenderer*
${IMDIR}/backends/imgui_impl_sdl2*
)

add_executable(rotatoexpress WIN32 ${rotatoexpress_src} ${ImGui} README.md LICENSE)
target_include_directories(rotatoexpress PRIVATE
${TPDIR}/openvr/headers
${TPDIR}/glm/
${IMDIR}
)

target_link_libraries(rotatoexpress
PUBLIC SDL2-static
SDL2main
${TPDIR}/openvr/lib/win64/openvr_api.lib
)

add_custom_command(TARGET rotatoexpress
POST_BUILD COMMAND
${CMAKE_COMMAND} -E copy "${TPDIR}/openvr/bin/win64/openvr_api.dll" $<TARGET_FILE_DIR:rotatoexpress>
)

install(TARGETS rotatoexpress)
install(FILES "${CMAKE_SOURCE_DIR}/README.md" "${TPDIR}/openvr/bin/win64/openvr_api.dll" TYPE BIN)
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019-2023 LIV Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# RotatoExpress

A tool to rotate your playspace, allowing you to create Mixed Reality recording where the camera apears to orbit around the player.

## License

Distributed under the terms of the MIT License agreement. Copyright (C) 2019-2023 LIV Inc.

[Upstream repository](https://github.com/LIV/RotatoExpress)

## How it works

Upon startup, this program caches the current playspace transform from OpenVR. The UI allows you to specify a rotation speed, and to offset the center. The rotation is locked by default, you will need to uncheck that

## Safety

This program *maniuplate* the Chaperone safety system of SteamVR in real time. It also add movement visible in the headset that do not match the real position of the headset. This needs to be used with care.

## Build instructions

You need to checkout this repository with submodules

```bash
git clone https://github.com/LIV/RotatoExpress
cd RotatoExpress
git submodule update --init --recursive
```

This program uses the CMake build system

```bash
mkdir build
cd build
cmake .. #You may want to specify the Visual Studio 17 2022 generator in 64bit using the -G -A arguments.
```

The build folder contains a Visual Studio solution you can open. You can also do the above using CMake-gui

267 changes: 267 additions & 0 deletions src/App.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
/*
* MIT License
*
* Copyright (c) 2019-2023 LIV Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

#include <iostream>

#include "imgui.h"
#include "backends/imgui_impl_sdl2.h"
#include "backends/imgui_impl_sdlrenderer.h"

#include "App.h"
#include "helpers.h"

#define FMTFLOAT "%.2f"

namespace RotatoExpress
{
int App::run()
{
while (running)
{
update();
draw();
}

restoreLaunchPlayArea();
cleanup();

return 0;
}

void App::setup()
{
SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO);
window = SDL_CreateWindow("LIV RotatoExpress v0.3",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
380, 170,
0);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();

#pragma region style
auto& colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_WindowBg] = ImVec4{ 0.095f, 0.047f, 0.17f, 1.0f };
colors[ImGuiCol_MenuBarBg] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };

// Border
colors[ImGuiCol_Border] = ImVec4{ 0.44f, 0.37f, 0.61f, 0.29f };
colors[ImGuiCol_BorderShadow] = ImVec4{ 0.0f, 0.0f, 0.0f, 0.24f };

// Text
colors[ImGuiCol_Text] = ImVec4{ 0.0f, 1.0f, .105f, 1.0f };
colors[ImGuiCol_TextDisabled] = ImVec4{ 0.5f, 0.5f, 0.5f, 1.0f };

// Headers
colors[ImGuiCol_Header] = ImVec4{ 0.13f, 0.13f, 0.17, 1.0f };
colors[ImGuiCol_HeaderHovered] = ImVec4{ 0.19f, 0.2f, 0.25f, 1.0f };
colors[ImGuiCol_HeaderActive] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };

// Buttons
colors[ImGuiCol_Button] = ImVec4{ 0.13f, 0.13f, 0.17, 1.0f };
colors[ImGuiCol_ButtonHovered] = ImVec4{ 0.19f, 0.2f, 0.25f, 1.0f };
colors[ImGuiCol_ButtonActive] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_CheckMark] = ImVec4{ 0.74f, 0.58f, 0.98f, 1.0f };

// Popups
colors[ImGuiCol_PopupBg] = ImVec4{ 0.1f, 0.1f, 0.13f, 0.92f };

// Slider
colors[ImGuiCol_SliderGrab] = ImVec4{ 0.44f, 0.37f, 0.61f, 0.54f };
colors[ImGuiCol_SliderGrabActive] = ImVec4{ 0.74f, 0.58f, 0.98f, 0.54f };

// Frame BG
colors[ImGuiCol_FrameBg] = ImVec4{ 0.13f, 0.13, 0.17, 1.0f };
colors[ImGuiCol_FrameBgHovered] = ImVec4{ 0.19f, 0.2f, 0.25f, 1.0f };
colors[ImGuiCol_FrameBgActive] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };

// Tabs
colors[ImGuiCol_Tab] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_TabHovered] = ImVec4{ 0.24, 0.24f, 0.32f, 1.0f };
colors[ImGuiCol_TabActive] = ImVec4{ 0.2f, 0.22f, 0.27f, 1.0f };
colors[ImGuiCol_TabUnfocused] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_TabUnfocusedActive] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };

// Title
colors[ImGuiCol_TitleBg] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_TitleBgActive] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_TitleBgCollapsed] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };

// Scrollbar
colors[ImGuiCol_ScrollbarBg] = ImVec4{ 0.1f, 0.1f, 0.13f, 1.0f };
colors[ImGuiCol_ScrollbarGrab] = ImVec4{ 0.16f, 0.16f, 0.21f, 1.0f };
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{ 0.19f, 0.2f, 0.25f, 1.0f };
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{ 0.24f, 0.24f, 0.32f, 1.0f };

// Seperator
colors[ImGuiCol_Separator] = ImVec4{ 0.44f, 0.37f, 0.61f, 1.0f };
colors[ImGuiCol_SeparatorHovered] = ImVec4{ 0.74f, 0.58f, 0.98f, 1.0f };
colors[ImGuiCol_SeparatorActive] = ImVec4{ 0.84f, 0.58f, 1.0f, 1.0f };

// Resize Grip
colors[ImGuiCol_ResizeGrip] = ImVec4{ 0.44f, 0.37f, 0.61f, 0.29f };
colors[ImGuiCol_ResizeGripHovered] = ImVec4{ 0.74f, 0.58f, 0.98f, 0.29f };
colors[ImGuiCol_ResizeGripActive] = ImVec4{ 0.84f, 0.58f, 1.0f, 0.29f };

// Docking
//colors[ImGuiCol_DockingPreview] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f};

auto& style = ImGui::GetStyle();
style.TabRounding = 4;
style.ScrollbarRounding = 9;
style.WindowRounding = 7;
style.GrabRounding = 3;
style.FrameRounding = 3;
style.PopupRounding = 4;
style.ChildRounding = 4;

#pragma endregion

ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer_Init(renderer);

vr::EVRInitError error = vr::VRInitError_None;
vr::VR_Init(&error, vr::VRApplication_Utility);
if (error != vr::VRInitError_None) {
vrEnabled = false;
std::cout << "No VR" << std::endl;
}
}

void App::update()
{
SDL_Event e{};
while (SDL_PollEvent(&e))
{
ImGui_ImplSDL2_ProcessEvent(&e);
switch (e.type)
{
case SDL_QUIT:
running = false;
break;
default:
break;
}
}

ImGui_ImplSDLRenderer_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();

auto& io = ImGui::GetIO();
ImGui::SetNextWindowPos({ 0,0 });
ImGui::SetNextWindowSize(io.DisplaySize);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::Begin("One window to rulez y'all", nullptr, ImGuiWindowFlags_NoDecoration);
ImGui::SliderFloat("Speed (dpt)", &speedVal, -1, 1, FMTFLOAT);
ImGui::Checkbox("Lock to Zero", &lockIdentityRotation);
ImGui::Checkbox("Seated Mode", &seatedMode);
ImGui::SliderFloat("X Offset", &xOffsetVal, -3, 3, FMTFLOAT);
ImGui::SliderFloat("Y Offset", &yOffsetVal, -3, 3, FMTFLOAT);
ImGui::SliderFloat("Z Offset", &zOffsetVal, -3, 3, FMTFLOAT);
if (ImGui::Checkbox("Stay on top", &alwaysOnTop))
SDL_SetWindowAlwaysOnTop(window, alwaysOnTop ? SDL_TRUE : SDL_FALSE);
ImGui::End();
ImGui::PopStyleVar();

if (vrEnabled)
{
if (SDL_GetTicks64() < lastTickTime + 1000 / 120)
return;

lastTickTime = SDL_GetTicks64();
vr::HmdMatrix34_t currentPlayArea{};

if (seatedMode)
vr::VRChaperoneSetup()->GetWorkingSeatedZeroPoseToRawTrackingPose(&currentPlayArea);
else
vr::VRChaperoneSetup()->GetWorkingStandingZeroPoseToRawTrackingPose(&currentPlayArea);

//Waiting 15 frames since to do the trick to make sure everything is setup. This is what old version of Rotato did anyways.
if (framecount++ == 15) [[unlikely]]
{
launchPlayArea = currentPlayArea;
launchPlayAreaSet = true;
}

if (!launchPlayAreaSet)
return;

const auto playAreaGlm = helpers::GetRotationMatrixFromHMDMatrix(currentPlayArea);
auto playAreaNewGlm = glm::rotate(playAreaGlm, glm::radians(speedVal), glm::vec3(0, 1, 0));
if (lockIdentityRotation)
playAreaNewGlm = glm::identity<glm::mat4>();

vr::HmdMatrix34_t playAreaNew = currentPlayArea;

memcpy(playAreaNew.m, glm::value_ptr(playAreaNewGlm), sizeof(float) * 12);
playAreaNew.m[0][3] = launchPlayArea.m[0][3] + xOffsetVal;
playAreaNew.m[1][3] = launchPlayArea.m[1][3] + yOffsetVal;
playAreaNew.m[2][3] = launchPlayArea.m[2][3] + zOffsetVal;

if (seatedMode)
vr::VRChaperoneSetup()->SetWorkingSeatedZeroPoseToRawTrackingPose(&playAreaNew);
else
vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose(&playAreaNew);
vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile::EChaperoneConfigFile_Live);
}
}

void App::draw() {
ImGui::Render();

if (SDL_GetTicks64() > lastRenderTickTime + 1000 / 240)
{
SDL_RenderClear(renderer);
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
SDL_RenderPresent(renderer);
lastRenderTickTime = SDL_GetTicks64();
}

SDL_Delay(1);
}

void App::cleanup()
{
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}

void App::restoreLaunchPlayArea()
{
if (launchPlayAreaSet)
{
if (seatedMode)
vr::VRChaperoneSetup()->SetWorkingSeatedZeroPoseToRawTrackingPose(&launchPlayArea);
else
vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose(&launchPlayArea);
vr::VRChaperoneSetup()->CommitWorkingCopy(vr::EChaperoneConfigFile::EChaperoneConfigFile_Live);
}
}
}
Loading

0 comments on commit ed26fe4

Please sign in to comment.