show pre-cut gear, as a reference

This commit is contained in:
numzero 2026-10-20 20:23:22 +03:00
parent f3169f6215
commit 4ee791c356
3 changed files with 95 additions and 14 deletions

View File

@ -1,6 +1,6 @@
use std::f32::consts::PI;
use glam::{Affine3A, Vec2, Vec3, Vec3Swizzles, vec3};
use glam::{Affine3A, Vec2, Vec3, Vec3Swizzles, vec2, vec3};
use crate::{crown_gear::CrownGearDesc, profile::Profile, render::mesh::MeshIndexed};
@ -30,6 +30,15 @@ impl BevelGearDesc {
pub fn pitch_distance(&self) -> f32 {
self.pitch_radius() / self.cone_halfangle.tan()
}
/// Projects a point, expressed as a distance from the pitch circle (perpendicular to the pitch cone), onto the pitch plane.
/// Returns the distance from the pitch circle center to the projection.
fn project(&self, add: f32) -> f32 {
let dir = Vec2::from_angle(self.cone_halfangle);
let pt = vec2(self.pitch_radius(), 0.) + dir * add;
let r = pt.x / (1. - pt.y / self.pitch_distance());
r
}
}
#[derive(Debug, Clone)]
@ -40,13 +49,29 @@ pub struct BevelGearShape {
impl BevelGearShape {
pub fn new_blank(desc: &BevelGearDesc, n_points: usize) -> Self {
let r = 0.5 * desc.teeth as f32 + desc.profile.addendum + 0.1;
let r = desc.project(desc.profile.addendum + 0.1);
BevelGearShape {
desc: *desc,
points: vec![r; n_points],
}
}
pub fn new_cut(desc: &BevelGearDesc, n_points: usize) -> Self {
let cutter = desc.cutter();
let points = (0..n_points)
.map(|k| {
let t = k as f32 / n_points as f32;
let tooth = t * desc.teeth as f32;
let cut_depth = cutter.profile.y_from_tooth(-tooth);
desc.project(-cut_depth)
})
.collect();
BevelGearShape {
desc: *desc,
points,
}
}
pub fn to_points(&self) -> impl Iterator<Item = Vec2> {
let angle = 2.0 * PI / self.points.len() as f32;
self.points

View File

@ -64,10 +64,13 @@ pub struct Core {
depth: wgpu::Texture,
mesh_pipeline: render::mesh::Pipeline,
mesh_pipeline_translucent: render::mesh::Pipeline,
mesh_gear_precut: render::mesh::GpuMeshIndexed,
mesh_cutter: render::mesh::GpuMeshIndexed,
params_gear: render::mesh::GpuParams,
params_gear_precut: render::mesh::GpuParams,
params_cutter: render::mesh::GpuParams,
bevel_gear_desc: bevel_gear::BevelGearDesc,
@ -111,6 +114,7 @@ impl Core {
let depth = Self::create_depth_buffer(&device, pixel_size);
let mesh_pipeline = render::mesh::Pipeline::new(&device);
let mesh_pipeline_translucent = render::mesh::Pipeline::new_translucent(&device);
let bevel_gear_desc = bevel_gear::BevelGearDesc {
profile: Default::default(),
@ -119,6 +123,18 @@ impl Core {
};
let bevel_gear_shape =
bevel_gear::BevelGearShape::new_blank(&bevel_gear_desc, 16 * args.tooth as usize);
let bevel_gear_mesh = bevel_gear::make_mesh(
&bevel_gear_desc,
&bevel_gear::BevelGearVisual {
pad_in: 4.,
pad_out: 0.,
},
&bevel_gear::BevelGearShape::new_cut(&bevel_gear_desc, 16 * args.tooth as usize),
)
.to_mesh()
.to_standard_mesh()
.into_indexed();
let mesh_gear_precut = render::mesh::GpuMeshIndexed::new(&device, &bevel_gear_mesh);
let crown_gear_desc = bevel_gear_desc.cutter();
let crown_gear_mesh = crown_gear::make_mesh_simple(
@ -138,6 +154,7 @@ impl Core {
let mesh_cutter = render::mesh::GpuMeshIndexed::new(&device, &crown_gear_mesh);
let params_gear = GpuParams::new(&device, &mesh_pipeline);
let params_gear_precut = GpuParams::new(&device, &mesh_pipeline_translucent);
let params_cutter = GpuParams::new(&device, &mesh_pipeline);
Self::configure_surface(&surface, &device, pixel_size);
@ -148,8 +165,11 @@ impl Core {
surface,
depth,
mesh_pipeline,
mesh_pipeline_translucent,
mesh_cutter,
mesh_gear_precut,
params_gear,
params_gear_precut,
params_cutter,
bevel_gear_desc,
bevel_gear_shape,
@ -159,13 +179,13 @@ impl Core {
}
fn gear_tfm(&self) -> Affine3A {
let turns = -self.t / self.bevel_gear_desc.teeth as f32;
let turns = 0.5 - self.t / self.bevel_gear_desc.teeth as f32;
Affine3A::from_rotation_translation(Quat::from_rotation_z(2. * PI * turns), Vec3::ZERO)
}
fn cutter_tfm(&self) -> Affine3A {
let skip_teeth = self.crown_gear_desc.pitch_radius.floor();
let turns = (self.t.fract_gl() + skip_teeth) / self.crown_gear_desc.teeth();
let turns = (self.t.fract_gl() - skip_teeth) / self.crown_gear_desc.teeth();
Affine3A::from_rotation_translation(
Quat::from_rotation_y(FRAC_PI_2 + self.bevel_gear_desc.cone_halfangle)
* Quat::from_rotation_z(2. * PI * turns),
@ -192,14 +212,14 @@ impl Core {
let view = Mat4::from_mat3(camera::rotation(-args.cam_yaw, -args.cam_pitch));
let light = vec3(0.7, 0.5, 1.0).normalize();
self.mesh_pipeline.set_params(
&self.queue,
render::mesh::SceneParams {
projection: proj * view,
light,
_zero: 0.,
},
);
let scene_params = render::mesh::SceneParams {
projection: proj * view,
light,
_zero: 0.,
};
self.mesh_pipeline.set_params(&self.queue, scene_params);
self.mesh_pipeline_translucent
.set_params(&self.queue, scene_params);
self.params_gear.set(
&self.queue,
@ -208,6 +228,13 @@ impl Core {
color: srgba_to_linear(vec4(0.2, 0.8, 0.1, 1.0)),
},
);
self.params_gear_precut.set(
&self.queue,
render::mesh::ObjectParams {
transform: self.gear_tfm().into(),
color: 0.3 * srgba_to_linear(vec4(0.1, 0.2, 0.8, 1.0)),
},
);
self.params_cutter.set(
&self.queue,
render::mesh::ObjectParams {
@ -259,6 +286,11 @@ impl Core {
self.mesh_cutter
.render(&mut pass, &self.mesh_pipeline, &self.params_cutter);
mesh_gear.render(&mut pass, &self.mesh_pipeline, &self.params_gear);
self.mesh_gear_precut.render(
&mut pass,
&self.mesh_pipeline_translucent,
&self.params_gear_precut,
);
drop(pass);
self.queue.submit(std::iter::once(encoder.finish()));
}

View File

@ -103,6 +103,30 @@ impl GpuMeshIndexed {
impl Pipeline {
pub fn new(device: &wgpu::Device) -> Self {
Self::new_any(
device,
wgpu::BlendState::REPLACE,
wgpu::DepthBiasState::default(),
)
}
pub fn new_translucent(device: &wgpu::Device) -> Self {
Self::new_any(
device,
wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING,
wgpu::DepthBiasState {
constant: -2,
slope_scale: -1. / 32.,
clamp: 0.,
},
)
}
fn new_any(
device: &wgpu::Device,
blend: wgpu::BlendState,
depth_bias: wgpu::DepthBiasState,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(SHADER.into()),
@ -141,7 +165,7 @@ impl Pipeline {
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
bias: depth_bias,
}),
multisample: wgpu::MultisampleState {
count: 1,
@ -154,7 +178,7 @@ impl Pipeline {
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: crate::render::OUTPUT_FORMAT,
blend: Some(wgpu::BlendState::REPLACE),
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
}),