use crate::scene::{Point, Segment, Transform};
use crate::PT;
use super::basic::{box_anchors, circle, ellipse_path, rectangle, rectangle_corners};
use super::{arc_to, Border, Geometry, Input, Layer, Usage};
fn ends(g: &mut Geometry, sw: Point, ne: Point) {
let y = 0.5 * (sw.y + ne.y);
g.anchor_at("input", Point::new(sw.x, y));
g.anchor_at("output", Point::new(ne.x, y));
}
pub fn rectangle_ee(input: &mut Input) -> Geometry {
let mut g = rectangle(input);
let (sw, ne) = rectangle_corners(input);
ends(&mut g, sw, ne);
g
}
pub fn circle_ee(input: &mut Input) -> Geometry {
let mut g = circle(input);
let (w, e) = (g.anchor("west").expect("circle"), g.anchor("east").expect("circle"));
g.anchor_at("input", w);
g.anchor_at("output", e);
g
}
fn ee_box(input: &Input) -> (Geometry, Point, Point) {
let (sw, ne) = rectangle_corners(input);
let mut g = Geometry::new(sw.lerp(ne, 0.5), Border::Rectangle(ne.minus(sw).scale(0.5)));
box_anchors(&mut g, sw, ne, input.ex);
ends(&mut g, sw, ne);
(g, sw, ne)
}
fn inner(input: &Input, sw: Point, ne: Point) -> (f64, f64, f64, f64) {
(sw.x + input.outer_xsep, sw.y + input.outer_ysep, ne.x - input.outer_xsep, ne.y - input.outer_ysep)
}
fn line(a: (f64, f64), b: (f64, f64)) -> [Segment; 2] {
[Segment::MoveTo(Point::new(a.0, a.1)), Segment::LineTo(Point::new(b.0, b.1))]
}
pub fn direction_ee(input: &mut Input) -> Geometry {
let spec = input.keys.text("/pgf/direction ee arrow");
let other = Some(spec.trim()).filter(|s| !s.is_empty() && *s != "direction ee").and_then(|s| crate::engine::arrows::tip_shapes(s, input.line_width));
if let Some((d, paths)) = other {
let ne = Point::new(0.0, 0.5 * input.minimum_height);
let sw = Point::new(-d, -0.5 * input.minimum_height);
let mut g = Geometry::new(sw.lerp(ne, 0.5), Border::Rectangle(ne.minus(sw).scale(0.5)));
box_anchors(&mut g, sw, ne, input.ex);
ends(&mut g, sw, ne);
g.parts.clear();
g.path(Layer::BehindBackground, line((sw.x, 0.0), (ne.x, 0.0)).to_vec(), Usage::Stroke);
for (segs, fill, stroke) in paths {
if fill {
g.path(Layer::BeforeBackground, segs.clone(), Usage::LineFill);
}
if let Some((cap, join)) = stroke {
g.path(Layer::BeforeBackground, segs, Usage::TipStroke(cap, join));
}
}
return g;
}
let d = input.length("/pgf/arrow options/direction ee", 4.0 * PT);
let ne = Point::new(0.0, 0.5 * input.minimum_height);
let sw = Point::new(-d, -0.5 * input.minimum_height);
let mut g = Geometry::new(sw.lerp(ne, 0.5), Border::Rectangle(ne.minus(sw).scale(0.5)));
box_anchors(&mut g, sw, ne, input.ex);
ends(&mut g, sw, ne);
g.parts.clear();
g.path(Layer::BehindBackground, line((sw.x, 0.0), (ne.x, 0.0)).to_vec(), Usage::Stroke);
let k = 0.38268343237 * d;
let tip = vec![Segment::MoveTo(Point::new(-d, k)), Segment::LineTo(Point::new(0.0, 0.0)), Segment::LineTo(Point::new(-d, -k)), Segment::Close];
g.path(Layer::BeforeBackground, tip, Usage::Node);
g
}
pub fn var_resistor(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (mut xa, ya, xb, yb) = inner(input, sw, ne);
let yc = 0.5 * (ya + yb);
let xc = 0.5 * (yb - ya);
let mut v = vec![Segment::MoveTo(Point::new(xa, yc))];
let end = xb + 0.1 * PT;
loop {
xa += 4.0 * xc;
if xa >= end || xc <= 0.0 {
break;
}
xa -= 3.0 * xc;
v.push(Segment::LineTo(Point::new(xa, yb)));
xa += 2.0 * xc;
v.push(Segment::LineTo(Point::new(xa, ya)));
xa += xc;
v.push(Segment::LineTo(Point::new(xa, yc)));
}
v.push(Segment::LineTo(Point::new(xb, yc)));
g.background(v);
g
}
fn base_box(input: &Input, half_left: bool) -> (Point, Point) {
let (ox, oy) = (input.outer_xsep, input.outer_ysep);
let ne = Point::new(ox + 0.5 * input.minimum_width, oy + input.minimum_height);
let sw = Point::new(-(if half_left { 0.5 } else { 1.0 }) * ox - 0.5 * input.minimum_width, -oy);
(sw, ne)
}
fn base_geometry(input: &Input, sw: Point, ne: Point, drop: f64) -> Geometry {
let (lx, ly) = (-sw.x, -sw.y + drop);
let outline = vec![
Point::new(ne.x, 0.0),
Point::new(ne.x, ne.y),
Point::new(-ne.x, ne.y),
Point::new(-ne.x, 0.0),
Point::new(-lx, 0.0),
Point::new(-lx, -ly),
Point::new(lx, -ly),
Point::new(lx, 0.0),
];
let mut g = Geometry::new(Point::new(0.0, 0.0), Border::Outline(outline));
g.anchor_at("center", Point::new(0.0, 0.0));
let mut b = Geometry::new(Point::new(0.0, 0.0), Border::Point);
box_anchors(&mut b, sw, ne, input.ex);
for (name, p) in b.anchors {
if ["north", "south", "east", "west", "north east", "north west", "south east", "south west"].contains(&name.as_str()) {
g.anchor_at(&name, p);
}
}
ends(&mut g, sw, ne);
g
}
pub fn inductor(input: &mut Input) -> Geometry {
let (sw, ne) = base_box(input, false);
let mut g = base_geometry(input, sw, ne, 0.5 * PT);
let xb = ne.x - input.outer_xsep;
let yb = ne.y - input.outer_ysep;
let mut xa = -xb;
let mut v = vec![Segment::MoveTo(Point::new(xa, 0.0))];
let mut at = Point::new(xa, 0.0);
loop {
xa += 2.0 * yb;
if xa >= xb + 0.1 * PT || yb <= 0.0 {
break;
}
at = arc_to(&mut v, at, 180.0, 0.0, yb, yb);
}
v.push(Segment::LineTo(Point::new(xb, 0.0)));
g.background(v);
g
}
pub fn capacitor(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (xa, ya, xb, yb) = inner(input, sw, ne);
g.background([line((xa, ya), (xa, yb)), line((xb, ya), (xb, yb))].concat());
g
}
pub fn ground(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (xa, mut ya, xb, mut yb) = inner(input, sw, ne);
let mut v = line((xa, ya), (xa, yb)).to_vec();
let xm = 0.5 * xa + 0.5 * xb;
let yc = 0.16666 * yb - 0.16666 * ya;
ya += yc;
yb -= yc;
v.extend(line((xm, ya), (xm, yb)));
ya += yc;
yb -= yc;
v.extend(line((xb, ya), (xb, yb)));
g.background(v);
g
}
pub fn battery(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (xa, mut ya, xb, mut yb) = inner(input, sw, ne);
let mut v = line((xa, ya), (xa, yb)).to_vec();
let yc = 0.25 * yb - 0.25 * ya;
ya += yc;
yb -= yc;
v.extend(line((xb, ya), (xb, yb)));
g.background(v);
g
}
pub fn breakdown_diode(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (xa, ya, xb, yb) = inner(input, sw, ne);
let (xc, yc) = (0.5 * (xa + xb), 0.5 * (ya + yb));
let p = Point::new;
g.background(vec![
Segment::MoveTo(p(xa, ya)),
Segment::LineTo(p(xc, yc)),
Segment::LineTo(p(xa, yb)),
Segment::Close,
Segment::MoveTo(p(xb, ya)),
Segment::LineTo(p(xc, yc)),
Segment::LineTo(p(xb, yb)),
Segment::Close,
]);
g.path(Layer::BeforeBackground, line((xa, yc), (xb, yc)).to_vec(), Usage::Stroke);
let s = 0.5 * (yb - ya);
let q = |x: f64, y: f64| p(xc + x * s, yc + y * s);
g.path(Layer::BeforeBackground, vec![Segment::MoveTo(q(-0.5, -1.0)), Segment::LineTo(q(0.0, -1.0)), Segment::LineTo(q(0.0, 1.0)), Segment::LineTo(q(0.5, 1.0))], Usage::Stroke);
g
}
fn key_code(input: &mut Input, g: &mut Geometry, shape: &str, origin: Point, size: f64) {
let p = |x: f64, y: f64| origin.plus(Point::new(x * size, y * size));
let mark = input.keys.text(&format!("/pgf/{shape}/tikra mark"));
let mut v = Vec::new();
if mark.trim() == "wave" {
let q = |a: (f64, f64), c: (f64, f64), b: (f64, f64)| {
Segment::CurveTo(p(a.0 + 2.0 / 3.0 * (c.0 - a.0), a.1 + 2.0 / 3.0 * (c.1 - a.1)), p(b.0 + 2.0 / 3.0 * (c.0 - b.0), b.1 + 2.0 / 3.0 * (c.1 - b.1)), p(b.0, b.1))
};
v.extend([Segment::MoveTo(p(0.7, 0.0)), q((0.7, 0.0), (0.35, -0.5), (0.0, 0.0)), q((0.0, 0.0), (-0.35, 0.5), (-0.7, 0.0))]);
} else {
for line in mark.split(';') {
let pts: Vec<Point> =
line.split_whitespace().filter_map(|xy| xy.split_once(',')).filter_map(|(x, y)| Some(p(x.trim().parse().ok()?, y.trim().parse().ok()?))).collect();
for (k, q) in pts.iter().enumerate() {
v.push(if k == 0 { Segment::MoveTo(*q) } else { Segment::LineTo(*q) });
}
}
}
if !v.is_empty() {
g.path(Layer::BeforeBackground, v, Usage::Stroke);
}
let t = Transform::translation(origin.x, origin.y).compose(&Transform::scaling(size / PT, size / PT));
for segs in input.keys.code_paths(&format!("/pgf/{shape}/before background"), t) {
g.path(Layer::BeforeBackground, segs, Usage::Stroke);
}
}
pub fn generic_circle(input: &mut Input) -> Geometry {
let mut g = circle_ee(input);
let c = g.center;
let r = g.anchor("east").expect("circle").x - c.x - input.outer_sep();
key_code(input, &mut g, "generic circle IEC", c, r);
g
}
pub fn generic_diode(input: &mut Input) -> Geometry {
let (mut g, sw, ne) = ee_box(input);
let (xa, ya, xb, yb) = inner(input, sw, ne);
let yc = 0.5 * (ya + yb);
let p = Point::new;
g.background(vec![Segment::MoveTo(p(xa, ya)), Segment::LineTo(p(xa, yb)), Segment::LineTo(p(xb - 0.5 * input.line_width, yc)), Segment::Close]);
g.path(Layer::BeforeBackground, line((xa, yc), (xb, yc)).to_vec(), Usage::Stroke);
key_code(input, &mut g, "generic diode IEC", p(xb, yc), 0.5 * (yb - ya));
g
}
pub fn make_contact(input: &mut Input) -> Geometry {
let (sw, ne) = base_box(input, true);
let mut g = base_geometry(input, sw, ne, 0.5 * PT);
let (xb, yb) = (ne.x - input.outer_xsep, ne.y - input.outer_ysep);
g.background(line((-xb, 0.0), (xb, yb)).to_vec());
g
}
pub fn var_make_contact(input: &mut Input) -> Geometry {
let (mut sw, ne) = base_box(input, true);
sw.y += 0.083333 * sw.x;
let mut g = base_geometry(input, sw, ne, 0.0);
let (xb, yb) = (ne.x - input.outer_xsep, ne.y - input.outer_ysep);
let r = 0.083333 * 2.0 * xb;
let xa = -xb + r;
let mut v = ellipse_path(Point::new(xa, 0.0), r, r);
let d = Point::new(xb - xa, yb);
let u = d.scale(1.0 / d.norm().max(1e-12));
v.extend([Segment::MoveTo(Point::new(xa, 0.0).plus(u.scale(r))), Segment::LineTo(Point::new(xb, yb))]);
g.background(v);
g
}
pub fn break_contact(input: &mut Input) -> Geometry {
let (sw, ne) = base_box(input, true);
let mut g = base_geometry(input, sw, ne, 0.5 * PT);
let (xb, yb) = (ne.x - input.outer_xsep, ne.y - input.outer_ysep);
let xa = -xb;
let xc = 0.2 * xb - 0.2 * xa;
let mut v = line((xa, 0.0), (xb, yb)).to_vec();
v.extend([Segment::MoveTo(Point::new(xb, 0.0)), Segment::LineTo(Point::new(xb - xc, 0.0)), Segment::LineTo(Point::new(xb - xc, yb))]);
g.background(v);
g
}