add export

This commit is contained in:
numzero 2026-07-20 16:07:43 +03:00
parent cf72e340c7
commit d0155e00f7
10 changed files with 116 additions and 11 deletions

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.18)
project(hyperboloid VERSION 1.0.0 LANGUAGES CXX) project(hyperboloid VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(POLICY CMP0175) if(POLICY CMP0175)

5
Cargo.lock generated
View File

@ -640,6 +640,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"glam", "glam",
"hyperboloid", "hyperboloid",
"libc",
"pollster", "pollster",
"raw-window-handle", "raw-window-handle",
"wgpu", "wgpu",
@ -716,9 +717,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.180" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libloading" name = "libloading"

View File

@ -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<E: Error + 'static>( pub async fn init_gpu_inner<E: Error + 'static>(
make_surface: impl FnOnce(&wgpu::Instance) -> Result<wgpu::Surface<'static>, E>, make_surface: impl FnOnce(&wgpu::Instance) -> Result<wgpu::Surface<'static>, E>,
) -> Result<Gpu, Box<dyn Error>> { ) -> Result<Gpu, Box<dyn Error>> {

View File

@ -13,3 +13,4 @@ glam = { version = "0.30" }
pollster = "0.4.0" pollster = "0.4.0"
raw-window-handle = "0.6.2" raw-window-handle = "0.6.2"
wgpu = "27.0.1" wgpu = "27.0.1"
libc = "0.2.186"

View File

@ -1,13 +1,17 @@
#include "api.hxx" #include "api.hxx"
#include <memory>
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include <QByteArray>
namespace ffi { 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" 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_destroy(Core* viewport);
extern "C" void rt4_viewport_configure(Core* viewport, std::uint32_t width, std::uint32_t height); 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" void rt4_viewport_redraw(Core* viewport, const RedrawArgs* args);
extern "C" ByteArray rt4_make_obj(const ShapeArgs* args);
} // namespace ffi } // namespace ffi
BoxCore::BoxCore(BoxCore&& b) 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 { void MutCore::redraw(const RedrawArgs& args) const {
rt4_viewport_redraw(use(), &args); 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<void, free_deleter> ptr(arr.data);
return QByteArray(reinterpret_cast<const char*>(arr.data), arr.length);
}

View File

@ -1,9 +1,12 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint> #include <cstdint>
struct xcb_connection_t; struct xcb_connection_t;
class QByteArray;
struct alignas(16) Vec4 { struct alignas(16) Vec4 {
float x, y, z, w; float x, y, z, w;
}; };
@ -11,6 +14,11 @@ struct alignas(16) Vec4 {
namespace ffi { namespace ffi {
struct Core; struct Core;
struct ByteArray {
std::byte* data;
std::size_t length;
};
enum class Mode: std::uint32_t { enum class Mode: std::uint32_t {
Solid, Solid,
Shell, Shell,
@ -82,3 +90,5 @@ public:
private: private:
MutCore ptr; MutCore ptr;
}; };
QByteArray make_obj(const ShapeArgs& args);

View File

@ -1,9 +1,15 @@
use std::{ffi::c_void, num::NonZero, ptr::NonNull}; use std::{ffi::c_void, num::NonZero, ptr::NonNull};
use hyperboloid::{Core, RedrawArgs, init_gpu_inner};
use glam::{UVec2, uvec2}; use glam::{UVec2, uvec2};
use hyperboloid::{Core, RedrawArgs, ShapeArgs, init_gpu_inner, write_obj};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
#[repr(C)]
pub struct ByteArray {
data: *mut u8,
length: usize,
}
unsafe fn create_viewport( unsafe fn create_viewport(
display: impl Into<RawDisplayHandle>, display: impl Into<RawDisplayHandle>,
window: impl Into<RawWindowHandle>, window: impl Into<RawWindowHandle>,
@ -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) { unsafe extern "C" fn rt4_viewport_redraw(viewport: &mut Core, args: &RedrawArgs) {
viewport.redraw(args); 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(),
}
}

View File

@ -1,11 +1,14 @@
#include "main_window.hxx" #include "main_window.hxx"
#include <QFileDialog>
#include "ui_main_window.h" #include "ui_main_window.h"
Hyperboloid::Hyperboloid(QWidget* parent) Hyperboloid::Hyperboloid(QWidget* parent)
: QMainWindow(parent), : QMainWindow(parent),
m_ui(new Ui::MainWindow) { m_ui(new Ui::MainWindow) {
m_ui->setupUi(this); 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->modeSolid, (int)Mode::Solid);
m_ui->mode->setId(m_ui->modeShell, (int)Mode::Shell); m_ui->mode->setId(m_ui->modeShell, (int)Mode::Shell);
m_ui->mode->setId(m_ui->modeMesh, (int)Mode::Mesh); m_ui->mode->setId(m_ui->modeMesh, (int)Mode::Mesh);
@ -31,12 +34,8 @@ void Hyperboloid::updateModeUi() {
m_ui->lineWidth->setEnabled(has_line_width); m_ui->lineWidth->setEnabled(has_line_width);
} }
void Hyperboloid::updateView() { ffi::ShapeArgs Hyperboloid::makeShapeArgs() const {
const int max_layers = (m_ui->sides->value() - 1) / 2; return {
m_ui->layersUp->setMaximum(max_layers);
m_ui->layersDown->setMaximum(max_layers);
const auto color = m_ui->inBackground->color();
const ShapeArgs shape {
.mode = modeFromInt(m_ui->mode->checkedId()), .mode = modeFromInt(m_ui->mode->checkedId()),
.sides = (std::uint32_t)m_ui->sides->value(), .sides = (std::uint32_t)m_ui->sides->value(),
.layers_up = (std::uint32_t)m_ui->layersUp->value(), .layers_up = (std::uint32_t)m_ui->layersUp->value(),
@ -46,8 +45,15 @@ void Hyperboloid::updateView() {
.thickness = (float)m_ui->thickness->value(), .thickness = (float)m_ui->thickness->value(),
.line_width = (float)m_ui->lineWidth->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{ const RedrawArgs args{
.shape = shape, .shape = makeShapeArgs(),
.background = { color.redF(), color.greenF(), color.blueF(), 1.00 }, .background = { color.redF(), color.greenF(), color.blueF(), 1.00 },
.camera_yaw = qDegreesToRadians((float)m_ui->camYaw->value()), .camera_yaw = qDegreesToRadians((float)m_ui->camYaw->value()),
.camera_pitch = qDegreesToRadians((float)m_ui->camPitch->value()), .camera_pitch = qDegreesToRadians((float)m_ui->camPitch->value()),
@ -57,4 +63,10 @@ void Hyperboloid::updateView() {
m_ui->viewport->setView(args); 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" #include "moc_main_window.cpp"

View File

@ -7,6 +7,10 @@ namespace Ui {
class MainWindow; class MainWindow;
} }
namespace ffi {
struct ShapeArgs;
}
class Hyperboloid : public QMainWindow { class Hyperboloid : public QMainWindow {
Q_OBJECT Q_OBJECT
@ -17,7 +21,10 @@ public:
public slots: public slots:
void updateModeUi(); void updateModeUi();
void updateView(); void updateView();
void on_actionExport_triggered();
private: private:
const std::unique_ptr<Ui::MainWindow> m_ui; const std::unique_ptr<Ui::MainWindow> m_ui;
ffi::ShapeArgs makeShapeArgs() const;
}; };

View File

@ -375,6 +375,22 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="btnExport">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Export...</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -391,6 +407,20 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<action name="actionExport">
<property name="icon">
<iconset theme="QIcon::ThemeIcon::DocumentSaveAs"/>
</property>
<property name="text">
<string>&amp;Export...</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>