2025-08-28 17:43:36 +09:00
|
|
|
#include "AnimationStrategy.h"
|
2025-08-28 18:23:59 +09:00
|
|
|
#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; // 즉시 완료
|
|
|
|
|
}
|
|
|
|
|
};
|