android_multiviewwer/app/src/main/cpp/AnimationStrategy.cpp
2025-08-28 18:23:59 +09:00

236 lines
9.2 KiB
C++

#include "AnimationStrategy.h"
#include "Renderer.h" // Renderer의 그리기 헬퍼 함수들을 사용하기 위해 포함
#include <algorithm> // for std::max, std::clamp
#include <vector>
#include <numeric>
#include <random>
// ====================================================================
// --- PAN (왕복 이동) 애니메이션 ---
// ====================================================================
class PanAnimation : public AnimationStrategy {
public:
PanAnimation(float speed) : AnimationStrategy(speed) { reset(); }
void reset() override {
offsetX_ = 0.0f;
offsetY_ = 0.0f;
cycleComplete_ = false;
xDirection_ = 1;
yDirection_ = 1;
}
bool execute(Renderer* renderer, ANativeWindow_Buffer& buffer, MediaAsset& media, int surfaceWidth, int surfaceHeight) override {
// 1. 애니메이션이 이미 끝났으면 더 이상 계산하지 않음
if (cycleComplete_) {
// 마지막 위치에 고정하여 그림
renderer->drawMedia(buffer, media, 1.0f, offsetX_, offsetY_, getBaseScale(media, surfaceWidth, surfaceHeight));
return cycleComplete_;
}
// 2. Pan 모드에 필요한 overflow(화면 밖으로 넘친 영역) 계산
float overflowX = 0.0f, overflowY = 0.0f;
float scale = getBaseScale(media, surfaceWidth, surfaceHeight);
float mediaW = static_cast<float>(media.getWidth());
float mediaH = static_cast<float>(media.getHeight());
if ((mediaW / mediaH) > ((float)surfaceWidth / surfaceHeight)) {
overflowX = std::max(0.0f, mediaW * scale - surfaceWidth);
} else {
overflowY = std::max(0.0f, mediaH * scale - surfaceHeight);
}
// 3. 좌표 업데이트
bool xDone = (overflowX <= 0);
bool yDone = (overflowY <= 0);
if (overflowX > 0) {
offsetX_ += animationSpeed_ * xDirection_;
if (xDirection_ == 1 && offsetX_ >= overflowX) { offsetX_ = overflowX; xDirection_ = -1; }
else if (xDirection_ == -1 && offsetX_ <= 0) { offsetX_ = 0; xDirection_ = 1; xDone = true; }
}
if (overflowY > 0) {
offsetY_ += animationSpeed_ * yDirection_;
if (yDirection_ == 1 && offsetY_ >= overflowY) { offsetY_ = overflowY; yDirection_ = -1; }
else if (yDirection_ == -1 && offsetY_ <= 0) { offsetY_ = 0; yDirection_ = 1; yDone = true; }
}
// 4. X, Y축 왕복이 모두 끝났는지 확인
if (xDone && yDone) {
cycleComplete_ = true;
}
// 5. 계산된 최종 좌표로 그림
renderer->drawMedia(buffer, media, 1.0f, offsetX_, offsetY_, scale);
return cycleComplete_;
}
private:
float offsetX_, offsetY_;
int xDirection_, yDirection_;
bool cycleComplete_;
float getBaseScale(MediaAsset& media, int surfaceWidth, int surfaceHeight) {
float scale;
if ((static_cast<float>(media.getWidth()) / media.getHeight()) > ((float)surfaceWidth / surfaceHeight)) {
scale = (float)surfaceHeight / media.getHeight();
} else {
scale = (float)surfaceWidth / media.getWidth();
}
return scale;
}
};
// ====================================================================
// --- PAN_ONE_WAY (편도 이동) 애니메이션 ---
// ====================================================================
class PanOneWayAnimation : public AnimationStrategy {
public:
PanOneWayAnimation(float speed) : AnimationStrategy(speed) { reset(); }
void reset() override {
offsetX_ = 0.0f;
offsetY_ = 0.0f;
cycleComplete_ = false;
}
bool execute(Renderer* renderer, ANativeWindow_Buffer& buffer, MediaAsset& media, int surfaceWidth, int surfaceHeight) override {
if (cycleComplete_) {
renderer->drawMedia(buffer, media, 1.0f, offsetX_, offsetY_, getBaseScale(media, surfaceWidth, surfaceHeight));
return cycleComplete_;
}
float overflowX = 0.0f, overflowY = 0.0f;
float scale = getBaseScale(media, surfaceWidth, surfaceHeight);
float mediaW = static_cast<float>(media.getWidth());
float mediaH = static_cast<float>(media.getHeight());
if ((mediaW / mediaH) > ((float)surfaceWidth / surfaceHeight)) {
overflowX = std::max(0.0f, mediaW * scale - surfaceWidth);
} else {
overflowY = std::max(0.0f, mediaH * scale - surfaceHeight);
}
bool xReachedEnd = (overflowX <= 0);
bool yReachedEnd = (overflowY <= 0);
if (overflowX > 0) {
offsetX_ += animationSpeed_;
if (offsetX_ >= overflowX) { offsetX_ = overflowX; xReachedEnd = true; }
}
if (overflowY > 0) {
offsetY_ += animationSpeed_;
if (offsetY_ >= overflowY) { offsetY_ = overflowY; yReachedEnd = true; }
}
if (xReachedEnd && yReachedEnd) {
cycleComplete_ = true;
}
renderer->drawMedia(buffer, media, 1.0f, offsetX_, offsetY_, scale);
return cycleComplete_;
}
private:
float offsetX_, offsetY_;
bool cycleComplete_;
float getBaseScale(MediaAsset& media, int surfaceWidth, int surfaceHeight) {
// (PanAnimation과 중복되지만, 각 클래스의 독립성을 위해 포함)
float scale;
if ((static_cast<float>(media.getWidth()) / media.getHeight()) > ((float)surfaceWidth / surfaceHeight)) {
scale = (float)surfaceHeight / media.getHeight();
} else {
scale = (float)surfaceWidth / media.getWidth();
}
return scale;
}
};
// ====================================================================
// --- ZOOM 애니메이션 ---
// ====================================================================
class ZoomAnimation : public AnimationStrategy {
public:
ZoomAnimation(float speed) : AnimationStrategy(speed) { reset(); }
void reset() override {
scaleMultiplier_ = 1.0f;
cycleComplete_ = false;
zoomDirection_ = 1;
}
bool execute(Renderer* renderer, ANativeWindow_Buffer& buffer, MediaAsset& media, int surfaceWidth, int surfaceHeight) override {
if (!cycleComplete_) {
scaleMultiplier_ += 0.0005f * animationSpeed_ * zoomDirection_;
if (zoomDirection_ == 1 && scaleMultiplier_ >= 1.2f) { scaleMultiplier_ = 1.2f; zoomDirection_ = -1; }
else if (zoomDirection_ == -1 && scaleMultiplier_ <= 1.0f) { scaleMultiplier_ = 1.0f; zoomDirection_ = 1; cycleComplete_ = true; }
}
// ZOOM은 중앙 정렬을 기본으로 함
float baseScale, baseOffsetX, baseOffsetY;
renderer->calculateFitScaleAndOffset(media, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY);
renderer->drawMedia(buffer, media, 1.0f, baseOffsetX, baseOffsetY, baseScale * scaleMultiplier_);
return cycleComplete_;
}
private:
float scaleMultiplier_;
int zoomDirection_;
bool cycleComplete_;
};
// ====================================================================
// --- PAGE_TURN (대기) 애니메이션 ---
// ====================================================================
class PageTurnAnimation : public AnimationStrategy {
public:
PageTurnAnimation(float speed, long long delay) : AnimationStrategy(speed), delayMs_(delay) { reset(); }
void reset() override {
cycleComplete_ = false;
startTime_ = std::chrono::steady_clock::now();
}
bool execute(Renderer* renderer, ANativeWindow_Buffer& buffer, MediaAsset& media, int surfaceWidth, int surfaceHeight) override {
if (!cycleComplete_) {
long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime_).count();
if (elapsed >= delayMs_) {
cycleComplete_ = true;
}
}
// 대기하는 동안 중앙에 고정된 이미지를 그림
float baseScale, baseOffsetX, baseOffsetY;
renderer->calculateFitScaleAndOffset(media, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY);
renderer->drawMedia(buffer, media, 1.0f, baseOffsetX, baseOffsetY, baseScale);
return cycleComplete_;
}
private:
long long delayMs_;
std::chrono::steady_clock::time_point startTime_;
bool cycleComplete_;
};
// ====================================================================
// --- NONE (애니메이션 없음) ---
// ====================================================================
class NoneAnimation : public AnimationStrategy {
public:
NoneAnimation(float speed) : AnimationStrategy(speed) {}
void reset() override {} // 아무것도 안 함
bool execute(Renderer* renderer, ANativeWindow_Buffer& buffer, MediaAsset& media, int surfaceWidth, int surfaceHeight) override {
// 중앙에 고정된 이미지만 그림
float baseScale, baseOffsetX, baseOffsetY;
renderer->calculateFitScaleAndOffset(media, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY);
renderer->drawMedia(buffer, media, 1.0f, baseOffsetX, baseOffsetY, baseScale);
return true; // 즉시 완료
}
};