#pragma once #include "Python.h" #include #include // ============================================================================ // MapOps - Template abstractions for 2D grid map operations // ============================================================================ // // Provides common operations for HeightMap (float) and DiscreteMap (uint8_t). // Uses policy-based design for type-specific behavior (clamping, conversion). // // Benefits: // - Single implementation for fill, copy, region iteration // - Type-appropriate clamping via saturation policies // - Compile-time polymorphism (no virtual overhead) // - Shared region parameter parsing from Python kwargs // ============================================================================ // Forward declarations class PyPositionHelper; // ============================================================================ // Unified region struct for all map operations // ============================================================================ struct MapRegion { // Validated region coordinates int dest_x, dest_y; // Destination origin in target map int src_x, src_y; // Source origin (for binary ops, 0 for scalar ops) int width, height; // Region dimensions // Full map dimensions (for iteration) int dest_w, dest_h; int src_w, src_h; // Direct indexing helpers inline int dest_idx(int x, int y) const { return (dest_y + y) * dest_w + (dest_x + x); } inline int src_idx(int x, int y) const { return (src_y + y) * src_w + (src_x + x); } }; // ============================================================================ // Saturation Policies - type-specific clamping behavior // ============================================================================ struct FloatPolicy { using Type = float; static float clamp(float v) { return v; } // No clamping for float static float clamp(int v) { return static_cast(v); } static float from_int(int v) { return static_cast(v); } static float from_float(float v) { return v; } static float zero() { return 0.0f; } static float one() { return 1.0f; } }; struct Uint8Policy { using Type = uint8_t; static uint8_t clamp(int v) { return static_cast(std::clamp(v, 0, 255)); } static uint8_t clamp(float v) { return static_cast(std::clamp(static_cast(v), 0, 255)); } static uint8_t from_int(int v) { return clamp(v); } static uint8_t from_float(float v) { return clamp(static_cast(v)); } static uint8_t zero() { return 0; } static uint8_t one() { return 1; } }; // ============================================================================ // Region Parameter Parsing - shared helpers // ============================================================================ namespace MapOpsInternal { // Parse optional position tuple, returning (0, 0) if None/not provided inline bool parseOptionalPos(PyObject* pos_obj, int* out_x, int* out_y, const char* param_name) { *out_x = 0; *out_y = 0; if (!pos_obj || pos_obj == Py_None) { return true; // Default to (0, 0) } // Try to parse as tuple/list of 2 ints if (PyTuple_Check(pos_obj) && PyTuple_Size(pos_obj) == 2) { PyObject* x_obj = PyTuple_GetItem(pos_obj, 0); PyObject* y_obj = PyTuple_GetItem(pos_obj, 1); if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) { *out_x = (int)PyLong_AsLong(x_obj); *out_y = (int)PyLong_AsLong(y_obj); return true; } } else if (PyList_Check(pos_obj) && PyList_Size(pos_obj) == 2) { PyObject* x_obj = PyList_GetItem(pos_obj, 0); PyObject* y_obj = PyList_GetItem(pos_obj, 1); if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) { *out_x = (int)PyLong_AsLong(x_obj); *out_y = (int)PyLong_AsLong(y_obj); return true; } } PyErr_Format(PyExc_TypeError, "%s must be a (x, y) tuple or list", param_name); return false; } // Parse optional size tuple inline bool parseOptionalSize(PyObject* size_obj, int* out_w, int* out_h, const char* param_name) { *out_w = -1; // -1 means "not specified" *out_h = -1; if (!size_obj || size_obj == Py_None) { return true; } if (PyTuple_Check(size_obj) && PyTuple_Size(size_obj) == 2) { PyObject* w_obj = PyTuple_GetItem(size_obj, 0); PyObject* h_obj = PyTuple_GetItem(size_obj, 1); if (PyLong_Check(w_obj) && PyLong_Check(h_obj)) { *out_w = (int)PyLong_AsLong(w_obj); *out_h = (int)PyLong_AsLong(h_obj); if (*out_w <= 0 || *out_h <= 0) { PyErr_Format(PyExc_ValueError, "%s dimensions must be positive", param_name); return false; } return true; } } else if (PyList_Check(size_obj) && PyList_Size(size_obj) == 2) { PyObject* w_obj = PyList_GetItem(size_obj, 0); PyObject* h_obj = PyList_GetItem(size_obj, 1); if (PyLong_Check(w_obj) && PyLong_Check(h_obj)) { *out_w = (int)PyLong_AsLong(w_obj); *out_h = (int)PyLong_AsLong(h_obj); if (*out_w <= 0 || *out_h <= 0) { PyErr_Format(PyExc_ValueError, "%s dimensions must be positive", param_name); return false; } return true; } } PyErr_Format(PyExc_TypeError, "%s must be a (width, height) tuple or list", param_name); return false; } } // namespace MapOpsInternal // ============================================================================ // parseMapRegion - Parse region parameters for binary operations // ============================================================================ // For binary operations (two maps) inline bool parseMapRegion( int dest_w, int dest_h, int src_w, int src_h, PyObject* pos, // (x, y) or None - destination position PyObject* source_pos, // (x, y) or None - source position PyObject* size, // (w, h) or None MapRegion& out ) { using namespace MapOpsInternal; // Store full dimensions out.dest_w = dest_w; out.dest_h = dest_h; out.src_w = src_w; out.src_h = src_h; // Parse positions, default to (0, 0) if (!parseOptionalPos(pos, &out.dest_x, &out.dest_y, "pos")) { return false; } if (!parseOptionalPos(source_pos, &out.src_x, &out.src_y, "source_pos")) { return false; } // Validate positions are within bounds if (out.dest_x < 0 || out.dest_y < 0) { PyErr_SetString(PyExc_ValueError, "pos coordinates cannot be negative"); return false; } if (out.dest_x >= out.dest_w || out.dest_y >= out.dest_h) { PyErr_Format(PyExc_ValueError, "pos (%d, %d) is out of bounds for destination of size (%d, %d)", out.dest_x, out.dest_y, out.dest_w, out.dest_h); return false; } if (out.src_x < 0 || out.src_y < 0) { PyErr_SetString(PyExc_ValueError, "source_pos coordinates cannot be negative"); return false; } if (out.src_x >= out.src_w || out.src_y >= out.src_h) { PyErr_Format(PyExc_ValueError, "source_pos (%d, %d) is out of bounds for source of size (%d, %d)", out.src_x, out.src_y, out.src_w, out.src_h); return false; } // Calculate remaining space from each position int dest_remaining_w = out.dest_w - out.dest_x; int dest_remaining_h = out.dest_h - out.dest_y; int src_remaining_w = out.src_w - out.src_x; int src_remaining_h = out.src_h - out.src_y; // Parse or infer size int requested_w = -1, requested_h = -1; if (!parseOptionalSize(size, &requested_w, &requested_h, "size")) { return false; } if (requested_w > 0 && requested_h > 0) { // Explicit size - must fit in both if (requested_w > dest_remaining_w || requested_h > dest_remaining_h) { PyErr_Format(PyExc_ValueError, "size (%d, %d) exceeds available space in destination (%d, %d) from pos (%d, %d)", requested_w, requested_h, dest_remaining_w, dest_remaining_h, out.dest_x, out.dest_y); return false; } if (requested_w > src_remaining_w || requested_h > src_remaining_h) { PyErr_Format(PyExc_ValueError, "size (%d, %d) exceeds available space in source (%d, %d) from source_pos (%d, %d)", requested_w, requested_h, src_remaining_w, src_remaining_h, out.src_x, out.src_y); return false; } out.width = requested_w; out.height = requested_h; } else { // Infer size: smaller of remaining space in each out.width = std::min(dest_remaining_w, src_remaining_w); out.height = std::min(dest_remaining_h, src_remaining_h); } // Final validation: non-zero region if (out.width <= 0 || out.height <= 0) { PyErr_SetString(PyExc_ValueError, "computed region has zero size"); return false; } return true; } // For scalar operations (single map, just destination region) inline bool parseMapRegionScalar( int dest_w, int dest_h, PyObject* pos, PyObject* size, MapRegion& out ) { return parseMapRegion(dest_w, dest_h, dest_w, dest_h, pos, nullptr, size, out); } // ============================================================================ // Core map operations as free functions (used by both HeightMap and DiscreteMap) // ============================================================================ namespace MapOps { // Fill region with value template void fill(typename Policy::Type* data, int w, int h, typename Policy::Type value, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { data[region.dest_idx(x, y)] = value; } } } // Clear (fill with zero) template void clear(typename Policy::Type* data, int w, int h, const MapRegion& region) { fill(data, w, h, Policy::zero(), region); } // Copy from source (same type) template void copy(typename Policy::Type* dst, const typename Policy::Type* src, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { dst[region.dest_idx(x, y)] = src[region.src_idx(x, y)]; } } } // Add with saturation template void add(typename Policy::Type* dst, const typename Policy::Type* src, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); // Use int accumulator to detect overflow int result = static_cast(dst[idx]) + static_cast(src[region.src_idx(x, y)]); dst[idx] = Policy::clamp(result); } } } // Add scalar template void add_scalar(typename Policy::Type* data, int w, int h, typename Policy::Type value, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); int result = static_cast(data[idx]) + static_cast(value); data[idx] = Policy::clamp(result); } } } // Subtract with saturation template void subtract(typename Policy::Type* dst, const typename Policy::Type* src, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); int result = static_cast(dst[idx]) - static_cast(src[region.src_idx(x, y)]); dst[idx] = Policy::clamp(result); } } } // Multiply by scalar template void multiply_scalar(typename Policy::Type* data, int w, int h, float factor, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); float result = static_cast(data[idx]) * factor; data[idx] = Policy::clamp(result); } } } // Element-wise max template void element_max(typename Policy::Type* dst, const typename Policy::Type* src, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); T src_val = src[region.src_idx(x, y)]; if (src_val > dst[idx]) dst[idx] = src_val; } } } // Element-wise min template void element_min(typename Policy::Type* dst, const typename Policy::Type* src, const MapRegion& region) { using T = typename Policy::Type; for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); T src_val = src[region.src_idx(x, y)]; if (src_val < dst[idx]) dst[idx] = src_val; } } } } // namespace MapOps // ============================================================================ // Cross-type operations (HeightMap <-> DiscreteMap conversion) // ============================================================================ namespace MapConvert { // Copy float to uint8_t (floors and clamps) inline void float_to_uint8(uint8_t* dst, const float* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { float val = src[region.src_idx(x, y)]; dst[region.dest_idx(x, y)] = Uint8Policy::clamp(val); } } } // Copy uint8_t to float (simple promotion) inline void uint8_to_float(float* dst, const uint8_t* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { dst[region.dest_idx(x, y)] = static_cast(src[region.src_idx(x, y)]); } } } // Add float to uint8_t (with clamping) inline void add_float_to_uint8(uint8_t* dst, const float* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); float result = static_cast(dst[idx]) + src[region.src_idx(x, y)]; dst[idx] = Uint8Policy::clamp(result); } } } // Add uint8_t to float inline void add_uint8_to_float(float* dst, const uint8_t* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); dst[idx] += static_cast(src[region.src_idx(x, y)]); } } } } // namespace MapConvert // ============================================================================ // Uint8-only bitwise operations // ============================================================================ namespace MapBitwise { inline void bitwise_and(uint8_t* dst, const uint8_t* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { dst[region.dest_idx(x, y)] &= src[region.src_idx(x, y)]; } } } inline void bitwise_or(uint8_t* dst, const uint8_t* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { dst[region.dest_idx(x, y)] |= src[region.src_idx(x, y)]; } } } inline void bitwise_xor(uint8_t* dst, const uint8_t* src, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { dst[region.dest_idx(x, y)] ^= src[region.src_idx(x, y)]; } } } inline void invert(uint8_t* data, int w, int h, const MapRegion& region) { for (int y = 0; y < region.height; y++) { for (int x = 0; x < region.width; x++) { int idx = region.dest_idx(x, y); data[idx] = 255 - data[idx]; } } } } // namespace MapBitwise