Skip to content
Snippets Groups Projects
Commit 466ebf96 authored by John Mitchell's avatar John Mitchell
Browse files

Native rust TTF to MSDF loader.

parent 1809fde1
No related branches found
No related tags found
No related merge requests found
Showing
with 895 additions and 28 deletions
...@@ -18,6 +18,11 @@ bevy_renderer = ["bevy"] ...@@ -18,6 +18,11 @@ bevy_renderer = ["bevy"]
anyhow = { version = "1.0" } anyhow = { version = "1.0" }
nanoserde = "0.1.30" nanoserde = "0.1.30"
unicode-segmentation = "1.10.0" unicode-segmentation = "1.10.0"
num = "0.4"
num-derive = "0.3"
num-traits = "0.2"
ttf-parser = "0.17"
image = "0.24"
# Provides UAX #14 line break segmentation # Provides UAX #14 line break segmentation
xi-unicode = "0.3" xi-unicode = "0.3"
......
{
"file": "roboto.ttf",
"char_range_start": "0x20",
"char_range_end": "0x7f"
}
\ No newline at end of file
kayak_font/assets/roboto.kttf-cached.png

150 KiB

File added
...@@ -18,7 +18,7 @@ const FONT_SIZE: f32 = 24.0; ...@@ -18,7 +18,7 @@ const FONT_SIZE: f32 = 24.0;
const INITIAL_SIZE: Vec2 = Vec2::from_array([400.0, 300.0]); const INITIAL_SIZE: Vec2 = Vec2::from_array([400.0, 300.0]);
const INITIAL_POS: Vec2 = Vec2::from_array([-200.0, 0.0]); const INITIAL_POS: Vec2 = Vec2::from_array([-200.0, 0.0]);
const INSTRUCTIONS: &str = const INSTRUCTIONS: &str =
"Press 'A' and 'D' to shrink and grow the text box.\nPress 'Space' to cycle text alignment."; "Press 'A' and 'D' to shrink and grow the text box.\nPress \"Space\" to cycle text alignment.";
#[derive(Component)] #[derive(Component)]
struct Instructions; struct Instructions;
...@@ -26,7 +26,8 @@ struct Instructions; ...@@ -26,7 +26,8 @@ struct Instructions;
fn startup(mut commands: Commands, asset_server: Res<AssetServer>) { fn startup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default()); commands.spawn(Camera2dBundle::default());
let font_handle: Handle<KayakFont> = asset_server.load("roboto.kayak_font"); let font_handle: Handle<KayakFont> = asset_server.load("roboto.kttf");
// let font_handle: Handle<KayakFont> = asset_server.load("roboto.kayak_font");
commands commands
.spawn(Text { .spawn(Text {
......
kayak_font/roboto.kttf-cached.png

150 KiB

...@@ -6,6 +6,12 @@ pub enum SDFType { ...@@ -6,6 +6,12 @@ pub enum SDFType {
Msdf, Msdf,
} }
impl Default for SDFType {
fn default() -> Self {
Self::Msdf
}
}
#[derive(DeJson, Debug, Copy, Clone, PartialEq, Eq)] #[derive(DeJson, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Origin { pub enum Origin {
#[nserde(rename = "bottom")] #[nserde(rename = "bottom")]
...@@ -18,7 +24,13 @@ pub enum Origin { ...@@ -18,7 +24,13 @@ pub enum Origin {
Top, Top,
} }
#[derive(DeJson, Debug, Copy, Clone, PartialEq)] impl Default for Origin {
fn default() -> Self {
Self::Bottom
}
}
#[derive(DeJson, Default, Debug, Copy, Clone, PartialEq)]
pub struct Atlas { pub struct Atlas {
#[nserde(rename = "type")] #[nserde(rename = "type")]
pub sdf_type: SDFType, pub sdf_type: SDFType,
......
...@@ -22,7 +22,7 @@ pub fn init_font_texture( ...@@ -22,7 +22,7 @@ pub fn init_font_texture(
let not_processed_fonts = not_processed.drain(..).collect::<Vec<_>>(); let not_processed_fonts = not_processed.drain(..).collect::<Vec<_>>();
for font_handle in not_processed_fonts { for font_handle in not_processed_fonts {
if let Some(font) = fonts.get(&font_handle) { if let Some(font) = fonts.get(&font_handle) {
if let Some(mut texture) = images.get_mut(&font.atlas_image) { if let Some(mut texture) = images.get_mut(font.image.get()) {
texture.texture_descriptor.format = TextureFormat::Rgba8Unorm; texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor { texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor {
label: Some("Present Sampler"), label: Some("Present Sampler"),
......
use crate::{KayakFont, Sdf}; use crate::{KayakFont, Sdf, ImageType};
use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset}; use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset};
#[derive(Default)] #[derive(Default)]
...@@ -16,7 +16,7 @@ impl AssetLoader for KayakFontLoader { ...@@ -16,7 +16,7 @@ impl AssetLoader for KayakFontLoader {
let atlas_image_path = AssetPath::new(path, None); let atlas_image_path = AssetPath::new(path, None);
let font = KayakFont::new( let font = KayakFont::new(
Sdf::from_bytes(bytes), Sdf::from_bytes(bytes),
load_context.get_handle(atlas_image_path.clone()), ImageType::Atlas(load_context.get_handle(atlas_image_path.clone())),
); );
let asset = LoadedAsset::new(font).with_dependency(atlas_image_path); let asset = LoadedAsset::new(font).with_dependency(atlas_image_path);
......
...@@ -24,6 +24,7 @@ mod plugin { ...@@ -24,6 +24,7 @@ mod plugin {
impl Plugin for KayakFontPlugin { impl Plugin for KayakFontPlugin {
fn build(&self, app: &mut bevy::prelude::App) { fn build(&self, app: &mut bevy::prelude::App) {
app.add_asset::<KayakFont>() app.add_asset::<KayakFont>()
.add_asset_loader(crate::ttf::loader::TTFLoader)
.add_asset_loader(KayakFontLoader) .add_asset_loader(KayakFontLoader)
.add_system(init_font_texture); .add_system(init_font_texture);
......
...@@ -46,7 +46,7 @@ pub(crate) fn extract_fonts( ...@@ -46,7 +46,7 @@ pub(crate) fn extract_fonts(
for handle in changed_assets { for handle in changed_assets {
let font_asset = font_assets.get(&handle).unwrap(); let font_asset = font_assets.get(&handle).unwrap();
if let Some(image) = textures.get(&font_asset.atlas_image) { if let Some(image) = textures.get(font_asset.image.get()) {
if !image if !image
.texture_descriptor .texture_descriptor
.usage .usage
......
use crate::{KayakFont, Sdf}; use crate::{KayakFont, Sdf, ImageType};
use bevy::{ use bevy::{
math::Vec2, math::Vec2,
prelude::{Handle, Res, Resource}, prelude::{Handle, Res, Resource},
...@@ -73,20 +73,34 @@ impl FontTextureCache { ...@@ -73,20 +73,34 @@ impl FontTextureCache {
for kayak_font_handle in new_fonts { for kayak_font_handle in new_fonts {
let mut was_processed = true; let mut was_processed = true;
if let Some(font) = self.fonts.get(&kayak_font_handle) { if let Some(font) = self.fonts.get(&kayak_font_handle) {
if let Some(atlas_texture) = render_images.get(&font.atlas_image) { if matches!(font.image, ImageType::Array(..)) {
Self::create_from_atlas( if let Some(array_texture) = render_images.get(font.image.get()) {
&mut self.images, Self::create_from_array(
&mut self.bind_groups, &mut self.bind_groups,
&font.sdf, kayak_font_handle.clone_weak(),
kayak_font_handle.clone_weak(), device,
device, pipeline,
queue, array_texture,
pipeline, );
atlas_texture, } else {
font.sdf.max_glyph_size().into(), was_processed = false;
); }
} else { } else {
was_processed = false; if let Some(atlas_texture) = render_images.get(font.image.get()) {
Self::create_from_atlas(
&mut self.images,
&mut self.bind_groups,
&font.sdf,
kayak_font_handle.clone_weak(),
device,
queue,
pipeline,
atlas_texture,
font.sdf.max_glyph_size().into(),
);
} else {
was_processed = false;
}
} }
} }
if !was_processed { if !was_processed {
...@@ -300,4 +314,30 @@ impl FontTextureCache { ...@@ -300,4 +314,30 @@ impl FontTextureCache {
let command_buffer = command_encoder.finish(); let command_buffer = command_encoder.finish();
queue.submit(vec![command_buffer]); queue.submit(vec![command_buffer]);
} }
pub fn create_from_array<T: FontRenderingPipeline>(
bind_groups: &mut HashMap<Handle<KayakFont>, BindGroup>,
font_handle: Handle<KayakFont>,
device: &RenderDevice,
pipeline: &T,
array_texture: &GpuImage,
) {
// create bind group
let binding = device.create_bind_group(&BindGroupDescriptor {
label: Some("text_image_bind_group"),
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&array_texture.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&array_texture.sampler),
},
],
layout: pipeline.get_font_image_layout(),
});
bind_groups.insert(font_handle.clone_weak(), binding);
}
} }
...@@ -14,12 +14,28 @@ use crate::{ ...@@ -14,12 +14,28 @@ use crate::{
#[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"] #[uuid = "4fe4732c-6731-49bb-bafc-4690d636b848"]
pub struct KayakFont { pub struct KayakFont {
pub sdf: Sdf, pub sdf: Sdf,
pub atlas_image: Handle<Image>, pub image: ImageType,
pub missing_glyph: Option<char>, pub missing_glyph: Option<char>,
char_ids: HashMap<char, u32>, char_ids: HashMap<char, u32>,
max_glyph_size: (f32, f32), max_glyph_size: (f32, f32),
} }
#[cfg(feature = "bevy_renderer")]
#[derive(Debug, Clone, PartialEq)]
pub enum ImageType {
Atlas(Handle<Image>),
Array(Handle<Image>),
}
impl ImageType {
pub fn get(&self) -> &Handle<Image> {
match self {
Self::Atlas(handle) => handle,
Self::Array(handle) => handle,
}
}
}
#[cfg(not(feature = "bevy_renderer"))] #[cfg(not(feature = "bevy_renderer"))]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct KayakFont { pub struct KayakFont {
...@@ -30,7 +46,7 @@ pub struct KayakFont { ...@@ -30,7 +46,7 @@ pub struct KayakFont {
} }
impl KayakFont { impl KayakFont {
pub fn new(sdf: Sdf, #[cfg(feature = "bevy_renderer")] atlas_image: Handle<Image>) -> Self { pub fn new(sdf: Sdf, #[cfg(feature = "bevy_renderer")] image_type: ImageType) -> Self {
let max_glyph_size = sdf.max_glyph_size(); let max_glyph_size = sdf.max_glyph_size();
assert!( assert!(
sdf.glyphs.len() < u32::MAX as usize, sdf.glyphs.len() < u32::MAX as usize,
...@@ -55,7 +71,7 @@ impl KayakFont { ...@@ -55,7 +71,7 @@ impl KayakFont {
Self { Self {
sdf, sdf,
#[cfg(feature = "bevy_renderer")] #[cfg(feature = "bevy_renderer")]
atlas_image, image: image_type,
missing_glyph, missing_glyph,
char_ids, char_ids,
max_glyph_size, max_glyph_size,
...@@ -260,7 +276,6 @@ impl KayakFont { ...@@ -260,7 +276,6 @@ impl KayakFont {
rect.position.0 += shift_x; rect.position.0 += shift_x;
} }
} }
TextLayout::new(glyph_rects, lines, size, properties) TextLayout::new(glyph_rects, lines, size, properties)
} }
......
...@@ -5,6 +5,8 @@ mod layout; ...@@ -5,6 +5,8 @@ mod layout;
mod metrics; mod metrics;
mod sdf; mod sdf;
mod utility; mod utility;
mod ttf;
mod msdf;
pub use atlas::*; pub use atlas::*;
pub use font::*; pub use font::*;
...@@ -18,14 +20,14 @@ pub mod bevy; ...@@ -18,14 +20,14 @@ pub mod bevy;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{Alignment, KayakFont, Sdf, TextProperties}; use crate::{Alignment, KayakFont, Sdf, TextProperties, ImageType};
fn make_font() -> KayakFont { fn make_font() -> KayakFont {
let bytes = std::fs::read("assets/roboto.kayak_font") let bytes = std::fs::read("assets/roboto.kayak_font")
.expect("a `roboto.kayak_font` file in the `assets/` directory of this crate"); .expect("a `roboto.kayak_font` file in the `assets/` directory of this crate");
#[cfg(feature = "bevy_renderer")] #[cfg(feature = "bevy_renderer")]
return KayakFont::new(Sdf::from_bytes(&bytes), bevy::asset::Handle::default()); return KayakFont::new(Sdf::from_bytes(&bytes), ImageType::Atlas(bevy::asset::Handle::default()));
#[cfg(not(feature = "bevy_renderer"))] #[cfg(not(feature = "bevy_renderer"))]
return KayakFont::new(Sdf::from_bytes(&bytes)); return KayakFont::new(Sdf::from_bytes(&bytes));
......
use nanoserde::DeJson; use nanoserde::DeJson;
#[derive(DeJson, Debug, Copy, Clone, PartialEq)] #[derive(DeJson, Default, Debug, Copy, Clone, PartialEq)]
pub struct Metrics { pub struct Metrics {
#[nserde(rename = "emSize")] #[nserde(rename = "emSize")]
em_size: f32, em_size: f32,
......
#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct FloatRGB {
pub r: f32,
pub g: f32,
pub b: f32,
}
impl FloatRGB {
pub fn new(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b }
}
}
#[derive(Debug, Clone)]
pub struct FloatBmp {
buffer: Vec<f32>,
w: usize,
h: usize,
}
impl FloatBmp {
pub fn new(w: usize, h: usize) -> Self {
Self {
buffer: Vec::with_capacity(w * h),
w,
h,
}
}
pub fn width(&self) -> usize {
self.w
}
pub fn height(&self) -> usize {
self.h
}
pub fn set_pixel(&mut self, x: usize, y: usize, value: f32) {
self.buffer[x + (y * self.w)] = value;
}
pub fn get_pixel(&self, x: usize, y: usize) -> f32 {
self.buffer[x + (y * self.w)]
}
}
#[derive(Debug, Clone)]
pub struct FloatRGBBmp {
pub buffer: Vec<FloatRGB>,
w: usize,
h: usize,
}
impl FloatRGBBmp {
pub fn new(w: usize, h: usize) -> Self {
Self {
buffer: vec![FloatRGB::new(0.0, 0.0, 0.0); w * h],
w,
h,
}
}
pub fn width(&self) -> usize {
self.w
}
pub fn height(&self) -> usize {
self.h
}
pub fn set_pixel(&mut self, x: usize, y: usize, value: FloatRGB) {
self.buffer[x + (y * self.w)] = value;
}
pub fn get_pixel(&self, x: usize, y: usize) -> FloatRGB {
self.buffer[x + (y * self.w)]
}
}
#![allow(dead_code)]
use crate::msdf::{edge_segment::EdgeSegment, vector::Vector2, EdgeColor};
#[derive(Debug, Default, Clone)]
pub struct Contour {
pub edges: Vec<EdgeSegment>,
has_calculated_bounds: bool,
bounds_left: f64,
bounds_right: f64,
bounds_top: f64,
bounds_bottom: f64,
}
impl Contour {
pub fn new() -> Self {
Self {
edges: Vec::new(),
has_calculated_bounds: false,
bounds_left: 0.0,
bounds_right: 0.0,
bounds_top: 0.0,
bounds_bottom: 0.0,
}
}
pub fn add_edge(&mut self, edge: EdgeSegment) -> &EdgeSegment {
self.edges.push(edge);
self.edges.last().unwrap()
}
pub fn add_line(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> &EdgeSegment {
self.add_edge(EdgeSegment::new_linear(
Vector2::new(x0, y0),
Vector2::new(x1, y1),
EdgeColor::WHITE,
))
}
pub fn add_quadratic_segment(
&mut self,
x0: f64,
y0: f64,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
) -> &EdgeSegment {
self.add_edge(EdgeSegment::new_quadratic(
Vector2::new(x0, y0),
Vector2::new(x1, y1),
Vector2::new(x2, y2),
EdgeColor::WHITE,
))
}
pub fn add_cubic_segment(
&mut self,
x0: f64,
y0: f64,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
x3: f64,
y3: f64,
) -> &EdgeSegment {
self.add_edge(EdgeSegment::new_cubic(
Vector2::new(x0, y0),
Vector2::new(x1, y1),
Vector2::new(x2, y2),
Vector2::new(x3, y3),
EdgeColor::WHITE,
))
}
pub fn find_bounds(
&mut self,
left: &mut f64,
bottom: &mut f64,
right: &mut f64,
top: &mut f64,
) {
if !self.has_calculated_bounds {
self.bounds_left = std::f64::MAX;
self.bounds_right = std::f64::MIN;
self.bounds_top = std::f64::MIN;
self.bounds_bottom = std::f64::MAX;
for edge in self.edges.iter() {
edge.find_bounds(
&mut self.bounds_left,
&mut self.bounds_bottom,
&mut self.bounds_right,
&mut self.bounds_top,
);
}
self.has_calculated_bounds = true;
}
if self.bounds_left > *left {
*left = self.bounds_left;
}
if self.bounds_right < *right {
*right = self.bounds_right;
}
if self.bounds_bottom < *bottom {
*bottom = self.bounds_bottom;
}
if self.bounds_top > *top {
*top = self.bounds_top;
}
}
pub fn winding(&self) -> i32 {
let mut total: f64 = 0.0;
match self.edges.len() {
0 => {
return 0;
}
1 => {
let a = self.edges[0].point(0.0);
let b = self.edges[0].point(1.0 / 3.0);
let c = self.edges[0].point(2.0 / 3.0);
total += Vector2::shoelace(a, b);
total += Vector2::shoelace(b, c);
total += Vector2::shoelace(c, a);
}
2 => {
let a = self.edges[0].point(0.0);
let b = self.edges[0].point(0.5);
let c = self.edges[1].point(0.0);
let d = self.edges[1].point(0.5);
total += Vector2::shoelace(a, b);
total += Vector2::shoelace(b, c);
total += Vector2::shoelace(c, d);
total += Vector2::shoelace(d, a);
}
_ => {
let mut prev = self.edges.last().unwrap().point(0.0);
for edge in self.edges.iter() {
let cur = edge.point(0.0);
total += Vector2::shoelace(prev, cur);
prev = cur;
}
}
}
return Vector2::sign(total) as i32;
}
pub fn bound_miters(&self, l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64, border: f64, miter_limit: f64, polarity: i32) {
if self.edges.is_empty() {
return;
}
let mut prev_dir = self.edges.last().unwrap().direction(1.0).normalize(true);
for edge in self.edges.iter() {
let mut dir = edge.direction(0.0).normalize(true);
dir = Vector2::new(-dir.x, -dir.y);
if polarity as f64 * Vector2::cross_product(prev_dir, dir) >= 0.0 {
let miter_length;
let q = 0.5 * (1.0 - Vector2::dot_product(prev_dir, dir));
if q > 0.0 {
miter_length = (1.0 / q.sqrt()).min(miter_limit);
let miter = edge.point(0.0) + border * miter_length * (prev_dir + dir).normalize(true);
bound_point(l, b, r, t, miter);
}
}
prev_dir = edge.direction(1.0).normalize(true);
}
}
}
fn bound_point(l: &mut f64, b: &mut f64, r: &mut f64, t: &mut f64, p: Vector2) {
if p.x < *l {
*l = p.x;
}
if p.y < *b {
*b = p.y;
}
if p.x > *r {
*r = p.x;
}
if p.y > *t {
*t = p.y;
}
}
\ No newline at end of file
#![allow(dead_code)]
use crate::msdf::{edge_segment::EdgeSegment, shape::Shape, vector::Vector2, EdgeColor};
fn is_corner(a_dir: Vector2, b_dir: Vector2, cross_threshold: f64) -> bool {
Vector2::dot_product(a_dir, b_dir) <= 0.0
|| Vector2::cross_product(a_dir, b_dir).abs() > cross_threshold
}
const MSDFGEN_EDGE_LENGTH_PRECISION: usize = 4;
fn estimate_edge_length(edge: &EdgeSegment) -> f64 {
let mut len = 0.0;
let mut prev = edge.point(0.0);
for i in 1..MSDFGEN_EDGE_LENGTH_PRECISION {
let cur = edge.point(1.0 / MSDFGEN_EDGE_LENGTH_PRECISION as f64 * i as f64);
len += (cur - prev).length();
prev = cur;
}
return len;
}
fn switch_color(color: &mut EdgeColor, seed: &mut usize, banned: EdgeColor) {
let combined: EdgeColor =
num::cast::FromPrimitive::from_usize(*color as usize & banned as usize).unwrap();
if combined == EdgeColor::RED || combined == EdgeColor::GREEN || combined == EdgeColor::BLUE {
*color =
num::cast::FromPrimitive::from_usize(combined as usize ^ EdgeColor::WHITE as usize)
.unwrap();
return;
}
if *color == EdgeColor::BLACK || *color == EdgeColor::WHITE {
match *seed % 3 {
0 => {
*color = EdgeColor::CYAN;
}
1 => {
*color = EdgeColor::MAGENTA;
}
2 => {
*color = EdgeColor::YELLOW;
}
_ => panic!("Not supported!"),
}
*seed /= 3;
return;
}
let shifted = (*color as usize) << (1 + (*seed & 1));
*color = num::cast::FromPrimitive::from_usize(
(shifted | shifted >> 3) & (EdgeColor::WHITE as usize),
)
.unwrap();
*seed >>= 1;
}
pub fn simple(shape: &mut Shape, angle_threshold: f64, mut seed: usize) {
let cross_threshold = angle_threshold.sin();
let mut corners = Vec::new();
for contour in shape.contours.iter_mut() {
corners.clear();
let edges = &mut contour.edges;
let edge_count = edges.len();
if edge_count != 0 {
let mut prev_dir = edges.last().unwrap().direction(1.0);
for i in 0..edge_count {
let edge = &edges[i];
if is_corner(
prev_dir.normalize(false),
edge.direction(0.0).normalize(false),
cross_threshold,
) {
corners.push(i);
}
prev_dir = edge.direction(1.0);
}
}
if corners.len() == 0 {
for i in 0..edge_count {
edges[i].set_color(EdgeColor::WHITE);
}
} else if corners.len() == 1 {
let mut colors = vec![EdgeColor::WHITE, EdgeColor::WHITE, EdgeColor::BLACK];
switch_color(&mut colors[0], &mut seed, EdgeColor::BLACK);
colors[2] = colors[0];
switch_color(&mut colors[2], &mut seed, EdgeColor::BLACK);
let corner = corners[0];
if edge_count >= 3 {
let m = edge_count;
for i in 0..m {
let lookup =
((3.0 + 2.875 * i as f64 / (m as f64 - 1.0) - 1.4375 + 0.5) as i32 - 3) + 1;
contour.edges[(corner + i) % m].set_color(colors[lookup as usize]);
}
} else if edge_count >= 1 {
let mut parts = [EdgeSegment::default(); 7];
let (o1, o2, o3) = edges[0].split_in_thirds();
parts[0 + 3 * corner] = o1;
parts[1 + 3 * corner] = o2;
parts[2 + 3 * corner] = o3;
if edge_count >= 2 {
let (o1, o2, o3) = edges[1].split_in_thirds();
parts[3 - 3 * corner] = o1;
parts[4 - 3 * corner] = o2;
parts[5 - 3 * corner] = o3;
parts[1].set_color(colors[0]);
parts[0].set_color(parts[1].get_color());
parts[3].set_color(colors[1]);
parts[2].set_color(parts[3].get_color());
parts[5].set_color(colors[2]);
parts[4].set_color(parts[5].get_color());
} else {
parts[0].set_color(colors[0]);
parts[1].set_color(colors[1]);
parts[2].set_color(colors[2]);
}
edges.clear();
for i in 0..7 {
edges.push(parts[i]);
}
}
} else {
let corner_count = corners.len();
let mut spline = 0;
let start = corners[0];
let mut color = EdgeColor::WHITE;
switch_color(&mut color, &mut seed, EdgeColor::BLACK);
let initial_color = color;
for i in 0..edge_count {
let index = (start + i) % edge_count;
if spline + 1 < corner_count && corners[spline + 1] == index {
spline += 1;
let banned_color =
(if spline == corner_count - 1 { 1 } else { 0 }) * initial_color as usize;
switch_color(
&mut color,
&mut seed,
num::cast::FromPrimitive::from_usize(banned_color).unwrap(),
);
}
edges[index].set_color(color);
}
}
}
}
struct EdgeColoringInkTrapCorner {
pub index: i32,
pub prev_edge_length_estimate: f64,
pub minor: bool,
pub color: EdgeColor,
pub spline_length: f64,
}
pub fn ink_trap(shape: &mut Shape, angle_threshold: f64, mut seed: usize) {
let cross_threshold = angle_threshold.sin();
let mut corners = Vec::new();
for contour in shape.contours.iter_mut() {
let mut spline_length = 0.0;
corners.clear();
if !contour.edges.is_empty() {
let mut prev_direction = contour.edges.last().unwrap().direction(1.0);
let mut index = 0;
for edge in contour.edges.iter() {
if is_corner(
prev_direction.normalize(false),
edge.direction(0.0).normalize(false),
cross_threshold,
) {
let corner = EdgeColoringInkTrapCorner {
index,
spline_length,
color: EdgeColor::BLACK,
prev_edge_length_estimate: 0.0,
minor: false,
};
corners.push(corner);
spline_length = 0.0;
}
spline_length += estimate_edge_length(edge);
prev_direction = edge.direction(1.0);
index += 1;
}
}
if corners.is_empty() {
for edge in contour.edges.iter_mut() {
edge.set_color(EdgeColor::WHITE);
}
} else if corners.len() == 1 {
let mut colors = vec![EdgeColor::WHITE, EdgeColor::WHITE, EdgeColor::BLACK];
switch_color(&mut colors[0], &mut seed, EdgeColor::BLACK);
colors[2] = colors[0];
switch_color(&mut colors[2], &mut seed, EdgeColor::BLACK);
let corner = corners[0].index as usize;
if contour.edges.len() >= 3 {
let m = contour.edges.len();
for i in 0..m {
let lookup =
((3.0 + 2.875 * i as f64 / (m as f64 - 1.0) - 1.4375 + 0.5) as i32 - 3) + 1;
contour.edges[(corner + i) % m].set_color(colors[lookup as usize]);
}
} else if contour.edges.len() >= 1 {
let mut parts = vec![EdgeSegment::default(); 7];
let (o1, o2, o3) = contour.edges[0].split_in_thirds();
parts[0 + 3 * corner] = o1;
parts[1 + 3 * corner] = o2;
parts[2 + 3 * corner] = o3;
if contour.edges.len() >= 2 {
let (o1, o2, o3) = contour.edges[1].split_in_thirds();
parts[3 - 3 * corner] = o1;
parts[4 - 3 * corner] = o2;
parts[5 - 3 * corner] = o3;
parts[1].set_color(colors[0]);
let part1_color = parts[1].get_color();
parts[0].set_color(part1_color);
parts[3].set_color(colors[1]);
let part3_color = parts[3].get_color();
parts[2].set_color(part3_color);
parts[5].set_color(colors[2]);
let part5_color = parts[5].get_color();
parts[4].set_color(part5_color);
} else {
parts[0].set_color(colors[0]);
parts[1].set_color(colors[1]);
parts[2].set_color(colors[2]);
}
contour.edges.clear();
for part in parts.into_iter() {
contour.edges.push(part);
}
} else {
let corner_count = corners.len();
let mut major_corner_count = corner_count;
if corner_count > 3 {
corners.first_mut().unwrap().prev_edge_length_estimate += spline_length;
for i in 0..corner_count {
if corners[i].prev_edge_length_estimate
> corners[(i + 1) % corner_count].prev_edge_length_estimate
&& corners[(i + 1) % corner_count].prev_edge_length_estimate
< corners[(i + 2) % corner_count].prev_edge_length_estimate
{
corners[i].minor = true;
major_corner_count -= 1;
}
}
let mut color = EdgeColor::WHITE;
let mut initial_color = EdgeColor::BLACK;
for i in 0..corner_count {
if !corners[i].minor {
major_corner_count -= 1;
switch_color(&mut color, &mut seed, num::cast::FromPrimitive::from_usize(!major_corner_count * initial_color as usize).unwrap());
corners[i].color = color;
if initial_color != EdgeColor::BLACK {
initial_color = color;
}
}
}
for i in 0..corner_count {
if corners[i].minor {
let next_color = corners[(i + 1) % corner_count].color;
corners[i].color = num::cast::FromPrimitive::from_usize((color as usize & next_color as usize) ^ EdgeColor::WHITE as usize).unwrap();
} else {
color = corners[i].color;
}
}
let mut spline = 0;
let start = corners[0].index as usize;
let mut color = corners[0].color;
let m = contour.edges.len();
for i in 0..m {
let index = (start + i) % m;
if spline + 1 < corner_count && corners[spline + 1].index as usize == index {
spline += 1;
color = corners[spline].color;
}
contour.edges[index].set_color(color);
}
}
}
}
}
}
use crate::msdf::{edge_segment::EdgeSegment, signed_distance::SignedDistance};
#[derive(Debug, Clone, Copy)]
pub struct EdgePoint {
pub min_distance: SignedDistance,
pub near_edge: Option<EdgeSegment>,
pub near_param: f64,
}
impl EdgePoint {
// pub fn calculate_contour_color(&mut self, p: Vector2) -> f64 {
// if let Some(near_edge) = self.near_edge {
// near_edge.distance_to_pseudo_distance(&mut self.min_distance, p, self.near_param);
// }
// return self.min_distance.distance;
// }
}
use crate::msdf::{signed_distance::SignedDistance, vector::Vector2, EdgeColor};
use super::{
equation_solver::{self, fabs},
mix, non_zero_sign, EdgeSegment,
};
pub const MSDFGEN_CUBIC_SEARCH_STARTS: usize = 4;
pub const MSDFGEN_CUBIC_SEARCH_STEPS: usize = 4;
pub fn direction(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, param: f64) -> Vector2 {
let tangent = mix(
mix(p1 - p0, p2 - p1, param),
mix(p2 - p1, p3 - p2, param),
param,
);
if !tangent.is_zero() {
if param == 0.0 {
return p2 - p0;
}
if param == 1.0 {
return p3 - p1;
}
}
tangent
}
pub fn point(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, param: f64) -> Vector2 {
let p12 = mix(p1, p2, param);
mix(
mix(mix(p0, p1, param), p12, param),
mix(p12, mix(p2, p3, param), param),
param,
)
}
pub fn find_bounds(
p0: Vector2,
p1: Vector2,
p2: Vector2,
p3: Vector2,
l: &mut f64,
b: &mut f64,
r: &mut f64,
t: &mut f64,
) {
Vector2::point_bounds(p0, l, b, r, t);
Vector2::point_bounds(p3, l, b, r, t);
let a0 = p1 - p0;
let a1 = 2.0 * (p2 - p1 - a0);
let a2 = p3 - 3.0 * p2 + 3.0 * p1 - p0;
let (solutions, result) = equation_solver::solve_quadratic(a2.x, a1.x, a0.x);
for i in 0..solutions {
let par = result[i as usize];
if par > 0.0 && par < 1.0 {
Vector2::point_bounds(point(p0, p1, p2, p3, par), l, b, r, t);
}
}
let (solutions, result) = equation_solver::solve_quadratic(a2.y, a1.y, a0.y);
for i in 0..solutions {
let par = result[i as usize];
if par > 0.0 && par < 1.0 {
Vector2::point_bounds(point(p0, p1, p2, p3, par), l, b, r, t);
}
}
}
pub fn split_in_thirds(
p0: Vector2,
p1: Vector2,
p2: Vector2,
p3: Vector2,
color: EdgeColor,
) -> (EdgeSegment, EdgeSegment, EdgeSegment) {
(
EdgeSegment::new_cubic(
p0,
if p0 == p1 { p0 } else { mix(p0, p1, 1.0 / 3.0) },
mix(mix(p0, p1, 1.0 / 3.0), mix(p1, p2, 1.0 / 3.0), 1.0 / 3.0),
point(p0, p1, p2, p3, 1.0 / 3.0),
color,
),
EdgeSegment::new_cubic(
point(p0, p1, p2, p3, 1.0 / 3.0),
mix(
mix(mix(p0, p1, 1.0 / 3.0), mix(p1, p2, 1.0 / 3.0), 1.0 / 3.0),
mix(mix(p1, p2, 1.0 / 3.0), mix(p2, p3, 1.0 / 3.0), 1.0 / 3.0),
2.0 / 3.0,
),
mix(
mix(mix(p0, p1, 2.0 / 3.0), mix(p1, p2, 2.0 / 3.0), 2.0 / 3.0),
mix(mix(p1, p2, 2.0 / 3.0), mix(p2, p3, 2.0 / 3.0), 2.0 / 3.0),
1.0 / 3.0,
),
point(p0, p1, p2, p3, 2.0 / 3.0),
color,
),
EdgeSegment::new_cubic(
point(p0, p1, p2, p3, 2.0 / 3.0),
mix(mix(p1, p2, 2.0 / 3.0), mix(p2, p3, 2.0 / 3.0), 2.0 / 3.0),
if p2 == p3 { p3 } else { mix(p2, p3, 2.0 / 3.0) },
p3,
color,
),
)
}
pub fn signed_distance(
p0: Vector2,
p1: Vector2,
p2: Vector2,
p3: Vector2,
origin: Vector2,
) -> (SignedDistance, f64) {
let qa = p0 - origin;
let ab = p1 - p0;
let br = p2 - p1 - ab;
let as_ = (p3 - p2) - (p2 - p1) - br;
let mut ep_dir = direction(p0, p1, p2, p3, 0.0);
let mut min_distance = non_zero_sign(Vector2::cross_product(ep_dir, qa)) as f64 * qa.length();
let mut param = -Vector2::dot_product(qa, ep_dir) / Vector2::dot_product(ep_dir, ep_dir);
{
ep_dir = direction(p0, p1, p2, p3, 1.0);
let distance = (p3 - origin).length();
if distance.abs() < min_distance.abs() {
min_distance = non_zero_sign(Vector2::cross_product(ep_dir, p3 - origin)) as f64
* distance;
param = Vector2::dot_product(ep_dir - (p3 - origin), ep_dir)
/ Vector2::dot_product(ep_dir, ep_dir);
}
}
for i in 0..MSDFGEN_CUBIC_SEARCH_STARTS {
let mut t = (i / MSDFGEN_CUBIC_SEARCH_STARTS) as f64;
let mut qe = qa + 3.0 * t * ab + 3.0 * t * t * br + t * t *t * as_;
for _ in 0..MSDFGEN_CUBIC_SEARCH_STEPS {
let d1 = 3.0 * ab + 6.0 * t * br + 3.0 * t * t * as_;
let d2 = 6.0 * br + 6.0 * t * as_;
t -= Vector2::dot_product(qe, d1) / (Vector2::dot_product(d1, d1) + Vector2::dot_product(qe, d2));
if t < 0.0 || t > 1.0 {
break;
}
qe = qa + 3.0 * t * ab + 3.0 * t * t * br + t * t * t * as_;
let distance = qe.length();
if distance < min_distance.abs() {
min_distance = non_zero_sign(Vector2::cross_product(d1, qe)) as f64 * distance;
param = t;
}
// let qpt = point(p0, p1, p2, p3, t) - origin;
// let distance = non_zero_sign(Vector2::cross_product(direction(p0, p1, p2, p3, t), qpt))
// as f64
// * qpt.length();
// if fabs(distance) < fabs(min_distance) {
// min_distance = distance;
// param = t;
// }
// if step == MSDFGEN_CUBIC_SEARCH_STEPS {
// break;
// }
// let d1 = 3.0 * as_ * t * t + 6.0 * br * t + 3.0 * ab;
// let d2 = 6.0 * as_ * t + 6.0 * br;
// t -= Vector2::dot_product(qpt, d1)
// / (Vector2::dot_product(d1, d1) + Vector2::dot_product(qpt, d2));
// if t < 0.0 || t > 1.0 {
// break;
// }
}
}
if param >= 0.0 && param <= 1.0 {
return (SignedDistance::new(min_distance, 0.0), param);
} else if param < 0.5 {
return (
SignedDistance::new(
min_distance,
fabs(Vector2::dot_product(
direction(p0, p1, p2, p3, 0.0),
qa.normalize(false),
)),
),
param,
);
} else {
return (
SignedDistance::new(
min_distance,
fabs(Vector2::dot_product(
direction(p0, p1, p2, p3, 1.0).normalize(false),
(p3 - origin).normalize(false),
)),
),
param,
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment