From 9137cba3b45443e4912a5b59fa84ebb4e7dcf98a Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Thu, 30 Apr 2026 19:22:28 -0500 Subject: [PATCH] view: translate/rotate view with gamepad --- Makefile | 1 + include/view.h | 45 ++++++++++++++++ shader/minecraft.hlsl | 2 +- src/main.cpp | 121 +++++++++++++++++++++++++++++++++++++----- src/view.cpp | 18 +++++++ 5 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 include/view.h create mode 100644 src/view.cpp diff --git a/Makefile b/Makefile index e3cc623..eeda30c 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ endif OBJS = \ src/main.o \ + src/view.o \ src/volk/volk.o \ src/file.o \ src/pack.o \ diff --git a/include/view.h b/include/view.h new file mode 100644 index 0000000..d9be6d5 --- /dev/null +++ b/include/view.h @@ -0,0 +1,45 @@ +#pragma once + +#include "directxmath/directxmath.h" + +struct view { + // positions + XMVECTOR eye; + XMVECTOR at; + + // vectors + XMVECTOR up; + XMVECTOR forward; + XMVECTOR normal; // cross(forward, up) + XMVECTOR direction; // rotationaxis(forward, pitch) + + // angles + float pitch; + + void applyTransform(float delta_forward, float delta_strafe, float delta_elevation, + float delta_yaw, float delta_pitch); + + inline XMMATRIX getView() + { + return XMMatrixLookAtLH(eye, at, up); + } + + inline float clampPitch(float delta_pitch) + { + float newPitch = pitch + delta_pitch; + if (newPitch > 1.57f) newPitch = 1.57f; + if (newPitch < -1.57f) newPitch = -1.57f; + return newPitch; + } + + inline XMVECTOR getNormal() + { + return XMVector3Normalize(XMVector3Cross(forward, up)); + } + + inline XMVECTOR getDirection() + { + XMMATRIX mrn = XMMatrixRotationAxis(normal, pitch); + return XMVector3Transform(forward, mrn); + } +}; diff --git a/shader/minecraft.hlsl b/shader/minecraft.hlsl index 924020b..b75b924 100644 --- a/shader/minecraft.hlsl +++ b/shader/minecraft.hlsl @@ -44,7 +44,7 @@ VSOutput VSMain(VSInput input) { VSOutput output = (VSOutput)0; float4 Position = float4(input.Position.xyz + input.BlockPosition, 1.0); - output.Position = mul(Scene.Projection, mul(Scene.View, Position)); + output.Position = mul(Scene.Projection, mul(Scene.View, Position.xzyw)) * float4(-1, -1, 1, 1); float2 textureOffset = float2(input.TextureID % 8, input.TextureID / 8) * 16; output.Texture = float4(yf(input.Texture.xy, 16), textureOffset); diff --git a/src/main.cpp b/src/main.cpp index a9ea530..d28d4e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #include "vulkan_helper.h" #include "shader_data.h" #include "minmax.h" +#include "view.h" #include "collada/scene.h" #include "collada/scene/vulkan.h" @@ -285,9 +286,72 @@ inline static double getTime(int64_t start_time) return (double)(time / 1000) * 0.000001; } +static int const max_gamepads = 16; +static SDL_Gamepad * gamepads[max_gamepads]; +static int gamepad_count = 0; + +void add_gamepad(SDL_JoystickID instance_id) +{ + SDL_Gamepad * gamepad = SDL_OpenGamepad(instance_id); + char const * name = SDL_GetGamepadName(gamepad); + if (gamepad_count >= max_gamepads) { + printf("too many gamepads; ignoring gamepad %d %s\n", instance_id, name); + SDL_CloseGamepad(gamepad); + } else { + printf("add gamepad %d %s\n", instance_id, name); + gamepads[gamepad_count] = gamepad; + gamepad_count += 1; + } +} + +void remove_gamepad(SDL_JoystickID instance_id) +{ + for (int i = 0; i < gamepad_count; i++) { + if (SDL_GetGamepadID(gamepads[i]) == instance_id) { + int tail = (gamepad_count - i) - 1; + SDL_CloseGamepad(gamepads[i]); + memcpy(&gamepads[i], &gamepads[i+1], tail * (sizeof (gamepads[0]))); + gamepad_count -= 1; + return; + } + } + assert(!"remove_gamepad"); +} + +void gamepad_update(view & viewState) +{ + for (int i = 0; i < gamepad_count; i++) { + SDL_Gamepad * gamepad = gamepads[i]; + int16_t i_leftx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX); + int16_t i_lefty = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY); + int16_t i_rightx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX); + int16_t i_righty = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY); + int16_t i_left_trigger = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + int16_t i_right_trigger = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + + constexpr float scale = 1.0f / 32767.0f; + float leftx = (float)i_leftx * scale; + float lefty = (float)i_lefty * scale; + float rightx = (float)i_rightx * scale; + float righty = (float)i_righty * scale; + float left_trigger = (float)i_left_trigger * scale; + float right_trigger = (float)i_right_trigger * scale; + + constexpr float translate_rate = 0.5; + float delta_forward = -lefty * translate_rate; + float delta_strafe = leftx * translate_rate; + float delta_elevation = (left_trigger - right_trigger) * translate_rate; + float delta_yaw = rightx * -0.035; + float delta_pitch = righty * -0.035; + + viewState.applyTransform(delta_forward, delta_strafe, delta_elevation, + delta_yaw, delta_pitch); + } +} + int main() { - SDL_CHECK(SDL_Init(SDL_INIT_VIDEO)); + SDL_CHECK(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)); SDL_CHECK(SDL_Vulkan_LoadLibrary(NULL)); volkInitialize(); @@ -566,8 +630,8 @@ int main() VK_CHECK(vkCreateSampler(device, &samplerCreateInfo1, nullptr, &textureSamplers[1])); VkSamplerCreateInfo samplerCreateInfo2{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .magFilter = VK_FILTER_LINEAR, - .minFilter = VK_FILTER_LINEAR, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, @@ -610,10 +674,35 @@ int main() physicalDeviceMemoryProperties, surfaceFormat.format, depthFormat, - textureSamplers[0], + textureSamplers[2], shadowDepthImageViewDepth); minecraft_state.init(); + ////////////////////////////////////////////////////////////////////// + // initialize view + ////////////////////////////////////////////////////////////////////// + + int cameraIndex = collada_state.find_node_index_by_name("Camera001"); + int cameraTargetIndex = collada_state.find_node_index_by_name("EidelwindRigPelvis"); + int lightIndex = collada_state.find_node_index_by_name("Camera001"); + int lightTargetIndex = collada_state.find_node_index_by_name("EidelwindRigPelvis"); + //int lightMaterialIndex = collada_state.find_material_index_by_name("LightMaterial"); + int lightMaterialIndex = -1; + + // view + + XMVECTOR eye = XMVector3Transform(XMVectorZero(), collada_state.node_state.node_instances[cameraIndex].world); + XMVECTOR at = XMVector3Transform(XMVectorZero(), collada_state.node_state.node_instances[cameraTargetIndex].world); + XMVECTOR up = XMVectorSet(0, 0, 1, 0); + + view viewState; + viewState.eye = eye; + //viewState.at = at; + viewState.up = up; + viewState.forward = XMVectorSetZ(XMVector3Normalize(at - eye), 0); + viewState.pitch = 0; + viewState.applyTransform(0, 0, 0, 0, 0); + ////////////////////////////////////////////////////////////////////// // loop ////////////////////////////////////////////////////////////////////// @@ -626,13 +715,6 @@ int main() int64_t start_time; SDL_GetCurrentTime(&start_time); - int cameraIndex = collada_state.find_node_index_by_name("Camera001"); - int cameraTargetIndex = collada_state.find_node_index_by_name("EidelwindRigPelvis"); - int lightIndex = collada_state.find_node_index_by_name("Camera001"); - int lightTargetIndex = collada_state.find_node_index_by_name("EidelwindRigPelvis"); - //int lightMaterialIndex = collada_state.find_material_index_by_name("LightMaterial"); - int lightMaterialIndex = -1; - collada_state.update(0); while (quit == false) { @@ -671,8 +753,20 @@ int main() if (event.type == SDL_EVENT_WINDOW_RESIZED) { SDL_CHECK(SDL_GetWindowSize(window, &windowSize.x, &windowSize.y)); } + if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + add_gamepad(event.gdevice.which); + } + if (event.type == SDL_EVENT_GAMEPAD_REMOVED) { + remove_gamepad(event.gdevice.which); + } } + ////////////////////////////////////////////////////////////////////// + // gamepad update + ////////////////////////////////////////////////////////////////////// + + gamepad_update(viewState); + ////////////////////////////////////////////////////////////////////// // collada update ////////////////////////////////////////////////////////////////////// @@ -708,8 +802,9 @@ int main() collada_state.vulkan.change_frame(commandBuffer, frameIndex); XMMATRIX projection = currentProjection(); - XMMATRIX view = currentView(collada_state.node_state.node_instances[cameraIndex], - collada_state.node_state.node_instances[cameraTargetIndex]); + //XMMATRIX view = currentView(collada_state.node_state.node_instances[cameraIndex], + //collada_state.node_state.node_instances[cameraTargetIndex]); + XMMATRIX view = viewState.getView(); XMMATRIX shadowProjection = XMMatrixOrthographicLH(150, 150, -1000, 1000); XMMATRIX shadowView = currentView(collada_state.node_state.node_instances[lightIndex], collada_state.node_state.node_instances[lightTargetIndex]); diff --git a/src/view.cpp b/src/view.cpp new file mode 100644 index 0000000..7d5c3a1 --- /dev/null +++ b/src/view.cpp @@ -0,0 +1,18 @@ +#include "directxmath/directxmath.h" + +#include "view.h" + +constexpr float at_distance = 10.0; + +void view::applyTransform(float delta_forward, float delta_strafe, float delta_elevation, + float delta_yaw, float delta_pitch) +{ + pitch = clampPitch(delta_pitch); + + forward = XMVector3Transform(forward, XMMatrixRotationZ(delta_yaw)); + normal = getNormal(); // on forward change + direction = getDirection(); // on forward/normal/pitch change + + eye += forward * delta_forward + normal * delta_strafe + up * delta_elevation; + at = eye + direction * at_distance; +}