修改Tom总流程,完善动画机的状态设置以及游戏逻辑;在core/platform新增win和板端的鼠标点击/触控的逻辑代码/驱动;测试游戏内容。
This commit is contained in:
parent
24fe01af46
commit
8785368913
|
|
@ -21,6 +21,7 @@ set(CORE_SOURCES
|
||||||
src/Core/Platform/AlsaAudioInput.cpp
|
src/Core/Platform/AlsaAudioInput.cpp
|
||||||
src/Core/Platform/AlsaAudioOutput.cpp
|
src/Core/Platform/AlsaAudioOutput.cpp
|
||||||
src/Core/Platform/EvdevButtonInput.cpp
|
src/Core/Platform/EvdevButtonInput.cpp
|
||||||
|
src/Core/Platform/EvdevTouchInput.cpp
|
||||||
src/Core/Rasterizer/Rasterizer.cpp
|
src/Core/Rasterizer/Rasterizer.cpp
|
||||||
src/Core/Rasterizer/TriangleRasterizer.cpp
|
src/Core/Rasterizer/TriangleRasterizer.cpp
|
||||||
src/Core/Scene/Camera.cpp
|
src/Core/Scene/Camera.cpp
|
||||||
|
|
@ -37,6 +38,7 @@ else()
|
||||||
src/Core/Platform/SdlAudioInput.cpp
|
src/Core/Platform/SdlAudioInput.cpp
|
||||||
src/Core/Platform/SdlAudioOutput.cpp
|
src/Core/Platform/SdlAudioOutput.cpp
|
||||||
src/Core/Platform/SdlKeyboardButtonInput.cpp
|
src/Core/Platform/SdlKeyboardButtonInput.cpp
|
||||||
|
src/Core/Platform/SdlPointerInput.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,17 @@ set_source_files_properties(${TOM_ATLAS_HEADER} PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
add_executable(${TOM_GAME_TARGET}
|
add_executable(${TOM_GAME_TARGET}
|
||||||
Main.cpp
|
Main.cpp
|
||||||
|
src/app/TomGameApp.cpp
|
||||||
|
src/audio/VoiceEffect.cpp
|
||||||
|
src/audio/VoicePlayer.cpp
|
||||||
|
src/audio/VoiceRecorder.cpp
|
||||||
${TOM_ATLAS_HEADER}
|
${TOM_ATLAS_HEADER}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(${TOM_GAME_TARGET} PRIVATE
|
target_include_directories(${TOM_GAME_TARGET} PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/app
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/audio
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/generated
|
${CMAKE_CURRENT_SOURCE_DIR}/generated
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
@ -6,12 +5,12 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "Color.h"
|
#include "DefaultHardware.h"
|
||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
#include "DrawContext.h"
|
#include "DrawContext.h"
|
||||||
#include "TimeSource.h"
|
#include "TimeSource.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
#include "tom_atlas.h"
|
#include "app/TomGameApp.h"
|
||||||
|
|
||||||
#ifdef USE_FRAMEBUFFER
|
#ifdef USE_FRAMEBUFFER
|
||||||
#include "FBDisplay.h"
|
#include "FBDisplay.h"
|
||||||
|
|
@ -108,25 +107,6 @@ namespace
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const RenderData::SpriteRegion& SelectTomFrame(
|
|
||||||
uint32_t animation_time_ms,
|
|
||||||
const RenderData::SpriteRegion* const* speaking_frames,
|
|
||||||
size_t speaking_frame_count)
|
|
||||||
{
|
|
||||||
const uint32_t phase = (animation_time_ms / 1000u) % 6u;
|
|
||||||
if (phase < 2u)
|
|
||||||
{
|
|
||||||
return TomAtlas::tom_stand;
|
|
||||||
}
|
|
||||||
if (phase < 3u)
|
|
||||||
{
|
|
||||||
return TomAtlas::tom_listhen;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t frame_index = (animation_time_ms / 120u) % speaking_frame_count;
|
|
||||||
return *speaking_frames[frame_index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
|
|
@ -145,28 +125,37 @@ int main(int argc, char* argv[])
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderData::SpriteRegion* speaking_frames[] = {
|
Platform::DefaultAudioInput audioInput;
|
||||||
&TomAtlas::tom_say1,
|
Platform::DefaultAudioOutput audioOutput;
|
||||||
&TomAtlas::tom_say2,
|
Platform::DefaultButtonInput buttonInput;
|
||||||
&TomAtlas::tom_say3,
|
Platform::DefaultPointerInput pointerInput;
|
||||||
&TomAtlas::tom_say4,
|
|
||||||
&TomAtlas::tom_say3,
|
if (!buttonInput.init())
|
||||||
&TomAtlas::tom_say2
|
{
|
||||||
};
|
std::cerr << "[WARN] Button input init failed; physical key trigger is disabled." << std::endl;
|
||||||
const size_t speaking_frame_count = sizeof(speaking_frames) / sizeof(speaking_frames[0]);
|
}
|
||||||
|
if (!pointerInput.init("", ScreenWidth, ScreenHeight))
|
||||||
|
{
|
||||||
|
std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
Core::DrawContext ctx(ScreenWidth, ScreenHeight);
|
Core::DrawContext ctx(ScreenWidth, ScreenHeight);
|
||||||
Core::Timer timer(options.target_fps);
|
Core::Timer timer(options.target_fps);
|
||||||
Platform::SteadyTimeSource time_source;
|
Platform::SteadyTimeSource time_source;
|
||||||
|
Game::TomGameApp app(
|
||||||
|
ScreenWidth,
|
||||||
|
ScreenHeight,
|
||||||
|
&audioInput,
|
||||||
|
&audioOutput,
|
||||||
|
&buttonInput,
|
||||||
|
&pointerInput);
|
||||||
|
|
||||||
std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl;
|
std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl;
|
||||||
|
|
||||||
bool is_running = true;
|
bool is_running = true;
|
||||||
uint32_t animation_time_ms = 0;
|
|
||||||
while (is_running)
|
while (is_running)
|
||||||
{
|
{
|
||||||
timer.begin_frame(time_source.get_time_ms());
|
timer.begin_frame(time_source.get_time_ms());
|
||||||
animation_time_ms += timer.fixed_delta_ms();
|
|
||||||
|
|
||||||
bool should_quit = false;
|
bool should_quit = false;
|
||||||
display->poll_events(should_quit);
|
display->poll_events(should_quit);
|
||||||
|
|
@ -175,26 +164,16 @@ int main(int argc, char* argv[])
|
||||||
is_running = false;
|
is_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.clear(RenderData::Color(18, 18, 24, 255));
|
app.update(timer.fixed_delta_ms());
|
||||||
ctx.draw_sprite_region(0, 0, TomAtlas::background);
|
app.draw(ctx);
|
||||||
|
|
||||||
const RenderData::SpriteRegion& tom = SelectTomFrame(
|
|
||||||
animation_time_ms,
|
|
||||||
speaking_frames,
|
|
||||||
speaking_frame_count);
|
|
||||||
|
|
||||||
const int32_t tom_x = (ScreenWidth - tom.width) / 2;
|
|
||||||
const int32_t tom_y = std::max(0, ScreenHeight - tom.height - 72);
|
|
||||||
ctx.draw_sprite_region(tom_x, tom_y, tom);
|
|
||||||
|
|
||||||
const int32_t button_x = (ScreenWidth - TomAtlas::ui_record.width) / 2;
|
|
||||||
const int32_t button_y = ScreenHeight - TomAtlas::ui_record.height - 16;
|
|
||||||
ctx.draw_sprite_region(button_x, button_y, TomAtlas::ui_record);
|
|
||||||
|
|
||||||
ctx.present(display);
|
ctx.present(display);
|
||||||
SleepRemainingFrameTime(timer, time_source);
|
SleepRemainingFrameTime(timer, time_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pointerInput.shutdown();
|
||||||
|
buttonInput.shutdown();
|
||||||
|
audioInput.shutdown();
|
||||||
|
audioOutput.shutdown();
|
||||||
display->shutdown();
|
display->shutdown();
|
||||||
delete display;
|
delete display;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -3,56 +3,67 @@
|
||||||
#include "AudioInput.h"
|
#include "AudioInput.h"
|
||||||
#include "AudioOutput.h"
|
#include "AudioOutput.h"
|
||||||
#include "ButtonInput.h"
|
#include "ButtonInput.h"
|
||||||
#include "FrameBuffer.h"
|
#include "Color.h"
|
||||||
#include "SpriteRasterizer.h"
|
#include "DrawContext.h"
|
||||||
#include "Image.h"
|
#include "PointerInput.h"
|
||||||
|
#include "SpriteRegion.h"
|
||||||
|
#include "tom_atlas.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const uint32_t DefaultMaxRecordMs = 5000u;
|
||||||
|
const uint32_t SpeakingFrameMs = 90u;
|
||||||
|
const int32_t TomBottomPadding = 72;
|
||||||
|
const int32_t ButtonBottomPadding = 16;
|
||||||
|
|
||||||
|
const RenderData::SpriteRegion* const SpeakingFrames[] = {
|
||||||
|
&TomAtlas::tom_say1,
|
||||||
|
&TomAtlas::tom_say2,
|
||||||
|
&TomAtlas::tom_say3,
|
||||||
|
&TomAtlas::tom_say4,
|
||||||
|
&TomAtlas::tom_say3,
|
||||||
|
&TomAtlas::tom_say2
|
||||||
|
};
|
||||||
|
|
||||||
|
static const RenderData::SpriteRegion& SelectSpeakingFrame(uint32_t animationMs)
|
||||||
|
{
|
||||||
|
const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]);
|
||||||
|
const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount;
|
||||||
|
return *SpeakingFrames[frameIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace Game
|
namespace Game
|
||||||
{
|
{
|
||||||
TomGameApp::TomGameApp(
|
TomGameApp::TomGameApp(
|
||||||
Core::FrameBuffer* frameBuffer,
|
int32_t screenWidth,
|
||||||
Rasterizer::SpriteRasterizer* spriteRasterizer,
|
int32_t screenHeight,
|
||||||
Platform::IAudioInput* audioInput,
|
Platform::IAudioInput* audioInput,
|
||||||
Platform::IAudioOutput* audioOutput,
|
Platform::IAudioOutput* audioOutput,
|
||||||
Platform::IButtonInput* buttonInput) :
|
Platform::IButtonInput* buttonInput,
|
||||||
frameBuffer(frameBuffer),
|
Platform::IPointerInput* pointerInput)
|
||||||
spriteRasterizer(spriteRasterizer),
|
: audioInput(audioInput),
|
||||||
audioInput(audioInput),
|
audioOutput(audioOutput),
|
||||||
audioOutput(audioOutput),
|
buttonInput(buttonInput),
|
||||||
buttonInput(buttonInput),
|
pointerInput(pointerInput),
|
||||||
state(TomGameState::Idle),
|
state(TomGameState::Idle),
|
||||||
screenWidth(frameBuffer ? frameBuffer->get_width() : 0),
|
screenWidth(screenWidth),
|
||||||
screenHeight(frameBuffer ? frameBuffer->get_height() : 0),
|
screenHeight(screenHeight),
|
||||||
tomX(0),
|
buttonX(0),
|
||||||
tomY(0),
|
buttonY(0),
|
||||||
buttonX(0),
|
audioSampleRate(16000),
|
||||||
buttonY(0),
|
audioChannels(1),
|
||||||
audioSampleRate(16000),
|
maxRecordMs(DefaultMaxRecordMs),
|
||||||
audioChannels(1),
|
recordingElapsedMs(0),
|
||||||
pitchFactor(1.45f),
|
speakingAnimationMs(0),
|
||||||
outputGain(1.15f)
|
pitchFactor(1.45f),
|
||||||
|
outputGain(1.15f)
|
||||||
{
|
{
|
||||||
recorder.configure(audioSampleRate, audioChannels);
|
configure_audio(audioSampleRate, audioChannels);
|
||||||
}
|
calculate_button_layout();
|
||||||
|
|
||||||
void TomGameApp::set_assets(const TomGameAssets& assets)
|
|
||||||
{
|
|
||||||
this->assets = assets;
|
|
||||||
|
|
||||||
speakingFrames.clear();
|
|
||||||
if (assets.tomSay1 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay1));
|
|
||||||
if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2));
|
|
||||||
if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3));
|
|
||||||
if (assets.tomSay4 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay4));
|
|
||||||
if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3));
|
|
||||||
if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2));
|
|
||||||
|
|
||||||
speakingAnimator.set_frames(speakingFrames);
|
|
||||||
speakingAnimator.set_frame_time(0.08f);
|
|
||||||
speakingAnimator.set_loop(true);
|
|
||||||
speakingAnimator.stop();
|
|
||||||
|
|
||||||
calculate_layout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::configure_audio(uint32_t sampleRate, uint32_t channels)
|
void TomGameApp::configure_audio(uint32_t sampleRate, uint32_t channels)
|
||||||
|
|
@ -66,142 +77,142 @@ namespace Game
|
||||||
audioChannels = channels;
|
audioChannels = channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder.configure(audioSampleRate, audioChannels);
|
recorder.configure(
|
||||||
|
audioSampleRate,
|
||||||
|
audioChannels,
|
||||||
|
static_cast<float>(maxRecordMs) / 1000.0f,
|
||||||
|
0.0f,
|
||||||
|
static_cast<float>(maxRecordMs) / 1000.0f,
|
||||||
|
0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::update(float deltaTime, bool buttonPressed)
|
void TomGameApp::update(uint32_t deltaMs)
|
||||||
{
|
{
|
||||||
bool trigger = buttonPressed;
|
bool recordTrigger = false;
|
||||||
if (buttonInput != nullptr)
|
update_input(recordTrigger);
|
||||||
{
|
|
||||||
buttonInput->update();
|
|
||||||
trigger = trigger || buttonInput->was_pressed();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case TomGameState::Idle:
|
case TomGameState::Idle:
|
||||||
if (trigger)
|
if (recordTrigger)
|
||||||
{
|
{
|
||||||
start_recording();
|
start_recording();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TomGameState::Recording:
|
case TomGameState::Recording:
|
||||||
if (audioInput != nullptr)
|
update_recording(deltaMs);
|
||||||
{
|
|
||||||
recorder.update(*audioInput, 512);
|
|
||||||
}
|
|
||||||
if (trigger || recorder.is_finished())
|
|
||||||
{
|
|
||||||
finish_recording();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TomGameState::Processing:
|
|
||||||
process_voice();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TomGameState::Speaking:
|
case TomGameState::Speaking:
|
||||||
speakingAnimator.update(deltaTime);
|
update_speaking(deltaMs);
|
||||||
if (audioOutput == nullptr)
|
|
||||||
{
|
|
||||||
back_to_idle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.update(*audioOutput, 1024);
|
|
||||||
if (player.is_finished())
|
|
||||||
{
|
|
||||||
back_to_idle();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::draw()
|
void TomGameApp::draw(Core::DrawContext& ctx)
|
||||||
{
|
{
|
||||||
if (spriteRasterizer == nullptr)
|
ctx.clear_color(RenderData::Color(18, 18, 24, 255));
|
||||||
|
ctx.draw_sprite_region(0, 0, TomAtlas::background);
|
||||||
|
|
||||||
|
const RenderData::SpriteRegion* tom = &TomAtlas::tom_stand;
|
||||||
|
if (state == TomGameState::Recording)
|
||||||
{
|
{
|
||||||
return;
|
tom = &TomAtlas::tom_listhen;
|
||||||
|
}
|
||||||
|
else if (state == TomGameState::Speaking)
|
||||||
|
{
|
||||||
|
tom = &SelectSpeakingFrame(speakingAnimationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assets.background != nullptr)
|
const int32_t tomX = (screenWidth - tom->width) / 2;
|
||||||
|
int32_t tomY = screenHeight - tom->height - TomBottomPadding;
|
||||||
|
if (tomY < 0)
|
||||||
{
|
{
|
||||||
spriteRasterizer->DrawImage(*assets.background, 0, 0);
|
tomY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderData::Sprite tomSprite = get_current_tom_sprite();
|
ctx.draw_sprite_region(tomX, tomY, *tom);
|
||||||
if (tomSprite.is_valid())
|
ctx.draw_sprite_region(buttonX, buttonY, TomAtlas::ui_record);
|
||||||
{
|
|
||||||
spriteRasterizer->DrawSprite(tomSprite, tomX, tomY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assets.button != nullptr)
|
|
||||||
{
|
|
||||||
spriteRasterizer->DrawImage(*assets.button, buttonX, buttonY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TomGameApp::is_ready() const
|
void TomGameApp::update_input(bool& recordTrigger)
|
||||||
{
|
{
|
||||||
return frameBuffer != nullptr &&
|
recordTrigger = false;
|
||||||
spriteRasterizer != nullptr &&
|
|
||||||
assets.background != nullptr &&
|
|
||||||
assets.tomStand != nullptr &&
|
|
||||||
assets.tomListen != nullptr &&
|
|
||||||
assets.button != nullptr &&
|
|
||||||
speakingFrames.size() == 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TomGameApp::is_button_hit(int32_t x, int32_t y) const
|
if (buttonInput != nullptr)
|
||||||
{
|
|
||||||
if (assets.button == nullptr)
|
|
||||||
{
|
{
|
||||||
return false;
|
buttonInput->update();
|
||||||
|
recordTrigger = recordTrigger || buttonInput->was_pressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
return x >= buttonX &&
|
if (pointerInput != nullptr)
|
||||||
x < buttonX + assets.button->get_width() &&
|
|
||||||
y >= buttonY &&
|
|
||||||
y < buttonY + assets.button->get_height();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TomGameApp::calculate_layout()
|
|
||||||
{
|
|
||||||
screenWidth = frameBuffer ? frameBuffer->get_width() : 0;
|
|
||||||
screenHeight = frameBuffer ? frameBuffer->get_height() : 0;
|
|
||||||
|
|
||||||
if (assets.button != nullptr)
|
|
||||||
{
|
{
|
||||||
buttonX = (screenWidth - assets.button->get_width()) / 2;
|
pointerInput->update();
|
||||||
buttonY = screenHeight - assets.button->get_height() - 16;
|
if (pointerInput->was_pressed() &&
|
||||||
}
|
is_record_button_hit(pointerInput->get_x(), pointerInput->get_y()))
|
||||||
|
|
||||||
const RenderData::Sprite tomSprite = get_current_tom_sprite();
|
|
||||||
if (tomSprite.is_valid())
|
|
||||||
{
|
|
||||||
tomX = (screenWidth - tomSprite.width) / 2;
|
|
||||||
tomY = screenHeight - tomSprite.height - 72;
|
|
||||||
if (tomY < 0)
|
|
||||||
{
|
{
|
||||||
tomY = 0;
|
recordTrigger = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::start_recording()
|
void TomGameApp::start_recording()
|
||||||
{
|
{
|
||||||
if (audioInput != nullptr && !audioInput->is_open())
|
if (audioInput == nullptr)
|
||||||
{
|
{
|
||||||
audioInput->init("default", audioSampleRate, audioChannels);
|
std::cerr << "[WARN] Audio input is unavailable." << std::endl;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (audioOutput != nullptr && audioOutput->is_open())
|
||||||
|
{
|
||||||
|
audioOutput->shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!audioInput->is_open() &&
|
||||||
|
!audioInput->init("default", audioSampleRate, audioChannels))
|
||||||
|
{
|
||||||
|
std::cerr << "[WARN] Audio input init failed." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioSampleRate = audioInput->get_sample_rate();
|
||||||
|
audioChannels = audioInput->get_channels();
|
||||||
|
|
||||||
player.stop();
|
player.stop();
|
||||||
recorder.configure(audioSampleRate, audioChannels);
|
recorder.configure(
|
||||||
|
audioSampleRate,
|
||||||
|
audioChannels,
|
||||||
|
static_cast<float>(maxRecordMs) / 1000.0f,
|
||||||
|
0.0f,
|
||||||
|
static_cast<float>(maxRecordMs) / 1000.0f,
|
||||||
|
0.0f);
|
||||||
recorder.start();
|
recorder.start();
|
||||||
|
recordingElapsedMs = 0;
|
||||||
|
speakingAnimationMs = 0;
|
||||||
state = TomGameState::Recording;
|
state = TomGameState::Recording;
|
||||||
calculate_layout();
|
}
|
||||||
|
|
||||||
|
void TomGameApp::update_recording(uint32_t deltaMs)
|
||||||
|
{
|
||||||
|
recordingElapsedMs += deltaMs;
|
||||||
|
|
||||||
|
if (audioInput != nullptr)
|
||||||
|
{
|
||||||
|
uint32_t requestCount = (audioSampleRate * audioChannels * deltaMs) / 1000u;
|
||||||
|
if (requestCount < audioChannels)
|
||||||
|
{
|
||||||
|
requestCount = audioChannels;
|
||||||
|
}
|
||||||
|
requestCount -= requestCount % audioChannels;
|
||||||
|
recorder.update(*audioInput, static_cast<int>(std::min<uint32_t>(requestCount, 2048u)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordingElapsedMs >= maxRecordMs || recorder.is_finished())
|
||||||
|
{
|
||||||
|
finish_recording();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::finish_recording()
|
void TomGameApp::finish_recording()
|
||||||
|
|
@ -211,11 +222,11 @@ namespace Game
|
||||||
recorder.stop();
|
recorder.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = TomGameState::Processing;
|
if (audioInput != nullptr && audioInput->is_open())
|
||||||
}
|
{
|
||||||
|
audioInput->shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
void TomGameApp::process_voice()
|
|
||||||
{
|
|
||||||
std::vector<int16_t> samples = recorder.get_samples();
|
std::vector<int16_t> samples = recorder.get_samples();
|
||||||
samples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels);
|
samples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels);
|
||||||
samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels);
|
samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels);
|
||||||
|
|
@ -226,7 +237,7 @@ namespace Game
|
||||||
|
|
||||||
void TomGameApp::start_speaking(const std::vector<int16_t>& samples)
|
void TomGameApp::start_speaking(const std::vector<int16_t>& samples)
|
||||||
{
|
{
|
||||||
if (samples.empty())
|
if (samples.empty() || audioOutput == nullptr)
|
||||||
{
|
{
|
||||||
back_to_idle();
|
back_to_idle();
|
||||||
return;
|
return;
|
||||||
|
|
@ -234,47 +245,46 @@ namespace Game
|
||||||
|
|
||||||
player.set_voice(samples, audioSampleRate, audioChannels);
|
player.set_voice(samples, audioSampleRate, audioChannels);
|
||||||
player.play();
|
player.play();
|
||||||
|
speakingAnimationMs = 0;
|
||||||
speakingAnimator.reset();
|
|
||||||
speakingAnimator.play();
|
|
||||||
state = TomGameState::Speaking;
|
state = TomGameState::Speaking;
|
||||||
calculate_layout();
|
}
|
||||||
|
|
||||||
|
void TomGameApp::update_speaking(uint32_t deltaMs)
|
||||||
|
{
|
||||||
|
speakingAnimationMs += deltaMs;
|
||||||
|
|
||||||
|
if (audioOutput == nullptr)
|
||||||
|
{
|
||||||
|
back_to_idle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.update(*audioOutput, 1024);
|
||||||
|
if (player.is_finished())
|
||||||
|
{
|
||||||
|
back_to_idle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::back_to_idle()
|
void TomGameApp::back_to_idle()
|
||||||
{
|
{
|
||||||
player.stop();
|
player.stop();
|
||||||
speakingAnimator.stop();
|
recordingElapsedMs = 0;
|
||||||
|
speakingAnimationMs = 0;
|
||||||
state = TomGameState::Idle;
|
state = TomGameState::Idle;
|
||||||
calculate_layout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TomGameApp::draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y)
|
void TomGameApp::calculate_button_layout()
|
||||||
{
|
{
|
||||||
if (spriteRasterizer == nullptr || !sprite.is_valid())
|
buttonX = (screenWidth - TomAtlas::ui_record.width) / 2;
|
||||||
{
|
buttonY = screenHeight - TomAtlas::ui_record.height - ButtonBottomPadding;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
spriteRasterizer->DrawSprite(sprite, centerX - sprite.width / 2, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderData::Sprite TomGameApp::get_current_tom_sprite() const
|
bool TomGameApp::is_record_button_hit(int32_t x, int32_t y) const
|
||||||
{
|
{
|
||||||
if (state == TomGameState::Recording && assets.tomListen != nullptr)
|
return x >= buttonX &&
|
||||||
{
|
x < buttonX + TomAtlas::ui_record.width &&
|
||||||
return RenderData::Sprite(assets.tomListen);
|
y >= buttonY &&
|
||||||
}
|
y < buttonY + TomAtlas::ui_record.height;
|
||||||
|
|
||||||
if (state == TomGameState::Speaking)
|
|
||||||
{
|
|
||||||
const RenderData::Sprite* sprite = speakingAnimator.get_current_sprite();
|
|
||||||
if (sprite != nullptr)
|
|
||||||
{
|
|
||||||
return *sprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RenderData::Sprite(assets.tomStand);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "../audio/VoicePlayer.h"
|
#include "../audio/VoicePlayer.h"
|
||||||
#include "../audio/VoiceRecorder.h"
|
#include "../audio/VoiceRecorder.h"
|
||||||
#include "../components/SpriteAnimator.h"
|
|
||||||
#include "Sprite.h"
|
|
||||||
|
|
||||||
namespace Core
|
namespace Core
|
||||||
{
|
{
|
||||||
class FrameBuffer;
|
class DrawContext;
|
||||||
}
|
|
||||||
|
|
||||||
namespace Rasterizer
|
|
||||||
{
|
|
||||||
class SpriteRasterizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace RenderData
|
|
||||||
{
|
|
||||||
class Image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Platform
|
namespace Platform
|
||||||
|
|
@ -26,6 +15,7 @@ namespace Platform
|
||||||
class IAudioInput;
|
class IAudioInput;
|
||||||
class IAudioOutput;
|
class IAudioOutput;
|
||||||
class IButtonInput;
|
class IButtonInput;
|
||||||
|
class IPointerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Game
|
namespace Game
|
||||||
|
|
@ -34,85 +24,58 @@ namespace Game
|
||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
Recording,
|
Recording,
|
||||||
Processing,
|
|
||||||
Speaking
|
Speaking
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TomGameAssets
|
|
||||||
{
|
|
||||||
const RenderData::Image* background;
|
|
||||||
const RenderData::Image* tomStand;
|
|
||||||
const RenderData::Image* tomListen;
|
|
||||||
const RenderData::Image* tomSay1;
|
|
||||||
const RenderData::Image* tomSay2;
|
|
||||||
const RenderData::Image* tomSay3;
|
|
||||||
const RenderData::Image* tomSay4;
|
|
||||||
const RenderData::Image* button;
|
|
||||||
|
|
||||||
TomGameAssets() :
|
|
||||||
background(nullptr),
|
|
||||||
tomStand(nullptr),
|
|
||||||
tomListen(nullptr),
|
|
||||||
tomSay1(nullptr),
|
|
||||||
tomSay2(nullptr),
|
|
||||||
tomSay3(nullptr),
|
|
||||||
tomSay4(nullptr),
|
|
||||||
button(nullptr)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TomGameApp
|
class TomGameApp
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
Core::FrameBuffer* frameBuffer;
|
|
||||||
Rasterizer::SpriteRasterizer* spriteRasterizer;
|
|
||||||
Platform::IAudioInput* audioInput;
|
Platform::IAudioInput* audioInput;
|
||||||
Platform::IAudioOutput* audioOutput;
|
Platform::IAudioOutput* audioOutput;
|
||||||
Platform::IButtonInput* buttonInput;
|
Platform::IButtonInput* buttonInput;
|
||||||
|
Platform::IPointerInput* pointerInput;
|
||||||
|
|
||||||
TomGameAssets assets;
|
|
||||||
TomGameState state;
|
TomGameState state;
|
||||||
VoiceRecorder recorder;
|
VoiceRecorder recorder;
|
||||||
VoicePlayer player;
|
VoicePlayer player;
|
||||||
SpriteAnimator speakingAnimator;
|
|
||||||
std::vector<RenderData::Sprite> speakingFrames;
|
|
||||||
|
|
||||||
int32_t screenWidth;
|
int32_t screenWidth;
|
||||||
int32_t screenHeight;
|
int32_t screenHeight;
|
||||||
int32_t tomX;
|
|
||||||
int32_t tomY;
|
|
||||||
int32_t buttonX;
|
int32_t buttonX;
|
||||||
int32_t buttonY;
|
int32_t buttonY;
|
||||||
|
|
||||||
uint32_t audioSampleRate;
|
uint32_t audioSampleRate;
|
||||||
uint32_t audioChannels;
|
uint32_t audioChannels;
|
||||||
|
uint32_t maxRecordMs;
|
||||||
|
uint32_t recordingElapsedMs;
|
||||||
|
uint32_t speakingAnimationMs;
|
||||||
float pitchFactor;
|
float pitchFactor;
|
||||||
float outputGain;
|
float outputGain;
|
||||||
|
|
||||||
void calculate_layout();
|
void update_input(bool& recordTrigger);
|
||||||
void start_recording();
|
void start_recording();
|
||||||
|
void update_recording(uint32_t deltaMs);
|
||||||
void finish_recording();
|
void finish_recording();
|
||||||
void process_voice();
|
|
||||||
void start_speaking(const std::vector<int16_t>& samples);
|
void start_speaking(const std::vector<int16_t>& samples);
|
||||||
|
void update_speaking(uint32_t deltaMs);
|
||||||
void back_to_idle();
|
void back_to_idle();
|
||||||
void draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y);
|
|
||||||
const RenderData::Sprite get_current_tom_sprite() const;
|
void calculate_button_layout();
|
||||||
|
bool is_record_button_hit(int32_t x, int32_t y) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TomGameApp(
|
TomGameApp(
|
||||||
Core::FrameBuffer* frameBuffer,
|
int32_t screenWidth,
|
||||||
Rasterizer::SpriteRasterizer* spriteRasterizer,
|
int32_t screenHeight,
|
||||||
Platform::IAudioInput* audioInput,
|
Platform::IAudioInput* audioInput,
|
||||||
Platform::IAudioOutput* audioOutput,
|
Platform::IAudioOutput* audioOutput,
|
||||||
Platform::IButtonInput* buttonInput = nullptr);
|
Platform::IButtonInput* buttonInput,
|
||||||
|
Platform::IPointerInput* pointerInput);
|
||||||
|
|
||||||
void set_assets(const TomGameAssets& assets);
|
|
||||||
void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1);
|
void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1);
|
||||||
void update(float deltaTime, bool buttonPressed = false);
|
void update(uint32_t deltaMs);
|
||||||
void draw();
|
void draw(Core::DrawContext& ctx);
|
||||||
|
|
||||||
bool is_ready() const;
|
|
||||||
bool is_button_hit(int32_t x, int32_t y) const;
|
|
||||||
TomGameState get_state() const { return state; }
|
TomGameState get_state() const { return state; }
|
||||||
float get_record_volume() const { return recorder.get_last_volume(); }
|
float get_record_volume() const { return recorder.get_last_volume(); }
|
||||||
float get_play_progress() const { return player.get_progress(); }
|
float get_play_progress() const { return player.get_progress(); }
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
#include "AlsaAudioInput.h"
|
#include "AlsaAudioInput.h"
|
||||||
#include "AlsaAudioOutput.h"
|
#include "AlsaAudioOutput.h"
|
||||||
#include "EvdevButtonInput.h"
|
#include "EvdevButtonInput.h"
|
||||||
|
#include "EvdevTouchInput.h"
|
||||||
#else
|
#else
|
||||||
#include "SdlAudioInput.h"
|
#include "SdlAudioInput.h"
|
||||||
#include "SdlAudioOutput.h"
|
#include "SdlAudioOutput.h"
|
||||||
#include "SdlKeyboardButtonInput.h"
|
#include "SdlKeyboardButtonInput.h"
|
||||||
|
#include "SdlPointerInput.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Platform
|
namespace Platform
|
||||||
|
|
@ -16,9 +18,11 @@ namespace Platform
|
||||||
typedef AlsaAudioInput DefaultAudioInput;
|
typedef AlsaAudioInput DefaultAudioInput;
|
||||||
typedef AlsaAudioOutput DefaultAudioOutput;
|
typedef AlsaAudioOutput DefaultAudioOutput;
|
||||||
typedef EvdevButtonInput DefaultButtonInput;
|
typedef EvdevButtonInput DefaultButtonInput;
|
||||||
|
typedef EvdevTouchInput DefaultPointerInput;
|
||||||
#else
|
#else
|
||||||
typedef SdlAudioInput DefaultAudioInput;
|
typedef SdlAudioInput DefaultAudioInput;
|
||||||
typedef SdlAudioOutput DefaultAudioOutput;
|
typedef SdlAudioOutput DefaultAudioOutput;
|
||||||
typedef SdlKeyboardButtonInput DefaultButtonInput;
|
typedef SdlKeyboardButtonInput DefaultButtonInput;
|
||||||
|
typedef SdlPointerInput DefaultPointerInput;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
#include "EvdevTouchInput.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/input.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Platform
|
||||||
|
{
|
||||||
|
EvdevTouchInput::EvdevTouchInput()
|
||||||
|
: device_path_("/dev/input/event1"),
|
||||||
|
screen_width_(0),
|
||||||
|
screen_height_(0),
|
||||||
|
x_(0),
|
||||||
|
y_(0),
|
||||||
|
raw_x_(0),
|
||||||
|
raw_y_(0),
|
||||||
|
abs_x_min_(0),
|
||||||
|
abs_x_max_(0),
|
||||||
|
abs_y_min_(0),
|
||||||
|
abs_y_max_(0),
|
||||||
|
opened_(false),
|
||||||
|
down_(false),
|
||||||
|
pressed_(false),
|
||||||
|
released_(false),
|
||||||
|
has_abs_x_range_(false),
|
||||||
|
has_abs_y_range_(false)
|
||||||
|
#if defined(__linux__)
|
||||||
|
,
|
||||||
|
fd_(-1)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
EvdevTouchInput::~EvdevTouchInput()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EvdevTouchInput::init(
|
||||||
|
const std::string& device_path,
|
||||||
|
int32_t screen_width,
|
||||||
|
int32_t screen_height)
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
device_path_ = device_path.empty() ? "/dev/input/event1" : device_path;
|
||||||
|
screen_width_ = screen_width;
|
||||||
|
screen_height_ = screen_height;
|
||||||
|
x_ = 0;
|
||||||
|
y_ = 0;
|
||||||
|
raw_x_ = 0;
|
||||||
|
raw_y_ = 0;
|
||||||
|
down_ = false;
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
has_abs_x_range_ = false;
|
||||||
|
has_abs_y_range_ = false;
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
fd_ = open(device_path_.c_str(), O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd_ < 0)
|
||||||
|
{
|
||||||
|
std::cerr << "EvdevTouchInput open failed: " << device_path_ << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_absinfo abs_x;
|
||||||
|
if (ioctl(fd_, EVIOCGABS(ABS_X), &abs_x) == 0 ||
|
||||||
|
ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_X), &abs_x) == 0)
|
||||||
|
{
|
||||||
|
abs_x_min_ = abs_x.minimum;
|
||||||
|
abs_x_max_ = abs_x.maximum;
|
||||||
|
has_abs_x_range_ = abs_x_max_ > abs_x_min_;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_absinfo abs_y;
|
||||||
|
if (ioctl(fd_, EVIOCGABS(ABS_Y), &abs_y) == 0 ||
|
||||||
|
ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_Y), &abs_y) == 0)
|
||||||
|
{
|
||||||
|
abs_y_min_ = abs_y.minimum;
|
||||||
|
abs_y_max_ = abs_y.maximum;
|
||||||
|
has_abs_y_range_ = abs_y_max_ > abs_y_min_;
|
||||||
|
}
|
||||||
|
|
||||||
|
opened_ = true;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
std::cerr << "EvdevTouchInput is only implemented on Linux evdev." << std::endl;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevTouchInput::update()
|
||||||
|
{
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
|
||||||
|
if (!opened_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
input_event event;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
const ssize_t bytes = read(fd_, &event, sizeof(event));
|
||||||
|
if (bytes == static_cast<ssize_t>(sizeof(event)))
|
||||||
|
{
|
||||||
|
if (event.type == EV_ABS)
|
||||||
|
{
|
||||||
|
if (event.code == ABS_X || event.code == ABS_MT_POSITION_X)
|
||||||
|
{
|
||||||
|
raw_x_ = event.value;
|
||||||
|
x_ = map_x(raw_x_);
|
||||||
|
}
|
||||||
|
else if (event.code == ABS_Y || event.code == ABS_MT_POSITION_Y)
|
||||||
|
{
|
||||||
|
raw_y_ = event.value;
|
||||||
|
y_ = map_y(raw_y_);
|
||||||
|
}
|
||||||
|
else if (event.code == ABS_MT_TRACKING_ID)
|
||||||
|
{
|
||||||
|
set_down(event.value >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == EV_KEY)
|
||||||
|
{
|
||||||
|
if (event.code == BTN_TOUCH || event.code == BTN_LEFT)
|
||||||
|
{
|
||||||
|
set_down(event.value != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
||||||
|
{
|
||||||
|
std::cerr << "EvdevTouchInput read failed." << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevTouchInput::shutdown()
|
||||||
|
{
|
||||||
|
#if defined(__linux__)
|
||||||
|
if (fd_ >= 0)
|
||||||
|
{
|
||||||
|
close(fd_);
|
||||||
|
fd_ = -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
opened_ = false;
|
||||||
|
down_ = false;
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t EvdevTouchInput::map_x(int32_t raw_x) const
|
||||||
|
{
|
||||||
|
if (screen_width_ <= 0 || !has_abs_x_range_)
|
||||||
|
{
|
||||||
|
return raw_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t clamped = std::max(abs_x_min_, std::min(raw_x, abs_x_max_));
|
||||||
|
const int64_t numerator = static_cast<int64_t>(clamped - abs_x_min_) * static_cast<int64_t>(screen_width_ - 1);
|
||||||
|
return static_cast<int32_t>(numerator / static_cast<int64_t>(abs_x_max_ - abs_x_min_));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t EvdevTouchInput::map_y(int32_t raw_y) const
|
||||||
|
{
|
||||||
|
if (screen_height_ <= 0 || !has_abs_y_range_)
|
||||||
|
{
|
||||||
|
return raw_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t clamped = std::max(abs_y_min_, std::min(raw_y, abs_y_max_));
|
||||||
|
const int64_t numerator = static_cast<int64_t>(clamped - abs_y_min_) * static_cast<int64_t>(screen_height_ - 1);
|
||||||
|
return static_cast<int32_t>(numerator / static_cast<int64_t>(abs_y_max_ - abs_y_min_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvdevTouchInput::set_down(bool down)
|
||||||
|
{
|
||||||
|
if (down && !down_)
|
||||||
|
{
|
||||||
|
pressed_ = true;
|
||||||
|
}
|
||||||
|
else if (!down && down_)
|
||||||
|
{
|
||||||
|
released_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
down_ = down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PointerInput.h"
|
||||||
|
|
||||||
|
namespace Platform
|
||||||
|
{
|
||||||
|
class EvdevTouchInput : public IPointerInput
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string device_path_;
|
||||||
|
int32_t screen_width_;
|
||||||
|
int32_t screen_height_;
|
||||||
|
int32_t x_;
|
||||||
|
int32_t y_;
|
||||||
|
int32_t raw_x_;
|
||||||
|
int32_t raw_y_;
|
||||||
|
int32_t abs_x_min_;
|
||||||
|
int32_t abs_x_max_;
|
||||||
|
int32_t abs_y_min_;
|
||||||
|
int32_t abs_y_max_;
|
||||||
|
bool opened_;
|
||||||
|
bool down_;
|
||||||
|
bool pressed_;
|
||||||
|
bool released_;
|
||||||
|
bool has_abs_x_range_;
|
||||||
|
bool has_abs_y_range_;
|
||||||
|
#if defined(__linux__)
|
||||||
|
int fd_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int32_t map_x(int32_t raw_x) const;
|
||||||
|
int32_t map_y(int32_t raw_y) const;
|
||||||
|
void set_down(bool down);
|
||||||
|
|
||||||
|
public:
|
||||||
|
EvdevTouchInput();
|
||||||
|
~EvdevTouchInput();
|
||||||
|
|
||||||
|
bool init(
|
||||||
|
const std::string& device_path = "/dev/input/event1",
|
||||||
|
int32_t screen_width = 0,
|
||||||
|
int32_t screen_height = 0) override;
|
||||||
|
void update() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
bool is_open() const override { return opened_; }
|
||||||
|
bool is_down() const override { return down_; }
|
||||||
|
bool was_pressed() const override { return pressed_; }
|
||||||
|
bool was_released() const override { return released_; }
|
||||||
|
int32_t get_x() const override { return x_; }
|
||||||
|
int32_t get_y() const override { return y_; }
|
||||||
|
const std::string& get_device_path() const override { return device_path_; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Platform
|
||||||
|
{
|
||||||
|
class IPointerInput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IPointerInput() {}
|
||||||
|
|
||||||
|
virtual bool init(
|
||||||
|
const std::string& device_path = "",
|
||||||
|
int32_t screen_width = 0,
|
||||||
|
int32_t screen_height = 0) = 0;
|
||||||
|
virtual void update() = 0;
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
virtual bool is_open() const = 0;
|
||||||
|
virtual bool is_down() const = 0;
|
||||||
|
virtual bool was_pressed() const = 0;
|
||||||
|
virtual bool was_released() const = 0;
|
||||||
|
virtual int32_t get_x() const = 0;
|
||||||
|
virtual int32_t get_y() const = 0;
|
||||||
|
virtual const std::string& get_device_path() const = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
#include "SdlPointerInput.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static bool EnsureSdlEvents()
|
||||||
|
{
|
||||||
|
if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0)
|
||||||
|
{
|
||||||
|
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((SDL_WasInit(SDL_INIT_EVENTS) & SDL_INIT_EVENTS) == 0 &&
|
||||||
|
SDL_InitSubSystem(SDL_INIT_EVENTS) < 0)
|
||||||
|
{
|
||||||
|
std::cerr << "SDL events init failed: " << SDL_GetError() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Platform
|
||||||
|
{
|
||||||
|
SdlPointerInput::SdlPointerInput()
|
||||||
|
: device_path_("mouse"),
|
||||||
|
screen_width_(0),
|
||||||
|
screen_height_(0),
|
||||||
|
x_(0),
|
||||||
|
y_(0),
|
||||||
|
opened_(false),
|
||||||
|
down_(false),
|
||||||
|
pressed_(false),
|
||||||
|
released_(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SdlPointerInput::~SdlPointerInput()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SdlPointerInput::init(
|
||||||
|
const std::string& device_path,
|
||||||
|
int32_t screen_width,
|
||||||
|
int32_t screen_height)
|
||||||
|
{
|
||||||
|
if (!EnsureSdlEvents())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device_path_ = device_path.empty() ? "mouse" : device_path;
|
||||||
|
screen_width_ = screen_width;
|
||||||
|
screen_height_ = screen_height;
|
||||||
|
x_ = 0;
|
||||||
|
y_ = 0;
|
||||||
|
opened_ = true;
|
||||||
|
down_ = false;
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SdlPointerInput::update()
|
||||||
|
{
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
|
||||||
|
if (!opened_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PumpEvents();
|
||||||
|
|
||||||
|
int mouse_x = 0;
|
||||||
|
int mouse_y = 0;
|
||||||
|
const Uint32 state = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
|
if (screen_width_ > 0)
|
||||||
|
{
|
||||||
|
mouse_x = std::max(0, std::min(mouse_x, screen_width_ - 1));
|
||||||
|
}
|
||||||
|
if (screen_height_ > 0)
|
||||||
|
{
|
||||||
|
mouse_y = std::max(0, std::min(mouse_y, screen_height_ - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
x_ = mouse_x;
|
||||||
|
y_ = mouse_y;
|
||||||
|
|
||||||
|
const bool current_down = (state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
|
||||||
|
pressed_ = current_down && !down_;
|
||||||
|
released_ = !current_down && down_;
|
||||||
|
down_ = current_down;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SdlPointerInput::shutdown()
|
||||||
|
{
|
||||||
|
opened_ = false;
|
||||||
|
down_ = false;
|
||||||
|
pressed_ = false;
|
||||||
|
released_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PointerInput.h"
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace Platform
|
||||||
|
{
|
||||||
|
class SdlPointerInput : public IPointerInput
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string device_path_;
|
||||||
|
int32_t screen_width_;
|
||||||
|
int32_t screen_height_;
|
||||||
|
int32_t x_;
|
||||||
|
int32_t y_;
|
||||||
|
bool opened_;
|
||||||
|
bool down_;
|
||||||
|
bool pressed_;
|
||||||
|
bool released_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SdlPointerInput();
|
||||||
|
~SdlPointerInput();
|
||||||
|
|
||||||
|
bool init(
|
||||||
|
const std::string& device_path = "mouse",
|
||||||
|
int32_t screen_width = 0,
|
||||||
|
int32_t screen_height = 0) override;
|
||||||
|
void update() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
bool is_open() const override { return opened_; }
|
||||||
|
bool is_down() const override { return down_; }
|
||||||
|
bool was_pressed() const override { return pressed_; }
|
||||||
|
bool was_released() const override { return released_; }
|
||||||
|
int32_t get_x() const override { return x_; }
|
||||||
|
int32_t get_y() const override { return y_; }
|
||||||
|
const std::string& get_device_path() const override { return device_path_; }
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue