240 lines
10 KiB
C
240 lines
10 KiB
C
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "figure-draw.h"
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
/* Вспомогательная функция для установки пикселя */
|
|
static inline void set_pixel(uint8_t *data, int32_t width, int32_t height, int x, int y, uint32_t color)
|
|
{
|
|
if (x < 0 || x >= width || y < 0 || y >= height)
|
|
return;
|
|
|
|
uint32_t *pixel = (uint32_t *)(data + (y * width + x) * 4);
|
|
*pixel = color;
|
|
}
|
|
|
|
/* Проверка, находится ли точка внутри треугольника (барицентрические координаты) */
|
|
static int point_in_triangle(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3)
|
|
{
|
|
float d1 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2);
|
|
float d2 = (px - x3) * (y2 - y3) - (x2 - x3) * (py - y3);
|
|
float d3 = (px - x1) * (y3 - y1) - (x3 - x1) * (py - y1);
|
|
|
|
int has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
|
|
int has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
|
|
|
|
return !(has_neg && has_pos);
|
|
}
|
|
|
|
/* Расстояние от точки до отрезка */
|
|
static float point_to_segment_distance(float px, float py, float x1, float y1, float x2, float y2)
|
|
{
|
|
float dx = x2 - x1;
|
|
float dy = y2 - y1;
|
|
float len_sq = dx * dx + dy * dy;
|
|
|
|
if (len_sq < 0.0001f) {
|
|
dx = px - x1;
|
|
dy = py - y1;
|
|
return sqrtf(dx * dx + dy * dy);
|
|
}
|
|
|
|
float t = ((px - x1) * dx + (py - y1) * dy) / len_sq;
|
|
t = fmaxf(0.0f, fminf(1.0f, t));
|
|
|
|
float proj_x = x1 + t * dx;
|
|
float proj_y = y1 + t * dy;
|
|
|
|
dx = px - proj_x;
|
|
dy = py - proj_y;
|
|
|
|
return sqrtf(dx * dx + dy * dy);
|
|
}
|
|
|
|
/* Рисование круга */
|
|
static void draw_circle(struct window_draw_info* draw_info, float cx, float cy, float radius,
|
|
float border_thickness, uint32_t border_color, uint32_t fill_color)
|
|
{
|
|
int x_min = (int)fmaxf(0, cx - radius - border_thickness);
|
|
int x_max = (int)fminf(draw_info->width - 1, cx + radius + border_thickness);
|
|
int y_min = (int)fmaxf(0, cy - radius - border_thickness);
|
|
int y_max = (int)fminf(draw_info->height - 1, cy + radius + border_thickness);
|
|
|
|
for (int y = y_min; y <= y_max; y++) {
|
|
for (int x = x_min; x <= x_max; x++) {
|
|
float dx = x - cx;
|
|
float dy = y - cy;
|
|
float dist = sqrtf(dx * dx + dy * dy);
|
|
|
|
if (dist <= radius) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color);
|
|
} else if (dist <= radius + border_thickness) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Рисование треугольника */
|
|
static void draw_triangle(struct window_draw_info* draw_info, float cx, float cy, float radius, float angle,
|
|
float border_thickness, uint32_t border_color, uint32_t fill_color)
|
|
{
|
|
/* Вычисляем координаты вершин равностороннего треугольника */
|
|
/* Угол 0 означает, что одна вершина справа от центра */
|
|
float vertices[3][2];
|
|
for (int i = 0; i < 3; i++) {
|
|
float vertex_angle = angle + i * (2.0f * M_PI / 3.0f);
|
|
vertices[i][0] = cx + radius * cosf(vertex_angle);
|
|
vertices[i][1] = cy - radius * sinf(vertex_angle); /* Инвертируем Y для экранных координат */
|
|
}
|
|
|
|
/* Находим ограничивающий прямоугольник */
|
|
float min_x = fminf(vertices[0][0], fminf(vertices[1][0], vertices[2][0])) - border_thickness;
|
|
float max_x = fmaxf(vertices[0][0], fmaxf(vertices[1][0], vertices[2][0])) + border_thickness;
|
|
float min_y = fminf(vertices[0][1], fminf(vertices[1][1], vertices[2][1])) - border_thickness;
|
|
float max_y = fmaxf(vertices[0][1], fmaxf(vertices[1][1], vertices[2][1])) + border_thickness;
|
|
|
|
int x_min = (int)fmaxf(0, min_x);
|
|
int x_max = (int)fminf(draw_info->width - 1, max_x);
|
|
int y_min = (int)fmaxf(0, min_y);
|
|
int y_max = (int)fminf(draw_info->height - 1, max_y);
|
|
|
|
/* Рисуем треугольник */
|
|
for (int y = y_min; y <= y_max; y++) {
|
|
for (int x = x_min; x <= x_max; x++) {
|
|
int inside = point_in_triangle((float)x, (float)y,
|
|
vertices[0][0], vertices[0][1],
|
|
vertices[1][0], vertices[1][1],
|
|
vertices[2][0], vertices[2][1]);
|
|
|
|
if (inside) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color);
|
|
} else {
|
|
/* Проверяем расстояние до границ */
|
|
float dist1 = point_to_segment_distance((float)x, (float)y,
|
|
vertices[0][0], vertices[0][1],
|
|
vertices[1][0], vertices[1][1]);
|
|
float dist2 = point_to_segment_distance((float)x, (float)y,
|
|
vertices[1][0], vertices[1][1],
|
|
vertices[2][0], vertices[2][1]);
|
|
float dist3 = point_to_segment_distance((float)x, (float)y,
|
|
vertices[2][0], vertices[2][1],
|
|
vertices[0][0], vertices[0][1]);
|
|
|
|
float min_dist = fminf(dist1, fminf(dist2, dist3));
|
|
if (min_dist <= border_thickness) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Рисование квадрата */
|
|
static void draw_square(struct window_draw_info* draw_info, float cx, float cy, float radius, float angle,
|
|
float border_thickness, uint32_t border_color, uint32_t fill_color)
|
|
{
|
|
/* Вычисляем координаты вершин квадрата */
|
|
/* Угол 0 означает, что одна вершина справа от центра */
|
|
float vertices[4][2];
|
|
for (int i = 0; i < 4; i++) {
|
|
float vertex_angle = angle + i * (M_PI / 2.0f);
|
|
vertices[i][0] = cx + radius * cosf(vertex_angle);
|
|
vertices[i][1] = cy - radius * sinf(vertex_angle); /* Инвертируем Y для экранных координат */
|
|
}
|
|
|
|
/* Находим ограничивающий прямоугольник */
|
|
float min_x = vertices[0][0], max_x = vertices[0][0];
|
|
float min_y = vertices[0][1], max_y = vertices[0][1];
|
|
for (int i = 1; i < 4; i++) {
|
|
min_x = fminf(min_x, vertices[i][0]);
|
|
max_x = fmaxf(max_x, vertices[i][0]);
|
|
min_y = fminf(min_y, vertices[i][1]);
|
|
max_y = fmaxf(max_y, vertices[i][1]);
|
|
}
|
|
|
|
min_x -= border_thickness;
|
|
max_x += border_thickness;
|
|
min_y -= border_thickness;
|
|
max_y += border_thickness;
|
|
|
|
int x_min = (int)fmaxf(0, min_x);
|
|
int x_max = (int)fminf(draw_info->width - 1, max_x);
|
|
int y_min = (int)fmaxf(0, min_y);
|
|
int y_max = (int)fminf(draw_info->height - 1, max_y);
|
|
|
|
/* Рисуем квадрат */
|
|
for (int y = y_min; y <= y_max; y++) {
|
|
for (int x = x_min; x <= x_max; x++) {
|
|
float px = (float)x;
|
|
float py = (float)y;
|
|
|
|
/* Проверяем, находится ли точка внутри квадрата */
|
|
/* Используем два треугольника */
|
|
int inside = point_in_triangle(px, py,
|
|
vertices[0][0], vertices[0][1],
|
|
vertices[1][0], vertices[1][1],
|
|
vertices[2][0], vertices[2][1]) ||
|
|
point_in_triangle(px, py,
|
|
vertices[0][0], vertices[0][1],
|
|
vertices[2][0], vertices[2][1],
|
|
vertices[3][0], vertices[3][1]);
|
|
|
|
if (inside) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, fill_color);
|
|
} else {
|
|
/* Проверяем расстояние до границ */
|
|
float min_dist = INFINITY;
|
|
for (int i = 0; i < 4; i++) {
|
|
int next = (i + 1) % 4;
|
|
float dist = point_to_segment_distance(px, py,
|
|
vertices[i][0], vertices[i][1],
|
|
vertices[next][0], vertices[next][1]);
|
|
min_dist = fminf(min_dist, dist);
|
|
}
|
|
|
|
if (min_dist <= border_thickness) {
|
|
set_pixel(draw_info->data, draw_info->width, draw_info->height, x, y, border_color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void figure_draw(struct window_draw_info* draw_info, float border_thickness, uint32_t border_color, uint32_t fill_color)
|
|
{
|
|
if (!draw_info || !draw_info->data)
|
|
return;
|
|
|
|
/* Координаты приходят в относительных единицах
|
|
* Y: [0..1] -> умножаем на высоту
|
|
* X: нормализован относительно высоты -> умножаем на высоту же
|
|
*/
|
|
float cx = draw_info->figure.position.x * draw_info->height;
|
|
float cy = draw_info->figure.position.y * draw_info->height;
|
|
float radius = draw_info->figure.radius;
|
|
float angle = draw_info->figure.angle;
|
|
enum figure_type type = draw_info->figure.type;
|
|
|
|
switch (type) {
|
|
case FIGURE_CIRCLE:
|
|
draw_circle(draw_info, cx, cy, radius, border_thickness, border_color, fill_color);
|
|
break;
|
|
|
|
case FIGURE_TRIANGLE:
|
|
draw_triangle(draw_info, cx, cy, radius, angle, border_thickness, border_color, fill_color);
|
|
break;
|
|
|
|
case FIGURE_SQUARE:
|
|
draw_square(draw_info, cx, cy, radius, angle, border_thickness, border_color, fill_color);
|
|
break;
|
|
}
|
|
}
|
|
|