CHECKPOINT: polygon rendering

This commit is contained in:
boreddevnl 2026-04-03 23:16:03 +02:00
parent 3b24bc882c
commit f0c2963793

View file

@ -450,9 +450,10 @@ static double view_x_min = -10, view_x_max = 10;
static double view_y_min = -6.4, view_y_max = 6.4; static double view_y_min = -6.4, view_y_max = 6.4;
// 3D view // 3D view
static double rot_x = 0.5, rot_y = 0.6; // radians static double rot_x = 0.5, rot_y = 0.6;
static double range_3d = 5.0; static double range_3d = 5.0;
static double zoom_3d = 1.0; // multiplier, 1.0 = auto-fit viewport static double zoom_3d = 1.0;
static bool filled_mode = false;
static bool is_explicit_3d = false; static bool is_explicit_3d = false;
static bool is_explicit_2d = false; static bool is_explicit_2d = false;
@ -460,17 +461,16 @@ static bool is_explicit_2d = false;
static bool right_dragging = false; static bool right_dragging = false;
static int drag_last_x = 0, drag_last_y = 0; static int drag_last_x = 0, drag_last_y = 0;
// 3D surface data #define MAX_Z_PER_POINT 4
static double surf_x[GRID_3D][GRID_3D]; typedef struct {
static double surf_y_3d[GRID_3D][GRID_3D]; double z[MAX_Z_PER_POINT];
static double surf_z1[GRID_3D][GRID_3D]; // Front root float nx[MAX_Z_PER_POINT], ny[MAX_Z_PER_POINT], nz[MAX_Z_PER_POINT];
static double surf_z2[GRID_3D][GRID_3D]; // Back root int sx[MAX_Z_PER_POINT], sy[MAX_Z_PER_POINT], dz[MAX_Z_PER_POINT];
static bool surf_v1[GRID_3D][GRID_3D]; int count;
static bool surf_v2[GRID_3D][GRID_3D]; } surf_point_t;
// Screen-space vertex cache for parallel rasterization static double surf_x[GRID_3D][GRID_3D], surf_y_3d[GRID_3D][GRID_3D];
static int surf_sx1[GRID_3D][GRID_3D], surf_sy1[GRID_3D][GRID_3D], surf_dz1[GRID_3D][GRID_3D]; static surf_point_t surf[GRID_3D][GRID_3D];
static int surf_sx2[GRID_3D][GRID_3D], surf_sy2[GRID_3D][GRID_3D], surf_dz2[GRID_3D][GRID_3D];
static bool surface_needs_eval = true; static bool surface_needs_eval = true;
@ -480,6 +480,7 @@ static double rot_cx, rot_sx, rot_cy, rot_sy;
// Widgets // Widgets
static widget_textbox_t tb_equation; static widget_textbox_t tb_equation;
static widget_button_t btn_plot; static widget_button_t btn_plot;
static widget_button_t btn_mode;
static widget_button_t btn_range_minus; static widget_button_t btn_range_minus;
static widget_button_t btn_range_plus; static widget_button_t btn_range_plus;
@ -565,6 +566,39 @@ static void gfb_line_z(int x0, int y0, int z0, int x1, int y1, int z1, uint32_t
} }
} }
static void gfb_triangle_z(int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2, uint32_t c) {
if (y1 < y0) { int t; t=x0; x0=x1; x1=t; t=y0; y0=y1; y1=t; t=z0; z0=z1; z1=t; }
if (y2 < y0) { int t; t=x0; x0=x2; x2=t; t=y0; y0=y2; y2=t; t=z0; z0=z2; z2=t; }
if (y2 < y1) { int t; t=x1; x1=x2; x2=t; t=y1; y1=y2; y2=t; t=z1; z1=z2; z2=t; }
if (y0 == y2) return;
for (int y = y0; y <= y2; y++) {
if (y < 0 || y >= graph_h) continue;
bool second_half = y > y1 || y1 == y0;
int h1 = second_half ? (y2 - y1) : (y1 - y0);
int h2 = y2 - y0;
if (h1 == 0) h1 = 1;
if (h2 == 0) h2 = 1;
float alpha = (float)(y - y0) / h2;
float beta = (float)(y - (second_half ? y1 : y0)) / h1;
int ax = x0 + (int)((x2 - x0) * alpha);
int bx = second_half ? x1 + (int)((x2 - x1) * beta) : x0 + (int)((x1 - x0) * beta);
int az = z0 + (int)((z2 - z0) * alpha);
int bz = second_half ? z1 + (int)((z2 - z1) * beta) : z0 + (int)((z1 - z0) * beta);
if (ax > bx) { int t; t=ax; ax=bx; bx=t; t=az; az=bz; bz=t; }
for (int x = ax; x <= bx; x++) {
if (x < 0 || x >= graph_w) continue;
float phi = (ax == bx) ? 1.0f : (float)(x - ax) / (bx - ax);
int cz = az + (int)((bz - az) * phi);
gfb_pixel_z(x, y, cz, c);
}
}
}
static uint32_t color_by_height(double z, double zmin, double zmax) { static uint32_t color_by_height(double z, double zmin, double zmax) {
if (zmax <= zmin) return COLOR_CURVE; if (zmax <= zmin) return COLOR_CURVE;
double t = (z - zmin) / (zmax - zmin); double t = (z - zmin) / (zmax - zmin);
@ -578,6 +612,30 @@ static uint32_t color_by_height(double z, double zmin, double zmax) {
return 0xFF000000 | (r<<16) | (g<<8) | b; return 0xFF000000 | (r<<16) | (g<<8) | b;
} }
static uint32_t apply_shading(uint32_t color, double nx, double ny, double nz) {
double len = my_sqrt(nx*nx + ny*ny + nz*nz);
if (len > 1e-9) { nx /= len; ny /= len; nz /= len; }
else { nx = 0; ny = 1; nz = 0; }
double lx = 0.577, ly = 0.707, lz = 0.408;
double dot = nx * lx + ny * ly + nz * lz;
if (dot < 0) dot = -dot * 0.2;
double intensity = 0.3 + 0.7 * dot;
if (intensity > 1.0) intensity = 1.0;
int r = (color >> 16) & 0xFF;
int g = (color >> 8) & 0xFF;
int b = color & 0xFF;
r = (int)(r * intensity);
g = (int)(g * intensity);
b = (int)(b * intensity);
if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255;
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
// ======================== // ========================
// 2D coordinate transforms // 2D coordinate transforms
// ======================== // ========================
@ -920,69 +978,118 @@ static void eval_3d_explicit_job(void *arg) {
double wz = eval_rhs_only(wx, wy, 0); double wz = eval_rhs_only(wx, wy, 0);
surf_x[j][i] = wx; surf_x[j][i] = wx;
surf_y_3d[j][i] = wy; surf_y_3d[j][i] = wy;
surf[j][i].count = 0;
if (my_fabs(wz) > 1e10 || wz != wz) { if (my_fabs(wz) > 1e10 || wz != wz) {
surf_z1[j][i] = 0; surf_v1[j][i] = false; // Invalid value
} else { return;
surf_z1[j][i] = wz; surf_v1[j][i] = true;
} }
// Valid explicit surface point
surf[j][i].z[0] = wz;
// Compute normal for explicit surface z = f(x,y)
// Normal = (-df/dx, -df/dy, 1) normalized
double eps = 0.001;
double dfx = 0.5 * (eval_rhs_only(wx+eps, wy, 0) - eval_rhs_only(wx-eps, wy, 0)) / eps;
double dfy = 0.5 * (eval_rhs_only(wx, wy+eps, 0) - eval_rhs_only(wx, wy-eps, 0)) / eps;
double nx = -dfx;
double ny = -dfy;
double nz = 1.0;
double len = my_sqrt(nx*nx + ny*ny + nz*nz);
if (len > 1e-9) { nx /= len; ny /= len; nz /= len; }
else { nx = 0; ny = 0; nz = 1; }
surf[j][i].nx[0] = (float)nx;
surf[j][i].ny[0] = (float)ny;
surf[j][i].nz[0] = (float)nz;
surf[j][i].count = 1;
} }
} }
} }
static void eval_3d_implicit_job(void *arg) { static void eval_3d_implicit_job(void *arg) {
eval_job_t *job = (eval_job_t *)arg; eval_job_t *job = (eval_job_t *)arg;
int z_steps = 100; int z_steps = 512;
double z_step = job->range * 2.0 / z_steps; double z_step = job->range * 2.0 / z_steps;
for (int j = job->start_j; j < job->end_j; j++) { for (int j = job->start_j; j < job->end_j; j++) {
for (int i = 0; i < GRID_3D; i++) { for (int i = 0; i < GRID_3D; i++) {
surf_v1[j][i] = surf_v2[j][i] = false;
double wx = -job->range + i * job->step; double wx = -job->range + i * job->step;
double wy = -job->range + j * job->step; double wy = -job->range + j * job->step;
surf_x[j][i] = wx; surf_x[j][i] = wx;
surf_y_3d[j][i] = wy; surf_y_3d[j][i] = wy;
surf[j][i].count = 0;
double prev_f = eval_implicit(wx, wy, -job->range); double prev_f = eval_implicit(wx, wy, -job->range);
double found_roots[MAX_Z_PER_POINT];
int roots_found = 0; int roots_found = 0;
for (int k = 1; k <= z_steps && roots_found < 2; k++) {
for (int k = 1; k <= z_steps && roots_found < MAX_Z_PER_POINT; k++) {
double zz = -job->range + k * z_step; double zz = -job->range + k * z_step;
double cur_f = eval_implicit(wx, wy, zz); double cur_f = eval_implicit(wx, wy, zz);
if ((prev_f > 0) != (cur_f > 0) && my_fabs(prev_f) < 1e10 && my_fabs(cur_f) < 1e10) {
if ((prev_f > 0) != (cur_f > 0)) {
if (my_fabs(prev_f) < 1e10 && my_fabs(cur_f) < 1e10) {
double za = zz - z_step, zb = zz; double za = zz - z_step, zb = zz;
double fa = prev_f, fb = cur_f;
for (int b = 0; b < 15; b++) { for (int b = 0; b < 15; b++) {
double zm = (za + zb) * 0.5; double zm = (za + zb) * 0.5;
double fm = eval_implicit(wx, wy, zm); double fm = eval_implicit(wx, wy, zm);
if ((prev_f > 0) != (fm > 0)) zb = zm; else { za = zm; prev_f = fm; } if ((fa > 0) != (fm > 0)) {
} zb = zm;
if (roots_found == 0) { fb = fm;
surf_z1[j][i] = (za + zb) * 0.5; surf_v1[j][i] = true;
} else { } else {
surf_z2[j][i] = (za + zb) * 0.5; surf_v2[j][i] = true; za = zm;
// Simple sort: ensure z1 < z2 (in screen space d/zd will handle it, but keep it stable here) fa = fm;
if (surf_z1[j][i] > surf_z2[j][i]) {
double tmp = surf_z1[j][i];
surf_z1[j][i] = surf_z2[j][i];
surf_z2[j][i] = tmp;
} }
} }
roots_found++; found_roots[roots_found++] = (za + zb) * 0.5;
}
} }
prev_f = cur_f; prev_f = cur_f;
} }
for (int r = 0; r < roots_found - 1; r++) {
for (int s = r + 1; s < roots_found; s++) {
if (found_roots[r] > found_roots[s]) {
double tmp = found_roots[r];
found_roots[r] = found_roots[s];
found_roots[s] = tmp;
}
}
}
for (int r = 0; r < roots_found; r++) {
surf[j][i].z[r] = found_roots[r];
double wz = found_roots[r];
double eps = 0.001;
double nx = eval_implicit(wx+eps, wy, wz) - eval_implicit(wx-eps, wy, wz);
double ny = eval_implicit(wx, wy+eps, wz) - eval_implicit(wx, wy-eps, wz);
double nz = eval_implicit(wx, wy, wz+eps) - eval_implicit(wx, wy, wz-eps);
double d = my_sqrt(nx*nx + ny*ny + nz*nz);
if (d > 1e-12) { nx /= d; ny /= d; nz /= d; }
else { nx = 0; ny = 1; nz = 0; }
surf[j][i].nx[r] = (float)nx;
surf[j][i].ny[r] = (float)ny;
surf[j][i].nz[r] = (float)nz;
}
surf[j][i].count = roots_found;
} }
} }
} }
// Parallel Projection Job
static void eval_3d_project_job(void *arg) { static void eval_3d_project_job(void *arg) {
eval_job_t *job = (eval_job_t *)arg; eval_job_t *job = (eval_job_t *)arg;
for (int j = job->start_j; j < job->end_j; j++) { for (int j = job->start_j; j < job->end_j; j++) {
for (int i = 0; i < GRID_3D; i++) { for (int i = 0; i < GRID_3D; i++) {
if (surf_v1[j][i]) { for (int s = 0; s < surf[j][i].count; s++) {
project_3d(surf_x[j][i], surf_z1[j][i] * job->z_scale, surf_y_3d[j][i], &surf_sx1[j][i], &surf_sy1[j][i], &surf_dz1[j][i]); project_3d(surf_x[j][i], surf[j][i].z[s] * job->z_scale, surf_y_3d[j][i],
} &surf[j][i].sx[s], &surf[j][i].sy[s], &surf[j][i].dz[s]);
if (surf_v2[j][i]) {
project_3d(surf_x[j][i], surf_z2[j][i] * job->z_scale, surf_y_3d[j][i], &surf_sx2[j][i], &surf_sy2[j][i], &surf_dz2[j][i]);
} }
} }
} }
@ -993,50 +1100,101 @@ typedef struct {
double zmin, zmax; double zmin, zmax;
} draw_job_t; } draw_job_t;
// Parallel Draw Job
static void render_3d_draw_job(void *arg) { static void render_3d_draw_job(void *arg) {
draw_job_t *job = (draw_job_t *)arg; draw_job_t *job = (draw_job_t *)arg;
for (int j = job->start_j; j < job->end_j; j++) { for (int j = job->start_j; j < job->end_j; j++) {
for (int i = 0; i < GRID_3D; i++) { for (int i = 0; i < GRID_3D; i++) {
for (int s = 0; s < 2; s++) { if (surf[j][i].count == 0) continue;
bool *v = (s == 0) ? surf_v1[j] : surf_v2[j];
int *sx_row = (s == 0) ? surf_sx1[j] : surf_sx2[j];
int *sy_row = (s == 0) ? surf_sy1[j] : surf_sy2[j];
int *dz_row = (s == 0) ? surf_dz1[j] : surf_dz2[j];
double *z_row = (s == 0) ? surf_z1[j] : surf_z2[j];
if (!v[i]) continue; // 1. Regular Surface Rendering
for (int s = 0; s < surf[j][i].count; s++) {
int sx0 = surf[j][i].sx[s], sy0 = surf[j][i].sy[s], dz0 = surf[j][i].dz[s];
uint32_t col = color_by_height(surf[j][i].z[s], job->zmin, job->zmax);
int sx0 = sx_row[i], sy0 = sy_row[i], dz0 = dz_row[i]; if (filled_mode) {
uint32_t col = color_by_height(z_row[i], job->zmin, job->zmax); bool v_tr = (i+1 < GRID_3D) && (s < surf[j][i+1].count);
bool v_bl = (j+1 < GRID_3D) && (s < surf[j+1][i].count);
bool v_br = (i+1 < GRID_3D && j+1 < GRID_3D) && (s < surf[j+1][i+1].count);
if (i + 1 < GRID_3D) { if (v_tr && v_bl && v_br) {
bool v_next = (s == 0) ? surf_v1[j][i+1] : surf_v2[j][i+1]; int sx_tr = surf[j][i+1].sx[s], sy_tr = surf[j][i+1].sy[s], dz_tr = surf[j][i+1].dz[s];
if (v_next) { int sx_bl = surf[j+1][i].sx[s], sy_bl = surf[j+1][i].sy[s], dz_bl = surf[j+1][i].dz[s];
int *sx_next_row = (s == 0) ? surf_sx1[j] : surf_sx2[j]; int sx_br = surf[j+1][i+1].sx[s], sy_br = surf[j+1][i+1].sy[s], dz_br = surf[j+1][i+1].dz[s];
int *sy_next_row = (s == 0) ? surf_sy1[j] : surf_sy2[j];
int *dz_next_row = (s == 0) ? surf_dz1[j] : surf_dz2[j]; float avg_nx = surf[j][i].nx[s] + surf[j][i+1].nx[s] + surf[j+1][i].nx[s] + surf[j+1][i+1].nx[s];
gfb_line_z(sx0, sy0, dz0, sx_next_row[i+1], sy_next_row[i+1], dz_next_row[i+1], col); float avg_ny = surf[j][i].ny[s] + surf[j][i+1].ny[s] + surf[j+1][i].ny[s] + surf[j+1][i+1].ny[s];
float avg_nz = surf[j][i].nz[s] + surf[j][i+1].nz[s] + surf[j+1][i].nz[s] + surf[j+1][i+1].nz[s];
avg_nx *= 0.25f; avg_ny *= 0.25f; avg_nz *= 0.25f;
float nlen = (float)my_sqrt(avg_nx*avg_nx + avg_ny*avg_ny + avg_nz*avg_nz);
if (nlen > 1e-9) { avg_nx /= nlen; avg_ny /= nlen; avg_nz /= nlen; }
else { avg_nx = 0; avg_ny = 0; avg_nz = 1; }
uint32_t scol = apply_shading(col, avg_nx, avg_ny, avg_nz);
gfb_triangle_z(sx0, sy0, dz0, sx_tr, sy_tr, dz_tr, sx_bl, sy_bl, dz_bl, scol);
gfb_triangle_z(sx_tr, sy_tr, dz_tr, sx_br, sy_br, dz_br, sx_bl, sy_bl, dz_bl, scol);
} }
} else {
if (i + 1 < GRID_3D && s < surf[j][i+1].count) {
gfb_line_z(sx0, sy0, dz0, surf[j][i+1].sx[s], surf[j][i+1].sy[s], surf[j][i+1].dz[s], col);
}
if (j + 1 < GRID_3D && s < surf[j+1][i].count) {
gfb_line_z(sx0, sy0, dz0, surf[j+1][i].sx[s], surf[j+1][i].sy[s], surf[j+1][i].dz[s], col);
} }
if (j + 1 < GRID_3D) {
bool v_next = (s == 0) ? surf_v1[j+1][i] : surf_v2[j+1][i];
if (v_next) {
int *sx_next_row = (s == 0) ? surf_sx1[j+1] : surf_sx2[j+1];
int *sy_next_row = (s == 0) ? surf_sy1[j+1] : surf_sy2[j+1];
int *dz_next_row = (s == 0) ? surf_dz1[j+1] : surf_dz2[j+1];
gfb_line_z(sx0, sy0, dz0, sx_next_row[i], sy_next_row[i], dz_next_row[i], col);
} }
} }
if (s == 0 && surf_v1[j][i] && surf_v2[j][i]) { if (surf[j][i].count > 1) {
bool edge = false; int s_last = surf[j][i].count - 1;
if (i+1 < GRID_3D && !surf_v1[j][i+1]) edge = true; int sx0 = surf[j][i].sx[0], sy0 = surf[j][i].sy[0], dz0 = surf[j][i].dz[0];
if (i-1 >= 0 && !surf_v1[j][i-1]) edge = true; int sx1 = surf[j][i].sx[s_last], sy1 = surf[j][i].sy[s_last], dz1 = surf[j][i].dz[s_last];
if (j+1 < GRID_3D && !surf_v1[j+1][i]) edge = true;
if (j-1 >= 0 && !surf_v1[j-1][i]) edge = true; bool right_empty = (i + 1 >= GRID_3D || surf[j][i+1].count == 0);
if (edge) { bool left_empty = (i - 1 < 0 || surf[j][i-1].count == 0);
gfb_line_z(sx0, sy0, dz0, surf_sx2[j][i], surf_sy2[j][i], surf_dz2[j][i], col); bool bottom_empty = (j + 1 >= GRID_3D || surf[j+1][i].count == 0);
bool top_empty = (j - 1 < 0 || surf[j-1][i].count == 0);
bool is_edge = (right_empty || left_empty || bottom_empty || top_empty);
if (is_edge) {
if (!filled_mode) {
uint32_t col = color_by_height(surf[j][i].z[0], job->zmin, job->zmax);
gfb_line_z(sx0, sy0, dz0, sx1, sy1, dz1, col);
} else {
double mid_z = (surf[j][i].z[0] + surf[j][i].z[s_last]) * 0.5;
double eps = 0.001;
double wx = surf_x[j][i], wy = surf_y_3d[j][i];
double gnx = eval_implicit(wx + eps, wy, mid_z) - eval_implicit(wx - eps, wy, mid_z);
double gny = eval_implicit(wx, wy + eps, mid_z) - eval_implicit(wx, wy - eps, mid_z);
double dmag = my_sqrt(gnx*gnx + gny*gny);
if (dmag > 1e-12) { gnx /= dmag; gny /= dmag; }
else { gnx = 0; gny = 1; }
uint32_t wcol = apply_shading(color_by_height(mid_z, job->zmin, job->zmax), gnx, gny, 0);
if (j + 1 < GRID_3D && surf[j+1][i].count > 1) {
bool segment_is_right_edge = right_empty && (i + 1 >= GRID_3D || surf[j+1][i+1].count == 0);
bool segment_is_left_edge = left_empty && (i - 1 < 0 || surf[j+1][i-1].count == 0);
if (segment_is_right_edge || segment_is_left_edge) {
int ns0 = surf[j+1][i].sx[0], ny0 = surf[j+1][i].sy[0], nz0 = surf[j+1][i].dz[0];
int ns1 = surf[j+1][i].sx[surf[j+1][i].count-1], ny1 = surf[j+1][i].sy[surf[j+1][i].count-1], nz1 = surf[j+1][i].dz[surf[j+1][i].count-1];
gfb_triangle_z(sx0, sy0, dz0, sx1, sy1, dz1, ns0, ny0, nz0, wcol);
gfb_triangle_z(sx1, sy1, dz1, ns1, ny1, nz1, ns0, ny0, nz0, wcol);
}
}
if (i + 1 < GRID_3D && surf[j][i+1].count > 1) {
bool segment_is_bottom_edge = bottom_empty && (j + 1 >= GRID_3D || surf[j+1][i+1].count == 0);
bool segment_is_top_edge = top_empty && (j - 1 < 0 || surf[j-1][i+1].count == 0);
if (segment_is_bottom_edge || segment_is_top_edge) {
int ns0 = surf[j][i+1].sx[0], ny0 = surf[j][i+1].sy[0], nz0 = surf[j][i+1].dz[0];
int ns1 = surf[j][i+1].sx[surf[j][i+1].count-1], ny1 = surf[j][i+1].sy[surf[j][i+1].count-1], nz1 = surf[j][i+1].dz[surf[j][i+1].count-1];
gfb_triangle_z(sx0, sy0, dz0, sx1, sy1, dz1, ns0, ny0, nz0, wcol);
gfb_triangle_z(sx1, sy1, dz1, ns1, ny1, nz1, ns0, ny0, nz0, wcol);
}
}
} }
} }
} }
@ -1049,7 +1207,11 @@ static void render_3d_explicit(void) {
double zmin = 1e30, zmax = -1e30; double zmin = 1e30, zmax = -1e30;
if (surface_needs_eval) { if (surface_needs_eval) {
for (int j = 0; j < GRID_3D; j++) { for (int i = 0; i < GRID_3D; i++) { surf_v1[j][i] = false; surf_v2[j][i] = false; } } for (int j = 0; j < GRID_3D; j++) {
for (int i = 0; i < GRID_3D; i++) {
surf[j][i].count = 0;
}
}
int num_chunks = 4; int num_chunks = 4;
eval_job_t jobs[4]; eval_job_t jobs[4];
@ -1070,9 +1232,9 @@ static void render_3d_explicit(void) {
// Compute min/max for coloring // Compute min/max for coloring
for (int j = 0; j < GRID_3D; j++) { for (int j = 0; j < GRID_3D; j++) {
for (int i = 0; i < GRID_3D; i++) { for (int i = 0; i < GRID_3D; i++) {
if (surf_v1[j][i]) { for (int s = 0; s < surf[j][i].count; s++) {
if (surf_z1[j][i] < zmin) zmin = surf_z1[j][i]; if (surf[j][i].z[s] < zmin) zmin = surf[j][i].z[s];
if (surf_z1[j][i] > zmax) zmax = surf_z1[j][i]; if (surf[j][i].z[s] > zmax) zmax = surf[j][i].z[s];
} }
} }
} }
@ -1099,7 +1261,6 @@ static void render_3d_explicit(void) {
sys_parallel_run(eval_3d_project_job, job_args, num_chunks); sys_parallel_run(eval_3d_project_job, job_args, num_chunks);
} }
// Pass 3: Parallel Drawing
{ {
int num_chunks = 4; int num_chunks = 4;
draw_job_t jobs[4]; draw_job_t jobs[4];
@ -1125,7 +1286,11 @@ static void render_3d_implicit(void) {
int num_chunks = 4; int num_chunks = 4;
eval_job_t jobs[4]; eval_job_t jobs[4];
void *job_args[4]; void *job_args[4];
for (int j = 0; j < GRID_3D; j++) { for (int i = 0; i < GRID_3D; i++) { surf_v1[j][i] = false; surf_v2[j][i] = false; } } for (int j = 0; j < GRID_3D; j++) {
for (int i = 0; i < GRID_3D; i++) {
surf[j][i].count = 0;
}
}
int rows_per_chunk = GRID_3D / num_chunks; int rows_per_chunk = GRID_3D / num_chunks;
@ -1140,7 +1305,6 @@ static void render_3d_implicit(void) {
sys_parallel_run(eval_3d_implicit_job, job_args, num_chunks); sys_parallel_run(eval_3d_implicit_job, job_args, num_chunks);
} }
// Pass 2: Parallel Projection
{ {
int num_chunks = 4; int num_chunks = 4;
eval_job_t jobs[4]; eval_job_t jobs[4];
@ -1157,21 +1321,15 @@ static void render_3d_implicit(void) {
sys_parallel_run(eval_3d_project_job, job_args, num_chunks); sys_parallel_run(eval_3d_project_job, job_args, num_chunks);
} }
// Compute min/max for coloring based on what's visible
for (int j = 0; j < GRID_3D; j++) { for (int j = 0; j < GRID_3D; j++) {
for (int i = 0; i < GRID_3D; i++) { for (int i = 0; i < GRID_3D; i++) {
if (surf_v1[j][i]) { for (int s = 0; s < surf[j][i].count; s++) {
if (surf_z1[j][i] < zmin) zmin = surf_z1[j][i]; if (surf[j][i].z[s] < zmin) zmin = surf[j][i].z[s];
if (surf_z1[j][i] > zmax) zmax = surf_z1[j][i]; if (surf[j][i].z[s] > zmax) zmax = surf[j][i].z[s];
}
if (surf_v2[j][i]) {
if (surf_z2[j][i] < zmin) zmin = surf_z2[j][i];
if (surf_z2[j][i] > zmax) zmax = surf_z2[j][i];
} }
} }
} }
// Pass 3: Parallel Drawing
{ {
int num_chunks = 4; int num_chunks = 4;
draw_job_t jobs[4]; draw_job_t jobs[4];
@ -1232,10 +1390,9 @@ static void render_graph(void) {
} }
if (graph_mode == MODE_2D) { if (graph_mode == MODE_2D) {
if (is_explicit_2d) render_2d_explicit(); // auto-fits Y before grid if (is_explicit_2d) render_2d_explicit();
render_2d_grid(); render_2d_grid();
if (is_explicit_2d) { if (is_explicit_2d) {
// Re-draw curve on top of grid
int prev_sx = -1, prev_sy = -1; int prev_sx = -1, prev_sy = -1;
bool prev_valid = false; bool prev_valid = false;
for (int px = 0; px < graph_w; px++) { for (int px = 0; px < graph_w; px++) {
@ -1256,12 +1413,10 @@ static void render_graph(void) {
ui_draw_image(win_graph, 0, GRAPH_Y, graph_w, graph_h, graph_fb); ui_draw_image(win_graph, 0, GRAPH_Y, graph_w, graph_h, graph_fb);
// Post-image overlay (labels)
if (graph_mode == MODE_2D) { if (graph_mode == MODE_2D) {
double x_step = get_nice_step(view_x_max - view_x_min, 8); double x_step = get_nice_step(view_x_max - view_x_min, 8);
double y_step = get_nice_step(view_y_max - view_y_min, 6); double y_step = get_nice_step(view_y_max - view_y_min, 6);
// Labels need coordinate mapping to the window
int axis_y = screen_y_2d(0); int axis_y = screen_y_2d(0);
if (axis_y < 10) axis_y = 10; if (axis_y < 10) axis_y = 10;
if (axis_y > graph_h - 20) axis_y = graph_h - 20; if (axis_y > graph_h - 20) axis_y = graph_h - 20;
@ -1311,6 +1466,7 @@ static void paint_all(void) {
ui_draw_rect(win_graph, 0, 0, win_w, TOOLBAR_H, COLOR_TOOLBAR_BG); ui_draw_rect(win_graph, 0, 0, win_w, TOOLBAR_H, COLOR_TOOLBAR_BG);
widget_textbox_draw(&wctx, &tb_equation); widget_textbox_draw(&wctx, &tb_equation);
widget_button_draw(&wctx, &btn_plot); widget_button_draw(&wctx, &btn_plot);
widget_button_draw(&wctx, &btn_mode);
ui_draw_rounded_rect_filled(win_graph, win_w - 80, 4, 70, 22, 4, 0xFF3A3A5A); ui_draw_rounded_rect_filled(win_graph, win_w - 80, 4, 70, 22, 4, 0xFF3A3A5A);
ui_draw_string(win_graph, win_w - 72, 8, "Presets", COLOR_DARK_TEXT); ui_draw_string(win_graph, win_w - 72, 8, "Presets", COLOR_DARK_TEXT);
render_graph(); render_graph();
@ -1338,7 +1494,6 @@ static void paint_all(void) {
widget_button_draw(&wctx, &btn_range_plus); widget_button_draw(&wctx, &btn_range_plus);
} }
// Presets dropdown overlay
if (presets_open) { if (presets_open) {
int px = win_w - 150, py = TOOLBAR_H; int px = win_w - 150, py = TOOLBAR_H;
ui_draw_rounded_rect_filled(win_graph, px, py, 140, NUM_PRESETS * 20 + 4, 4, COLOR_DARK_PANEL); ui_draw_rounded_rect_filled(win_graph, px, py, 140, NUM_PRESETS * 20 + 4, 4, COLOR_DARK_PANEL);
@ -1357,9 +1512,8 @@ static void paint_all(void) {
static void reset_view(void) { static void reset_view(void) {
if (graph_mode == MODE_2D) { if (graph_mode == MODE_2D) {
view_x_min = -10.0; view_x_max = 10.0; view_x_min = -10.0; view_x_max = 10.0;
// Centralize 0,0
view_y_min = -6.4; view_y_max = 6.4; view_y_min = -6.4; view_y_max = 6.4;
apply_aspect_ratio(); // This will ensure 0,0 is at the center correctly apply_aspect_ratio();
} else { } else {
zoom_3d = 1.0; zoom_3d = 1.0;
rot_x = -0.5; rot_x = -0.5;
@ -1400,15 +1554,15 @@ static void handle_scroll(int dz) {
if (dz > 0) zoom_2d(0.85); if (dz > 0) zoom_2d(0.85);
else zoom_2d(1.18); else zoom_2d(1.18);
} else { } else {
// Scroll only alters zoom factor, not computational bounds
if (dz > 0) zoom_3d *= 1.15; if (dz > 0) zoom_3d *= 1.15;
else zoom_3d *= 0.87; else zoom_3d *= 0.87;
} }
} }
static void update_widget_layout(void) { static void update_widget_layout(void) {
tb_equation.w = win_w - 160; tb_equation.w = win_w - 220;
btn_plot.x = win_w - 140; btn_plot.x = win_w - 200;
widget_button_init(&btn_mode, win_w - 145, 4, 60, 22, filled_mode ? "Wire" : "Filled");
int sty = win_h - STATUSBAR_H - 20; int sty = win_h - STATUSBAR_H - 20;
widget_button_init(&btn_range_minus, win_w - 95, sty + 4, 30, 22, "-"); widget_button_init(&btn_range_minus, win_w - 95, sty + 4, 30, 22, "-");
@ -1465,10 +1619,14 @@ int main(void) {
eq_len = strlen(eq_buffer); eq_len = strlen(eq_buffer);
needs_repaint = true; needs_repaint = true;
} }
} else if ((c == 'r' || c == 'R') && ev.arg3 == 1) { // reset view with ctrl + R } else if ((c == 'r' || c == 'R') && ev.arg3 == 1) {
reset_view(); reset_view();
surface_needs_eval = true; surface_needs_eval = true;
needs_repaint = true; needs_repaint = true;
} else if ((c == 'f' || c == 'F')) {
filled_mode = !filled_mode;
update_widget_layout();
needs_repaint = true;
} }
} else if (ev.type == GUI_EVENT_RESIZE) { } else if (ev.type == GUI_EVENT_RESIZE) {
win_w = ev.arg1; win_w = ev.arg1;
@ -1526,6 +1684,13 @@ int main(void) {
continue; continue;
} }
if (widget_button_handle_mouse(&btn_mode, mx, my, false, true, NULL)) {
filled_mode = !filled_mode;
update_widget_layout();
needs_repaint = true;
continue;
}
if (graph_mode == MODE_3D) { if (graph_mode == MODE_3D) {
if (widget_button_handle_mouse(&btn_range_plus, mx, my, false, true, NULL)) { if (widget_button_handle_mouse(&btn_range_plus, mx, my, false, true, NULL)) {
range_3d *= 1.25; range_3d *= 1.25;
@ -1547,6 +1712,7 @@ int main(void) {
} else if (ev.type == GUI_EVENT_MOUSE_DOWN) { } else if (ev.type == GUI_EVENT_MOUSE_DOWN) {
int mx = ev.arg1, my = ev.arg2; int mx = ev.arg1, my = ev.arg2;
widget_button_handle_mouse(&btn_plot, mx, my, true, false, NULL); widget_button_handle_mouse(&btn_plot, mx, my, true, false, NULL);
widget_button_handle_mouse(&btn_mode, mx, my, true, false, NULL);
if (graph_mode == MODE_3D) { if (graph_mode == MODE_3D) {
widget_button_handle_mouse(&btn_range_plus, mx, my, true, false, NULL); widget_button_handle_mouse(&btn_range_plus, mx, my, true, false, NULL);
widget_button_handle_mouse(&btn_range_minus, mx, my, true, false, NULL); widget_button_handle_mouse(&btn_range_minus, mx, my, true, false, NULL);
@ -1556,6 +1722,7 @@ int main(void) {
} else if (ev.type == GUI_EVENT_MOUSE_UP) { } else if (ev.type == GUI_EVENT_MOUSE_UP) {
int mx = ev.arg1, my = ev.arg2; int mx = ev.arg1, my = ev.arg2;
widget_button_handle_mouse(&btn_plot, mx, my, false, false, NULL); widget_button_handle_mouse(&btn_plot, mx, my, false, false, NULL);
widget_button_handle_mouse(&btn_mode, mx, my, false, false, NULL);
if (graph_mode == MODE_3D) { if (graph_mode == MODE_3D) {
widget_button_handle_mouse(&btn_range_plus, mx, my, false, false, NULL); widget_button_handle_mouse(&btn_range_plus, mx, my, false, false, NULL);
widget_button_handle_mouse(&btn_range_minus, mx, my, false, false, NULL); widget_button_handle_mouse(&btn_range_minus, mx, my, false, false, NULL);