diff --git a/CMakeLists.txt b/CMakeLists.txt index 1360792..5ec059b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.18) project(hyperboloid VERSION 1.0.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(POLICY CMP0175) diff --git a/Cargo.lock b/Cargo.lock index 07cf9ee..96a81a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,6 +640,7 @@ version = "0.1.0" dependencies = [ "glam", "hyperboloid", + "libc", "pollster", "raw-window-handle", "wgpu", @@ -716,9 +717,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" diff --git a/src/lib.rs b/src/lib.rs index 389581e..e20fd95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,6 +229,19 @@ impl Core { } } +pub fn write_obj(mut target: impl std::io::Write, args: &ShapeArgs) -> std::io::Result<()> { + let mesh = mesh::make(args); + writeln!(target, "o Hyperboloid")?; + for v in mesh.vertices { + writeln!(target, "v {} {} {}", v.x, v.y, v.z)?; + } + for f in mesh.faces { + writeln!(target, "f {} {} {}", f[0] + 1, f[1] + 1, f[2] + 1)?; + } + target.flush()?; + Ok(()) +} + pub async fn init_gpu_inner( make_surface: impl FnOnce(&wgpu::Instance) -> Result, E>, ) -> Result> { diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 6cdd6b2..4cd5bd0 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -13,3 +13,4 @@ glam = { version = "0.30" } pollster = "0.4.0" raw-window-handle = "0.6.2" wgpu = "27.0.1" +libc = "0.2.186" diff --git a/ui/src/api.cxx b/ui/src/api.cxx index aaa2b64..7114667 100644 --- a/ui/src/api.cxx +++ b/ui/src/api.cxx @@ -1,13 +1,17 @@ #include "api.hxx" +#include #include #include +#include + namespace ffi { extern "C" Core* rt4_viewport_create(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height); extern "C" void rt4_viewport_destroy(Core* viewport); extern "C" void rt4_viewport_configure(Core* viewport, std::uint32_t width, std::uint32_t height); extern "C" void rt4_viewport_redraw(Core* viewport, const RedrawArgs* args); +extern "C" ByteArray rt4_make_obj(const ShapeArgs* args); } // namespace ffi BoxCore::BoxCore(BoxCore&& b) @@ -55,3 +59,11 @@ void MutCore::configure(std::uint32_t width, std::uint32_t height) const { void MutCore::redraw(const RedrawArgs& args) const { rt4_viewport_redraw(use(), &args); } + +using free_deleter = decltype([](void* ptr) { free(ptr); }); + +QByteArray make_obj(const ShapeArgs& args) { + ffi::ByteArray arr = rt4_make_obj(&args); + std::unique_ptr ptr(arr.data); + return QByteArray(reinterpret_cast(arr.data), arr.length); +} diff --git a/ui/src/api.hxx b/ui/src/api.hxx index 1a1162c..a0f62ea 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -1,9 +1,12 @@ #pragma once +#include #include struct xcb_connection_t; +class QByteArray; + struct alignas(16) Vec4 { float x, y, z, w; }; @@ -11,6 +14,11 @@ struct alignas(16) Vec4 { namespace ffi { struct Core; +struct ByteArray { + std::byte* data; + std::size_t length; +}; + enum class Mode: std::uint32_t { Solid, Shell, @@ -82,3 +90,5 @@ public: private: MutCore ptr; }; + +QByteArray make_obj(const ShapeArgs& args); diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 251f3a7..2fb43dc 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,9 +1,15 @@ use std::{ffi::c_void, num::NonZero, ptr::NonNull}; -use hyperboloid::{Core, RedrawArgs, init_gpu_inner}; use glam::{UVec2, uvec2}; +use hyperboloid::{Core, RedrawArgs, ShapeArgs, init_gpu_inner, write_obj}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; +#[repr(C)] +pub struct ByteArray { + data: *mut u8, + length: usize, +} + unsafe fn create_viewport( display: impl Into, window: impl Into, @@ -46,3 +52,16 @@ unsafe extern "C" fn rt4_viewport_configure(viewport: &mut Core, width: u32, hei unsafe extern "C" fn rt4_viewport_redraw(viewport: &mut Core, args: &RedrawArgs) { viewport.redraw(args); } + +#[unsafe(no_mangle)] +unsafe extern "C" fn rt4_make_obj(args: &ShapeArgs) -> ByteArray { + let mut buf = Vec::new(); + write_obj(&mut buf, args).expect("writing to a Vec is infallible"); + let data = unsafe { libc::malloc(buf.len()) }; + assert!(!data.is_null()); + unsafe { libc::memcpy(data, buf.as_ptr() as *const c_void, buf.len()) }; + ByteArray { + data: data as *mut u8, + length: buf.len(), + } +} diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 2325c55..b951acc 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -1,11 +1,14 @@ #include "main_window.hxx" +#include + #include "ui_main_window.h" Hyperboloid::Hyperboloid(QWidget* parent) : QMainWindow(parent), m_ui(new Ui::MainWindow) { m_ui->setupUi(this); + m_ui->btnExport->setDefaultAction(m_ui->actionExport); m_ui->mode->setId(m_ui->modeSolid, (int)Mode::Solid); m_ui->mode->setId(m_ui->modeShell, (int)Mode::Shell); m_ui->mode->setId(m_ui->modeMesh, (int)Mode::Mesh); @@ -31,12 +34,8 @@ void Hyperboloid::updateModeUi() { m_ui->lineWidth->setEnabled(has_line_width); } -void Hyperboloid::updateView() { - const int max_layers = (m_ui->sides->value() - 1) / 2; - m_ui->layersUp->setMaximum(max_layers); - m_ui->layersDown->setMaximum(max_layers); - const auto color = m_ui->inBackground->color(); - const ShapeArgs shape { +ffi::ShapeArgs Hyperboloid::makeShapeArgs() const { + return { .mode = modeFromInt(m_ui->mode->checkedId()), .sides = (std::uint32_t)m_ui->sides->value(), .layers_up = (std::uint32_t)m_ui->layersUp->value(), @@ -46,8 +45,15 @@ void Hyperboloid::updateView() { .thickness = (float)m_ui->thickness->value(), .line_width = (float)m_ui->lineWidth->value(), }; +} + +void Hyperboloid::updateView() { + const int max_layers = (m_ui->sides->value() - 1) / 2; + m_ui->layersUp->setMaximum(max_layers); + m_ui->layersDown->setMaximum(max_layers); + const auto color = m_ui->inBackground->color(); const RedrawArgs args{ - .shape = shape, + .shape = makeShapeArgs(), .background = { color.redF(), color.greenF(), color.blueF(), 1.00 }, .camera_yaw = qDegreesToRadians((float)m_ui->camYaw->value()), .camera_pitch = qDegreesToRadians((float)m_ui->camPitch->value()), @@ -57,4 +63,10 @@ void Hyperboloid::updateView() { m_ui->viewport->setView(args); } +void Hyperboloid::on_actionExport_triggered() { + const auto shape = makeShapeArgs(); + const auto obj = make_obj(shape); + QFileDialog::saveFileContent(obj, "hyperboloid.obj", this); +} + #include "moc_main_window.cpp" diff --git a/ui/src/main_window.hxx b/ui/src/main_window.hxx index 342f9c4..3e18a9e 100644 --- a/ui/src/main_window.hxx +++ b/ui/src/main_window.hxx @@ -7,6 +7,10 @@ namespace Ui { class MainWindow; } +namespace ffi { +struct ShapeArgs; +} + class Hyperboloid : public QMainWindow { Q_OBJECT @@ -17,7 +21,10 @@ public: public slots: void updateModeUi(); void updateView(); + void on_actionExport_triggered(); private: const std::unique_ptr m_ui; + + ffi::ShapeArgs makeShapeArgs() const; }; diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index ddac555..a9b65b6 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -375,6 +375,22 @@ + + + + + 0 + 0 + + + + &Export... + + + Qt::ToolButtonStyle::ToolButtonTextBesideIcon + + + @@ -391,6 +407,20 @@ + + + + + + &Export... + + + Ctrl+E + + + QAction::MenuRole::NoRole + +