NaraMotion / c_src /naramotion_motion.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
17.5 kB
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#define PI 3.14159265358979323846
#ifdef _WIN32
#define BL_MOTION_EXPORT __declspec(dllexport)
#else
#define BL_MOTION_EXPORT __attribute__((visibility("default")))
#endif
// Blend2D C Types & Signatures
typedef uint32_t BLResult;
struct BLPoint {
double x;
double y;
};
struct BLCircle {
double cx;
double cy;
double r;
};
struct BLPathCore {
void* impl;
};
struct BLContextCore {
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_add_path(BLPathCore* self, const BLPathCore* other, const void* range);
BLResult bl_context_fill_geometry_rgba32(BLContextCore* ctx, int geom_type, const void* geom_data, uint32_t rgba32);
}
extern "C" {
// ============================================================================
// 1. Native C++ Spring Dynamics & Cubic Bezier
// ============================================================================
BL_MOTION_EXPORT double bl_motion_cubic_bezier_evaluate(double t, double x1, double y1, double x2, double y2) {
if (t <= 0.0) return 0.0;
if (t >= 1.0) return 1.0;
double u = t;
for (int i = 0; i < 8; i++) {
double u2 = u * u;
double u3 = u2 * u;
double om_u = 1.0 - u;
double om_u2 = om_u * om_u;
double current_x = 3.0 * om_u2 * u * x1 + 3.0 * om_u * u2 * x2 + u3;
double dx = 3.0 * om_u2 * x1 + 6.0 * om_u * u * (x2 - x1) + 3.0 * u2 * (1.0 - x2);
double diff = current_x - t;
if (std::abs(diff) < 1e-6) break;
if (std::abs(dx) < 1e-6) break;
u -= diff / dx;
if (u < 0.0) u = 0.0;
if (u > 1.0) u = 1.0;
}
double om_u = 1.0 - u;
return 3.0 * om_u * om_u * u * y1 + 3.0 * om_u * u * u * y2 + u * u * u;
}
BL_MOTION_EXPORT double bl_motion_spring_evaluate(double t, double stiffness, double damping, double mass, double v0) {
if (t <= 0.0) return 0.0;
if (mass <= 0.0) mass = 1.0;
double omega0 = std::sqrt(stiffness / mass);
double zeta = damping / (2.0 * std::sqrt(stiffness * mass));
if (zeta < 1.0) {
// Underdamped
double omega_d = omega0 * std::sqrt(1.0 - zeta * zeta);
double alpha = zeta * omega0;
double c1 = 1.0;
double c2 = (omega_d > 0.0) ? (v0 + alpha * c1) / omega_d : 0.0;
double decay = std::exp(-alpha * t);
double osc = c1 * std::cos(omega_d * t) + c2 * std::sin(omega_d * t);
return 1.0 - decay * osc;
} else if (std::abs(zeta - 1.0) < 1e-5) {
// Critically damped
double alpha = omega0;
double c1 = 1.0;
double c2 = v0 + alpha * c1;
return 1.0 - (c1 + c2 * t) * std::exp(-alpha * t);
} else {
// Overdamped
double alpha = zeta * omega0;
double beta = omega0 * std::sqrt(zeta * zeta - 1.0);
double r1 = -alpha + beta;
double r2 = -alpha - beta;
double c2 = (v0 - r1) / (r2 - r1);
double c1 = 1.0 - c2;
return 1.0 - (c1 * std::exp(r1 * t) + c2 * std::exp(r2 * t));
}
}
// ============================================================================
// 2. Native C++ Particle Simulation Engine (SIMD / Zero Python Overhead)
// ============================================================================
struct BLMotionParticle {
double x, y;
double vx, vy;
double life, max_life;
double size, start_size, end_size;
uint32_t start_color, end_color;
double rotation, angular_velocity;
};
struct BLMotionForce {
int type; // 0 = gravity, 1 = drag, 2 = attractor
double param1, param2, param3;
};
struct BLMotionParticleSystem {
size_t max_particles;
std::vector<BLMotionParticle> particles;
std::vector<BLMotionForce> forces;
};
BL_MOTION_EXPORT void* bl_motion_ps_create(size_t max_particles) {
auto ps = new BLMotionParticleSystem();
ps->max_particles = max_particles > 0 ? max_particles : 10000;
ps->particles.reserve(std::min(ps->max_particles, (size_t)10000));
return (void*)ps;
}
BL_MOTION_EXPORT void bl_motion_ps_destroy(void* ps_ptr) {
if (ps_ptr) {
delete (BLMotionParticleSystem*)ps_ptr;
}
}
BL_MOTION_EXPORT void bl_motion_ps_clear(void* ps_ptr) {
if (ps_ptr) {
auto ps = (BLMotionParticleSystem*)ps_ptr;
ps->particles.clear();
ps->forces.clear();
}
}
BL_MOTION_EXPORT void bl_motion_ps_add_gravity(void* ps_ptr, double gx, double gy) {
if (ps_ptr) {
auto ps = (BLMotionParticleSystem*)ps_ptr;
ps->forces.push_back({0, gx, gy, 0.0});
}
}
BL_MOTION_EXPORT void bl_motion_ps_add_drag(void* ps_ptr, double drag) {
if (ps_ptr) {
auto ps = (BLMotionParticleSystem*)ps_ptr;
ps->forces.push_back({1, drag, 0.0, 0.0});
}
}
BL_MOTION_EXPORT void bl_motion_ps_add_attractor(void* ps_ptr, double x, double y, double strength) {
if (ps_ptr) {
auto ps = (BLMotionParticleSystem*)ps_ptr;
ps->forces.push_back({2, x, y, strength});
}
}
static inline double _rand_range(double min_val, double max_val) {
double f = (double)rand() / (double)RAND_MAX;
return min_val + f * (max_val - min_val);
}
BL_MOTION_EXPORT void bl_motion_ps_emit(
void* ps_ptr,
size_t count,
double x, double y,
double min_speed, double max_speed,
double min_angle, double max_angle,
double min_life, double max_life,
double start_size, double end_size,
uint32_t start_color, uint32_t end_color
) {
if (!ps_ptr) return;
auto ps = (BLMotionParticleSystem*)ps_ptr;
for (size_t i = 0; i < count; i++) {
if (ps->particles.size() >= ps->max_particles) break;
double speed = _rand_range(min_speed, max_speed);
double angle = _rand_range(min_angle, max_angle);
double life = _rand_range(min_life, max_life);
BLMotionParticle p;
p.x = x;
p.y = y;
p.vx = std::cos(angle) * speed;
p.vy = std::sin(angle) * speed;
p.life = life;
p.max_life = life;
p.size = start_size;
p.start_size = start_size;
p.end_size = end_size;
p.start_color = start_color;
p.end_color = end_color;
p.rotation = 0.0;
p.angular_velocity = _rand_range(-5.0, 5.0);
ps->particles.push_back(p);
}
}
BL_MOTION_EXPORT void bl_motion_ps_update(void* ps_ptr, double dt) {
if (!ps_ptr || dt <= 0.0) return;
auto ps = (BLMotionParticleSystem*)ps_ptr;
size_t alive_count = 0;
size_t n = ps->particles.size();
for (size_t i = 0; i < n; i++) {
BLMotionParticle& p = ps->particles[i];
p.life -= dt;
if (p.life <= 0.0) continue;
// Apply forces
for (const auto& f : ps->forces) {
if (f.type == 0) { // Gravity
p.vx += f.param1 * dt;
p.vy += f.param2 * dt;
} else if (f.type == 1) { // Drag
double factor = std::max(0.0, 1.0 - f.param1 * dt);
p.vx *= factor;
p.vy *= factor;
} else if (f.type == 2) { // Attractor
double dx = f.param1 - p.x;
double dy = f.param2 - p.y;
double dist_sq = dx * dx + dy * dy + 100.0;
double dist = std::sqrt(dist_sq);
double accel = (f.param3 / dist_sq) * dt;
p.vx += (dx / dist) * accel;
p.vy += (dy / dist) * accel;
}
}
p.x += p.vx * dt;
p.y += p.vy * dt;
p.rotation += p.angular_velocity * dt;
double prog = 1.0 - (p.life / p.max_life);
p.size = p.start_size + (p.end_size - p.start_size) * prog;
if (i != alive_count) {
ps->particles[alive_count] = p;
}
alive_count++;
}
ps->particles.resize(alive_count);
}
static inline uint32_t _lerp_rgba32(uint32_t c1, uint32_t c2, double t) {
uint32_t a1 = (c1 >> 24) & 0xFF;
uint32_t r1 = (c1 >> 16) & 0xFF;
uint32_t g1 = (c1 >> 8) & 0xFF;
uint32_t b1 = c1 & 0xFF;
uint32_t a2 = (c2 >> 24) & 0xFF;
uint32_t r2 = (c2 >> 16) & 0xFF;
uint32_t g2 = (c2 >> 8) & 0xFF;
uint32_t b2 = c2 & 0xFF;
uint32_t a = (uint32_t)((double)a1 + ((double)a2 - (double)a1) * t);
uint32_t r = (uint32_t)((double)r1 + ((double)r2 - (double)r1) * t);
uint32_t g = (uint32_t)((double)g1 + ((double)g2 - (double)g1) * t);
uint32_t b = (uint32_t)((double)b1 + ((double)b2 - (double)b1) * t);
return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
BL_MOTION_EXPORT void bl_motion_ps_render(void* ps_ptr, BLContextCore* ctx) {
if (!ps_ptr || !ctx) return;
auto ps = (BLMotionParticleSystem*)ps_ptr;
for (const auto& p : ps->particles) {
if (p.size <= 0.2) continue;
double prog = 1.0 - (p.life / p.max_life);
uint32_t color = _lerp_rgba32(p.start_color, p.end_color, prog);
BLCircle circle;
circle.cx = p.x;
circle.cy = p.y;
circle.r = p.size;
bl_context_fill_geometry_rgba32(ctx, 4 /* BL_GEOMETRY_TYPE_CIRCLE */, &circle, color);
}
}
BL_MOTION_EXPORT size_t bl_motion_ps_get_count(void* ps_ptr) {
if (!ps_ptr) return 0;
return ((BLMotionParticleSystem*)ps_ptr)->particles.size();
}
// ============================================================================
// 3. Native C++ Path Line Trimming (De Casteljau Arc Length)
// ============================================================================
struct Point2D {
double x, y;
};
BL_MOTION_EXPORT int bl_motion_trim_path(BLPathCore* src, BLPathCore* dst, double start, double end, double offset) {
if (!src || !dst) return -1;
size_t vtx_count = bl_path_get_size(src);
if (vtx_count == 0) return 0;
const BLPoint* vtx_data = bl_path_get_vertex_data(src);
std::vector<Point2D> pts;
pts.reserve(vtx_count);
for (size_t i = 0; i < vtx_count; i++) {
double x = vtx_data[i].x;
double y = vtx_data[i].y;
if (!std::isnan(x) && !std::isnan(y) && !std::isinf(x) && !std::isinf(y)) {
pts.push_back({x, y});
}
}
if (pts.size() < 2) return 0;
// Total length
std::vector<double> lengths;
lengths.reserve(pts.size());
lengths.push_back(0.0);
double total_len = 0.0;
for (size_t i = 1; i < pts.size(); i++) {
double dx = pts[i].x - pts[i-1].x;
double dy = pts[i].y - pts[i-1].y;
total_len += std::hypot(dx, dy);
lengths.push_back(total_len);
}
if (total_len <= 1e-6) return 0;
double s = std::fmod(start + offset, 1.0);
if (s < 0.0) s += 1.0;
double e = std::fmod(end + offset, 1.0);
if (e < 0.0) e += 1.0;
if (std::abs(start - end) >= 1.0) {
bl_path_add_path(dst, src, nullptr);
return 0;
}
if (s > e) {
// Two segments [s, 1.0] and [0.0, e]
double t0_a = s * total_len, t1_a = total_len;
double t0_b = 0.0, t1_b = e * total_len;
auto extract_seg = [&](double target_start, double target_end) {
bool started = false;
for (size_t i = 1; i < pts.size(); i++) {
double l0 = lengths[i-1];
double l1 = lengths[i];
if (l1 < target_start || l0 > target_end) continue;
double seg_len = l1 - l0;
double f0 = (seg_len > 0.0 && target_start > l0) ? (target_start - l0) / seg_len : 0.0;
double f1 = (seg_len > 0.0 && target_end < l1) ? (target_end - l0) / seg_len : 1.0;
f0 = std::max(0.0, std::min(1.0, f0));
f1 = std::max(0.0, std::min(1.0, f1));
double x_start = pts[i-1].x + (pts[i].x - pts[i-1].x) * f0;
double y_start = pts[i-1].y + (pts[i].y - pts[i-1].y) * f0;
double x_end = pts[i-1].x + (pts[i].x - pts[i-1].x) * f1;
double y_end = pts[i-1].y + (pts[i].y - pts[i-1].y) * f1;
if (!started) {
bl_path_move_to(dst, x_start, y_start);
started = true;
}
bl_path_line_to(dst, x_end, y_end);
}
};
extract_seg(t0_a, t1_a);
extract_seg(t0_b, t1_b);
return 0;
}
double target_start = s * total_len;
double target_end = e * total_len;
bool started = false;
for (size_t i = 1; i < pts.size(); i++) {
double l0 = lengths[i-1];
double l1 = lengths[i];
if (l1 < target_start || l0 > target_end) continue;
double seg_len = l1 - l0;
double f0 = (seg_len > 0.0 && target_start > l0) ? (target_start - l0) / seg_len : 0.0;
double f1 = (seg_len > 0.0 && target_end < l1) ? (target_end - l0) / seg_len : 1.0;
f0 = std::max(0.0, std::min(1.0, f0));
f1 = std::max(0.0, std::min(1.0, f1));
double x_start = pts[i-1].x + (pts[i].x - pts[i-1].x) * f0;
double y_start = pts[i-1].y + (pts[i].y - pts[i-1].y) * f0;
double x_end = pts[i-1].x + (pts[i].x - pts[i-1].x) * f1;
double y_end = pts[i-1].y + (pts[i].y - pts[i-1].y) * f1;
if (!started) {
bl_path_move_to(dst, x_start, y_start);
started = true;
}
bl_path_line_to(dst, x_end, y_end);
}
return 0;
}
// ============================================================================
// 4. Native C++ Chaikin's Corner Smoothing Algorithm
// ============================================================================
BL_MOTION_EXPORT int nm_motion_chaikin_smooth(
const double* in_pts, size_t in_count,
double* out_pts, size_t* out_count,
int iterations, double ratio, int closed
) {
if (!in_pts || in_count < 2 || !out_pts || !out_count) return -1;
std::vector<std::pair<double, double>> current(in_count);
for (size_t i = 0; i < in_count; i++) {
current[i] = {in_pts[i * 2], in_pts[i * 2 + 1]};
}
double r = std::max(0.01, std::min(0.49, ratio));
double inv_r = 1.0 - r;
for (int it = 0; it < iterations; it++) {
std::vector<std::pair<double, double>> next;
size_t n = current.size();
if (!closed) {
next.push_back(current[0]);
for (size_t i = 0; i < n - 1; i++) {
auto p0 = current[i];
auto p1 = current[i + 1];
next.push_back({inv_r * p0.first + r * p1.first, inv_r * p0.second + r * p1.second});
next.push_back({r * p0.first + inv_r * p1.first, r * p0.second + inv_r * p1.second});
}
next.push_back(current[n - 1]);
} else {
for (size_t i = 0; i < n; i++) {
auto p0 = current[i];
auto p1 = current[(i + 1) % n];
next.push_back({inv_r * p0.first + r * p1.first, inv_r * p0.second + r * p1.second});
next.push_back({r * p0.first + inv_r * p1.first, r * p0.second + inv_r * p1.second});
}
}
current = std::move(next);
}
*out_count = current.size();
for (size_t i = 0; i < current.size(); i++) {
out_pts[i * 2] = current[i].first;
out_pts[i * 2 + 1] = current[i].second;
}
return 0;
}
// ============================================================================
// 5. Native C++ Interpolation & Range Remapping
// ============================================================================
BL_MOTION_EXPORT double nm_motion_interpolate(
double value,
const double* in_range, const double* out_range, size_t count,
int extrapolate_left, int extrapolate_right
) {
if (!in_range || !out_range || count < 2) return value;
if (value < in_range[0]) {
if (extrapolate_left == 0) return out_range[0];
if (extrapolate_left == 2) return value;
double dx = in_range[1] - in_range[0];
if (dx == 0.0) return out_range[0];
return out_range[0] + ((value - in_range[0]) / dx) * (out_range[1] - out_range[0]);
}
if (value > in_range[count - 1]) {
if (extrapolate_right == 0) return out_range[count - 1];
if (extrapolate_right == 2) return value;
double dx = in_range[count - 1] - in_range[count - 2];
if (dx == 0.0) return out_range[count - 1];
return out_range[count - 1] + ((value - in_range[count - 1]) / dx) * (out_range[count - 1] - out_range[count - 2]);
}
size_t seg = 0;
for (size_t i = 0; i < count - 1; i++) {
if (value >= in_range[i] && value <= in_range[i + 1]) {
seg = i;
break;
}
}
double dx = in_range[seg + 1] - in_range[seg];
if (dx == 0.0) return out_range[seg];
double t = (value - in_range[seg]) / dx;
return out_range[seg] + t * (out_range[seg + 1] - out_range[seg]);
}
} // extern "C"