/* Hurry Curry! - a game about cooking Copyright (C) 2025 Hurry Curry! Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use anyhow::Result; use hurrycurry_protocol::{ Gamedata, Message, book::{Diagram, DiagramNode, NodeStyle}, glam::Vec2, }; use std::fmt::Write; const SIZE: f32 = 64.; const HSIZE: f32 = SIZE / 2.; pub fn diagram_svg(data: &Gamedata, diagram: &Diagram) -> Result { let mut out = String::new(); let (vb_min, vb_max) = diagram .nodes .iter() .map(|n| (n.position, n.position)) .reduce(|(xa, xb), (ya, yb)| (xa.min(ya), xb.max(yb))) .unwrap(); writeln!( out, r#""#, vb_min.x - HSIZE, vb_min.y - HSIZE, vb_max.x + HSIZE, vb_max.y + HSIZE, vb_max.x - vb_min.x + SIZE, vb_max.y - vb_min.y + SIZE, )?; for node in &diagram.nodes { match node.label { Message::Translation { .. } => {} Message::Text(_) => {} Message::Item(item_index) => { image_node( &mut out, node, format!("items/{}.png", data.item_name(item_index)), )?; } Message::Tile(tile_index) => { image_node( &mut out, node, format!("tiles/{}.png", data.tile_name(tile_index)), )?; } } } for edge in &diagram.edges { let src_node = &diagram.nodes[edge.src]; let dst_node = &diagram.nodes[edge.dst]; let src = node_edge_connect_pos(src_node, dst_node); let mut dst = node_edge_connect_pos(dst_node, src_node); let dir = (src - dst).normalize_or_zero(); let tip0 = dst; let tip1 = dst + (dir + dir.perp() * 0.5) * 10.; let tip2 = dst + (dir + dir.perp() * -0.5) * 10.; dst += dir * 5.; // prevent miter line cap from peeking out // line path writeln!( out, r#""#, src.x, src.y, dst.x, dst.y, )?; // tip path writeln!( out, r#""#, tip0.x, tip0.y, tip1.x, tip1.y, tip2.x, tip2.y )?; } writeln!(out, "")?; Ok(out) } fn node_edge_connect_pos(src: &DiagramNode, dst: &DiagramNode) -> Vec2 { let dir = (dst.position - src.position).normalize_or_zero(); if matches!( src.style, NodeStyle::FinalProduct | NodeStyle::IntermediateProduct ) { src.position + dir * HSIZE // circle } else { src.position + dir / dir.y.abs() * HSIZE // square (only +Y and -Y sides) } } pub(crate) fn node_color(node: &DiagramNode) -> &'static str { match node.style { NodeStyle::FinalProduct => "#555", NodeStyle::IntermediateProduct => "#333", NodeStyle::ProcessActive => "#47c42b", NodeStyle::ProcessPassive => "#c4a32b", NodeStyle::ProcessInstant => "#5452d8", } } fn image_node(out: &mut String, node: &DiagramNode, path: String) -> Result<()> { if matches!( node.style, NodeStyle::FinalProduct | NodeStyle::IntermediateProduct ) { writeln!( out, r#""#, node.position.x, node.position.y, HSIZE, node_color(node) )?; } else { writeln!( out, r#""#, node.position.x - HSIZE, node.position.y - HSIZE, node_color(node) )?; } writeln!( out, r#""#, node.position.x - HSIZE, node.position.y - HSIZE, )?; Ok(()) }