NaraMotion / tests /test_v26_engine.py
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
7.02 kB
"""
Unit tests for NaraMotion v0.26.0 High-Performance Engine Upgrades:
- Structure of Arrays (SoA) and Path Arena Allocator (NativeGeoLayerSoA)
- Direct Native WKB Batch Ingestion (Bypassing Python / Shapely)
- Flat Uniform Grid Spatial Indexing in C++ (UniformGridIndex)
- Dual-Buffer Polygon Render Pipeline (Batch Fills + Batch Strokes)
- Local Centroid Spring Pop-In Animation (Scale = Easing::back_out(t))
- BBox Overdraw & Viewport Culling
- High-Speed Retained GeoLayer Integration
"""
import os
import pytest
import numpy as np
import naramotion as nm
from naramotion import (
GeoCanvas,
NativeGeoLayerSoA,
UniformGridIndex,
RetainedGeoLayer,
Image,
Context,
GeoTransform,
Format,
VideoWriter,
)
try:
import shapely.geometry as sg
import shapely
_HAS_SHAPELY = True
except ImportError:
_HAS_SHAPELY = False
def test_soa_lifecycle_and_polygon_addition():
"""Verify NativeGeoLayerSoA memory allocation, polygon addition, and SoA arrays."""
layer = NativeGeoLayerSoA(capacity=100)
assert len(layer) == 0
# Add a rectangle polygon
coords = np.array([[10, 10], [90, 10], [90, 90], [10, 90], [10, 10]], dtype=np.float64)
rings = [0, 5]
idx = layer.add_polygon(coords, rings)
assert idx == 0
assert len(layer) == 1
assert layer.count == 1
# Add second polygon
coords2 = np.array([[100, 100], [200, 100], [200, 200], [100, 200], [100, 100]], dtype=np.float64)
idx2 = layer.add_polygon(coords2, rings)
assert idx2 == 1
assert len(layer) == 2
def test_soa_direct_wkb_batch_ingestion():
"""Verify parsing WKB buffers directly in C++ into BLPathCore with holes."""
if not _HAS_SHAPELY:
pytest.skip("Shapely required for WKB generation in test")
# Outer box with a square hole cut out
poly_with_hole = sg.Polygon(
[(0, 0), (100, 0), (100, 100), (0, 100), (0, 0)],
[[(25, 25), (75, 25), (75, 75), (25, 75), (25, 25)]]
)
wkb1 = shapely.to_wkb(poly_with_hole)
# MultiPolygon
mpoly = sg.MultiPolygon([
sg.Polygon([(110, 0), (150, 0), (150, 40), (110, 40)]),
sg.Polygon([(160, 0), (200, 0), (200, 40), (160, 40)]),
])
wkb2 = shapely.to_wkb(mpoly)
layer = NativeGeoLayerSoA(capacity=50)
parsed_count = layer.from_wkb_batch([wkb1, wkb2])
assert parsed_count == 2
assert len(layer) == 2
# Verify rendering to context succeeds
img = Image(300, 200, Format.PRGB32)
ctx = Context(img)
res = layer.render_dual_buffer(ctx, fill_color="#38BDF8", stroke_color="#FFFFFF", stroke_width=1.5)
ctx.end()
assert res == 0
def test_soa_propagation_and_culling():
"""Verify shockwave calculation and vectorized BBox viewport culling."""
layer = NativeGeoLayerSoA(capacity=10)
# Feature A: near (0, 0)
layer.add_polygon(np.array([[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], dtype=np.float64), [0, 5])
# Feature B: far (100, 100)
layer.add_polygon(np.array([[100, 100], [110, 100], [110, 110], [100, 110], [100, 100]], dtype=np.float64), [0, 5])
# Propagation from (0, 0)
res = layer.compute_propagation(epicenters=[(0.0, 0.0)], duration_frames=100.0, speed=1.0)
assert res == 0
# Viewport enclosing only Feature A: Feature B should be culled
vis_count = layer.cull_and_update(viewport_bbox=(-5, -5, 50, 50), current_frame=10.0)
assert vis_count == 1
# Viewport enclosing both at frame 0 (Feature B wave hasn't arrived)
vis_early = layer.cull_and_update(viewport_bbox=(-5, -5, 200, 200), current_frame=5.0)
assert vis_early == 1
# Viewport enclosing both at frame 90 (both waves arrived)
vis_late = layer.cull_and_update(viewport_bbox=(-5, -5, 200, 200), current_frame=90.0)
assert vis_late == 2
def test_soa_dual_buffer_and_spring_popin_rendering():
"""Verify C++ Dual-Buffer batch pass and Centroid Spring Pop-In (back_out) scaling."""
layer = NativeGeoLayerSoA(capacity=10)
coords = np.array([[50, 50], [150, 50], [150, 150], [50, 150], [50, 50]], dtype=np.float64)
layer.add_polygon(coords, [0, 5])
layer.compute_propagation(epicenters=[(100.0, 100.0)], duration_frames=60.0)
img = Image(300, 300, Format.PRGB32)
ctx = Context(img)
# 1. Dual-Buffer Batch Render
res1 = layer.render_dual_buffer(ctx, fill_color="#00FFCC", stroke_color="#000000", stroke_width=2.0)
assert res1 == 0
# 2. Centroid Spring Pop-In at mid-frame (elastic overshoot)
res2 = layer.render_popin(ctx, current_frame=30.0, fill_color="#FF0055", stroke_color="#FFFFFF", stroke_width=1.0)
assert res2 == 0
# 3. Centroid Spring Pop-In after completion (scale = 1.0)
res3 = layer.render_popin(ctx, current_frame=70.0, fill_color="#FF0055", stroke_color="#FFFFFF", stroke_width=1.0)
assert res3 == 0
ctx.end()
def test_uniform_grid_spatial_index():
"""Verify flat uniform grid spatial queries in C++."""
# Bounds: [0, 0, 1000, 1000] with 10x10 cells
grid = UniformGridIndex(bounds=(0.0, 0.0, 1000.0, 1000.0), grid_w=10, grid_h=10)
bboxes = np.array([
[10.0, 10.0, 50.0, 50.0], # In cell (0, 0)
[510.0, 510.0, 550.0, 550.0], # In cell (5, 5)
[910.0, 910.0, 950.0, 950.0], # In cell (9, 9)
], dtype=np.float64)
built = grid.build_from_bboxes(bboxes)
assert built == 0
# Query bottom-left region
q1 = grid.query_bbox(0.0, 0.0, 100.0, 100.0)
assert 0 in q1
assert 1 not in q1
assert 2 not in q1
# Query center region
q2 = grid.query_bbox(500.0, 500.0, 600.0, 600.0)
assert 1 in q2
assert 0 not in q2
assert 2 not in q2
# Query entire area
q_all = grid.query_bbox(0.0, 0.0, 1000.0, 1000.0)
assert len(q_all) == 3
def test_geocanvas_add_polygon_layer():
"""Verify GeoCanvas integration with NativeGeoLayerSoA and RetainedGeoLayer."""
canvas = GeoCanvas(400, 300)
# 1. Using NativeGeoLayerSoA
layer_soa = NativeGeoLayerSoA(capacity=10)
coords = np.array([[50, 50], [150, 50], [150, 150], [50, 150], [50, 50]], dtype=np.float64)
layer_soa.add_polygon(coords, [0, 5])
canvas.add_polygon_layer(layer_soa, fill_color="#00E5FF", stroke_color="#FFFFFF", stroke_width=2.0)
# 2. Using RetainedGeoLayer with popin and frame
if _HAS_SHAPELY:
poly = sg.Polygon([(200, 50), (280, 50), (280, 130), (200, 130)])
retained = canvas.create_retained_layer([poly], fill_color="#FF007F")
canvas.add_polygon_layer(retained, popin=True, frame=10.0)
img = canvas.render()
assert img.width == 400
assert img.height == 300
def test_video_writer_buffer_pipe_streaming():
"""Verify VideoWriter zero-copy buffer pipe access."""
img = Image(320, 240, Format.PRGB32)
with Context(img) as ctx:
ctx.fill_all("#111827")
ctx.fill_circle(160, 120, 40, "#00E5FF")
# Direct memoryview check
buf = img.buffer
assert isinstance(buf, memoryview)
assert len(buf) == 320 * 240 * 4