Release NaraMotion v0.26.0: Structure of Arrays (SoA), Path Arena Allocator, Uniform Grid Indexing, Dual-Buffer Rendering & Centroid Spring Pop-in
b516e00 verified Download c_src/blend2d_geo.cpp from ArifiIslam/NaraMotion: direct link, hf CLI and curl.
- Browser
- Download file 63.5 kB
-
https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/c_src/blend2d_geo.cpp
- Command line
-
hf download hf://ArifiIslam/NaraMotion/c_src/blend2d_geo.cpp
-
curl -L -o blend2d_geo.cpp https://huggingface.co/ArifiIslam/NaraMotion/resolve/main/c_src/blend2d_geo.cpp
63.5 kB
| // Constants | |
| static const double PI = 3.14159265358979323846; | |
| static const double DEG_TO_RAD = PI / 180.0; | |
| static const double RAD_TO_DEG = 180.0 / PI; | |
| static const double WGS84_A = 6378137.0; // Semi-major axis | |
| static const double WGS84_F = 1.0 / 298.257223563; // Flattening | |
| static const double WGS84_E2 = 2.0 * WGS84_F - WGS84_F * WGS84_F; // First eccentricity squared | |
| static const double WGS84_E = 0.08181919084262149; // First eccentricity | |
| // Blend2D C Types | |
| typedef uint32_t BLResult; | |
| struct BLPoint { | |
| double x; | |
| double y; | |
| }; | |
| struct BLMatrix2D { | |
| double m00; | |
| double m01; | |
| double m10; | |
| double m11; | |
| double m20; | |
| double m21; | |
| }; | |
| struct BLPathCore { | |
| uint64_t a; | |
| uint64_t b; | |
| }; | |
| struct BLContextCore { | |
| void* impl; | |
| }; | |
| extern "C" { | |
| BLResult bl_path_init(BLPathCore* self); | |
| BLResult bl_path_reset(BLPathCore* self); | |
| BLResult bl_path_destroy(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); | |
| BLResult bl_context_fill_geometry_rgba32(BLContextCore* ctx, uint32_t type, const void* data, uint32_t rgba32); | |
| BLResult bl_context_stroke_geometry_rgba32(BLContextCore* ctx, uint32_t type, const void* data, uint32_t rgba32); | |
| BLResult bl_context_set_stroke_width(BLContextCore* ctx, double width); | |
| BLResult bl_context_set_fill_rule(BLContextCore* ctx, uint32_t fill_rule); | |
| BLResult bl_context_apply_transform_op(BLContextCore* ctx, uint32_t opType, const void* opData); | |
| } | |
| // Geospatial Viewport Transformation | |
| struct GeoTransform { | |
| double minx; | |
| double miny; | |
| double maxx; | |
| double maxy; | |
| double canvas_w; | |
| double canvas_h; | |
| double offset_x; | |
| double offset_y; | |
| double scale; | |
| int flip_y; // 1: standard GIS coordinate system (latitude increases upwards) | |
| }; | |
| inline void transform_pt(const GeoTransform* gt, double gx, double gy, double& sx, double& sy) { | |
| if (!gt || gt->scale == 0.0) { | |
| sx = gx; | |
| sy = gy; | |
| return; | |
| } | |
| sx = gt->offset_x + (gx - gt->minx) * gt->scale; | |
| if (gt->flip_y) { | |
| sy = gt->canvas_h - (gt->offset_y + (gy - gt->miny) * gt->scale); | |
| } else { | |
| sy = gt->offset_y + (gy - gt->miny) * gt->scale; | |
| } | |
| } | |
| // Byte-swap helpers for Big-Endian integers in Shapefile headers | |
| inline int32_t swap_be32(int32_t val) { | |
| uint32_t u = (uint32_t)val; | |
| return (int32_t)(((u >> 24) & 0x000000ff) | | |
| ((u >> 8) & 0x0000ff00) | | |
| ((u << 8) & 0x00ff0000) | | |
| ((u << 24) & 0xff000000)); | |
| } | |
| // ============================================================================ | |
| // ESRI Shapefile C-ABI Structs | |
| // ============================================================================ | |
| struct ShpHeader { | |
| int32_t file_code; // 9994 | |
| int32_t file_length; // in 16-bit words | |
| int32_t version; // 1000 | |
| int32_t shape_type; // 1=Point, 3=PolyLine, 5=Polygon, 8=MultiPoint | |
| double minx, miny, maxx, maxy; | |
| double zmin, zmax, mmin, mmax; | |
| int32_t record_count; | |
| }; | |
| struct ShpParsedData { | |
| int32_t shape_type; | |
| int32_t num_records; | |
| int64_t total_parts; | |
| int64_t total_points; | |
| double* coords; // [total_points * 2] (x, y) | |
| int32_t* parts; // [total_parts] | |
| int32_t* record_parts; // [num_records + 1] (index into parts) | |
| int64_t* record_points; // [num_records + 1] (index into points) | |
| double* bboxes; // [num_records * 4] (minx, miny, maxx, maxy) | |
| }; | |
| extern "C" { | |
| // ============================================================================ | |
| // Projection Engine Mathematical Implementations | |
| // ============================================================================ | |
| BL_GEO_EXPORT int bl_geo_proj_3857_forward(const double* in_coords, double* out_coords, int64_t num_points) { | |
| if (!in_coords || !out_coords || num_points <= 0) return -1; | |
| for (int64_t i = 0; i < num_points; ++i) { | |
| double lon = in_coords[i * 2]; | |
| double lat = in_coords[i * 2 + 1]; | |
| if (lat > 85.0511287798) lat = 85.0511287798; | |
| else if (lat < -85.0511287798) lat = -85.0511287798; | |
| double x = lon * (WGS84_A * DEG_TO_RAD); | |
| double lat_rad = lat * DEG_TO_RAD; | |
| double y = WGS84_A * std::log(std::tan(PI * 0.25 + lat_rad * 0.5)); | |
| out_coords[i * 2] = x; | |
| out_coords[i * 2 + 1] = y; | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_proj_3857_inverse(const double* in_coords, double* out_coords, int64_t num_points) { | |
| if (!in_coords || !out_coords || num_points <= 0) return -1; | |
| for (int64_t i = 0; i < num_points; ++i) { | |
| double x = in_coords[i * 2]; | |
| double y = in_coords[i * 2 + 1]; | |
| double lon = (x / WGS84_A) * RAD_TO_DEG; | |
| double lat = (2.0 * std::atan(std::exp(y / WGS84_A)) - PI * 0.5) * RAD_TO_DEG; | |
| out_coords[i * 2] = lon; | |
| out_coords[i * 2 + 1] = lat; | |
| } | |
| return 0; | |
| } | |
| static inline double lcc_m(double phi, double e) { | |
| double s = std::sin(phi); | |
| return std::cos(phi) / std::sqrt(1.0 - e * e * s * s); | |
| } | |
| static inline double lcc_t(double phi, double e) { | |
| double s = e * std::sin(phi); | |
| double con = std::pow((1.0 - s) / (1.0 + s), e * 0.5); | |
| return std::tan(PI * 0.25 - phi * 0.5) / con; | |
| } | |
| BL_GEO_EXPORT int bl_geo_proj_lcc_forward( | |
| double phi1_deg, double phi2_deg, double phi0_deg, double lam0_deg, | |
| double x0, double y0, | |
| const double* in_coords, double* out_coords, int64_t num_points | |
| ) { | |
| if (!in_coords || !out_coords || num_points <= 0) return -1; | |
| double phi1 = phi1_deg * DEG_TO_RAD; | |
| double phi2 = phi2_deg * DEG_TO_RAD; | |
| double phi0 = phi0_deg * DEG_TO_RAD; | |
| double lam0 = lam0_deg * DEG_TO_RAD; | |
| double e = WGS84_E; | |
| double m1 = lcc_m(phi1, e); | |
| double m2 = lcc_m(phi2, e); | |
| double t1 = lcc_t(phi1, e); | |
| double t2 = lcc_t(phi2, e); | |
| double t0 = lcc_t(phi0, e); | |
| double n; | |
| if (std::abs(phi1 - phi2) > 1e-10) { | |
| n = (std::log(m1) - std::log(m2)) / (std::log(t1) - std::log(t2)); | |
| } else { | |
| n = std::sin(phi1); | |
| } | |
| double F = m1 / (n * std::pow(t1, n)); | |
| double rho0 = WGS84_A * F * std::pow(t0, n); | |
| for (int64_t i = 0; i < num_points; ++i) { | |
| double lon = in_coords[i * 2] * DEG_TO_RAD; | |
| double lat = in_coords[i * 2 + 1] * DEG_TO_RAD; | |
| if (std::abs(lat) > (PI * 0.5 - 1e-6)) { | |
| lat = (lat > 0 ? 1 : -1) * (PI * 0.5 - 1e-6); | |
| } | |
| double t = lcc_t(lat, e); | |
| double rho = WGS84_A * F * std::pow(t, n); | |
| double theta = n * (lon - lam0); | |
| out_coords[i * 2] = x0 + rho * std::sin(theta); | |
| out_coords[i * 2 + 1] = y0 + rho0 - rho * std::cos(theta); | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_proj_albers_forward( | |
| double phi1_deg, double phi2_deg, double phi0_deg, double lam0_deg, | |
| double x0, double y0, | |
| const double* in_coords, double* out_coords, int64_t num_points | |
| ) { | |
| if (!in_coords || !out_coords || num_points <= 0) return -1; | |
| double phi1 = phi1_deg * DEG_TO_RAD; | |
| double phi2 = phi2_deg * DEG_TO_RAD; | |
| double phi0 = phi0_deg * DEG_TO_RAD; | |
| double lam0 = lam0_deg * DEG_TO_RAD; | |
| double n = 0.5 * (std::sin(phi1) + std::sin(phi2)); | |
| double C = std::cos(phi1) * std::cos(phi1) + 2.0 * n * std::sin(phi1); | |
| double rho0 = (WGS84_A / n) * std::sqrt(std::max(0.0, C - 2.0 * n * std::sin(phi0))); | |
| for (int64_t i = 0; i < num_points; ++i) { | |
| double lon = in_coords[i * 2] * DEG_TO_RAD; | |
| double lat = in_coords[i * 2 + 1] * DEG_TO_RAD; | |
| double theta = n * (lon - lam0); | |
| double q = C - 2.0 * n * std::sin(lat); | |
| double rho = (WGS84_A / n) * std::sqrt(std::max(0.0, q)); | |
| out_coords[i * 2] = x0 + rho * std::sin(theta); | |
| out_coords[i * 2 + 1] = y0 + rho0 - rho * std::cos(theta); | |
| } | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Viewport & Geometry Builders | |
| // ============================================================================ | |
| BL_GEO_EXPORT void bl_geo_init_transform( | |
| GeoTransform* gt, | |
| double minx, double miny, double maxx, double maxy, | |
| double canvas_w, double canvas_h, | |
| double padding_ratio, | |
| int preserve_aspect_ratio, | |
| int flip_y | |
| ) { | |
| if (!gt) return; | |
| gt->minx = minx; | |
| gt->miny = miny; | |
| gt->maxx = maxx; | |
| gt->maxy = maxy; | |
| gt->canvas_w = canvas_w; | |
| gt->canvas_h = canvas_h; | |
| gt->flip_y = flip_y; | |
| double geo_w = maxx - minx; | |
| double geo_h = maxy - miny; | |
| if (geo_w <= 0.0) geo_w = 1e-6; | |
| if (geo_h <= 0.0) geo_h = 1e-6; | |
| double pad_x = canvas_w * padding_ratio; | |
| double pad_y = canvas_h * padding_ratio; | |
| double avail_w = canvas_w - 2.0 * pad_x; | |
| double avail_h = canvas_h - 2.0 * pad_y; | |
| double sx = avail_w / geo_w; | |
| double sy = avail_h / geo_h; | |
| if (preserve_aspect_ratio) { | |
| gt->scale = (sx < sy) ? sx : sy; | |
| double used_w = geo_w * gt->scale; | |
| double used_h = geo_h * gt->scale; | |
| gt->offset_x = pad_x + (avail_w - used_w) * 0.5; | |
| gt->offset_y = pad_y + (avail_h - used_h) * 0.5; | |
| } else { | |
| gt->scale = sx; | |
| gt->offset_x = pad_x; | |
| gt->offset_y = pad_y; | |
| } | |
| } | |
| BL_GEO_EXPORT int bl_geo_build_polygon( | |
| BLPathCore* path, | |
| const double* coords, | |
| const int64_t* ring_offsets, | |
| int64_t num_rings, | |
| const GeoTransform* gt | |
| ) { | |
| if (!path || !coords || !ring_offsets || num_rings <= 0) return -1; | |
| for (int64_t r = 0; r < num_rings; ++r) { | |
| int64_t start_idx = ring_offsets[r]; | |
| int64_t end_idx = ring_offsets[r + 1]; | |
| int64_t pt_count = end_idx - start_idx; | |
| if (pt_count < 3) continue; | |
| double sx, sy; | |
| transform_pt(gt, coords[start_idx * 2], coords[start_idx * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| for (int64_t i = start_idx + 1; i < end_idx; ++i) { | |
| transform_pt(gt, coords[i * 2], coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| } | |
| bl_path_close(path); | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_build_linestring( | |
| BLPathCore* path, | |
| const double* coords, | |
| int64_t num_points, | |
| const GeoTransform* gt | |
| ) { | |
| if (!path || !coords || num_points < 2) return -1; | |
| double sx, sy; | |
| transform_pt(gt, coords[0], coords[1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| for (int64_t i = 1; i < num_points; ++i) { | |
| transform_pt(gt, coords[i * 2], coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_build_multilinestring( | |
| BLPathCore* path, | |
| const double* coords, | |
| const int64_t* line_offsets, | |
| int64_t num_lines, | |
| const GeoTransform* gt | |
| ) { | |
| if (!path || !coords || !line_offsets || num_lines <= 0) return -1; | |
| for (int64_t l = 0; l < num_lines; ++l) { | |
| int64_t start_idx = line_offsets[l]; | |
| int64_t end_idx = line_offsets[l + 1]; | |
| int64_t pt_count = end_idx - start_idx; | |
| if (pt_count < 2) continue; | |
| double sx, sy; | |
| transform_pt(gt, coords[start_idx * 2], coords[start_idx * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| for (int64_t i = start_idx + 1; i < end_idx; ++i) { | |
| transform_pt(gt, coords[i * 2], coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| } | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_transform_coords( | |
| const double* in_coords, | |
| double* out_coords, | |
| int64_t num_points, | |
| const GeoTransform* gt | |
| ) { | |
| if (!in_coords || !out_coords || num_points <= 0) return -1; | |
| for (int64_t i = 0; i < num_points; ++i) { | |
| double sx, sy; | |
| transform_pt(gt, in_coords[i * 2], in_coords[i * 2 + 1], sx, sy); | |
| out_coords[i * 2] = sx; | |
| out_coords[i * 2 + 1] = sy; | |
| } | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // High-Speed Binary Shapefile (.shp) Parser (C++) | |
| // ============================================================================ | |
| BL_GEO_EXPORT int bl_geo_read_shp_header(const char* filepath, ShpHeader* hdr) { | |
| if (!filepath || !hdr) return -1; | |
| FILE* fp = fopen(filepath, "rb"); | |
| if (!fp) return -2; | |
| uint8_t buffer[100]; | |
| if (fread(buffer, 1, 100, fp) < 100) { | |
| fclose(fp); | |
| return -3; | |
| } | |
| int32_t file_code = swap_be32(*(int32_t*)(buffer + 0)); | |
| if (file_code != 9994) { | |
| fclose(fp); | |
| return -4; | |
| } | |
| hdr->file_code = file_code; | |
| hdr->file_length = swap_be32(*(int32_t*)(buffer + 24)) * 2; | |
| hdr->version = *(int32_t*)(buffer + 28); | |
| hdr->shape_type = *(int32_t*)(buffer + 32); | |
| hdr->minx = *(double*)(buffer + 36); | |
| hdr->miny = *(double*)(buffer + 44); | |
| hdr->maxx = *(double*)(buffer + 52); | |
| hdr->maxy = *(double*)(buffer + 60); | |
| hdr->zmin = *(double*)(buffer + 68); | |
| hdr->zmax = *(double*)(buffer + 76); | |
| hdr->mmin = *(double*)(buffer + 84); | |
| hdr->mmax = *(double*)(buffer + 92); | |
| int count = 0; | |
| while (true) { | |
| int32_t rec_hdr[2]; | |
| if (fread(rec_hdr, 4, 2, fp) < 2) break; | |
| int32_t rec_len = swap_be32(rec_hdr[1]) * 2; | |
| if (rec_len <= 0) continue; | |
| fseek(fp, rec_len, SEEK_CUR); | |
| count++; | |
| } | |
| hdr->record_count = count; | |
| fclose(fp); | |
| return 0; | |
| } | |
| BL_GEO_EXPORT ShpParsedData* bl_geo_parse_shp(const char* filepath) { | |
| if (!filepath) return nullptr; | |
| FILE* fp = fopen(filepath, "rb"); | |
| if (!fp) return nullptr; | |
| uint8_t hdr_buf[100]; | |
| if (fread(hdr_buf, 1, 100, fp) < 100) { | |
| fclose(fp); | |
| return nullptr; | |
| } | |
| int32_t shape_type = *(int32_t*)(hdr_buf + 32); | |
| std::vector<double> all_coords; | |
| std::vector<int32_t> all_parts; | |
| std::vector<int32_t> rec_parts; | |
| std::vector<int64_t> rec_points; | |
| std::vector<double> all_bboxes; | |
| rec_parts.push_back(0); | |
| rec_points.push_back(0); | |
| while (true) { | |
| int32_t rec_hdr[2]; | |
| if (fread(rec_hdr, 4, 2, fp) < 2) break; | |
| int32_t rec_num = swap_be32(rec_hdr[0]); | |
| int32_t rec_len = swap_be32(rec_hdr[1]) * 2; | |
| if (rec_len <= 0) continue; | |
| std::vector<uint8_t> rec_data(rec_len); | |
| if (fread(rec_data.data(), 1, rec_len, fp) < (size_t)rec_len) break; | |
| int32_t rec_shape = *(int32_t*)(rec_data.data()); | |
| if (rec_shape == 0) { | |
| all_bboxes.push_back(0); all_bboxes.push_back(0); all_bboxes.push_back(0); all_bboxes.push_back(0); | |
| rec_parts.push_back((int32_t)all_parts.size()); | |
| rec_points.push_back((int64_t)(all_coords.size() / 2)); | |
| continue; | |
| } | |
| if (rec_shape == 1 || rec_shape == 11 || rec_shape == 21) { | |
| double px = *(double*)(rec_data.data() + 4); | |
| double py = *(double*)(rec_data.data() + 12); | |
| all_bboxes.push_back(px); all_bboxes.push_back(py); all_bboxes.push_back(px); all_bboxes.push_back(py); | |
| all_coords.push_back(px); | |
| all_coords.push_back(py); | |
| all_parts.push_back((int32_t)(all_coords.size() / 2 - 1)); | |
| } else if (rec_shape == 3 || rec_shape == 5 || rec_shape == 13 || rec_shape == 15) { | |
| double r_minx = *(double*)(rec_data.data() + 4); | |
| double r_miny = *(double*)(rec_data.data() + 12); | |
| double r_maxx = *(double*)(rec_data.data() + 20); | |
| double r_maxy = *(double*)(rec_data.data() + 28); | |
| all_bboxes.push_back(r_minx); all_bboxes.push_back(r_miny); all_bboxes.push_back(r_maxx); all_bboxes.push_back(r_maxy); | |
| int32_t num_parts = *(int32_t*)(rec_data.data() + 36); | |
| int32_t num_points = *(int32_t*)(rec_data.data() + 40); | |
| const int32_t* parts_ptr = (const int32_t*)(rec_data.data() + 44); | |
| const double* pts_ptr = (const double*)(rec_data.data() + 44 + num_parts * 4); | |
| int64_t base_pt = all_coords.size() / 2; | |
| for (int p = 0; p < num_parts; ++p) { | |
| all_parts.push_back((int32_t)(base_pt + parts_ptr[p])); | |
| } | |
| for (int i = 0; i < num_points * 2; ++i) { | |
| all_coords.push_back(pts_ptr[i]); | |
| } | |
| } | |
| rec_parts.push_back((int32_t)all_parts.size()); | |
| rec_points.push_back((int64_t)(all_coords.size() / 2)); | |
| } | |
| fclose(fp); | |
| auto* result = new ShpParsedData(); | |
| result->shape_type = shape_type; | |
| result->num_records = (int32_t)(rec_points.size() - 1); | |
| result->total_parts = (int64_t)all_parts.size(); | |
| result->total_points = (int64_t)(all_coords.size() / 2); | |
| result->coords = (double*)malloc(all_coords.size() * sizeof(double)); | |
| memcpy(result->coords, all_coords.data(), all_coords.size() * sizeof(double)); | |
| result->parts = (int32_t*)malloc(all_parts.size() * sizeof(int32_t)); | |
| memcpy(result->parts, all_parts.data(), all_parts.size() * sizeof(int32_t)); | |
| result->record_parts = (int32_t*)malloc(rec_parts.size() * sizeof(int32_t)); | |
| memcpy(result->record_parts, rec_parts.data(), rec_parts.size() * sizeof(int32_t)); | |
| result->record_points = (int64_t*)malloc(rec_points.size() * sizeof(int64_t)); | |
| memcpy(result->record_points, rec_points.data(), rec_points.size() * sizeof(int64_t)); | |
| result->bboxes = (double*)malloc(all_bboxes.size() * sizeof(double)); | |
| memcpy(result->bboxes, all_bboxes.data(), all_bboxes.size() * sizeof(double)); | |
| return result; | |
| } | |
| BL_GEO_EXPORT void bl_geo_free_shp_data(ShpParsedData* data) { | |
| if (!data) return; | |
| if (data->coords) free(data->coords); | |
| if (data->parts) free(data->parts); | |
| if (data->record_parts) free(data->record_parts); | |
| if (data->record_points) free(data->record_points); | |
| if (data->bboxes) free(data->bboxes); | |
| delete data; | |
| } | |
| // ============================================================================ | |
| // High-Speed HTTP/HTTPS Downloader (Native WinHTTP C-API) | |
| // ============================================================================ | |
| BL_GEO_EXPORT int bl_geo_http_download_file(const char* url_utf8, const char* dest_path_utf8) { | |
| if (!url_utf8 || !dest_path_utf8) return -1; | |
| // Convert UTF-8 URL to WCHAR | |
| int url_wlen = MultiByteToWideChar(CP_UTF8, 0, url_utf8, -1, NULL, 0); | |
| if (url_wlen <= 0) return -2; | |
| std::vector<WCHAR> wurl(url_wlen); | |
| MultiByteToWideChar(CP_UTF8, 0, url_utf8, -1, wurl.data(), url_wlen); | |
| URL_COMPONENTS urlComp; | |
| ZeroMemory(&urlComp, sizeof(urlComp)); | |
| urlComp.dwStructSize = sizeof(urlComp); | |
| urlComp.dwSchemeLength = (DWORD)-1; | |
| urlComp.dwHostNameLength = (DWORD)-1; | |
| urlComp.dwUrlPathLength = (DWORD)-1; | |
| urlComp.dwExtraInfoLength = (DWORD)-1; | |
| if (!WinHttpCrackUrl(wurl.data(), (DWORD)wcslen(wurl.data()), 0, &urlComp)) { | |
| return -3; | |
| } | |
| std::wstring hostName(urlComp.lpszHostName, urlComp.dwHostNameLength); | |
| std::wstring urlPath(urlComp.lpszUrlPath, urlComp.dwUrlPathLength + urlComp.dwExtraInfoLength); | |
| HINTERNET hSession = WinHttpOpen(L"NaraMotion-GeoEngine/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); | |
| if (!hSession) return -4; | |
| HINTERNET hConnect = WinHttpConnect(hSession, hostName.c_str(), urlComp.nPort, 0); | |
| if (!hConnect) { | |
| WinHttpCloseHandle(hSession); | |
| return -5; | |
| } | |
| DWORD dwFlags = (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0; | |
| HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", urlPath.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags); | |
| if (!hRequest) { | |
| WinHttpCloseHandle(hConnect); | |
| WinHttpCloseHandle(hSession); | |
| return -6; | |
| } | |
| // Set auto-redirect policy | |
| DWORD redirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS; | |
| WinHttpSetOption(hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy)); | |
| if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || | |
| !WinHttpReceiveResponse(hRequest, NULL)) { | |
| WinHttpCloseHandle(hRequest); | |
| WinHttpCloseHandle(hConnect); | |
| WinHttpCloseHandle(hSession); | |
| return -7; | |
| } | |
| FILE* fp = fopen(dest_path_utf8, "wb"); | |
| if (!fp) { | |
| WinHttpCloseHandle(hRequest); | |
| WinHttpCloseHandle(hConnect); | |
| WinHttpCloseHandle(hSession); | |
| return -8; | |
| } | |
| DWORD dwSize = 0; | |
| std::vector<uint8_t> buffer(65536); | |
| while (WinHttpQueryDataAvailable(hRequest, &dwSize) && dwSize > 0) { | |
| DWORD dwDownloaded = 0; | |
| DWORD toRead = (dwSize < (DWORD)buffer.size()) ? dwSize : (DWORD)buffer.size(); | |
| if (!WinHttpReadData(hRequest, buffer.data(), toRead, &dwDownloaded) || dwDownloaded == 0) { | |
| break; | |
| } | |
| fwrite(buffer.data(), 1, dwDownloaded, fp); | |
| } | |
| fclose(fp); | |
| WinHttpCloseHandle(hRequest); | |
| WinHttpCloseHandle(hConnect); | |
| WinHttpCloseHandle(hSession); | |
| return 0; | |
| return -99; // Fallback to python on non-windows | |
| } | |
| // ============================================================================ | |
| // Multi-Threaded WKB / GeoParquet Geometry Decoder Engine | |
| // ============================================================================ | |
| struct WkbParsedGeometry { | |
| int32_t geom_type; // 1=Point, 2=LineString, 3=Polygon, 4=MultiPoint, 5=MultiLineString, 6=MultiPolygon | |
| int32_t num_parts; | |
| int64_t num_points; | |
| std::vector<double> coords; | |
| std::vector<int32_t> part_offsets; | |
| }; | |
| static void _parse_wkb_single(const uint8_t* data, size_t size, WkbParsedGeometry& out) { | |
| if (!data || size < 5) return; | |
| uint8_t byte_order = data[0]; | |
| bool is_little_endian = (byte_order == 1); | |
| auto read_u32 = [is_little_endian](const uint8_t* ptr) -> uint32_t { | |
| uint32_t val; | |
| memcpy(&val, ptr, 4); | |
| if (!is_little_endian) { | |
| val = ((val >> 24) & 0xff) | ((val >> 8) & 0xff00) | ((val << 8) & 0xff0000) | ((val << 24) & 0xff000000); | |
| } | |
| return val; | |
| }; | |
| auto read_double = [is_little_endian](const uint8_t* ptr) -> double { | |
| if (is_little_endian) { | |
| double val; | |
| memcpy(&val, ptr, 8); | |
| return val; | |
| } else { | |
| uint64_t val; | |
| memcpy(&val, ptr, 8); | |
| val = ((val >> 56) & 0x00000000000000ffULL) | | |
| ((val >> 40) & 0x000000000000ff00ULL) | | |
| ((val >> 24) & 0x0000000000ff0000ULL) | | |
| ((val >> 8) & 0x00000000ff000000ULL) | | |
| ((val << 8) & 0x000000ff00000000ULL) | | |
| ((val << 24) & 0x0000ff0000000000ULL) | | |
| ((val << 40) & 0x00ff000000000000ULL) | | |
| ((val << 56) & 0xff00000000000000ULL); | |
| double res; | |
| memcpy(&res, &val, 8); | |
| return res; | |
| } | |
| }; | |
| uint32_t raw_type = read_u32(data + 1); | |
| uint32_t base_type = raw_type % 1000; | |
| out.geom_type = (int32_t)base_type; | |
| size_t offset = 5; | |
| if (base_type == 1) { // Point | |
| if (offset + 16 <= size) { | |
| out.coords.push_back(read_double(data + offset)); | |
| out.coords.push_back(read_double(data + offset + 8)); | |
| out.num_points = 1; | |
| out.num_parts = 1; | |
| out.part_offsets.push_back(0); | |
| } | |
| } else if (base_type == 2) { // LineString | |
| if (offset + 4 <= size) { | |
| uint32_t n_pts = read_u32(data + offset); | |
| offset += 4; | |
| out.num_parts = 1; | |
| out.part_offsets.push_back(0); | |
| out.num_points = n_pts; | |
| out.coords.reserve(n_pts * 2); | |
| for (uint32_t i = 0; i < n_pts && (offset + 16 <= size); ++i) { | |
| out.coords.push_back(read_double(data + offset)); | |
| out.coords.push_back(read_double(data + offset + 8)); | |
| offset += 16; | |
| } | |
| } | |
| } else if (base_type == 3) { // Polygon | |
| if (offset + 4 <= size) { | |
| uint32_t n_rings = read_u32(data + offset); | |
| offset += 4; | |
| out.num_parts = n_rings; | |
| int64_t pt_count = 0; | |
| for (uint32_t r = 0; r < n_rings && (offset + 4 <= size); ++r) { | |
| out.part_offsets.push_back((int32_t)pt_count); | |
| uint32_t n_pts = read_u32(data + offset); | |
| offset += 4; | |
| pt_count += n_pts; | |
| for (uint32_t i = 0; i < n_pts && (offset + 16 <= size); ++i) { | |
| out.coords.push_back(read_double(data + offset)); | |
| out.coords.push_back(read_double(data + offset + 8)); | |
| offset += 16; | |
| } | |
| } | |
| out.num_points = pt_count; | |
| } | |
| } else if (base_type == 5) { // MultiLineString | |
| if (offset + 4 <= size) { | |
| uint32_t n_lines = read_u32(data + offset); | |
| offset += 4; | |
| out.num_parts = n_lines; | |
| int64_t pt_count = 0; | |
| for (uint32_t l = 0; l < n_lines && (offset + 9 <= size); ++l) { | |
| out.part_offsets.push_back((int32_t)pt_count); | |
| offset += 5; // skip endian & sub-type | |
| if (offset + 4 > size) break; | |
| uint32_t n_pts = read_u32(data + offset); | |
| offset += 4; | |
| pt_count += n_pts; | |
| for (uint32_t i = 0; i < n_pts && (offset + 16 <= size); ++i) { | |
| out.coords.push_back(read_double(data + offset)); | |
| out.coords.push_back(read_double(data + offset + 8)); | |
| offset += 16; | |
| } | |
| } | |
| out.num_points = pt_count; | |
| } | |
| } else if (base_type == 6) { // MultiPolygon | |
| if (offset + 4 <= size) { | |
| uint32_t n_polys = read_u32(data + offset); | |
| offset += 4; | |
| int64_t pt_count = 0; | |
| int32_t part_count = 0; | |
| for (uint32_t p = 0; p < n_polys && (offset + 9 <= size); ++p) { | |
| offset += 5; // skip endian & sub-type | |
| if (offset + 4 > size) break; | |
| uint32_t n_rings = read_u32(data + offset); | |
| offset += 4; | |
| for (uint32_t r = 0; r < n_rings && (offset + 4 <= size); ++r) { | |
| out.part_offsets.push_back((int32_t)pt_count); | |
| part_count++; | |
| uint32_t n_pts = read_u32(data + offset); | |
| offset += 4; | |
| pt_count += n_pts; | |
| for (uint32_t i = 0; i < n_pts && (offset + 16 <= size); ++i) { | |
| out.coords.push_back(read_double(data + offset)); | |
| out.coords.push_back(read_double(data + offset + 8)); | |
| offset += 16; | |
| } | |
| } | |
| } | |
| out.num_parts = part_count; | |
| out.num_points = pt_count; | |
| } | |
| } | |
| } | |
| BL_GEO_EXPORT int nm_geo_parse_wkb_batch( | |
| const uint8_t* const* wkb_ptrs, const size_t* wkb_sizes, size_t num_geoms, | |
| double** out_coords, int64_t* out_total_points, | |
| int32_t** out_parts, int64_t* out_total_parts, | |
| int32_t** out_geom_types, int64_t** out_geom_pt_offsets | |
| ) { | |
| if (!wkb_ptrs || !wkb_sizes || num_geoms == 0) return -1; | |
| std::vector<WkbParsedGeometry> parsed(num_geoms); | |
| // Parse geometries in parallel chunks | |
| size_t num_threads = 4; | |
| std::vector<size_t> chunk_starts(num_threads), chunk_ends(num_threads); | |
| size_t chunk_size = (num_geoms + num_threads - 1) / num_threads; | |
| for (size_t g = 0; g < num_geoms; ++g) { | |
| if (wkb_ptrs[g] && wkb_sizes[g] > 0) { | |
| _parse_wkb_single(wkb_ptrs[g], wkb_sizes[g], parsed[g]); | |
| } | |
| } | |
| int64_t tot_pts = 0; | |
| int64_t tot_prts = 0; | |
| for (size_t g = 0; g < num_geoms; ++g) { | |
| tot_pts += parsed[g].num_points; | |
| tot_prts += parsed[g].num_parts; | |
| } | |
| *out_total_points = tot_pts; | |
| *out_total_parts = tot_prts; | |
| *out_coords = (double*)malloc(tot_pts * 2 * sizeof(double)); | |
| *out_parts = (int32_t*)malloc(tot_prts * sizeof(int32_t)); | |
| *out_geom_types = (int32_t*)malloc(num_geoms * sizeof(int32_t)); | |
| *out_geom_pt_offsets = (int64_t*)malloc((num_geoms + 1) * sizeof(int64_t)); | |
| double* c_ptr = *out_coords; | |
| int32_t* p_ptr = *out_parts; | |
| int64_t curr_pt_offset = 0; | |
| int64_t curr_part_offset = 0; | |
| for (size_t g = 0; g < num_geoms; ++g) { | |
| (*out_geom_types)[g] = parsed[g].geom_type; | |
| (*out_geom_pt_offsets)[g] = curr_pt_offset; | |
| for (size_t p = 0; p < parsed[g].part_offsets.size(); ++p) { | |
| p_ptr[curr_part_offset + p] = (int32_t)(curr_pt_offset + parsed[g].part_offsets[p]); | |
| } | |
| curr_part_offset += parsed[g].part_offsets.size(); | |
| if (parsed[g].coords.size() > 0) { | |
| memcpy(c_ptr + curr_pt_offset * 2, parsed[g].coords.data(), parsed[g].coords.size() * sizeof(double)); | |
| } | |
| curr_pt_offset += parsed[g].num_points; | |
| } | |
| (*out_geom_pt_offsets)[num_geoms] = curr_pt_offset; | |
| return 0; | |
| } | |
| BL_GEO_EXPORT void nm_geo_free_wkb_batch(double* coords, int32_t* parts, int32_t* geom_types, int64_t* geom_pt_offsets) { | |
| if (coords) free(coords); | |
| if (parts) free(parts); | |
| if (geom_types) free(geom_types); | |
| if (geom_pt_offsets) free(geom_pt_offsets); | |
| } | |
| // ============================================================================ | |
| // Native Fast CRS & Metric Projection Engine | |
| // ============================================================================ | |
| // CRS IDs: 4326=WGS84, 3857=WebMercator, 2263=NY State Plane Long Island, 5070=Albers CONUS | |
| BL_GEO_EXPORT int nm_geo_transform_batch_crs( | |
| double* coords, int64_t num_points, | |
| int crs_from, int crs_to | |
| ) { | |
| if (!coords || num_points <= 0) return -1; | |
| if (crs_from == crs_to) return 0; | |
| // 1. From 4326 -> 3857 | |
| if (crs_from == 4326 && crs_to == 3857) { | |
| return bl_geo_proj_3857_forward(coords, coords, num_points); | |
| } | |
| // 2. From 3857 -> 4326 | |
| if (crs_from == 3857 && crs_to == 4326) { | |
| return bl_geo_proj_3857_inverse(coords, coords, num_points); | |
| } | |
| // 3. From 4326 -> 2263 (NY State Plane Long Island ftUS) | |
| if (crs_from == 4326 && crs_to == 2263) { | |
| // EPSG:2263 Standard Parameters: phi1=40°40'N, phi2=41°02'N, phi0=40°10'N, lam0=-74°00'W, x0=300000m, y0=0m | |
| double phi1 = 40.66666666666667; | |
| double phi2 = 41.03333333333333; | |
| double phi0 = 40.16666666666667; | |
| double lam0 = -74.0; | |
| double x0 = 300000.0; | |
| double y0 = 0.0; | |
| return bl_geo_proj_lcc_forward(phi1, phi2, phi0, lam0, x0, y0, coords, coords, num_points); | |
| } | |
| // 4. From 4326 -> 5070 (Albers Equal Area Conic CONUS) | |
| if (crs_from == 4326 && crs_to == 5070) { | |
| double phi1 = 29.5; | |
| double phi2 = 45.5; | |
| double phi0 = 23.0; | |
| double lam0 = -96.0; | |
| double x0 = 0.0; | |
| double y0 = 0.0; | |
| return bl_geo_proj_albers_forward(phi1, phi2, phi0, lam0, x0, y0, coords, coords, num_points); | |
| } | |
| return -2; // Unsupported combination | |
| } | |
| // ============================================================================ | |
| // Hardware-Accelerated Bounding Box Liang-Barsky Vector Clipping | |
| // ============================================================================ | |
| static inline bool _clip_line_liang_barsky( | |
| double minx, double miny, double maxx, double maxy, | |
| double x0, double y0, double x1, double y1, | |
| double& cx0, double& cy0, double& cx1, double& cy1 | |
| ) { | |
| double p[4] = { -(x1 - x0), x1 - x0, -(y1 - y0), y1 - y0 }; | |
| double q[4] = { x0 - minx, maxx - x0, y0 - miny, maxy - y0 }; | |
| double u1 = 0.0, u2 = 1.0; | |
| for (int i = 0; i < 4; ++i) { | |
| if (p[i] == 0.0) { | |
| if (q[i] < 0.0) return false; | |
| } else { | |
| double t = q[i] / p[i]; | |
| if (p[i] < 0.0) { | |
| if (t > u1) u1 = t; | |
| } else { | |
| if (t < u2) u2 = t; | |
| } | |
| } | |
| } | |
| if (u1 > u2) return false; | |
| cx0 = x0 + u1 * (x1 - x0); | |
| cy0 = y0 + u1 * (y1 - y0); | |
| cx1 = x0 + u2 * (x1 - x0); | |
| cy1 = y0 + u2 * (y1 - y0); | |
| return true; | |
| } | |
| BL_GEO_EXPORT int nm_geo_clip_lines_bbox( | |
| const double* in_coords, int64_t num_segments, | |
| double minx, double miny, double maxx, double maxy, | |
| double* out_coords, int64_t* out_count | |
| ) { | |
| if (!in_coords || num_segments <= 0 || !out_coords || !out_count) return -1; | |
| int64_t count = 0; | |
| for (int64_t i = 0; i < num_segments; ++i) { | |
| double x0 = in_coords[i * 4]; | |
| double y0 = in_coords[i * 4 + 1]; | |
| double x1 = in_coords[i * 4 + 2]; | |
| double y1 = in_coords[i * 4 + 3]; | |
| double cx0, cy0, cx1, cy1; | |
| if (_clip_line_liang_barsky(minx, miny, maxx, maxy, x0, y0, x1, y1, cx0, cy0, cx1, cy1)) { | |
| out_coords[count * 4] = cx0; | |
| out_coords[count * 4 + 1] = cy0; | |
| out_coords[count * 4 + 2] = cx1; | |
| out_coords[count * 4 + 3] = cy1; | |
| count++; | |
| } | |
| } | |
| *out_count = count; | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Procedural Spatial Animation & Wave Propagation Generator | |
| // ============================================================================ | |
| BL_GEO_EXPORT int nm_geo_calculate_propagation( | |
| const double* centroids, size_t num_items, | |
| const double* epicenters, size_t num_epicenters, | |
| double duration_frames, double speed, | |
| double* out_start_frames, double* out_end_frames | |
| ) { | |
| if (!centroids || num_items == 0 || !epicenters || num_epicenters == 0 || !out_start_frames || !out_end_frames) { | |
| return -1; | |
| } | |
| std::vector<double> min_dists(num_items, 1e18); | |
| double max_d = 0.0; | |
| for (size_t i = 0; i < num_items; ++i) { | |
| double cx = centroids[i * 2]; | |
| double cy = centroids[i * 2 + 1]; | |
| for (size_t e = 0; e < num_epicenters; ++e) { | |
| double ex = epicenters[e * 2]; | |
| double ey = epicenters[e * 2 + 1]; | |
| double d = std::hypot(cx - ex, cy - ey); | |
| if (d < min_dists[i]) { | |
| min_dists[i] = d; | |
| } | |
| } | |
| if (min_dists[i] > max_d) max_d = min_dists[i]; | |
| } | |
| if (max_d <= 0.0) max_d = 1.0; | |
| double travel_time = duration_frames * 0.75 * (speed > 0 ? speed : 1.0); | |
| double reveal_window = duration_frames * 0.25; | |
| for (size_t i = 0; i < num_items; ++i) { | |
| double norm_d = min_dists[i] / max_d; | |
| double start_f = norm_d * travel_time; | |
| out_start_frames[i] = start_f; | |
| out_end_frames[i] = start_f + reveal_window; | |
| } | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Sutherland-Hodgman Boolean Polygon Vector Clipping against BBox | |
| // ============================================================================ | |
| struct Point2D { double x, y; }; | |
| static std::vector<Point2D> _clip_poly_single_edge( | |
| const std::vector<Point2D>& poly, | |
| int edge, double edge_val | |
| ) { | |
| std::vector<Point2D> out; | |
| if (poly.empty()) return out; | |
| auto is_inside = [edge, edge_val](const Point2D& p) -> bool { | |
| switch (edge) { | |
| case 0: return p.x >= edge_val; // Left: x >= minx | |
| case 1: return p.x <= edge_val; // Right: x <= maxx | |
| case 2: return p.y >= edge_val; // Bottom: y >= miny | |
| case 3: return p.y <= edge_val; // Top: y <= maxy | |
| } | |
| return true; | |
| }; | |
| auto compute_intersect = [edge, edge_val](const Point2D& p1, const Point2D& p2) -> Point2D { | |
| Point2D res; | |
| double dx = p2.x - p1.x; | |
| double dy = p2.y - p1.y; | |
| switch (edge) { | |
| case 0: // x = minx | |
| case 1: // x = maxx | |
| res.x = edge_val; | |
| res.y = (std::abs(dx) > 1e-12) ? p1.y + dy * (edge_val - p1.x) / dx : p1.y; | |
| break; | |
| case 2: // y = miny | |
| case 3: // y = maxy | |
| res.y = edge_val; | |
| res.x = (std::abs(dy) > 1e-12) ? p1.x + dx * (edge_val - p1.y) / dy : p1.x; | |
| break; | |
| } | |
| return res; | |
| }; | |
| Point2D prev = poly.back(); | |
| bool prev_inside = is_inside(prev); | |
| for (const auto& curr : poly) { | |
| bool curr_inside = is_inside(curr); | |
| if (curr_inside) { | |
| if (!prev_inside) { | |
| out.push_back(compute_intersect(prev, curr)); | |
| } | |
| out.push_back(curr); | |
| } else if (prev_inside) { | |
| out.push_back(compute_intersect(prev, curr)); | |
| } | |
| prev = curr; | |
| prev_inside = curr_inside; | |
| } | |
| return out; | |
| } | |
| BL_GEO_EXPORT int nm_geo_clip_polygon_bbox( | |
| const double* in_coords, int64_t in_count, | |
| double minx, double miny, double maxx, double maxy, | |
| double* out_coords, int64_t* out_count | |
| ) { | |
| if (!in_coords || in_count < 3 || !out_coords || !out_count) return -1; | |
| std::vector<Point2D> poly(in_count); | |
| for (int64_t i = 0; i < in_count; ++i) { | |
| poly[i] = { in_coords[i * 2], in_coords[i * 2 + 1] }; | |
| } | |
| std::vector<Point2D> clipped = _clip_poly_single_edge(poly, 0, minx); | |
| if (!clipped.empty()) clipped = _clip_poly_single_edge(clipped, 1, maxx); | |
| if (!clipped.empty()) clipped = _clip_poly_single_edge(clipped, 2, miny); | |
| if (!clipped.empty()) clipped = _clip_poly_single_edge(clipped, 3, maxy); | |
| *out_count = (int64_t)clipped.size(); | |
| for (size_t i = 0; i < clipped.size(); ++i) { | |
| out_coords[i * 2] = clipped[i].x; | |
| out_coords[i * 2 + 1] = clipped[i].y; | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int bl_geo_build_polygon_rings( | |
| BLPathCore* path, | |
| const double* coords, | |
| const int64_t* ring_offsets, | |
| int64_t num_rings, | |
| const GeoTransform* gt | |
| ) { | |
| if (!path || !coords || !ring_offsets || num_rings <= 0) return -1; | |
| for (int64_t r = 0; r < num_rings; ++r) { | |
| int64_t start_idx = ring_offsets[r]; | |
| int64_t end_idx = ring_offsets[r + 1]; | |
| int64_t pt_count = end_idx - start_idx; | |
| if (pt_count < 3) continue; | |
| double sx, sy; | |
| transform_pt(gt, coords[start_idx * 2], coords[start_idx * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| for (int64_t i = start_idx + 1; i < end_idx; ++i) { | |
| transform_pt(gt, coords[i * 2], coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| } | |
| bl_path_close(path); // Essential: prevents spiderweb/fanning glitches! | |
| } | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Structure of Arrays (SoA) & Path Memory Arena | |
| // ============================================================================ | |
| struct BLBox2D { | |
| double x0, y0, x1, y1; | |
| }; | |
| struct NativeGeoLayerSoA { | |
| size_t count; | |
| size_t capacity; | |
| BLPathCore* paths; // contiguous array of BLPathCore [capacity] | |
| BLBox2D* bboxes; // contiguous array of BLBox2D [capacity] | |
| double* centroids; // contiguous array of (cx, cy) [capacity * 2] | |
| float* distances; // contiguous array of distances [capacity] | |
| double* start_frames; // contiguous array of start_frames [capacity] | |
| double* end_frames; // contiguous array of end_frames [capacity] | |
| uint8_t* is_active; // contiguous array of active visibility flags [capacity] | |
| void* arena_memory; // backing contiguous memory arena | |
| size_t arena_size; | |
| }; | |
| BL_GEO_EXPORT NativeGeoLayerSoA* nm_geo_soa_create(size_t capacity) { | |
| if (capacity == 0) capacity = 1024; | |
| size_t sz_paths = capacity * sizeof(BLPathCore); | |
| size_t sz_bboxes = capacity * sizeof(BLBox2D); | |
| size_t sz_centroids = capacity * 2 * sizeof(double); | |
| size_t sz_distances = capacity * sizeof(float); | |
| size_t sz_start_frames = capacity * sizeof(double); | |
| size_t sz_end_frames = capacity * sizeof(double); | |
| size_t sz_active = capacity * sizeof(uint8_t); | |
| size_t total_alloc = sz_paths + sz_bboxes + sz_centroids + sz_distances + sz_start_frames + sz_end_frames + sz_active + 256; | |
| uint8_t* mem = (uint8_t*)malloc(total_alloc); | |
| if (!mem) return nullptr; | |
| memset(mem, 0, total_alloc); | |
| NativeGeoLayerSoA* layer = new NativeGeoLayerSoA(); | |
| layer->count = 0; | |
| layer->capacity = capacity; | |
| layer->arena_memory = mem; | |
| layer->arena_size = total_alloc; | |
| uint8_t* ptr = mem; | |
| layer->paths = (BLPathCore*)ptr; ptr += sz_paths; | |
| layer->bboxes = (BLBox2D*)ptr; ptr += sz_bboxes; | |
| layer->centroids = (double*)ptr; ptr += sz_centroids; | |
| layer->distances = (float*)ptr; ptr += sz_distances; | |
| layer->start_frames = (double*)ptr; ptr += sz_start_frames; | |
| layer->end_frames = (double*)ptr; ptr += sz_end_frames; | |
| layer->is_active = (uint8_t*)ptr; ptr += sz_active; | |
| for (size_t i = 0; i < capacity; ++i) { | |
| bl_path_init(&layer->paths[i]); | |
| layer->is_active[i] = 1; | |
| } | |
| return layer; | |
| } | |
| BL_GEO_EXPORT void nm_geo_soa_destroy(NativeGeoLayerSoA* layer) { | |
| if (!layer) return; | |
| for (size_t i = 0; i < layer->capacity; ++i) { | |
| bl_path_destroy(&layer->paths[i]); | |
| } | |
| if (layer->arena_memory) { | |
| free(layer->arena_memory); | |
| } | |
| delete layer; | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_add_polygon( | |
| NativeGeoLayerSoA* layer, | |
| const double* coords, | |
| const int64_t* ring_offsets, | |
| int64_t num_rings, | |
| const GeoTransform* gt | |
| ) { | |
| if (!layer || layer->count >= layer->capacity || !coords || !ring_offsets || num_rings <= 0) { | |
| return -1; | |
| } | |
| size_t idx = layer->count; | |
| BLPathCore* path = &layer->paths[idx]; | |
| bl_path_reset(path); | |
| double minx = 1e30, miny = 1e30, maxx = -1e30, maxy = -1e30; | |
| double sum_x = 0.0, sum_y = 0.0; | |
| int64_t total_pts = 0; | |
| for (int64_t r = 0; r < num_rings; ++r) { | |
| int64_t start_idx = ring_offsets[r]; | |
| int64_t end_idx = ring_offsets[r + 1]; | |
| int64_t pt_count = end_idx - start_idx; | |
| if (pt_count < 3) continue; | |
| double sx, sy; | |
| transform_pt(gt, coords[start_idx * 2], coords[start_idx * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| if (sx < minx) minx = sx; | |
| if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; | |
| if (sy > maxy) maxy = sy; | |
| sum_x += sx; | |
| sum_y += sy; | |
| total_pts++; | |
| for (int64_t i = start_idx + 1; i < end_idx; ++i) { | |
| transform_pt(gt, coords[i * 2], coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| if (sx < minx) minx = sx; | |
| if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; | |
| if (sy > maxy) maxy = sy; | |
| sum_x += sx; | |
| sum_y += sy; | |
| total_pts++; | |
| } | |
| bl_path_close(path); // Auto-close ring | |
| } | |
| if (total_pts == 0) { | |
| minx = miny = maxx = maxy = 0.0; | |
| sum_x = sum_y = 0.0; | |
| total_pts = 1; | |
| } | |
| layer->bboxes[idx].x0 = minx; | |
| layer->bboxes[idx].y0 = miny; | |
| layer->bboxes[idx].x1 = maxx; | |
| layer->bboxes[idx].y1 = maxy; | |
| layer->centroids[idx * 2] = sum_x / (double)total_pts; | |
| layer->centroids[idx * 2 + 1] = sum_y / (double)total_pts; | |
| layer->is_active[idx] = 1; | |
| layer->count++; | |
| return (int)idx; | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_from_wkb_batch( | |
| NativeGeoLayerSoA* layer, | |
| const uint8_t* const* wkb_ptrs, | |
| const size_t* wkb_sizes, | |
| size_t num_geoms, | |
| const GeoTransform* gt | |
| ) { | |
| if (!layer || !wkb_ptrs || !wkb_sizes || num_geoms == 0) return -1; | |
| for (size_t g = 0; g < num_geoms; ++g) { | |
| if (layer->count >= layer->capacity) break; | |
| if (!wkb_ptrs[g] || wkb_sizes[g] < 5) continue; | |
| WkbParsedGeometry geom; | |
| _parse_wkb_single(wkb_ptrs[g], wkb_sizes[g], geom); | |
| if (geom.num_points == 0) continue; | |
| size_t idx = layer->count; | |
| BLPathCore* path = &layer->paths[idx]; | |
| bl_path_reset(path); | |
| double minx = 1e30, miny = 1e30, maxx = -1e30, maxy = -1e30; | |
| double sum_x = 0.0, sum_y = 0.0; | |
| int64_t total_pts = 0; | |
| if (geom.geom_type == 1) { // Point | |
| if (geom.coords.size() >= 2) { | |
| double sx, sy; | |
| transform_pt(gt, geom.coords[0], geom.coords[1], sx, sy); | |
| bl_path_move_to(path, sx - 2.0, sy); | |
| bl_path_line_to(path, sx + 2.0, sy); | |
| minx = maxx = sx; miny = maxy = sy; | |
| sum_x = sx; sum_y = sy; total_pts = 1; | |
| } | |
| } else if (geom.geom_type == 2) { // LineString | |
| int64_t n_pts = (int64_t)(geom.coords.size() / 2); | |
| if (n_pts >= 2) { | |
| double sx, sy; | |
| transform_pt(gt, geom.coords[0], geom.coords[1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| for (int64_t i = 1; i < n_pts; ++i) { | |
| transform_pt(gt, geom.coords[i * 2], geom.coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| } | |
| } | |
| } else if (geom.geom_type == 3 || geom.geom_type == 6) { // Polygon or MultiPolygon | |
| size_t n_parts = geom.part_offsets.size(); | |
| for (size_t p = 0; p < n_parts; ++p) { | |
| int64_t start_pt = geom.part_offsets[p]; | |
| int64_t end_pt = (p + 1 < n_parts) ? geom.part_offsets[p + 1] : (int64_t)(geom.coords.size() / 2); | |
| int64_t pt_cnt = end_pt - start_pt; | |
| if (pt_cnt < 3) continue; | |
| double sx, sy; | |
| transform_pt(gt, geom.coords[start_pt * 2], geom.coords[start_pt * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| for (int64_t i = start_pt + 1; i < end_pt; ++i) { | |
| transform_pt(gt, geom.coords[i * 2], geom.coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| } | |
| bl_path_close(path); // Auto-close ring: cuts interior holes with EVEN_ODD! | |
| } | |
| } else if (geom.geom_type == 5) { // MultiLineString | |
| size_t n_parts = geom.part_offsets.size(); | |
| for (size_t p = 0; p < n_parts; ++p) { | |
| int64_t start_pt = geom.part_offsets[p]; | |
| int64_t end_pt = (p + 1 < n_parts) ? geom.part_offsets[p + 1] : (int64_t)(geom.coords.size() / 2); | |
| int64_t pt_cnt = end_pt - start_pt; | |
| if (pt_cnt < 2) continue; | |
| double sx, sy; | |
| transform_pt(gt, geom.coords[start_pt * 2], geom.coords[start_pt * 2 + 1], sx, sy); | |
| bl_path_move_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| for (int64_t i = start_pt + 1; i < end_pt; ++i) { | |
| transform_pt(gt, geom.coords[i * 2], geom.coords[i * 2 + 1], sx, sy); | |
| bl_path_line_to(path, sx, sy); | |
| if (sx < minx) minx = sx; if (sx > maxx) maxx = sx; | |
| if (sy < miny) miny = sy; if (sy > maxy) maxy = sy; | |
| sum_x += sx; sum_y += sy; total_pts++; | |
| } | |
| } | |
| } | |
| if (total_pts == 0) { | |
| minx = miny = maxx = maxy = 0.0; | |
| sum_x = sum_y = 0.0; | |
| total_pts = 1; | |
| } | |
| layer->bboxes[idx].x0 = minx; | |
| layer->bboxes[idx].y0 = miny; | |
| layer->bboxes[idx].x1 = maxx; | |
| layer->bboxes[idx].y1 = maxy; | |
| layer->centroids[idx * 2] = sum_x / (double)total_pts; | |
| layer->centroids[idx * 2 + 1] = sum_y / (double)total_pts; | |
| layer->is_active[idx] = 1; | |
| layer->count++; | |
| } | |
| return (int)layer->count; | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_compute_propagation( | |
| NativeGeoLayerSoA* layer, | |
| const double* epicenters, | |
| size_t num_epicenters, | |
| double duration_frames, | |
| double speed | |
| ) { | |
| if (!layer || layer->count == 0 || !epicenters || num_epicenters == 0) return -1; | |
| size_t n = layer->count; | |
| double max_d = 0.0; | |
| for (size_t i = 0; i < n; ++i) { | |
| double cx = layer->centroids[i * 2]; | |
| double cy = layer->centroids[i * 2 + 1]; | |
| double min_d = 1e18; | |
| for (size_t e = 0; e < num_epicenters; ++e) { | |
| double ex = epicenters[e * 2]; | |
| double ey = epicenters[e * 2 + 1]; | |
| double d = std::hypot(cx - ex, cy - ey); | |
| if (d < min_d) min_d = d; | |
| } | |
| layer->distances[i] = (float)min_d; | |
| if (min_d > max_d) max_d = min_d; | |
| } | |
| if (max_d <= 0.0) max_d = 1.0; | |
| double travel_time = duration_frames * 0.75 * (speed > 0.0 ? speed : 1.0); | |
| double reveal_window = duration_frames * 0.25; | |
| for (size_t i = 0; i < n; ++i) { | |
| double norm_d = (double)layer->distances[i] / max_d; | |
| double sf = norm_d * travel_time; | |
| layer->start_frames[i] = sf; | |
| layer->end_frames[i] = sf + reveal_window; | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_cull_and_update( | |
| NativeGeoLayerSoA* layer, | |
| double clip_x0, double clip_y0, double clip_x1, double clip_y1, | |
| double current_frame, | |
| int64_t* out_visible_count | |
| ) { | |
| if (!layer) return -1; | |
| size_t n = layer->count; | |
| int64_t vis_count = 0; | |
| for (size_t i = 0; i < n; ++i) { | |
| if (current_frame >= 0.0 && current_frame < layer->start_frames[i]) { | |
| layer->is_active[i] = 0; | |
| continue; | |
| } | |
| const BLBox2D& b = layer->bboxes[i]; | |
| if (b.x1 < clip_x0 || b.x0 > clip_x1 || b.y1 < clip_y0 || b.y0 > clip_y1) { | |
| layer->is_active[i] = 0; | |
| continue; | |
| } | |
| layer->is_active[i] = 1; | |
| vis_count++; | |
| } | |
| if (out_visible_count) *out_visible_count = vis_count; | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_render_dual_buffer( | |
| void* ctx_ptr, | |
| const NativeGeoLayerSoA* layer, | |
| uint32_t fill_rgba, | |
| uint32_t stroke_rgba, | |
| double stroke_width, | |
| int fill_enabled, | |
| int stroke_enabled | |
| ) { | |
| if (!ctx_ptr || !layer || layer->count == 0) return -1; | |
| BLContextCore* ctx = (BLContextCore*)ctx_ptr; | |
| size_t n = layer->count; | |
| bl_context_set_fill_rule(ctx, 1 /* EVEN_ODD */); | |
| // Pass 1: Batch Fill Pass (all fills in a tight unrolled loop) | |
| if (fill_enabled) { | |
| for (size_t i = 0; i < n; ++i) { | |
| if (layer->is_active[i]) { | |
| bl_context_fill_geometry_rgba32(ctx, 21 /* BL_GEOMETRY_TYPE_PATH */, &layer->paths[i], fill_rgba); | |
| } | |
| } | |
| } | |
| // Pass 2: Batch Stroke Pass (all strokes in a tight loop) | |
| if (stroke_enabled && stroke_width > 0.0) { | |
| bl_context_set_stroke_width(ctx, stroke_width); | |
| for (size_t i = 0; i < n; ++i) { | |
| if (layer->is_active[i]) { | |
| bl_context_stroke_geometry_rgba32(ctx, 21 /* BL_GEOMETRY_TYPE_PATH */, &layer->paths[i], stroke_rgba); | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| static inline double _geo_easing_back_out(double t) { | |
| if (t <= 0.0) return 0.0; | |
| if (t >= 1.0) return 1.0; | |
| double c1 = 1.70158; | |
| double c3 = c1 + 1.0; | |
| double t_m_1 = t - 1.0; | |
| return 1.0 + c3 * (t_m_1 * t_m_1 * t_m_1) + c1 * (t_m_1 * t_m_1); | |
| } | |
| BL_GEO_EXPORT int nm_geo_soa_render_popin( | |
| void* ctx_ptr, | |
| const NativeGeoLayerSoA* layer, | |
| double current_frame, | |
| uint32_t fill_rgba, | |
| uint32_t stroke_rgba, | |
| double stroke_width, | |
| int fill_enabled, | |
| int stroke_enabled | |
| ) { | |
| if (!ctx_ptr || !layer || layer->count == 0) return -1; | |
| BLContextCore* ctx = (BLContextCore*)ctx_ptr; | |
| size_t n = layer->count; | |
| bl_context_set_fill_rule(ctx, 1 /* EVEN_ODD */); | |
| // Pass 1: Batch Fill Pass with Centroid Spring Pop-In | |
| if (fill_enabled) { | |
| for (size_t i = 0; i < n; ++i) { | |
| if (!layer->is_active[i]) continue; | |
| double sf = layer->start_frames[i]; | |
| double ef = layer->end_frames[i]; | |
| if (current_frame < sf) continue; | |
| if (current_frame >= ef) { | |
| bl_context_fill_geometry_rgba32(ctx, 21, &layer->paths[i], fill_rgba); | |
| } else { | |
| double t = (current_frame - sf) / (ef - sf); | |
| double scale = _geo_easing_back_out(t); | |
| if (scale > 0.001) { | |
| double cx = layer->centroids[i * 2]; | |
| double cy = layer->centroids[i * 2 + 1]; | |
| BLMatrix2D m; | |
| m.m00 = scale; | |
| m.m01 = 0.0; | |
| m.m10 = 0.0; | |
| m.m11 = scale; | |
| m.m20 = cx * (1.0 - scale); | |
| m.m21 = cy * (1.0 - scale); | |
| bl_context_apply_transform_op(ctx, 1 /* ASSIGN */, &m); | |
| bl_context_fill_geometry_rgba32(ctx, 21, &layer->paths[i], fill_rgba); | |
| bl_context_apply_transform_op(ctx, 0 /* RESET */, NULL); | |
| } | |
| } | |
| } | |
| } | |
| // Pass 2: Batch Stroke Pass with Centroid Spring Pop-In | |
| if (stroke_enabled && stroke_width > 0.0) { | |
| bl_context_set_stroke_width(ctx, stroke_width); | |
| for (size_t i = 0; i < n; ++i) { | |
| if (!layer->is_active[i]) continue; | |
| double sf = layer->start_frames[i]; | |
| double ef = layer->end_frames[i]; | |
| if (current_frame < sf) continue; | |
| if (current_frame >= ef) { | |
| bl_context_stroke_geometry_rgba32(ctx, 21, &layer->paths[i], stroke_rgba); | |
| } else { | |
| double t = (current_frame - sf) / (ef - sf); | |
| double scale = _geo_easing_back_out(t); | |
| if (scale > 0.001) { | |
| double cx = layer->centroids[i * 2]; | |
| double cy = layer->centroids[i * 2 + 1]; | |
| BLMatrix2D m; | |
| m.m00 = scale; | |
| m.m01 = 0.0; | |
| m.m10 = 0.0; | |
| m.m11 = scale; | |
| m.m20 = cx * (1.0 - scale); | |
| m.m21 = cy * (1.0 - scale); | |
| bl_context_apply_transform_op(ctx, 1 /* ASSIGN */, &m); | |
| bl_context_stroke_geometry_rgba32(ctx, 21, &layer->paths[i], stroke_rgba); | |
| bl_context_apply_transform_op(ctx, 0 /* RESET */, NULL); | |
| } | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| // ============================================================================ | |
| // Flat Uniform Grid Spatial Indexing | |
| // ============================================================================ | |
| struct UniformGrid2D { | |
| double minx, miny, maxx, maxy; | |
| double cell_w, cell_h; | |
| int32_t grid_w, grid_h; | |
| int32_t* cell_counts; // [grid_w * grid_h] | |
| int32_t* cell_offsets; // [grid_w * grid_h + 1] | |
| int32_t* item_indices; // [total_references] | |
| int32_t total_references; | |
| }; | |
| BL_GEO_EXPORT UniformGrid2D* nm_geo_grid_create( | |
| double minx, double miny, double maxx, double maxy, | |
| int32_t grid_w, int32_t grid_h | |
| ) { | |
| if (grid_w <= 0) grid_w = 32; | |
| if (grid_h <= 0) grid_h = 32; | |
| if (maxx <= minx) maxx = minx + 1.0; | |
| if (maxy <= miny) maxy = miny + 1.0; | |
| UniformGrid2D* grid = new UniformGrid2D(); | |
| grid->minx = minx; | |
| grid->miny = miny; | |
| grid->maxx = maxx; | |
| grid->maxy = maxy; | |
| grid->grid_w = grid_w; | |
| grid->grid_h = grid_h; | |
| grid->cell_w = (maxx - minx) / (double)grid_w; | |
| grid->cell_h = (maxy - miny) / (double)grid_h; | |
| size_t num_cells = (size_t)(grid_w * grid_h); | |
| grid->cell_counts = (int32_t*)calloc(num_cells, sizeof(int32_t)); | |
| grid->cell_offsets = (int32_t*)calloc(num_cells + 1, sizeof(int32_t)); | |
| grid->item_indices = nullptr; | |
| grid->total_references = 0; | |
| return grid; | |
| } | |
| BL_GEO_EXPORT int nm_geo_grid_build( | |
| UniformGrid2D* grid, | |
| const BLBox2D* bboxes, | |
| size_t count | |
| ) { | |
| if (!grid || !bboxes || count == 0) return -1; | |
| int32_t gw = grid->grid_w; | |
| int32_t gh = grid->grid_h; | |
| int32_t num_cells = gw * gh; | |
| memset(grid->cell_counts, 0, num_cells * sizeof(int32_t)); | |
| auto to_cx = [grid, gw](double x) -> int32_t { | |
| int32_t c = (int32_t)((x - grid->minx) / grid->cell_w); | |
| if (c < 0) c = 0; | |
| if (c >= gw) c = gw - 1; | |
| return c; | |
| }; | |
| auto to_cy = [grid, gh](double y) -> int32_t { | |
| int32_t c = (int32_t)((y - grid->miny) / grid->cell_h); | |
| if (c < 0) c = 0; | |
| if (c >= gh) c = gh - 1; | |
| return c; | |
| }; | |
| int32_t total_refs = 0; | |
| for (size_t i = 0; i < count; ++i) { | |
| int32_t cx0 = to_cx(bboxes[i].x0); | |
| int32_t cx1 = to_cx(bboxes[i].x1); | |
| int32_t cy0 = to_cy(bboxes[i].y0); | |
| int32_t cy1 = to_cy(bboxes[i].y1); | |
| for (int32_t y = cy0; y <= cy1; ++y) { | |
| for (int32_t x = cx0; x <= cx1; ++x) { | |
| int32_t cell = y * gw + x; | |
| grid->cell_counts[cell]++; | |
| total_refs++; | |
| } | |
| } | |
| } | |
| grid->total_references = total_refs; | |
| if (grid->item_indices) free(grid->item_indices); | |
| grid->item_indices = (int32_t*)malloc(total_refs * sizeof(int32_t)); | |
| int32_t cur_offset = 0; | |
| for (int32_t c = 0; c < num_cells; ++c) { | |
| grid->cell_offsets[c] = cur_offset; | |
| cur_offset += grid->cell_counts[c]; | |
| } | |
| grid->cell_offsets[num_cells] = cur_offset; | |
| memset(grid->cell_counts, 0, num_cells * sizeof(int32_t)); | |
| for (size_t i = 0; i < count; ++i) { | |
| int32_t cx0 = to_cx(bboxes[i].x0); | |
| int32_t cx1 = to_cx(bboxes[i].x1); | |
| int32_t cy0 = to_cy(bboxes[i].y0); | |
| int32_t cy1 = to_cy(bboxes[i].y1); | |
| for (int32_t y = cy0; y <= cy1; ++y) { | |
| for (int32_t x = cx0; x <= cx1; ++x) { | |
| int32_t cell = y * gw + x; | |
| int32_t write_pos = grid->cell_offsets[cell] + grid->cell_counts[cell]; | |
| grid->item_indices[write_pos] = (int32_t)i; | |
| grid->cell_counts[cell]++; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| BL_GEO_EXPORT int nm_geo_grid_query_bbox( | |
| const UniformGrid2D* grid, | |
| double qx0, double qy0, double qx1, double qy1, | |
| int32_t* out_indices, | |
| int32_t max_indices, | |
| int32_t* out_count | |
| ) { | |
| if (!grid || !out_indices || max_indices <= 0 || !out_count) return -1; | |
| int32_t gw = grid->grid_w; | |
| int32_t gh = grid->grid_h; | |
| auto to_cx = [grid, gw](double x) -> int32_t { | |
| int32_t c = (int32_t)((x - grid->minx) / grid->cell_w); | |
| if (c < 0) c = 0; | |
| if (c >= gw) c = gw - 1; | |
| return c; | |
| }; | |
| auto to_cy = [grid, gh](double y) -> int32_t { | |
| int32_t c = (int32_t)((y - grid->miny) / grid->cell_h); | |
| if (c < 0) c = 0; | |
| if (c >= gh) c = gh - 1; | |
| return c; | |
| }; | |
| int32_t cx0 = to_cx(qx0); | |
| int32_t cx1 = to_cx(qx1); | |
| int32_t cy0 = to_cy(qy0); | |
| int32_t cy1 = to_cy(qy1); | |
| int32_t found = 0; | |
| int32_t max_item_id = -1; | |
| for (int32_t y = cy0; y <= cy1; ++y) { | |
| for (int32_t x = cx0; x <= cx1; ++x) { | |
| int32_t cell = y * gw + x; | |
| int32_t cnt = grid->cell_counts[cell]; | |
| int32_t off = grid->cell_offsets[cell]; | |
| for (int32_t k = 0; k < cnt; ++k) { | |
| int32_t idx = grid->item_indices[off + k]; | |
| if (idx > max_item_id) max_item_id = idx; | |
| } | |
| } | |
| } | |
| if (max_item_id < 0) { | |
| *out_count = 0; | |
| return 0; | |
| } | |
| std::vector<bool> visited(max_item_id + 1, false); | |
| for (int32_t y = cy0; y <= cy1 && found < max_indices; ++y) { | |
| for (int32_t x = cx0; x <= cx1 && found < max_indices; ++x) { | |
| int32_t cell = y * gw + x; | |
| int32_t cnt = grid->cell_counts[cell]; | |
| int32_t off = grid->cell_offsets[cell]; | |
| for (int32_t k = 0; k < cnt && found < max_indices; ++k) { | |
| int32_t idx = grid->item_indices[off + k]; | |
| if (!visited[idx]) { | |
| visited[idx] = true; | |
| out_indices[found++] = idx; | |
| } | |
| } | |
| } | |
| } | |
| *out_count = found; | |
| return 0; | |
| } | |
| BL_GEO_EXPORT void nm_geo_grid_destroy(UniformGrid2D* grid) { | |
| if (!grid) return; | |
| if (grid->cell_counts) free(grid->cell_counts); | |
| if (grid->cell_offsets) free(grid->cell_offsets); | |
| if (grid->item_indices) free(grid->item_indices); | |
| delete grid; | |
| } | |
| } // extern "C" | |