application.cpp
C++ demo · Navigation integration guide · Raw source
Demo files
- cpp_demo_3d_app/main.cpp
- cpp_demo_3d_app/integration.hpp
- cpp_demo_3d_app/diagnostic_view.cpp
- cpp_demo_3d_app/diagnostic_view.hpp
- cpp_demo_3d_app/application.cpp
- cpp_demo_3d_app/application.hpp
- python_demo_3d_app/application.py
- python_demo_3d_app/integration.py
- python_demo_3d_app/main.py
- csharp_demo_3d_app/MyApplication.cs
- csharp_demo_3d_app/RaylibWindow.cs
- csharp_demo_3d_app/MyOpenAxisIntegration.cs
- csharp_demo_3d_app/Program.cs
- typescript_demo_3d_app/application.ts
- typescript_demo_3d_app/integration.ts
- typescript_demo_3d_app/main.ts
- csharp_demo_3d_app/DemoScene.cs
#include "application.hpp"#include <GLFW/glfw3.h>#include <algorithm>#include <fstream>#include <limits>namespace {using V = openaxis::Vec3;using Q = openaxis::Quat;V vec(const nlohmann::json &j) { return {j[0], j[1], j[2]}; }void vertex(V v) { glVertex3d(v.x, v.y, v.z); }void rotation(Q q) { auto x = q.rotate({1, 0, 0}), y = q.rotate({0, 1, 0}), z = q.rotate({0, 0, 1}); double m[] = {x.x, x.y, x.z, 0, y.x, y.y, y.z, 0, z.x, z.y, z.z, 0, 0, 0, 0, 1}; glMultMatrixd(m);}} // namespaceMyApplication::MyApplication(const std::string &path) { std::ifstream input(path); if (!input) throw std::runtime_error("Cannot open scene.json: " + path); Json scene; input >> scene; ground_size = scene["ground"]["size"]; ground_step = scene["ground"]["step"]; ground_y = scene["ground"]["y"]; auto c = scene.at("camera"); initial_camera = {vec(c["t"]), vec(c["r"]), c["fov"], 0}; initial_extent = scene["orthoExtent"]; for (const auto &j : scene["objects"]) { Mesh m; m.name = j["name"]; m.color = j["color"]; // Shared scene stores intrinsic XYZ Euler angles, not rotation vectors. auto e = vec(j["rotation"]); auto q = Q::from_rotvec({e.x, 0, 0}) * Q::from_rotvec({0, e.y, 0}) * Q::from_rotvec({0, 0, e.z}); m.initial = {vec(j["position"]), q.rotvec()}; m.pose = m.initial; for (std::size_t i = 0; i < j["vertices"].size(); i += 3) m.vertices.push_back({j["vertices"][i], j["vertices"][i + 1], j["vertices"][i + 2]}); if (j.contains("normals")) for (std::size_t i = 0; i < j["normals"].size(); i += 3) m.normals.push_back({j["normals"][i], j["normals"][i + 1], j["normals"][i + 2]}); m.indices = j["indices"].get<std::vector<unsigned>>(); meshes.push_back(std::move(m)); } reset();}void MyApplication::Bounds::add(Vec3 p) { if (!valid) { min = max = p; valid = true; return; } min = {std::min(min.x, p.x), std::min(min.y, p.y), std::min(min.z, p.z)}; max = {std::max(max.x, p.x), std::max(max.y, p.y), std::max(max.z, p.z)};}MyApplication::Json MyApplication::Bounds::value() const { if (!valid) return nullptr; return {{"min", {min.x, min.y, min.z}}, {"max", {max.x, max.y, max.z}}};}MyApplication::Bounds MyApplication::bounds(int index) const { Bounds b; for (std::size_t i = 0; i < meshes.size(); ++i) { if (index >= 0 && int(i) != index) continue; const auto &m = meshes[i]; auto q = Q::from_rotvec(m.pose.r); for (auto v : m.vertices) b.add(q.rotate(v) + m.pose.t); } return b;}void MyApplication::reset() { camera = initial_camera; target = {}; for (auto &m : meshes) m.pose = m.initial; selected = editing = -1; undo.clear(); pivot.reset(); object_pivot.reset(); ++generation; if (on_context_changed) on_context_changed();}void MyApplication::begin_edit() { if (selected < 0 || editing >= 0) return; editing = selected; edit_start = meshes[editing].pose; ++generation; if (on_context_changed) on_context_changed();}void MyApplication::finish_edit(bool accept) { if (editing < 0) return; if (accept) undo.emplace_back(editing, edit_start); else meshes[editing].pose = edit_start; editing = selected = -1; ++generation; if (on_context_changed) on_context_changed();}void MyApplication::undo_edit() { if (editing >= 0) { finish_edit(false); return; } if (undo.empty()) return; auto e = undo.back(); undo.pop_back(); meshes[e.first].pose = e.second; ++generation; if (on_context_changed) on_context_changed();}void MyApplication::toggle_projection() { if (camera.fov > 0) { camera.fov = 0; camera.ortho_extent = initial_extent; } else { camera.fov = initial_camera.fov; camera.ortho_extent = 0; } if (on_camera_changed) on_camera_changed();}std::optional<MyApplication::Hit> MyApplication::pick(double x, double y, bool only, bool ground, std::array<Vec3, 2> *ray) const { if (width <= 0 || height <= 0 || x < 0 || x > width || y < 0 || y > height) return {}; double nx = 2 * x / width - 1, ny = 1 - 2 * y / height, aspect = double(width) / height; auto q = Q::from_rotvec(camera.r); V origin = camera.t, direction; if (camera.fov > 0) direction = q.rotate(V{nx * aspect * std::tan(camera.fov / 2), ny * std::tan(camera.fov / 2), -1} .normalized()); else { origin = origin + q.rotate({nx * aspect * camera.ortho_extent / 2, ny * camera.ortho_extent / 2, -clipping()[0]}); direction = q.rotate({0, 0, -1}); } std::optional<Hit> hit; if (ray) *ray = {origin, origin + direction * 10000}; for (std::size_t i = 0; i < meshes.size(); ++i) { if (only && int(i) != selected) continue; const auto &m = meshes[i]; auto inv = Q::from_rotvec(m.pose.r).inverse(); auto o = inv.rotate(origin - m.pose.t), d = inv.rotate(direction); for (std::size_t k = 0; k < m.indices.size(); k += 3) { auto a = m.vertices[m.indices[k]], b = m.vertices[m.indices[k + 1]], c = m.vertices[m.indices[k + 2]]; auto e1 = b - a, e2 = c - a, h = d.cross(e2); double det = e1.dot(h); if (std::abs(det) < 1e-12) continue; auto s = o - a; double u = s.dot(h) / det; if (u < 0 || u > 1) continue; auto r = s.cross(e1); double v = d.dot(r) / det; if (v < 0 || u + v > 1) continue; double t = e2.dot(r) / det; if (t > 0 && (!hit || t < hit->distance)) hit = Hit{origin + direction * t, int(i), t}; } } if (ground && !only && std::abs(direction.y) > 1e-12) { double t = (ground_y - origin.y) / direction.y; auto p = origin + direction * t; if (t > 0 && std::abs(p.x) <= ground_size / 2 && std::abs(p.z) <= ground_size / 2 && (!hit || t < hit->distance)) hit = Hit{p, -1, t}; } return hit;}void MyApplication::drag(double dx, double dy, bool rotate) { auto q = Q::from_rotvec(camera.r); auto center = editing >= 0 ? meshes[editing].pose.t : target; double extent = camera.fov > 0 ? 2 * std::max(.01, -q.inverse().rotate(center - camera.t).z) * std::tan(camera.fov / 2) : camera.ortho_extent; if (rotate) { double sign = editing >= 0 ? 1 : -1; auto delta = Q::from_rotvec(q.rotate({sign * dy * .006, sign * dx * .006, 0})); if (editing >= 0) { auto &p = meshes[editing].pose; p.r = (delta * Q::from_rotvec(p.r)).rotvec(); } else { camera.t = center + delta.rotate(camera.t - center); camera.r = (delta * q).rotvec(); } } else { auto move = q.rotate({dx * extent / std::max(height, 1), -dy * extent / std::max(height, 1), 0}); if (editing >= 0) meshes[editing].pose.t = meshes[editing].pose.t + move; else { camera.t = camera.t - move; target = target - move; } } auto &changed = editing >= 0 ? on_object_changed : on_camera_changed; if (changed) changed();}void MyApplication::wheel(double d) { auto q = Q::from_rotvec(camera.r); auto forward = q.rotate({0, 0, -1}); if (editing >= 0) { auto &p = meshes[editing].pose; double span = camera.fov > 0 ? 2 * std::max(.01, -q.inverse().rotate(p.t - camera.t).z) * std::tan(camera.fov / 2) : camera.ortho_extent; p.t = p.t + forward * (d * span * .08); } else if (camera.ortho_extent > 0) camera.ortho_extent = std::clamp(camera.ortho_extent * std::pow(.85, d), .01, 10000.); else { auto b = bounds(selected); double distance = std::max(.02, -q.inverse().rotate((b.min + b.max) * .5 - camera.t).z); camera.t = camera.t + forward * (distance * (1 - std::pow(.85, d))); } auto &changed = editing >= 0 ? on_object_changed : on_camera_changed; if (changed) changed();}MyApplication::Bounds MyApplication::ground_bounds() const { Bounds b; b.add({-ground_size / 2, ground_y, -ground_size / 2}); b.add({ground_size / 2, ground_y, ground_size / 2}); return b;}std::array<double, 2> MyApplication::clipping() const { if (camera.fov > 0) return {.01, 10000}; auto b = bounds(); auto g = ground_bounds(); b.add(g.min); b.add(g.max); auto q = Q::from_rotvec(camera.r).inverse(); double low = std::numeric_limits<double>::infinity(), high = -low; auto add = [&](V p) { double z = -q.rotate(p - camera.t).z; low = std::min(low, z); high = std::max(high, z); }; for (double x : {b.min.x, b.max.x}) for (double y : {b.min.y, b.max.y}) for (double z : {b.min.z, b.max.z}) add({x, y, z}); for (auto p : diagnostic_points) add(p); double margin = std::max(1., (high - low) * .1); return {low - margin, high + margin};}void MyApplication::render() const { glEnable(GL_DEPTH_TEST); glClearColor(20 / 255.f, 31 / 255.f, 46 / 255.f, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); double aspect = double(width) / std::max(height, 1); if (camera.fov > 0) { double h = .01 * std::tan(camera.fov / 2); glFrustum(-h * aspect, h * aspect, -h, h, .01, 10000); } else { double h = camera.ortho_extent / 2; auto range = clipping(); glOrtho(-h * aspect, h * aspect, -h, h, range[0], range[1]); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); rotation(Q::from_rotvec(camera.r).inverse()); glTranslated(-camera.t.x, -camera.t.y, -camera.t.z); glDisable(GL_LIGHTING); glBegin(GL_LINES); for (double i = -ground_size / 2; i <= ground_size / 2; i += ground_step) { bool major = std::abs(std::remainder(i, 10.)) < 1e-8; glColor3f(major ? 107 / 255.f : 61 / 255.f, major ? 122 / 255.f : 77 / 255.f, major ? 143 / 255.f : 94 / 255.f); vertex({i, ground_y, -ground_size / 2}); vertex({i, ground_y, ground_size / 2}); vertex({-ground_size / 2, ground_y, i}); vertex({ground_size / 2, ground_y, i}); } glEnd(); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); glEnable(GL_NORMALIZE); glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); const GLfloat ambient[] = {.45f, .45f, .45f, 1}, sun[] = {.7f, .7f, .7f, 1}, direction[] = {1, 2, 3, 0}; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, sun); glLightfv(GL_LIGHT0, GL_POSITION, direction); for (std::size_t i = 0; i < meshes.size(); ++i) { const auto &m = meshes[i]; glPushMatrix(); glTranslated(m.pose.t.x, m.pose.t.y, m.pose.t.z); rotation(Q::from_rotvec(m.pose.r)); float r = ((m.color >> 16) & 255) / 255.f, g = ((m.color >> 8) & 255) / 255.f, b = (m.color & 255) / 255.f; if (int(i) == selected) { r = 1; g = .85f; b = .3f; } if (int(i) == editing) { r = 1; g = .25f; b = .75f; } glColor3f(r, g, b); glBegin(GL_TRIANGLES); for (std::size_t k = 0; k < m.indices.size(); k += 3) { auto face = (m.vertices[m.indices[k + 1]] - m.vertices[m.indices[k]]) .cross(m.vertices[m.indices[k + 2]] - m.vertices[m.indices[k]]) .normalized(); for (int j = 0; j < 3; ++j) { auto index = m.indices[k + j]; auto n = index < m.normals.size() ? m.normals[index] : face; glNormal3d(n.x, n.y, n.z); vertex(m.vertices[index]); } } glEnd(); glPopMatrix(); } glDisable(GL_LIGHTING); // World-space billboards use the scene depth, unlike the ImGui overlay. glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_CURRENT_BIT); glDepthMask(GL_FALSE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); auto orientation = Q::from_rotvec(camera.r); auto right = orientation.rotate({1, 0, 0}); auto up = orientation.rotate({0, 1, 0}); for (auto point : {pivot, object_pivot}) { if (!point || !project(*point)) continue; auto local = orientation.inverse().rotate(*point - camera.t); double span = camera.fov > 0 ? 2 * -local.z * std::tan(camera.fov / 2) : camera.ortho_extent; double radius = 4 * span / std::max(height, 1); for (bool hidden : {false, true}) { glDepthFunc(hidden ? GL_GREATER : GL_LEQUAL); float alpha = hidden ? .2f : 1.f; glColor4f(0, 1, 0, alpha); glBegin(GL_TRIANGLE_FAN); vertex(*point); for (int i = 0; i <= 64; ++i) { double a = i * 6.283185307179586 / 64; vertex(*point + (right * std::cos(a) + up * std::sin(a)) * radius); } glEnd(); glColor4f(0, 0, 0, alpha); glBegin(GL_TRIANGLE_STRIP); for (int i = 0; i <= 64; ++i) { double a = i * 6.283185307179586 / 64; auto offset = (right * std::cos(a) + up * std::sin(a)) * radius; vertex(*point + offset); vertex(*point + offset * (5.5 / 4)); } glEnd(); } } glPopAttrib();}
std::optional<std::array<double, 2>> MyApplication::project(Vec3 p) const { if (width <= 0 || height <= 0) return {}; auto v = Q::from_rotvec(camera.r).inverse().rotate(p - camera.t); double depth = -v.z; auto range = clipping(); if (depth < range[0] - 1e-9 || depth > range[1] + 1e-9) return {}; double half = camera.fov > 0 ? depth * std::tan(camera.fov / 2) : camera.ortho_extent / 2; if (half <= 0) return {}; return std::array<double, 2>{width * .5 + v.x * height / (2 * half), height * .5 - v.y * height / (2 * half)};}bool MyApplication::clip_segment(Vec3 &a, Vec3 &b) const { if (width <= 0 || height <= 0) return false; auto q = Q::from_rotvec(camera.r).inverse(); auto av = q.rotate(a - camera.t), bv = q.rotate(b - camera.t); auto planes = [&](Vec3 p) { double z = -p.z, h = camera.fov > 0 ? z * std::tan(camera.fov / 2) : camera.ortho_extent / 2; double w = h * double(width) / height; auto range = clipping(); return std::array<double, 6>{z - range[0], range[1] - z, w + p.x, w - p.x, h + p.y, h - p.y}; }; auto pa = planes(av), pb = planes(bv); double low = 0, high = 1; for (int i = 0; i < 6; ++i) { if (pa[i] < 0 && pb[i] < 0) return false; if (pa[i] < 0) low = std::max(low, pa[i] / (pa[i] - pb[i])); if (pb[i] < 0) high = std::min(high, pa[i] / (pa[i] - pb[i])); } if (low > high) return false; auto original = a, delta = b - a; a = original + delta * low; b = original + delta * high; return true;}