#define Matrix rlMatrix #define Vector3 rlVector3 #define Quaternion rlQuaternion #include #include #include #define RAYGUI_IMPLEMENTATION #include #undef Quaternion #undef Vector3 #undef Matrix #include "jet.hpp" #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include #endif using namespace Eigen; template using Vector6 = Eigen::Vector; template using Matrix6 = Eigen::Matrix; template requires(scalar::n_vars >= 6) struct transform{ Vector3 translation; Vector3 euler_rotation_angles; transform() : translation(0,0,0), euler_rotation_angles(0,0,0){ for(size_t i = 0;i < 3;i++){ translation[i].deriv(i) = 1; euler_rotation_angles[i].deriv(i + 3) = 1; } } Matrix3 matrix()const noexcept{ return quat().toRotationMatrix(); } Quaternion quat()const noexcept{ Eigen::AngleAxis rollAngle(euler_rotation_angles.x(), Eigen::Vector3::UnitX()); Eigen::AngleAxis yawAngle(euler_rotation_angles.y(), Eigen::Vector3::UnitY()); Eigen::AngleAxis pitchAngle(euler_rotation_angles.z(), Eigen::Vector3::UnitZ()); return rollAngle * yawAngle * pitchAngle; } template Matrix apply(const Matrix& input){ Matrix trfed = matrix() * input; trfed.colwise() += translation; return trfed; } }; template Matrix generate_hexagon(T scale, Vector3 translation = Vector3(0, 0, 0)){ T scale3 = scale * std::sqrt(T(3)); T scale32 = scale3 / T(2); translation.x() -= scale * 0.5; translation.y() -= scale32; Matrix vertices; vertices.col(0) = translation + Vector3(0, 0, 0); vertices.col(1) = translation + Vector3(scale, 0, 0); vertices.col(2) = translation + Vector3(scale * 1.5, scale32, 0); vertices.col(3) = translation + Vector3(scale, scale3, 0); vertices.col(4) = translation + Vector3(0, scale3, 0); vertices.col(5) = translation + Vector3(-scale * 0.5, scale32, 0); return vertices; } template Matrix generate_scronched_hexagon(T scale, Vector3 translation = Vector3(0, 0, 0)){ T scale3 = scale * std::sqrt(T(3)); T scale32 = scale3 / T(2); translation.x() -= scale * 0.5; translation.y() -= scale32; Matrix vertices; vertices.col(0) = translation + Vector3(0, 0, 0); vertices.col(1) = translation + Vector3(0, 0, 0); vertices.col(2) = translation + Vector3(scale * 1.5, scale32, 0); vertices.col(3) = translation + Vector3(scale * 1.5, scale32, 0); vertices.col(4) = translation + Vector3(0, scale3, 0); vertices.col(5) = translation + Vector3(0, scale3, 0); return vertices; } int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, double *value, float minValue, float maxValue){ float v = *value; int res = GuiSlider(bounds, textLeft, textRight, &v, minValue, maxValue); *value = v; return res; } template using jet_type = jet; template using hexagon = Matrix, 3, 6>; constexpr double distance = 1.5; template std::pair, Matrix6> evaluate_with_jacobian(const transform>& trf, const hexagon& points, const hexagon& anchors){ hexagon trfed = trf.matrix() * points; trfed.colwise() += trf.translation; Vector6> distances; for(size_t i = 0;i < 6;i++){ distances(i) = (trfed.col(i) - anchors.col(i)).norm(); } std::pair, Matrix6> ret; for(size_t i = 0;i < 6;i++){ ret.first(i) = distances(i).value - distance; ret.second.array().row(i) = distances(i).deriv.template head<6>(); } return ret; } template std::pair, Matrix6> evaluate_with_jacobian_wrt_actuators(const transform>& trf, const hexagon& points, const hexagon& anchors){ hexagon trfed = trf.matrix() * points; trfed.colwise() += trf.translation; Vector6> distances; for(size_t i = 0;i < 6;i++){ distances(i) = (trfed.col(i) - anchors.col(i)).norm(); } std::pair, Matrix6> ret; for(size_t i = 0;i < 6;i++){ ret.first(i) = distances(i).value - distance; ret.second.array().row(i) = distances(i).deriv.template tail<6>(); } return ret; } template std::array convert_to_raylib(const Matrix& mat){ std::array ret; for(int i = 0;i < N;i++){ ret[i] = rlVector3(mat.col(i)[0], mat.col(i)[1], mat.col(i)[2]); } return ret; } rlVector3 erl(const auto& v){ rlVector3 ret(v(0), v(1), v(2)); return ret; } RenderTexture vp; float camangle = 0.0f; float campitch = 1.0f; bool camdragging = false; Vector6 actuator_angles = Vector6::Zero(); transform> current_transform; // Identity void gameLoop(){ using scalar = double; rlVector3 targ{0.5f,std::sqrt(3.0f) / 2.0f,0}; campitch = std::clamp(campitch, -1.2f, 1.2f); Camera3D cam{.position = rlVector3{std::sin(camangle) * 4.0f + targ.x,std::cos(camangle) * 4.0f + targ.y, std::tan(campitch)}, .target = targ, .up = {0,0,1},.fovy = 60.0f, .projection = CAMERA_PERSPECTIVE}; BeginDrawing(); if(!IsMouseButtonDown(MOUSE_LEFT_BUTTON)){ camdragging = false; } if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)){ if(GetMouseX() <= 1000){ //std::cout << "oof\n"; camdragging = true; } }else if(IsMouseButtonDown(MOUSE_BUTTON_LEFT) && camdragging){ camangle += GetMouseDelta().x * 0.01f; campitch += GetMouseDelta().y * 0.01f; } ClearBackground(Color(30,30,10,255)); BeginTextureMode(vp); ClearBackground(Color(30,30,10,255)); BeginMode3D(cam); auto unit_hexagon = generate_scronched_hexagon(jet_type(1.0)); auto anchors = generate_hexagon(jet_type(1.6), Vector3>(0, 0, -1.0)); std::array, Vector3>, 6> rotary_planes; double rad = 0.4; for(int i = 0;i < 6;i++){ Vector3 diag = anchors.col((i + 3) % 6).cast() - anchors.col(i).cast(); rotary_planes[i].first = Vector3(-diag.y(), diag.x(), 0).normalized(); rotary_planes[i].second = Vector3(0,0,1); for(int j = 0;j < 128;j++){ double angle = 2.0 * M_PI * double(j) / 127; double anglen = 2.0 * M_PI * double(j + 1) / 127; Vector3 from = anchors.col(i).cast() + rotary_planes[i].first * std::cos(angle) * rad + rotary_planes[i].second * std::sin(angle) * rad; Vector3 to = anchors.col(i).cast() + rotary_planes[i].first * std::cos(anglen) * rad + rotary_planes[i].second * std::sin(anglen) * rad; rlVector3 rlf = erl(from); rlVector3 rlt = erl(to); DrawLine3D(rlf, rlt, ORANGE); } } for(int i = 0;i < 6;i++){ jet_type actuator_angle_jet(actuator_angles[i]); actuator_angle_jet.deriv(i + 6) = 1; anchors.col(i) += (jet_type(rad) * cos(actuator_angle_jet) * rotary_planes[i].first .cast>()) + (jet_type(rad) * sin(actuator_angle_jet) * rotary_planes[i].second.cast>()); } //anchors.col(0)(2) += 0.1 * std::sin(5.0 * GetTime()); auto trf_backup = current_transform; current_transform = transform>(); for(int i = 0;i < 20;i++){ auto[v, J] = evaluate_with_jacobian(current_transform, unit_hexagon, anchors); FullPivHouseholderQR> qr(J); Vector6 subtract = qr.solve(v); current_transform.translation -= subtract.head<3>().cast>(); current_transform.euler_rotation_angles -= subtract.segment(3, 3).cast>(); //std::cout << v << "\n"; //std::cout << "Rank: " << J.fullPivHouseholderQr().rank() << "\n"; //std::cout << "Rank: " << J.fullPivHouseholderQr().solve(v) << "\n"; auto[vi, Ji] = evaluate_with_jacobian_wrt_actuators(current_transform, unit_hexagon, anchors); if(vi.norm() > 0.1){ current_transform = trf_backup; } } Matrix hexagon_vertices = current_transform.apply(unit_hexagon).cast(); std::array hvrl = convert_to_raylib(hexagon_vertices); std::array arl = convert_to_raylib(anchors); for(int i = 0;i < 6;i++){ DrawLine3D(hvrl[i], arl[i], WHITE); } DrawTriangle3D(hvrl[0], hvrl[2], hvrl[4], Color{255, 255, 0, 120}); EndMode3D(); EndTextureMode(); SetTextureFilter(vp.texture, TEXTURE_FILTER_BILINEAR); GenTextureMipmaps(&vp.texture); DrawTexturePro(vp.texture, Rectangle(0,0,vp.texture.width, -float(vp.texture.height)), Rectangle(0,0,1000,1000), ::Vector2(0,0), 0.0f, WHITE); DrawText(TextFormat("FPS: %d", GetFPS()), 0,0 , 40, GREEN); for(int i = 0;i < 6;i++){ GuiSlider(Rectangle(1100, i * 50 + 100, 200, 40), TextFormat("Actuator %d", i + 1), "", actuator_angles.data() + i, 0.0f, 4.0f * M_PI); } constexpr const char* names[] = {"X", "Y", "Z", "Roll", "Pitch", "Yaw"}; bool changed = false; auto aa_backup = actuator_angles; for(int i = 0;i < 3;i++){ //float x = 0; //std::cout << current_transform.translation(i).value << ", "; double pp = current_transform.translation(i).value; double pv = current_transform.euler_rotation_angles(i).value; GuiSlider(Rectangle(1100, i * 50 + 500, 200, 40), names[i + 0], "", ¤t_transform.translation(i).value, -1.0f, 1.0f); GuiSlider(Rectangle(1100, i * 50 + 700, 200, 40), names[i + 3], "", ¤t_transform.euler_rotation_angles(i).value, -0.5f, 0.5f); if(std::abs(current_transform.translation(i).value - pp) > 1e-5){ changed = true; } if(std::abs(current_transform.euler_rotation_angles(i).value - pv) > 1e-5){ changed = true; } } if (GuiButton(Rectangle(1100, 55, 200, 40), "Reset")){ changed = false; actuator_angles.fill(0); } if(changed){ //std::cout << "Changed\n"; for(int i = 0;i < 20;i++){ auto[v, J] = evaluate_with_jacobian_wrt_actuators(current_transform, unit_hexagon, anchors); FullPivHouseholderQR> qr(J); Vector6 subtract = qr.solve(v); actuator_angles -= subtract.cast(); anchors = generate_hexagon(jet_type(1.6), Vector3>(0, 0, -1.0)); for(int j = 0;j < 6;j++){ jet_type actuator_angle_jet(actuator_angles[j]); actuator_angle_jet.deriv(j + 6) = 1; anchors.col(j) += (jet_type(rad) * cos(actuator_angle_jet) * rotary_planes[j].first .cast>()) + (jet_type(rad) * sin(actuator_angle_jet) * rotary_planes[j].second.cast>()); } //std::cout << v << "\n"; if(i == 19){ auto[vi, Ji] = evaluate_with_jacobian_wrt_actuators(current_transform, unit_hexagon, anchors); if(vi.norm() > 0.1){ actuator_angles = aa_backup; } } //std::cout << anchors << "\n"; } for(int j = 0;j < 6;j++){ actuator_angles(j) = std::fmod(actuator_angles(j) + 2.0 * M_PI, 2.0 * M_PI); } } EndDrawing(); } int main(){ InitWindow(1500, 1000, "Simulator"); rlSetLineWidth(5.0f); vp = LoadRenderTexture(4000, 4000); GuiSetStyle(DEFAULT, TEXT_SIZE, 30); GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, ColorToInt(Color{100,255,100,255})); GuiSetStyle(DEFAULT, TEXT_SPACING, 4.0f); SetTargetFPS(60); rlDisableBackfaceCulling(); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(gameLoop, 0, 0); #else while(!WindowShouldClose()){ gameLoop(); } #endif //std::cout << current_guess.translation << "\n"; //std::cout << current_guess.euler_rotation_angles << "\n\n"; //for(int i = 0;i < 6;i++) // std::cout << (current_guess.apply(unit_hexagon).col(i).cast() - anchors.col(i).cast()).norm() << "\n"; //auto[v, J] = evaluate_with_jacobian(current_guess, unit_hexagon, anchors); }