Compare commits
3 Commits
2f21d70658
...
02707dd095
| Author | SHA1 | Date | |
|---|---|---|---|
| 02707dd095 | |||
| 2bb28c240b | |||
| 586d78b3b8 |
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -6,6 +6,7 @@ version = 4
|
|||
name = "PROJECT-NAME"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"glam",
|
||||
"pollster",
|
||||
"wgpu",
|
||||
|
|
@ -177,9 +178,9 @@ checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.24.0"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
|
@ -526,6 +527,9 @@ name = "glam"
|
|||
version = "0.30.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ debug = false
|
|||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
glam = { version = "0.30.9" }
|
||||
bytemuck = "1.25.2"
|
||||
glam = { version = "0.30.9", features = ["bytemuck"] }
|
||||
pollster = "0.4.0"
|
||||
wgpu = "27.0.1"
|
||||
winit = "0.30.12"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ This example combines Qt6 GUI (C++) with WGPU rendering (Rust). It doesn’t do
|
|||
|
||||
Currently only X11 (XCB) is supported but it should be easy to extend to other platforms.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
This program is intended as a starting point rather than a dependency, so just copy it.
|
||||
|
|
|
|||
1
init.sh
1
init.sh
|
|
@ -56,6 +56,7 @@ git rm -r skel-test
|
|||
|
||||
# replace the README with a stub
|
||||
echo "# $name" >| README.md
|
||||
git rm screenshot.png
|
||||
|
||||
# escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /)
|
||||
htmlname=$(echo -n "$name" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s:[\\/&]:\\&:g')
|
||||
|
|
|
|||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
71
src/lib.rs
71
src/lib.rs
|
|
@ -1,22 +1,28 @@
|
|||
use std::error::Error;
|
||||
|
||||
use glam::{UVec2, Vec4, vec4};
|
||||
use glam::{Mat4, UVec2, Vec3, Vec4, vec3, vec4};
|
||||
|
||||
mod camera;
|
||||
|
||||
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth16Unorm;
|
||||
const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||
mod render;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct RedrawArgs {
|
||||
pub background: Vec4,
|
||||
pub cam_yaw: f32,
|
||||
pub cam_pitch: f32,
|
||||
pub cam_distance: f32,
|
||||
pub cam_fovy: f32,
|
||||
}
|
||||
|
||||
impl Default for RedrawArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
background: vec4(0.05, 0.20, 0.85, 1.00),
|
||||
cam_yaw: std::f32::consts::FRAC_PI_4,
|
||||
cam_pitch: std::f32::consts::FRAC_PI_6,
|
||||
cam_distance: 3.,
|
||||
cam_fovy: std::f32::consts::FRAC_PI_2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +38,39 @@ pub struct Core {
|
|||
queue: wgpu::Queue,
|
||||
surface: wgpu::Surface<'static>,
|
||||
|
||||
mesh_pipeline: render::mesh::Pipeline,
|
||||
mesh_color: render::mesh::GpuMeshIndexed,
|
||||
mesh_gray: render::mesh::GpuMeshIndexed,
|
||||
depth: wgpu::Texture,
|
||||
}
|
||||
|
||||
fn cube(a: f32, b: f32) -> render::mesh::CpuMeshIndexed {
|
||||
let gray_level = (b + a) / 2.;
|
||||
let gray = Vec3::splat(gray_level);
|
||||
let color_level = (b - a) / 2.;
|
||||
let mut mesh = render::mesh::CpuMeshIndexed::default();
|
||||
for (u, v, w) in [
|
||||
(Vec3::X, Vec3::Y, Vec3::Z),
|
||||
(Vec3::Y, Vec3::Z, Vec3::X),
|
||||
(Vec3::Z, Vec3::X, Vec3::Y),
|
||||
] {
|
||||
for m in [1., -1.] {
|
||||
let base = mesh.vertices.len() as u16;
|
||||
for offset in [m * v + u, -m * v + u, -m * v - u, m * v - u] {
|
||||
let vertex = render::mesh::Vertex {
|
||||
position: (m * w + offset),
|
||||
color: gray + color_level * m * w,
|
||||
normal: m * w,
|
||||
};
|
||||
mesh.vertices.push(vertex);
|
||||
}
|
||||
mesh.faces.push([base, base + 1, base + 2]);
|
||||
mesh.faces.push([base, base + 2, base + 3]);
|
||||
}
|
||||
}
|
||||
mesh
|
||||
}
|
||||
|
||||
impl Core {
|
||||
pub fn new(gpu: Gpu, pixel_size: UVec2) -> Self {
|
||||
let Gpu {
|
||||
|
|
@ -44,7 +80,9 @@ impl Core {
|
|||
} = gpu;
|
||||
|
||||
let depth = Self::create_depth_buffer(&device, pixel_size);
|
||||
queue.submit([]); // flush buffer updates
|
||||
let mesh_pipeline = render::mesh::Pipeline::new(&device);
|
||||
let mesh_color = render::mesh::GpuMeshIndexed::new(&device, &cube(0.2, 0.8));
|
||||
let mesh_gray = render::mesh::GpuMeshIndexed::new(&device, &cube(0.7, 0.7));
|
||||
|
||||
Self::configure_surface(&surface, &device, pixel_size);
|
||||
|
||||
|
|
@ -52,6 +90,9 @@ impl Core {
|
|||
device,
|
||||
queue,
|
||||
surface,
|
||||
mesh_pipeline,
|
||||
mesh_color,
|
||||
mesh_gray,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +104,20 @@ impl Core {
|
|||
let h = size.height as f32;
|
||||
w / h
|
||||
};
|
||||
let view = Mat4::from(camera::view(
|
||||
args.cam_yaw,
|
||||
args.cam_pitch,
|
||||
args.cam_distance,
|
||||
));
|
||||
let proj = camera::projection(args.cam_fovy, aspect);
|
||||
self.mesh_pipeline.set_params(
|
||||
&self.queue,
|
||||
render::mesh::Params {
|
||||
mvp: proj * view,
|
||||
light: vec3(0.7, 0.5, 1.0).normalize(),
|
||||
_zero: 0.,
|
||||
},
|
||||
);
|
||||
self.queue.submit([]); // flush buffer updates
|
||||
|
||||
let view = output.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
|
@ -97,7 +152,7 @@ impl Core {
|
|||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.mesh_gray.render(&self.mesh_pipeline, &mut pass);
|
||||
drop(pass);
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
|
@ -113,7 +168,7 @@ impl Core {
|
|||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: DEPTH_FORMAT,
|
||||
format: render::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
|
|
@ -124,7 +179,7 @@ impl Core {
|
|||
device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||
format: OUTPUT_FORMAT,
|
||||
format: render::OUTPUT_FORMAT,
|
||||
width: pixel_size.x,
|
||||
height: pixel_size.y,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
|
|
|
|||
209
src/render/mesh.rs
Normal file
209
src/render/mesh.rs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
use std::mem::offset_of;
|
||||
|
||||
use bytemuck::{Pod, Zeroable, bytes_of, cast_slice};
|
||||
use glam::{Mat4, Vec3};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
static SHADER: &str = include_str!("mesh.wgsl");
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Vertex {
|
||||
pub position: Vec3,
|
||||
pub color: Vec3,
|
||||
pub normal: Vec3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct Params {
|
||||
pub mvp: Mat4,
|
||||
pub light: Vec3,
|
||||
pub _zero: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CpuMesh {
|
||||
pub faces: Vec<[Vertex; 3]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CpuMeshIndexed {
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub faces: Vec<[u16; 3]>,
|
||||
}
|
||||
|
||||
pub struct GpuMesh {
|
||||
vertices: wgpu::Buffer,
|
||||
vertex_count: u32,
|
||||
}
|
||||
|
||||
pub struct GpuMeshIndexed {
|
||||
vertices: wgpu::Buffer,
|
||||
indices: wgpu::Buffer,
|
||||
index_count: u32,
|
||||
}
|
||||
|
||||
impl GpuMesh {
|
||||
pub fn new(device: &wgpu::Device, data: &CpuMesh) -> Self {
|
||||
let vertices = cast_slice::<_, Vertex>(&data.faces);
|
||||
let vertex_count = vertices.len().try_into().expect("too many faces");
|
||||
let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
contents: cast_slice(&vertices),
|
||||
});
|
||||
Self {
|
||||
vertices,
|
||||
vertex_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuMeshIndexed {
|
||||
pub fn new(device: &wgpu::Device, data: &CpuMeshIndexed) -> Self {
|
||||
let vertex_count = data.vertices.len();
|
||||
if vertex_count >= 1 << 16 {
|
||||
panic!("too many vertices");
|
||||
}
|
||||
let indices = cast_slice::<_, u16>(&data.faces);
|
||||
let index_count = indices.len().try_into().expect("too many faces");
|
||||
for index in indices {
|
||||
if *index as usize >= vertex_count {
|
||||
panic!("vertex index out of bounds: {index} out of {vertex_count}");
|
||||
}
|
||||
}
|
||||
let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
contents: cast_slice(&data.vertices),
|
||||
});
|
||||
let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
contents: cast_slice(&indices),
|
||||
});
|
||||
Self {
|
||||
vertices,
|
||||
indices,
|
||||
index_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pipeline {
|
||||
params: wgpu::Buffer,
|
||||
bindings: wgpu::BindGroup,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl Pipeline {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let params = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: size_of::<Params>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(SHADER.into()),
|
||||
});
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: None,
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: None,
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: size_of::<Vertex>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
shader_location: 0,
|
||||
offset: offset_of!(Vertex, position) as u64,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
shader_location: 1,
|
||||
offset: offset_of!(Vertex, color) as u64,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
shader_location: 2,
|
||||
offset: offset_of!(Vertex, normal) as u64,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: crate::render::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: None,
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: crate::render::OUTPUT_FORMAT,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &pipeline.get_bind_group_layout(0),
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: params.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
Self {
|
||||
params,
|
||||
bindings,
|
||||
pipeline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_params(&self, queue: &wgpu::Queue, params: Params) {
|
||||
queue.write_buffer(&self.params, 0, bytes_of(¶ms));
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuMesh {
|
||||
pub fn render(&self, pipeline: &Pipeline, pass: &mut wgpu::RenderPass) {
|
||||
pass.set_pipeline(&pipeline.pipeline);
|
||||
pass.set_bind_group(0, &pipeline.bindings, &[]);
|
||||
pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
pass.draw(0..self.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuMeshIndexed {
|
||||
pub fn render(&self, pipeline: &Pipeline, pass: &mut wgpu::RenderPass) {
|
||||
pass.set_pipeline(&pipeline.pipeline);
|
||||
pass.set_bind_group(0, &pipeline.bindings, &[]);
|
||||
pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||
pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint16);
|
||||
pass.draw_indexed(0..self.index_count, 0, 0..1);
|
||||
}
|
||||
}
|
||||
36
src/render/mesh.wgsl
Normal file
36
src/render/mesh.wgsl
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
struct Params {
|
||||
mvp: mat4x4f,
|
||||
light: vec3f,
|
||||
_zero: f32,
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
@location(0) position: vec3f,
|
||||
@location(1) color: vec3f,
|
||||
@location(2) normal: vec3f,
|
||||
}
|
||||
|
||||
struct Varying {
|
||||
@builtin(position) screen: vec4f,
|
||||
@location(0) color: vec4f,
|
||||
@location(1) normal: vec3f,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
|
||||
@vertex
|
||||
fn on_vertex(in: Vertex) -> Varying {
|
||||
let position = params.mvp * vec4f(in.position, 1.0);
|
||||
let color = vec4f(in.color, 1.0);
|
||||
let normal = in.normal;
|
||||
return Varying(position, color, normal);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn on_fragment(in: Varying) -> @location(0) vec4f {
|
||||
let normal = normalize(in.normal);
|
||||
let light = dot(params.light, normal);
|
||||
let u = 0.5 + 0.5 * light;
|
||||
let color = vec4f(u * in.color.xyz, in.color.w);
|
||||
return color;
|
||||
}
|
||||
4
src/render/mod.rs
Normal file
4
src/render/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod mesh;
|
||||
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus;
|
||||
pub const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||
|
|
@ -13,6 +13,10 @@ struct Core;
|
|||
|
||||
struct RedrawArgs {
|
||||
Vec4 background;
|
||||
float cam_yaw;
|
||||
float cam_pitch;
|
||||
float cam_distance;
|
||||
float cam_fovy;
|
||||
};
|
||||
} // namespace ffi
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ void PROJECTNAME::updateView() {
|
|||
const auto color = m_ui->inBackground->color();
|
||||
RedrawArgs args{
|
||||
.background = { color.redF(), color.greenF(), color.blueF(), 1.00 },
|
||||
.cam_yaw = qDegreesToRadians((float)m_ui->camYaw->value()),
|
||||
.cam_pitch = qDegreesToRadians((float)m_ui->camPitch->value()),
|
||||
.cam_distance = (float)m_ui->camDistance->value(),
|
||||
.cam_fovy = qDegreesToRadians((float)m_ui->camFoV->value()),
|
||||
};
|
||||
m_ui->viewport->setView(args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,122 @@
|
|||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Camera</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Yaw</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="camYaw">
|
||||
<property name="suffix">
|
||||
<string> °</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-180.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>180.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>45.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Pitch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="camPitch">
|
||||
<property name="suffix">
|
||||
<string> °</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-90.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>30.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Distance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="camDistance">
|
||||
<property name="maximum">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="stepType">
|
||||
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>4.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Field of view</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="camFoV">
|
||||
<property name="suffix">
|
||||
<string> °</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>15.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>150.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
|
|
@ -51,9 +167,9 @@
|
|||
<widget class="KColorCombo" name="inBackground">
|
||||
<property name="color">
|
||||
<color>
|
||||
<red>25</red>
|
||||
<green>220</green>
|
||||
<blue>0</blue>
|
||||
<red>0</red>
|
||||
<green>32</green>
|
||||
<blue>60</blue>
|
||||
</color>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
@ -106,6 +222,70 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>camYaw</sender>
|
||||
<signal>valueChanged(double)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>updateView()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>1507</x>
|
||||
<y>136</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>1475</x>
|
||||
<y>183</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>camPitch</sender>
|
||||
<signal>valueChanged(double)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>updateView()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>1507</x>
|
||||
<y>214</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>1479</x>
|
||||
<y>280</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>camDistance</sender>
|
||||
<signal>valueChanged(double)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>updateView()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>1536</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>1478</x>
|
||||
<y>345</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>camFoV</sender>
|
||||
<signal>valueChanged(double)</signal>
|
||||
<receiver>MainWindow</receiver>
|
||||
<slot>updateView()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>1541</x>
|
||||
<y>325</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>1476</x>
|
||||
<y>432</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>updateView()</slot>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user