#include #include #include #include #include #include #define PI 3.14159265358979323846 #define DEG_TO_RAD (PI / 180.0) #define RAD_TO_DEG (180.0 / PI) #ifdef _WIN32 #define BL_PERSPECTIVE_EXPORT __declspec(dllexport) #else #define BL_PERSPECTIVE_EXPORT __attribute__((visibility("default"))) #endif // Blend2D C Types typedef uint32_t BLResult; struct BLPoint { double x; double y; }; struct BLPathCore { void* impl; }; extern "C" { BLResult bl_path_init(BLPathCore* self); BLResult bl_path_reset(BLPathCore* self); BLResult bl_path_clear(BLPathCore* self); BLResult bl_path_destroy(BLPathCore* self); size_t bl_path_get_size(const BLPathCore* self); const BLPoint* bl_path_get_vertex_data(const BLPathCore* self); const uint8_t* bl_path_get_command_data(const BLPathCore* self); BLResult bl_path_move_to(BLPathCore* self, double x0, double y0); BLResult bl_path_line_to(BLPathCore* self, double x1, double y1); BLResult bl_path_close(BLPathCore* self); } // 3D Camera Parameters struct struct BLCameraParams { double viewport_width; double viewport_height; double fov_degrees; double pitch_degrees; // Tilt angle (0 = top-down, 80 = grazing horizon) double yaw_degrees; // Horizontal rotation double roll_degrees; // Side-to-side banking double distance; // Camera distance / altitude double target_x; // Focus center X double target_y; // Focus center Y double near_clip; // Minimum depth W before clipping }; extern "C" { // ============================================================================ // 1. Perspective Matrix & 3D Projector Core // ============================================================================ static inline bool _project_single_point( double x, double y, const BLCameraParams* cam, double* out_sx, double* out_sy, double* out_depth_scale ) { double dx = x - cam->target_x; double dy = y - cam->target_y; double yaw_rad = cam->yaw_degrees * DEG_TO_RAD; double pitch_rad = cam->pitch_degrees * DEG_TO_RAD; double roll_rad = cam->roll_degrees * DEG_TO_RAD; double cos_yaw = std::cos(yaw_rad), sin_yaw = std::sin(yaw_rad); double cos_pitch = std::cos(pitch_rad), sin_pitch = std::sin(pitch_rad); double cos_roll = std::cos(roll_rad), sin_roll = std::sin(roll_rad); // 1. Yaw rotation (horizontal rotation around Z axis) double x1 = dx * cos_yaw - dy * sin_yaw; double y1 = dx * sin_yaw + dy * cos_yaw; // 2. Pitch rotation (tilt towards horizon) // Z depth along optical axis: Z = y1 * sin(pitch) + distance double z_cam = y1 * sin_pitch + cam->distance; double y_cam = y1 * cos_pitch; double x_cam = x1; // Near-plane clipping: if point is behind camera or too close to lens double near_plane = (cam->near_clip > 1.0) ? cam->near_clip : 10.0; if (z_cam <= near_plane) { if (out_sx) *out_sx = 0.0; if (out_sy) *out_sy = 0.0; if (out_depth_scale) *out_depth_scale = 0.0; return false; } // 3. Roll rotation (camera banking) double x_final = x_cam * cos_roll - y_cam * sin_roll; double y_final = x_cam * sin_roll + y_cam * cos_roll; // 4. Perspective Projection double fov_rad = cam->fov_degrees * DEG_TO_RAD; double f = 1.0 / std::tan(fov_rad * 0.5); double scale = (f * cam->viewport_height * 0.5) / z_cam; double sx = cam->viewport_width * 0.5 + x_final * scale; double sy = cam->viewport_height * 0.5 - y_final * scale; double depth_scale = cam->distance / z_cam; if (out_sx) *out_sx = sx; if (out_sy) *out_sy = sy; if (out_depth_scale) *out_depth_scale = depth_scale; return true; } BL_PERSPECTIVE_EXPORT int bl_perspective_project_point( double x, double y, const BLCameraParams* cam, double* out_sx, double* out_sy, double* out_depth_scale ) { if (!cam) return 0; return _project_single_point(x, y, cam, out_sx, out_sy, out_depth_scale) ? 1 : 0; } BL_PERSPECTIVE_EXPORT size_t bl_perspective_transform_points( const double* in_pts_xy, double* out_pts_xy, double* out_depth_scales, uint8_t* out_valid_mask, size_t count, const BLCameraParams* cam ) { if (!in_pts_xy || !out_pts_xy || !cam || count == 0) return 0; size_t valid_count = 0; for (size_t i = 0; i < count; i++) { double px = in_pts_xy[i * 2 + 0]; double py = in_pts_xy[i * 2 + 1]; double sx = 0.0, sy = 0.0, ds = 0.0; bool ok = _project_single_point(px, py, cam, &sx, &sy, &ds); out_pts_xy[i * 2 + 0] = sx; out_pts_xy[i * 2 + 1] = sy; if (out_depth_scales) out_depth_scales[i] = ds; if (out_valid_mask) out_valid_mask[i] = ok ? 1 : 0; if (ok) valid_count++; } return valid_count; } BL_PERSPECTIVE_EXPORT size_t bl_perspective_transform_lines( const double* in_lines_x1y1x2y2, double* out_lines_x1y1x2y2, double* out_depth_scales, uint8_t* out_valid_mask, size_t line_count, const BLCameraParams* cam ) { if (!in_lines_x1y1x2y2 || !out_lines_x1y1x2y2 || !cam || line_count == 0) return 0; size_t valid_count = 0; for (size_t i = 0; i < line_count; i++) { double x1 = in_lines_x1y1x2y2[i * 4 + 0]; double y1 = in_lines_x1y1x2y2[i * 4 + 1]; double x2 = in_lines_x1y1x2y2[i * 4 + 2]; double y2 = in_lines_x1y1x2y2[i * 4 + 3]; double sx1 = 0.0, sy1 = 0.0, ds1 = 0.0; double sx2 = 0.0, sy2 = 0.0, ds2 = 0.0; bool ok1 = _project_single_point(x1, y1, cam, &sx1, &sy1, &ds1); bool ok2 = _project_single_point(x2, y2, cam, &sx2, &sy2, &ds2); out_lines_x1y1x2y2[i * 4 + 0] = sx1; out_lines_x1y1x2y2[i * 4 + 1] = sy1; out_lines_x1y1x2y2[i * 4 + 2] = sx2; out_lines_x1y1x2y2[i * 4 + 3] = sy2; double avg_ds = (ds1 + ds2) * 0.5; if (out_depth_scales) out_depth_scales[i] = avg_ds; bool line_valid = (ok1 && ok2); if (out_valid_mask) out_valid_mask[i] = line_valid ? 1 : 0; if (line_valid) valid_count++; } return valid_count; } BL_PERSPECTIVE_EXPORT int bl_perspective_transform_path( const BLPathCore* src, BLPathCore* dst, double* out_avg_depth, const BLCameraParams* cam ) { if (!src || !dst || !cam) return -1; size_t count = bl_path_get_size(src); if (count == 0) return 0; const BLPoint* vtx_data = bl_path_get_vertex_data(src); const uint8_t* cmd_data = bl_path_get_command_data(src); bl_path_clear(dst); double total_depth = 0.0; size_t depth_samples = 0; bool in_figure = false; for (size_t i = 0; i < count; i++) { uint8_t cmd = cmd_data[i]; double vx = vtx_data[i].x; double vy = vtx_data[i].y; if (cmd == 5) { // Close if (in_figure) { bl_path_close(dst); in_figure = false; } continue; } if (std::isnan(vx) || std::isnan(vy) || std::isinf(vx) || std::isinf(vy)) { continue; } double sx = 0.0, sy = 0.0, ds = 0.0; bool ok = _project_single_point(vx, vy, cam, &sx, &sy, &ds); if (!ok) { in_figure = false; continue; } total_depth += ds; depth_samples++; if (cmd == 0 || !in_figure) { // MoveTo bl_path_move_to(dst, sx, sy); in_figure = true; } else if (cmd == 1) { // LineTo bl_path_line_to(dst, sx, sy); } else { bl_path_line_to(dst, sx, sy); } } if (out_avg_depth) { *out_avg_depth = (depth_samples > 0) ? (total_depth / depth_samples) : 1.0; } return 0; } // ============================================================================ // 2. High-Performance 3D Spherical Globe Renderer (Ray-Sphere Texture Mapping) // ============================================================================ static inline uint32_t _sample_bilinear_rgba32( const uint32_t* src, int width, int height, int stride_bytes, double u, double v ) { u = std::fmod(u, 1.0); if (u < 0.0) u += 1.0; v = std::max(0.0, std::min(1.0, v)); double px = u * (width - 1); double py = v * (height - 1); int x0 = (int)px; int y0 = (int)py; int x1 = std::min(x0 + 1, width - 1); int y1 = std::min(y0 + 1, height - 1); double fx = px - x0; double fy = py - y0; int stride_pixels = stride_bytes / 4; uint32_t c00 = src[y0 * stride_pixels + x0]; uint32_t c10 = src[y0 * stride_pixels + x1]; uint32_t c01 = src[y1 * stride_pixels + x0]; uint32_t c11 = src[y1 * stride_pixels + x1]; auto unpack_lerp = [&](int shift) -> uint32_t { double v00 = (c00 >> shift) & 0xFF; double v10 = (c10 >> shift) & 0xFF; double v01 = (c01 >> shift) & 0xFF; double v11 = (c11 >> shift) & 0xFF; double top = v00 + (v10 - v00) * fx; double bot = v01 + (v11 - v01) * fx; double val = top + (bot - top) * fy; return (uint32_t)std::max(0.0, std::min(255.0, val)); }; uint32_t a = unpack_lerp(24); uint32_t r = unpack_lerp(16); uint32_t g = unpack_lerp(8); uint32_t b = unpack_lerp(0); return (a << 24) | (r << 16) | (g << 8) | b; } BL_PERSPECTIVE_EXPORT int bl_perspective_render_globe( uint32_t* dst_pixels, int dst_w, int dst_h, int dst_stride_bytes, const uint32_t* src_texture, int src_w, int src_h, int src_stride_bytes, double globe_cx, double globe_cy, double globe_radius, double rot_lon_deg, double rot_lat_deg, uint32_t glow_color ) { if (!dst_pixels || !src_texture || dst_w <= 0 || dst_h <= 0 || globe_radius <= 0.0) { return -1; } int dst_stride_px = dst_stride_bytes / 4; double r_sq = globe_radius * globe_radius; double inv_radius = 1.0 / globe_radius; double rot_lon_rad = rot_lon_deg * DEG_TO_RAD; double rot_lat_rad = rot_lat_deg * DEG_TO_RAD; double c_lat = std::cos(rot_lat_rad), s_lat = std::sin(rot_lat_rad); int min_y = std::max(0, (int)std::floor(globe_cy - globe_radius * 1.15)); int max_y = std::min(dst_h - 1, (int)std::ceil(globe_cy + globe_radius * 1.15)); int min_x = std::max(0, (int)std::floor(globe_cx - globe_radius * 1.15)); int max_x = std::min(dst_w - 1, (int)std::ceil(globe_cx + globe_radius * 1.15)); uint32_t glow_a = (glow_color >> 24) & 0xFF; uint32_t glow_r = (glow_color >> 16) & 0xFF; uint32_t glow_g = (glow_color >> 8) & 0xFF; uint32_t glow_b = glow_color & 0xFF; for (int y = min_y; y <= max_y; y++) { double dy = (double)y - globe_cy; double dy_sq = dy * dy; for (int x = min_x; x <= max_x; x++) { double dx = (double)x - globe_cx; double dist_sq = dx * dx + dy_sq; double dist = std::sqrt(dist_sq); if (dist_sq <= r_sq) { // Ray intersects front sphere hemisphere: z = sqrt(R^2 - x^2 - y^2) double dz = std::sqrt(r_sq - dist_sq); // Local normal (nx, ny, nz) double nx = dx * inv_radius; double ny = dy * inv_radius; double nz = dz * inv_radius; // Latitude & Longitude rotation double ny2 = ny * c_lat - nz * s_lat; double nz2 = ny * s_lat + nz * c_lat; double nx2 = nx; double lat = std::asin(std::max(-1.0, std::min(1.0, -ny2))); double lon = std::atan2(nx2, nz2) + rot_lon_rad; // Map to UV coordinates double u = (lon / (2.0 * PI)) + 0.5; double v = 0.5 - (lat / PI); uint32_t tex_col = _sample_bilinear_rgba32(src_texture, src_w, src_h, src_stride_bytes, u, v); // Shading: limb darkening & atmospheric edge glow double limb = 1.0 - nz; double shade = 0.6 + 0.4 * nz; uint32_t a = (tex_col >> 24) & 0xFF; uint32_t r = (uint32_t)(((tex_col >> 16) & 0xFF) * shade); uint32_t g = (uint32_t)(((tex_col >> 8) & 0xFF) * shade); uint32_t b = (uint32_t)((tex_col & 0xFF) * shade); // Add subtle atmosphere rim if (limb > 0.6 && glow_a > 0) { double rim_factor = (limb - 0.6) / 0.4; r = (uint32_t)std::min(255.0, r + glow_r * rim_factor * 0.5); g = (uint32_t)std::min(255.0, g + glow_g * rim_factor * 0.5); b = (uint32_t)std::min(255.0, b + glow_b * rim_factor * 0.5); } dst_pixels[y * dst_stride_px + x] = (a << 24) | (r << 16) | (g << 8) | b; } else if (dist < globe_radius * 1.15 && glow_a > 0) { // Atmospheric Outer Glow Halo double halo_prog = 1.0 - (dist - globe_radius) / (globe_radius * 0.15); double alpha_factor = std::pow(halo_prog, 2.5); uint32_t cur_pixel = dst_pixels[y * dst_stride_px + x]; uint32_t cur_a = (cur_pixel >> 24) & 0xFF; uint32_t cur_r = (cur_pixel >> 16) & 0xFF; uint32_t cur_g = (cur_pixel >> 8) & 0xFF; uint32_t cur_b = cur_pixel & 0xFF; uint32_t out_r = (uint32_t)std::min(255.0, cur_r + glow_r * alpha_factor); uint32_t out_g = (uint32_t)std::min(255.0, cur_g + glow_g * alpha_factor); uint32_t out_b = (uint32_t)std::min(255.0, cur_b + glow_b * alpha_factor); uint32_t out_a = (uint32_t)std::min(255.0, cur_a + glow_a * alpha_factor); dst_pixels[y * dst_stride_px + x] = (out_a << 24) | (out_r << 16) | (out_g << 8) | out_b; } } } return 0; } } // extern "C"