#include #include #include #include #include #include #include using namespace faset::render; namespace { void require(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } bool near(float actual, float expected) { return std::abs(actual - expected) < 0.0001f; } template void rejects(F&& action, const char* message) { bool rejected = false; try { action(); } catch (const std::invalid_argument&) { rejected = true; } require(rejected, message); } void bounds_cover_rotated_and_reflected_mesh() { Mesh mesh; mesh.vertices = {{{0, 0, 0}}, {{2, 0, 0}}, {{0, 1, 0}}, {{0, 0, 1}}, {{2, 1, 1}}}; const auto model = transform({5, 7, 11}, {0, 0, 1.57079632679f}, {-2, 3, 4}); const auto box = transformed_bounds(mesh, model); require(near(box.min[0], 2) && near(box.max[0], 5) && near(box.min[1], 3) && near(box.max[1], 7) && near(box.min[2], 11) && near(box.max[2], 15), "Rotated and reflected mesh must have an enclosing world AABB"); mesh.vertices[0].position[0] = std::numeric_limits::infinity(); rejects([&] { transformed_bounds(mesh, model); }, "Nonfinite geometry cannot produce a conservative culling bound"); } void ids_track_rendered_frames_and_generation() { auto meshA = std::make_shared(); auto meshB = std::make_shared(); const Bounds box{{-1, -1, -1}, {1, 1, 1}}; const auto moved = transform({3, 0, 0}); InstanceTracker tracker; const auto a0 = tracker.update("a", meshA.get(), identity, box, "game"); const auto b0 = tracker.update("b", meshA.get(), identity, box, "game"); require(a0.slot != b0.slot && !a0.previous_valid && !b0.previous_valid, "New instances have unique slots and no temporal state"); const auto duplicate = tracker.update("a", meshA.get(), moved, box, "game"); require(!duplicate.previous_valid, "An update before frame completion cannot become previous rendered state"); tracker.finish_frame(); const auto b1 = tracker.update("b", meshA.get(), moved, box, "game"); const auto a1 = tracker.update("a", meshA.get(), moved, box, "game"); require(a1.slot == a0.slot && b1.slot == b0.slot && a1.previous_valid && b1.previous_valid && a1.previous_model == moved && b1.previous_model == identity, "Reordering does not change slots or the last rendered transforms"); tracker.finish_frame(); const auto changed = tracker.update("b", meshB.get(), moved, box, "game"); require(changed.slot == b0.slot && changed.generation != b0.generation && !changed.previous_valid, "Replacing mesh identity invalidates temporal state and bumps generation"); tracker.finish_frame(); // a was absent and is now retired. const auto c0 = tracker.update("c", meshA.get(), identity, box, "game"); require(c0.slot == a0.slot && c0.generation != a0.generation && !c0.previous_valid, "Reused slots cannot inherit a removed object's history"); tracker.finish_frame(); const auto c1 = tracker.update("c", meshA.get(), identity, box, "game"); require(c1.previous_valid, "Same instance in a completed second frame may use history"); tracker.finish_frame(); tracker.invalidate_view("game"); const auto cut = tracker.update("c", meshA.get(), identity, box, "game"); require(!cut.previous_valid, "Camera cut invalidates view history"); tracker.finish_frame(); const auto switched = tracker.update("c", meshA.get(), identity, box, "editor"); require(!switched.previous_valid, "Different viewport cannot reuse another view's history"); } void owned_mesh_identity_outlives_reimport_gap() { InstanceTracker tracker; auto old_mesh = std::make_shared(); std::weak_ptr old_reference = old_mesh; const Bounds box{{-1, -1, -1}, {1, 1, 1}}; const auto first = tracker.update("imported", old_mesh, identity, box, "game"); tracker.finish_frame(); old_mesh.reset(); require(!old_reference.expired(), "Tracker must retain mesh identity until the instance is retired or replaced"); auto replacement_mesh = std::make_shared(); const auto replaced = tracker.update("imported", replacement_mesh, identity, box, "game"); require(replaced.generation != first.generation && !replaced.previous_valid, "Reimported mesh cannot reuse the previous mesh's occlusion history"); } void lod_thresholds_have_hysteresis_and_fallback() { const std::array thresholds{100.f, 25.f}; require(select_lod(95, 0, thresholds, .1f) == 0 && select_lod(89, 0, thresholds, .1f) == 1, "LOD downgrade waits for the lower hysteresis edge"); require(select_lod(105, 1, thresholds, .1f) == 1 && select_lod(111, 1, thresholds, .1f) == 0, "LOD upgrade waits for the upper hysteresis edge"); require(select_lod(24, 1, thresholds, .1f) == 1 && select_lod(22, 1, thresholds, .1f) == 2 && select_lod(27, 2, thresholds, .1f) == 2 && select_lod(28, 2, thresholds, .1f) == 1, "Second threshold has the same hysteresis contract"); require(select_lod(50, std::numeric_limits::max(), thresholds, .1f) == 1, "An instance without a previous level chooses its size without hysteresis"); const std::array sparse{1, 0, 1}; require(select_lod(50, 0, thresholds, 0, sparse) == 0, "Missing requested LOD prefers the nearest available finer level"); const std::array coarse_only{0, 0, 1}; require(select_lod(500, 0, thresholds, 0, coarse_only) == 2, "Missing fine LODs fall back to available coarse geometry"); const std::array none{0, 0, 0}; rejects([&] { select_lod(50, 0, thresholds, .1f, none); }, "No available mesh is a content error"); rejects([&] { select_lod(std::numeric_limits::quiet_NaN(), 0, thresholds, .1f); }, "Nonfinite projected size cannot silently select a level"); } } // namespace int main() { try { bounds_cover_rotated_and_reflected_mesh(); ids_track_rendered_frames_and_generation(); owned_mesh_identity_outlives_reimport_gap(); lod_thresholds_have_hysteresis_and_fallback(); std::cout << "Visibility bounds, instance identity and LOD policy passed\n"; return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } }