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)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(POLICY CMP0175)

5
Cargo.lock generated
View File

@ -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"

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

View File

@ -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"

View File

@ -1,13 +1,17 @@
#include "api.hxx"
#include <memory>
#include <stdexcept>
#include <utility>
#include <QByteArray>
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<void, free_deleter> ptr(arr.data);
return QByteArray(reinterpret_cast<const char*>(arr.data), arr.length);
}

View File

@ -1,9 +1,12 @@
#pragma once
#include <cstddef>
#include <cstdint>
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);

View File

@ -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<RawDisplayHandle>,
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) {
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 <QFileDialog>
#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"

View File

@ -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<Ui::MainWindow> m_ui;
ffi::ShapeArgs makeShapeArgs() const;
};

View File

@ -375,6 +375,22 @@
</layout>
</widget>
</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>
<spacer name="verticalSpacer">
<property name="orientation">
@ -391,6 +407,20 @@
</layout>
</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>
<customwidgets>
<customwidget>