From 4ee791c356547c47e618fef4d14f9cd86dd1e11e Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 20 Oct 2026 20:23:22 +0300 Subject: [PATCH] show pre-cut gear, as a reference --- src/bevel_gear.rs | 29 ++++++++++++++++++++++++-- src/lib.rs | 52 +++++++++++++++++++++++++++++++++++++--------- src/render/mesh.rs | 28 +++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/bevel_gear.rs b/src/bevel_gear.rs index 78d4b0e..d4f8ac1 100644 --- a/src/bevel_gear.rs +++ b/src/bevel_gear.rs @@ -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 { let angle = 2.0 * PI / self.points.len() as f32; self.points diff --git a/src/lib.rs b/src/lib.rs index 7284762..32edd33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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())); } diff --git a/src/render/mesh.rs b/src/render/mesh.rs index 41bba08..9bb931f 100644 --- a/src/render/mesh.rs +++ b/src/render/mesh.rs @@ -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, })], }),