NaraMotion / c_src /blend2d_perspective.cpp
ArifiIslam's picture
Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified
Raw History Blame Contribute Delete
14 kB
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#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"