diff --git a/resources/characters/characters.yaml b/resources/characters/characters.yaml index 0db48347..4b476c5a 100644 --- a/resources/characters/characters.yaml +++ b/resources/characters/characters.yaml @@ -14,6 +14,10 @@ pants: style: Regular color: Blue + shirt: + sleeve_style: Long + neckline: Crew + color: White head: ears: type: Normal @@ -164,6 +168,10 @@ pants: style: Balloon color: Red + shirt: + sleeve_style: Long + neckline: DeepV + color: Red head: ears: type: Normal @@ -221,7 +229,11 @@ clothing: type: Simple pants: - style: Regular + style: Shorts + color: Purple + shirt: + sleeve_style: None + neckline: Crew color: Purple head: ears: @@ -270,6 +282,10 @@ pants: style: Regular color: Gray + shirt: + sleeve_style: Short + neckline: Crew + color: Gray head: ears: type: Normal diff --git a/resources/templates/appearance_edit.html.tera b/resources/templates/appearance_edit.html.tera index 00f7aef6..94118e13 100644 --- a/resources/templates/appearance_edit.html.tera +++ b/resources/templates/appearance_edit.html.tera @@ -453,6 +453,20 @@ +
  • + Shirt + +
  • {% endif %} diff --git a/resources/templates/character.html.tera b/resources/templates/character.html.tera index 4f5888e1..f9814ca8 100644 --- a/resources/templates/character.html.tera +++ b/resources/templates/character.html.tera @@ -454,6 +454,20 @@ +
  • + Shirt + +
  • {% endif %} diff --git a/rpg_tools_core/src/model/equipment/appearance/mod.rs b/rpg_tools_core/src/model/equipment/appearance/mod.rs index 03c3a92f..c398d23d 100644 --- a/rpg_tools_core/src/model/equipment/appearance/mod.rs +++ b/rpg_tools_core/src/model/equipment/appearance/mod.rs @@ -1,8 +1,10 @@ use crate::model::equipment::appearance::pants::Pants; +use crate::model::equipment::appearance::shirt::Shirt; use macro_ui::ui; use serde::{Deserialize, Serialize}; pub mod pants; +pub mod shirt; #[derive(ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] @@ -11,5 +13,6 @@ pub enum Clothing { None, Simple { pants: Pants, + shirt: Shirt, }, } diff --git a/rpg_tools_core/src/model/equipment/appearance/pants.rs b/rpg_tools_core/src/model/equipment/appearance/pants.rs index 599776b3..6c1d3ed2 100644 --- a/rpg_tools_core/src/model/equipment/appearance/pants.rs +++ b/rpg_tools_core/src/model/equipment/appearance/pants.rs @@ -3,14 +3,14 @@ use macro_convert::Convert; use macro_ui::ui; use serde::{Deserialize, Serialize}; -/// Clothing for the lower body. +/// The pants of the [`character`](crate::model::character::Character). #[derive(ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Pants { pub style: PantsStyle, pub color: Color, } -/// What style of pants? +/// What style of [`pants`](Pants)? #[derive(Convert, ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PantsStyle { Balloon, diff --git a/rpg_tools_core/src/model/equipment/appearance/shirt.rs b/rpg_tools_core/src/model/equipment/appearance/shirt.rs new file mode 100644 index 00000000..b0a6c2d7 --- /dev/null +++ b/rpg_tools_core/src/model/equipment/appearance/shirt.rs @@ -0,0 +1,33 @@ +use crate::model::color::Color; +use macro_convert::Convert; +use macro_ui::ui; +use serde::{Deserialize, Serialize}; + +/// The shirt of the [`character`](crate::model::character::Character). +#[derive(ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Shirt { + pub sleeve_style: SleeveStyle, + pub neckline: Neckline, + pub color: Color, +} + +/// What style of neckline? +#[derive(Convert, ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum Neckline { + Boat, + Crew, + DeepV, + #[default] + None, + Scoop, + V, +} + +/// What style of sleeves? +#[derive(Convert, ui, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SleeveStyle { + #[default] + Long, + None, + Short, +} diff --git a/rpg_tools_rendering/examples/pants.rs b/rpg_tools_rendering/examples/pants.rs index a4756a2a..9b19d7bd 100644 --- a/rpg_tools_rendering/examples/pants.rs +++ b/rpg_tools_rendering/examples/pants.rs @@ -3,13 +3,11 @@ extern crate rpg_tools_rendering; use crate::utils::render::render_2_sets; use rpg_tools_core::model::character::appearance::body::{Body, BodyShape}; -use rpg_tools_core::model::character::appearance::head::Head; use rpg_tools_core::model::character::appearance::Appearance; use rpg_tools_core::model::color::Color; use rpg_tools_core::model::equipment::appearance::pants::{Pants, PantsStyle}; use rpg_tools_core::model::equipment::appearance::Clothing; use rpg_tools_core::model::length::Length; -use rpg_tools_core::model::width::Width; pub mod utils; @@ -40,11 +38,14 @@ fn create_appearance(height: Length, pants: &Pants, shape: &BodyShape) -> Appear Appearance::humanoid( Body { shape: *shape, - width: Width::default(), + width: Default::default(), skin: Default::default(), - clothing: Clothing::Simple { pants: *pants }, + clothing: Clothing::Simple { + pants: *pants, + shirt: Default::default(), + }, }, - Head::default(), + Default::default(), height, ) } diff --git a/rpg_tools_rendering/examples/shirts.rs b/rpg_tools_rendering/examples/shirts.rs new file mode 100644 index 00000000..200ed3ea --- /dev/null +++ b/rpg_tools_rendering/examples/shirts.rs @@ -0,0 +1,50 @@ +extern crate rpg_tools_core; +extern crate rpg_tools_rendering; + +use crate::utils::render::render_2_sets; +use rpg_tools_core::model::character::appearance::body::{Body, BodyShape}; +use rpg_tools_core::model::character::appearance::Appearance; +use rpg_tools_core::model::color::Color; +use rpg_tools_core::model::equipment::appearance::shirt::{Neckline, Shirt, SleeveStyle}; +use rpg_tools_core::model::equipment::appearance::Clothing; +use rpg_tools_core::model::length::Length; + +pub mod utils; + +fn main() { + let mut shirts = vec![]; + + for neckline in Neckline::get_all() { + for sleeve_style in SleeveStyle::get_all() { + shirts.push(Shirt { + sleeve_style, + neckline, + color: Color::Aqua, + }) + } + } + + render_2_sets( + "shirts.svg", + shirts, + BodyShape::get_all(), + create_appearance, + false, + ); +} + +fn create_appearance(height: Length, shirt: &Shirt, shape: &BodyShape) -> Appearance { + Appearance::humanoid( + Body { + shape: *shape, + width: Default::default(), + skin: Default::default(), + clothing: Clothing::Simple { + pants: Default::default(), + shirt: *shirt, + }, + }, + Default::default(), + height, + ) +} diff --git a/rpg_tools_rendering/src/rendering/body/mod.rs b/rpg_tools_rendering/src/rendering/body/mod.rs index 243e6ee1..8a2698b1 100644 --- a/rpg_tools_rendering/src/rendering/body/mod.rs +++ b/rpg_tools_rendering/src/rendering/body/mod.rs @@ -1,11 +1,12 @@ use crate::math::aabb2d::{get_end_x, get_start_x, AABB}; use crate::math::orientation::Orientation; use crate::math::point2d::Point2d; -use crate::math::polygon2d::Polygon2d; +use crate::math::polygon2d::builder::Polygon2dBuilder; use crate::math::size2d::Size2d; use crate::renderer::{RenderOptions, Renderer}; use crate::rendering::body::torso::render_torso; use crate::rendering::config::RenderConfig; +use crate::rendering::equipment::pants::interpolate; use rpg_tools_core::model::character::appearance::body::Body; use std::ops::Mul; @@ -15,8 +16,7 @@ pub fn render_body(renderer: &mut dyn Renderer, config: &RenderConfig, aabb: &AA let options = config.get_skin_options(&body.skin); render_legs(renderer, config, aabb, body, &options); - render_arms(renderer, config, &aabb, body, &options); - render_hands(renderer, config, aabb, body, &options); + render_arms(renderer, config, aabb, body, &options); render_torso(renderer, config, aabb, body, &options); } @@ -50,67 +50,65 @@ fn render_legs( renderer.render_circle_arc(&right_foot_start, foot_radius, offset, angle, options); } -fn render_hands( - renderer: &mut dyn Renderer, - config: &RenderConfig, - aabb: &AABB, - body: &Body, - options: &RenderOptions, -) { +pub fn render_hands(renderer: &mut dyn Renderer, config: &RenderConfig, aabb: &AABB, body: &Body) { + let options = config.get_skin_options(&body.skin); let hand_radius = config.body.get_hand_radius(body, aabb); - let distance_between_hands = config.body.get_shoulder_width(body) - + config.body.get_arm_width(body) - + config.body.get_fat_offset_factor(body); + let distance_between_hands = + config.body.get_distance_between_hands(body) + config.body.get_hand_radius_factor(body); let hand_y = config.body.get_arm_y() + config.body.height_arm; let (left_hand_center, right_hand_center) = aabb.get_mirrored_points(distance_between_hands, hand_y); - renderer.render_circle(&left_hand_center, hand_radius, options); - renderer.render_circle(&right_hand_center, hand_radius, options); + renderer.render_circle(&left_hand_center, hand_radius, &options); + renderer.render_circle(&right_hand_center, hand_radius, &options); } fn render_arms( renderer: &mut dyn Renderer, config: &RenderConfig, - aabb: &&AABB, + aabb: &AABB, body: &Body, options: &RenderOptions, ) { - let arm_size = aabb - .size() - .scale(config.body.get_arm_width(body), config.body.height_arm); - let arm_start_x = get_start_x(config.body.get_shoulder_width(body)); - let right_arm_start = aabb.get_point( - arm_start_x - config.body.get_arm_width(body), - config.body.get_arm_y(), - ); - let fat_offset = aabb.convert_to_height(config.body.get_fat_offset_factor(body) / 2.0); - let polygon = create_arm(arm_size, right_arm_start, fat_offset as i32); + let polygon = get_left_arm(config, aabb, body).build(); renderer.render_rounded_polygon(&polygon, options); renderer.render_rounded_polygon(&aabb.mirrored(&polygon), options); } -fn create_arm(arm_size: Size2d, right_arm_start: Point2d, offset: i32) -> Polygon2d { - let right_arm = AABB::new(right_arm_start, arm_size); - let mut polygon: Polygon2d = (&right_arm).into(); - - if offset != 0 { - let corners = polygon.corners_mut(); - - update_corner(corners, 2, offset); - update_corner(corners, 3, offset); - } +pub fn get_left_arm(config: &RenderConfig, aabb: &AABB, body: &Body) -> Polygon2dBuilder { + let mut builder = get_left_arm_short(config, aabb, body, false); + let width = config.body.get_arm_width(body); + let bottom_x = get_end_x(config.body.get_distance_between_hands(body)); + let y = config.body.get_arm_y() + config.body.height_arm; - polygon.create_sharp_corner(1); + builder.add_point(aabb.get_point(bottom_x, y), false); + builder.add_point_cw(aabb.get_point(bottom_x + width, y), false); - polygon + builder } -fn update_corner(corners: &mut [Point2d], index: usize, offset: i32) { - if let Some(p) = corners.get_mut(index) { - p.x -= offset; - } +pub fn get_left_arm_short( + config: &RenderConfig, + aabb: &AABB, + body: &Body, + bottom_sharp: bool, +) -> Polygon2dBuilder { + let mut builder = Polygon2dBuilder::new(); + let width = config.body.get_arm_width(body); + let top_x = get_end_x(config.body.get_shoulder_width(body) * 0.94); + let bottom_x = get_end_x(config.body.get_distance_between_hands(body)); + let bottom_x = interpolate(top_x, bottom_x, 0.7); + let y = config.body.get_arm_y(); + let mid_y = y + 0.2; + + builder.add_point(aabb.get_point(top_x, y), true); + builder.add_point_cw(aabb.get_point(top_x + width, y), false); + builder.add_point(aabb.get_point(top_x, y + 0.1), true); + builder.add_point(aabb.get_point(bottom_x, mid_y), bottom_sharp); + builder.add_point_cw(aabb.get_point(bottom_x + width, mid_y), bottom_sharp); + + builder } fn render_leg(renderer: &mut dyn Renderer, options: &RenderOptions, start: Point2d, size: Size2d) { diff --git a/rpg_tools_rendering/src/rendering/body/torso.rs b/rpg_tools_rendering/src/rendering/body/torso.rs index 0b7ac964..f7f9fdb2 100644 --- a/rpg_tools_rendering/src/rendering/body/torso.rs +++ b/rpg_tools_rendering/src/rendering/body/torso.rs @@ -1,5 +1,5 @@ use crate::math::aabb2d::AABB; -use crate::math::polygon2d::Polygon2d; +use crate::math::polygon2d::builder::Polygon2dBuilder; use crate::renderer::{RenderOptions, Renderer}; use crate::rendering::config::body::torso::TorsoConfig; use crate::rendering::config::body::BodyConfig; @@ -18,27 +18,19 @@ pub fn render_torso( &torso_aabb, &config.body, config.body.get_torso_config(body.shape), - ); + ) + .build(); renderer.render_rounded_polygon(&polygon, options); } -fn create_torso(aabb: &AABB, config: &BodyConfig, torso: &TorsoConfig) -> Polygon2d { - let (top_left, top_right) = aabb.get_mirrored_points(torso.shoulder_width, 0.0); - let (upper_left, upper_right) = aabb.get_mirrored_points(torso.shoulder_width, config.y_upper); - let (waist_left, waist_right) = aabb.get_mirrored_points(torso.waist_width, config.y_waist); - let (lower_left, lower_right) = aabb.get_mirrored_points(torso.hip_width, config.y_lower); - let (bottom_left, bottom_right) = aabb.get_mirrored_points(torso.hip_width, 1.0); +pub fn create_torso(aabb: &AABB, config: &BodyConfig, torso: &TorsoConfig) -> Polygon2dBuilder { + let mut builder = Polygon2dBuilder::new(); - Polygon2d::new(vec![ - top_left, - upper_left, - waist_left, - lower_left, - bottom_left, - bottom_right, - lower_right, - waist_right, - upper_right, - top_right, - ]) + builder.add_mirrored_points(aabb, torso.hip_width, 1.0, false); + builder.add_mirrored_points(aabb, torso.hip_width, config.y_lower, false); + builder.add_mirrored_points(aabb, torso.waist_width, config.y_waist, false); + builder.add_mirrored_points(aabb, torso.shoulder_width, config.y_upper, false); + builder.add_mirrored_points(aabb, torso.shoulder_width, 0.0, false); + + builder } diff --git a/rpg_tools_rendering/src/rendering/character.rs b/rpg_tools_rendering/src/rendering/character.rs index 5bd885f3..e52f09b9 100644 --- a/rpg_tools_rendering/src/rendering/character.rs +++ b/rpg_tools_rendering/src/rendering/character.rs @@ -1,7 +1,7 @@ use crate::math::aabb2d::AABB; use crate::math::size2d::Size2d; use crate::renderer::Renderer; -use crate::rendering::body::{calculate_head_aabb, render_body}; +use crate::rendering::body::{calculate_head_aabb, render_body, render_hands}; use crate::rendering::config::RenderConfig; use crate::rendering::ear::render_ears; use crate::rendering::equipment::render_clothing; @@ -39,7 +39,8 @@ pub fn render_character_from_front( let head_aabb = calculate_head_aabb(config, &inner); render_head_behind_body_from_front(renderer, config, head, &head_aabb); render_body(renderer, config, &inner, body); - render_clothing(renderer, config, &inner, body); + render_clothing(renderer, config, &inner, body, true); + render_hands(renderer, config, &inner, body); render_head_before_body_from_front(renderer, config, head, &head_aabb); } } @@ -59,7 +60,8 @@ pub fn render_character_from_back( } Appearance::Humanoid { body, head, .. } => { render_body(renderer, config, &inner, &body); - render_clothing(renderer, config, &inner, &body); + render_clothing(renderer, config, &inner, &body, false); + render_hands(renderer, config, &inner, &body); let head_aabb = calculate_head_aabb(config, &inner); render_head_from_back(renderer, config, &head, &head_aabb); } diff --git a/rpg_tools_rendering/src/rendering/config/body/mod.rs b/rpg_tools_rendering/src/rendering/config/body/mod.rs index 8f8ba2b7..3779a0de 100644 --- a/rpg_tools_rendering/src/rendering/config/body/mod.rs +++ b/rpg_tools_rendering/src/rendering/config/body/mod.rs @@ -84,23 +84,23 @@ impl BodyConfig { } pub fn get_arm_y(&self) -> f32 { - self.y_torso + 0.05 + self.y_torso + 0.02 } pub fn get_leg_y(&self) -> f32 { self.get_torso_bottom() - 0.05 } - pub fn get_fat_offset_factor(&self, body: &Body) -> f32 { - if body.shape == BodyShape::Fat { - self.get_hip_width(body) - self.get_shoulder_width(body) - } else { - 0.0 - } + pub fn get_distance_between_hands(&self, body: &Body) -> f32 { + self.get_torso_width(body) + 0.08 } pub fn get_hand_radius(&self, body: &Body, aabb: &AABB) -> u32 { - aabb.convert_to_height(self.hand_factor * self.get_width_factor(body)) + aabb.convert_to_height(self.get_hand_radius_factor(body)) + } + + pub fn get_hand_radius_factor(&self, body: &Body) -> f32 { + self.hand_factor * self.get_width_factor(body) } pub fn get_foot_radius(&self, body: &Body, aabb: &AABB) -> u32 { diff --git a/rpg_tools_rendering/src/rendering/config/equipment/mod.rs b/rpg_tools_rendering/src/rendering/config/equipment/mod.rs index 9cb84244..cd7eb969 100644 --- a/rpg_tools_rendering/src/rendering/config/equipment/mod.rs +++ b/rpg_tools_rendering/src/rendering/config/equipment/mod.rs @@ -1 +1,2 @@ pub mod pants; +pub mod shirt; diff --git a/rpg_tools_rendering/src/rendering/config/equipment/shirt.rs b/rpg_tools_rendering/src/rendering/config/equipment/shirt.rs new file mode 100644 index 00000000..3887b41e --- /dev/null +++ b/rpg_tools_rendering/src/rendering/config/equipment/shirt.rs @@ -0,0 +1,12 @@ +/// The rendering config of the [`shirts`](rpg_tools_core::model::equipment::appearance::shirt::Shirt). +#[derive(Debug, PartialEq)] +pub struct ShirtConfig { + pub boat_depth: f32, + pub boat_width: f32, + pub crew_depth: f32, + pub crew_width: f32, + pub deep_v_depth: f32, + pub scoop_depth: f32, + pub scoop_width: f32, + pub v_depth: f32, +} diff --git a/rpg_tools_rendering/src/rendering/config/example.rs b/rpg_tools_rendering/src/rendering/config/example.rs index 4f51fd2a..6fd9147a 100644 --- a/rpg_tools_rendering/src/rendering/config/example.rs +++ b/rpg_tools_rendering/src/rendering/config/example.rs @@ -6,6 +6,7 @@ use crate::rendering::config::body::torso::TorsoConfig; use crate::rendering::config::body::BodyConfig; use crate::rendering::config::ear::EarConfig; use crate::rendering::config::equipment::pants::PantsConfig; +use crate::rendering::config::equipment::shirt::ShirtConfig; use crate::rendering::config::eye::eyebrow::EyebrowConfig; use crate::rendering::config::eye::EyeConfig; use crate::rendering::config::hair::hairline::HairlineConfig; @@ -127,6 +128,16 @@ pub fn create_config() -> RenderConfig { width_padding: 0.05, balloon_padding: 0.2, }, + shirt: ShirtConfig { + boat_depth: 0.05, + boat_width: 0.7, + crew_depth: 0.1, + crew_width: 0.3, + deep_v_depth: 0.4, + scoop_depth: 0.2, + scoop_width: 0.5, + v_depth: 0.2, + }, } } diff --git a/rpg_tools_rendering/src/rendering/config/mod.rs b/rpg_tools_rendering/src/rendering/config/mod.rs index b04bbf93..3d3b9b07 100644 --- a/rpg_tools_rendering/src/rendering/config/mod.rs +++ b/rpg_tools_rendering/src/rendering/config/mod.rs @@ -3,6 +3,7 @@ use crate::renderer::RenderOptions; use crate::rendering::config::body::BodyConfig; use crate::rendering::config::ear::EarConfig; use crate::rendering::config::equipment::pants::PantsConfig; +use crate::rendering::config::equipment::shirt::ShirtConfig; use crate::rendering::config::eye::EyeConfig; use crate::rendering::config::hair::HairConfig; use crate::rendering::config::head::HeadConfig; @@ -35,6 +36,7 @@ pub struct RenderConfig { pub head: HeadConfig, pub mouth: MouthConfig, pub pants: PantsConfig, + pub shirt: ShirtConfig, } impl RenderConfig { diff --git a/rpg_tools_rendering/src/rendering/equipment/mod.rs b/rpg_tools_rendering/src/rendering/equipment/mod.rs index 86545cba..0d2d7193 100644 --- a/rpg_tools_rendering/src/rendering/equipment/mod.rs +++ b/rpg_tools_rendering/src/rendering/equipment/mod.rs @@ -2,18 +2,22 @@ use crate::math::aabb2d::AABB; use crate::renderer::Renderer; use crate::rendering::config::RenderConfig; use crate::rendering::equipment::pants::render_pants; +use crate::rendering::equipment::shirt::render_shirt; use rpg_tools_core::model::character::appearance::body::Body; use rpg_tools_core::model::equipment::appearance::Clothing; pub mod pants; +pub mod shirt; pub fn render_clothing( renderer: &mut dyn Renderer, config: &RenderConfig, aabb: &AABB, body: &Body, + from_front: bool, ) { - if let Clothing::Simple { pants } = &body.clothing { - render_pants(renderer, config, aabb, body, pants) + if let Clothing::Simple { pants, shirt } = &body.clothing { + render_shirt(renderer, config, aabb, body, shirt, from_front); + render_pants(renderer, config, aabb, body, pants); } } diff --git a/rpg_tools_rendering/src/rendering/equipment/pants.rs b/rpg_tools_rendering/src/rendering/equipment/pants.rs index e18fb44e..9fd0485a 100644 --- a/rpg_tools_rendering/src/rendering/equipment/pants.rs +++ b/rpg_tools_rendering/src/rendering/equipment/pants.rs @@ -99,6 +99,6 @@ fn get_base(config: &RenderConfig, aabb: &AABB, body: &Body) -> Polygon2dBuilder builder } -fn interpolate(start: f32, end: f32, factor: f32) -> f32 { +pub fn interpolate(start: f32, end: f32, factor: f32) -> f32 { start * (1.0 - factor) + end * factor } diff --git a/rpg_tools_rendering/src/rendering/equipment/shirt.rs b/rpg_tools_rendering/src/rendering/equipment/shirt.rs new file mode 100644 index 00000000..bd779b08 --- /dev/null +++ b/rpg_tools_rendering/src/rendering/equipment/shirt.rs @@ -0,0 +1,104 @@ +use crate::math::aabb2d::AABB; +use crate::math::polygon2d::builder::Polygon2dBuilder; +use crate::renderer::Renderer; +use crate::rendering::body::torso::create_torso; +use crate::rendering::body::{get_left_arm, get_left_arm_short}; +use crate::rendering::config::body::torso::TorsoConfig; +use crate::rendering::config::equipment::shirt::ShirtConfig; +use crate::rendering::config::RenderConfig; +use rpg_tools_core::model::character::appearance::body::Body; +use rpg_tools_core::model::equipment::appearance::shirt::{Neckline, Shirt, SleeveStyle}; + +pub fn render_shirt( + renderer: &mut dyn Renderer, + config: &RenderConfig, + aabb: &AABB, + body: &Body, + shirt: &Shirt, + from_front: bool, +) { + render_sleeves(renderer, config, aabb, body, shirt); + render_torso(renderer, config, aabb, body, shirt, from_front); +} + +fn render_torso( + renderer: &mut dyn Renderer, + config: &RenderConfig, + aabb: &AABB, + body: &Body, + shirt: &Shirt, + from_front: bool, +) { + let options = config.get_options(shirt.color); + let torso_aabb = config.body.get_torso_aabb(body, aabb); + let torso = config.body.get_torso_config(body.shape); + let mut builder = create_torso(&torso_aabb, &config.body, torso); + + if from_front { + add_neckline(&torso_aabb, &config.shirt, torso, shirt, &mut builder); + } else { + add_straight(&torso_aabb, torso, &mut builder) + } + + let polygon = builder.build(); + renderer.render_rounded_polygon(&polygon, &options); +} + +fn render_sleeves( + renderer: &mut dyn Renderer, + config: &RenderConfig, + aabb: &AABB, + body: &Body, + shirt: &Shirt, +) { + let options = config.get_options(shirt.color); + + let polygon = match shirt.sleeve_style { + SleeveStyle::Long => get_left_arm(config, aabb, body), + SleeveStyle::None => return, + SleeveStyle::Short => get_left_arm_short(config, aabb, body, true), + } + .build(); + + renderer.render_rounded_polygon(&polygon, &options); + renderer.render_rounded_polygon(&aabb.mirrored(&polygon), &options); +} + +fn add_neckline( + aabb: &AABB, + config: &ShirtConfig, + torso: &TorsoConfig, + shirt: &Shirt, + builder: &mut Polygon2dBuilder, +) { + match shirt.neckline { + Neckline::Boat => add_round(aabb, torso, builder, config.boat_width, config.boat_depth), + Neckline::Crew => add_round(aabb, torso, builder, config.crew_width, config.crew_depth), + Neckline::DeepV => add_v(aabb, torso, builder, config.deep_v_depth), + Neckline::None => add_straight(aabb, torso, builder), + Neckline::Scoop => add_round(aabb, torso, builder, config.scoop_width, config.scoop_depth), + Neckline::V => add_v(aabb, torso, builder, config.v_depth), + } +} + +fn add_round( + aabb: &AABB, + torso: &TorsoConfig, + builder: &mut Polygon2dBuilder, + width: f32, + depth: f32, +) { + let width = torso.shoulder_width * width; + builder.add_mirrored_points(aabb, width, 0.0, true); + builder.add_mirrored_points(aabb, width * 0.7, depth, false); +} + +fn add_v(aabb: &AABB, torso: &TorsoConfig, builder: &mut Polygon2dBuilder, depth: f32) { + add_straight(aabb, torso, builder); + builder.add_point(aabb.get_point(0.5, depth), true); +} + +fn add_straight(aabb: &AABB, torso: &TorsoConfig, builder: &mut Polygon2dBuilder) { + let width = torso.shoulder_width / 3.0; + builder.add_mirrored_points(aabb, width, 0.0, true); +}