266 lines
8.5 KiB
C
266 lines
8.5 KiB
C
#include <math.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h> /**< rand qsort */
|
||
|
||
#include "sww/app.h"
|
||
#include "sww/color.h"
|
||
#include "sww/fps_helper.h"
|
||
#include "sww/rate_helper.h"
|
||
#include "sww/renderer.h"
|
||
#include "sww/texture.h"
|
||
#include "sww/util.h"
|
||
#include "sww/vmath.h"
|
||
#include "sww/window.h"
|
||
|
||
#define ROTATION_SPEED 0.015f
|
||
#define VIEW_DISTANCE 800.0f
|
||
#define ANTIALIASING 1 // 抗锯齿采样级别(1为关闭)
|
||
|
||
#define MAX_SCENE 2
|
||
|
||
#define TITLE "SWW Texture"
|
||
|
||
typedef struct
|
||
{
|
||
int w;
|
||
int h;
|
||
int method;
|
||
swwRenderer* renderer;
|
||
} UserData;
|
||
|
||
void OnKey(swwWindow* o, swwKeycode key, int pressed)
|
||
{
|
||
if (pressed) {
|
||
if (key == swwKeycode_ESCAPE) {
|
||
swwApp_PostQuitEvent();
|
||
} else {
|
||
UserData* ud = swwWindow_GetUserData(o);
|
||
ud->method = (ud->method + 1) % MAX_SCENE;
|
||
}
|
||
}
|
||
}
|
||
|
||
void OnButton(swwWindow* o, swwButton btn, int pressed)
|
||
{
|
||
if (btn == swwButton_R && pressed) {
|
||
// Save to png file
|
||
UserData* ud = swwWindow_GetUserData(o);
|
||
uint32_t buf_size = swwUtil_PngBufferSize(ud->w, ud->h, 1);
|
||
uint8_t* buf = (uint8_t*)malloc(buf_size);
|
||
uint8_t* wbuf = swwRenderer_LockBuffer(ud->renderer);
|
||
FILE* fp = NULL;
|
||
swwUtil_SaveBufferToPngBuffer(buf, buf_size, wbuf, ud->w, ud->h, 1);
|
||
fp = fopen("output.png", "wb+");
|
||
fwrite(buf, 1, buf_size, fp);
|
||
fclose(fp);
|
||
free(buf);
|
||
}
|
||
}
|
||
|
||
void OnScroll(swwWindow* o, float offset)
|
||
{}
|
||
|
||
typedef struct
|
||
{
|
||
int ori_x; // 原始X坐标
|
||
float depth; // 深度值,Z坐标
|
||
int screen_x; // 投影后屏幕X坐标
|
||
float scale; // 垂直缩放因子
|
||
} ColumnInfo;
|
||
|
||
typedef struct
|
||
{
|
||
Point2i p;
|
||
int rx;
|
||
int ry;
|
||
} EyeGround;
|
||
|
||
int CompareDepth(const void* a, const void* b)
|
||
{
|
||
ColumnInfo* c1 = (ColumnInfo*)a;
|
||
ColumnInfo* c2 = (ColumnInfo*)b;
|
||
return c1->depth < c2->depth ? 1 : -1;
|
||
}
|
||
|
||
int main()
|
||
{
|
||
uint32_t w = 512, h = 512;
|
||
uint32_t img_w, img_h;
|
||
int cw, ch;
|
||
swwWindowCallback callback = {OnKey, OnButton, OnScroll};
|
||
swwFpsHelper fps;
|
||
swwApp_Initialize();
|
||
|
||
swwWindow* window = swwWindow_Create(TITLE, w, h);
|
||
UserData ud = {w, h, 0, NULL};
|
||
ud.renderer = swwRenderer_CreateAttachWindow(window, kSoftwareRenderer);
|
||
swwWindow_SetUserData(window, &ud);
|
||
|
||
swwWindow_SetCallback(window, &callback);
|
||
|
||
int ww2 = w / 2;
|
||
int hh2 = h / 2;
|
||
|
||
const int center_x = w / 2;
|
||
const int center_y = h / 2;
|
||
|
||
int i = 0;
|
||
|
||
swwRateHelper rh;
|
||
|
||
EyeGround left_eye_grd = {{ww2 + 147, hh2 + 166}, 87, 96};
|
||
EyeGround right_eye_grd = {{ww2 + 357, hh2 + 166}, 87, 96};
|
||
|
||
Point2i left_eye_pos[2] = {{ww2 + 110, hh2 + 120}, {ww2 + 110, hh2 + 80}};
|
||
Point2i right_eye_pos[2] = {{ww2 + 320, hh2 + 120}, {ww2 + 320, hh2 + 80}};
|
||
Rangef left_eye_range = {left_eye_pos[1].y, left_eye_pos[0].y};
|
||
Rangef right_eye_range = {right_eye_pos[1].y, right_eye_pos[0].y};
|
||
|
||
swwTexture* tex = swwTexture_LoadFromFile("eye.png");
|
||
swwTexture* rotated_tex = swwTexture_LoadFromFile("avatar.png");
|
||
swwTexture_GetSize(rotated_tex, &img_w, &img_h);
|
||
|
||
ColumnInfo* columns = (ColumnInfo*)malloc(img_w * sizeof(ColumnInfo));
|
||
float angle = 0.0f;
|
||
|
||
swwRenderer_EnablePerfMonitor(ud.renderer, 1);
|
||
swwFpsHelper_Construct(&fps, 1.f);
|
||
swwRateHelper_Construct(&rh, 30.f);
|
||
int cur_step = 0;
|
||
int step = 1;
|
||
Rangef r = {0, 30};
|
||
while (!swwApp_ShouldExit()) {
|
||
swwRenderer_ClearBlack(ud.renderer);
|
||
switch (ud.method) {
|
||
case 0: {
|
||
int lpy = (int)Mapf(cur_step, r, left_eye_range);
|
||
int rpy = (int)Mapf(cur_step, r, right_eye_range);
|
||
|
||
// Draw the eye ground
|
||
swwRenderer_DrawEllipse(ud.renderer, left_eye_grd.p, left_eye_grd.rx, left_eye_grd.ry,
|
||
kWhite, 1);
|
||
swwRenderer_DrawEllipse(ud.renderer, right_eye_grd.p, right_eye_grd.rx,
|
||
right_eye_grd.ry, kWhite, 1);
|
||
|
||
// Draw eyes
|
||
swwRenderer_DrawTexture(ud.renderer, tex, (Point2i){left_eye_pos[0].x, lpy});
|
||
swwRenderer_DrawTexture(ud.renderer, tex, (Point2i){right_eye_pos[0].x, rpy});
|
||
if (cur_step == 30) {
|
||
step = -1;
|
||
} else if (cur_step == 0) {
|
||
step = 1;
|
||
}
|
||
|
||
cur_step += step;
|
||
} break;
|
||
case 1:
|
||
default: {
|
||
angle += ROTATION_SPEED;
|
||
const float cos_theta = cos(angle);
|
||
const float sin_theta = sin(angle);
|
||
|
||
int valid_columns = 0;
|
||
uint32_t x = 0;
|
||
for (; x < img_w; x++) {
|
||
const float ori_x = (x - img_w / 2.0f);
|
||
const float rotated_z = -ori_x * sin_theta;
|
||
const float divisor = VIEW_DISTANCE + rotated_z;
|
||
|
||
if (divisor <= 0.1f)
|
||
continue; // 避免除以零
|
||
|
||
ColumnInfo* col = &columns[valid_columns];
|
||
col->ori_x = x;
|
||
col->depth = rotated_z;
|
||
|
||
// 计算投影参数
|
||
const float proj_x = (ori_x * cos_theta * VIEW_DISTANCE) / divisor;
|
||
col->screen_x = center_x + (int)round(proj_x);
|
||
col->scale = VIEW_DISTANCE / divisor; // 垂直缩放因子
|
||
|
||
valid_columns++;
|
||
}
|
||
|
||
qsort(columns, valid_columns, sizeof(ColumnInfo), CompareDepth);
|
||
|
||
for (int i = 0; i < valid_columns; i++) {
|
||
const ColumnInfo* col = &columns[i];
|
||
if (col->screen_x < 0 || col->screen_x >= w)
|
||
continue;
|
||
|
||
// 计算垂直缩放范围
|
||
const int draw_height = (int)(img_h * col->scale);
|
||
const int y_start = center_y - draw_height / 2;
|
||
const int y_end = y_start + draw_height;
|
||
|
||
int sy = y_start;
|
||
for (; sy < y_end; sy++) {
|
||
if (sy < 0 || sy >= h)
|
||
continue;
|
||
|
||
// 逆向计算原始Y坐标(带抗锯齿)
|
||
const float src_y = ((sy - y_start) / (float)draw_height) * img_h;
|
||
const int y0 = (int)src_y;
|
||
const int y1 = MIN(y0 + 1, img_h - 1);
|
||
const float fy = src_y - y0;
|
||
|
||
// 抗锯齿采样
|
||
if (ANTIALIASING > 1) {
|
||
uint32_t sum[3] = {0};
|
||
for (int dy = 0; dy < ANTIALIASING; dy++) {
|
||
for (int dx = 0; dx < ANTIALIASING; dx++) {
|
||
const float sample_y = src_y + dy / (float)ANTIALIASING;
|
||
const int y = (int)sample_y;
|
||
const int offset = y * img_w + col->ori_x;
|
||
uint32_t c = swwTexture_Get(rotated_tex, col->ori_x, y);
|
||
uint8_t r, g, b;
|
||
|
||
Color_GetRGB(c, r, g, b);
|
||
sum[0] += r;
|
||
sum[1] += g;
|
||
sum[2] += b;
|
||
}
|
||
}
|
||
const int divisor = ANTIALIASING * ANTIALIASING;
|
||
swwRenderer_DrawPixel(
|
||
ud.renderer, (Point2i){col->screen_x, sy},
|
||
Color_RGB(sum[0] / divisor, sum[1] / divisor, sum[2] / divisor));
|
||
} else {
|
||
// 双线性插值
|
||
uint32_t c1 = swwTexture_Get(rotated_tex, col->ori_x, y0);
|
||
uint32_t c2 = swwTexture_Get(rotated_tex, col->ori_x, y1);
|
||
|
||
uint8_t r1, g1, b1, r2, b2, g2;
|
||
Color_GetRGB(c1, r1, g1, b1);
|
||
Color_GetRGB(c2, r2, g2, b2);
|
||
|
||
swwRenderer_DrawPixel(
|
||
ud.renderer, (Point2i){col->screen_x, sy},
|
||
Color_RGB((1 - fy) * r1 + fy * r2, (1 - fy) * g1 + fy * g2,
|
||
(1 - fy) * b1 + fy * b2));
|
||
}
|
||
}
|
||
}
|
||
} break;
|
||
}
|
||
|
||
if (swwFpsHelper_Update(&fps, ud.renderer)) {
|
||
swwFpsHelper_SetTitleWithFps(&fps, window, TITLE);
|
||
}
|
||
|
||
swwRenderer_Present(ud.renderer);
|
||
|
||
swwApp_PollEvent();
|
||
swwRateHelper_Sleep(&rh);
|
||
}
|
||
|
||
free(columns);
|
||
swwTexture_Destroy(rotated_tex);
|
||
swwTexture_Destroy(tex);
|
||
swwRenderer_Destroy(ud.renderer);
|
||
swwWindow_Destroy(window);
|
||
swwApp_Cleanup();
|
||
|
||
return 0;
|
||
}
|