From bf3779bf89eb922d4b50cd73a7dbbc90c45a2361 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 27 Oct 2017 15:12:33 -0700 Subject: [PATCH] Calculate normals for B-vertices. I'm planning to use this for fixing hairlines in XCAA. --- demo/client/src/mesh-debugger.ts | 77 +++++--- demo/client/src/meshes.ts | 6 + partitioner/src/lib.rs | 2 +- partitioner/src/mesh_library.rs | 19 +- partitioner/src/{bold.rs => normal.rs} | 57 +++--- partitioner/src/partitioner.rs | 241 +++++++++++++++++++------ 6 files changed, 302 insertions(+), 100 deletions(-) rename partitioner/src/{bold.rs => normal.rs} (66%) diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts index d9cbfa82..5747d701 100644 --- a/demo/client/src/mesh-debugger.ts +++ b/demo/client/src/mesh-debugger.ts @@ -39,7 +39,11 @@ const SEGMENT_POINT_RADIUS: number = 3.0; const SEGMENT_STROKE_WIDTH: number = 1.0; const SEGMENT_CONTROL_POINT_STROKE_WIDTH: number = 1.0; -const NORMAL_LENGTH: number = 14.0; +const NORMAL_LENGTHS: NormalStyleParameter = { + bVertex: 10.0, + edge: 14.0, +}; + const NORMAL_ARROWHEAD_LENGTH: number = 4.0; const NORMAL_ARROWHEAD_ANGLE: number = Math.PI * 5.0 / 6.0; @@ -48,9 +52,13 @@ const SEGMENT_POINT_FILL_STYLE: string = "rgb(0, 0, 128)"; const LIGHT_STROKE_STYLE: string = "rgb(192, 192, 192)"; const LINE_STROKE_STYLE: string = "rgb(0, 128, 0)"; const CURVE_STROKE_STYLE: string = "rgb(128, 0, 0)"; -const NORMAL_STROKE_STYLE: string = '#cc5500'; const SEGMENT_CONTROL_POINT_STROKE_STYLE: string = "rgb(0, 0, 128)"; +const NORMAL_STROKE_STYLES: NormalStyleParameter = { + bVertex: '#e6aa00', + edge: '#cc5500', +}; + const BUILTIN_URIS = { font: BUILTIN_FONT_URI, svg: BUILTIN_SVG_URI, @@ -60,6 +68,13 @@ const SVG_SCALE: number = 1.0; type FileType = 'font' | 'svg'; +type NormalType = 'edge' | 'bVertex'; + +interface NormalStyleParameter { + edge: T; + bVertex: T; +} + interface NormalsTable { lowerCurve: T; lowerLine: T; @@ -245,6 +260,7 @@ class MeshDebuggerView extends PathfinderView { const bQuads = new Uint32Array(meshes.bQuads); const positions = new Float32Array(meshes.bVertexPositions); + const bVertexNormals = new Float32Array(meshes.bVertexNormals); const normals: NormalsTable = { lowerCurve: new Float32Array(0), @@ -301,44 +317,54 @@ class MeshDebuggerView extends PathfinderView { invScaleFactor); context.beginPath(); - context.moveTo(upperLeftPosition[0], upperLeftPosition[1]); + context.moveTo(upperLeftPosition[0], -upperLeftPosition[1]); if (upperControlPointPosition != null) { context.strokeStyle = CURVE_STROKE_STYLE; context.quadraticCurveTo(upperControlPointPosition[0], - upperControlPointPosition[1], + -upperControlPointPosition[1], upperRightPosition[0], - upperRightPosition[1]); + -upperRightPosition[1]); } else { context.strokeStyle = LINE_STROKE_STYLE; - context.lineTo(upperRightPosition[0], upperRightPosition[1]); + context.lineTo(upperRightPosition[0], -upperRightPosition[1]); } context.stroke(); context.strokeStyle = LIGHT_STROKE_STYLE; context.beginPath(); - context.moveTo(upperRightPosition[0], upperRightPosition[1]); - context.lineTo(lowerRightPosition[0], lowerRightPosition[1]); + context.moveTo(upperRightPosition[0], -upperRightPosition[1]); + context.lineTo(lowerRightPosition[0], -lowerRightPosition[1]); context.stroke(); context.beginPath(); - context.moveTo(lowerRightPosition[0], lowerRightPosition[1]); + context.moveTo(lowerRightPosition[0], -lowerRightPosition[1]); if (lowerControlPointPosition != null) { context.strokeStyle = CURVE_STROKE_STYLE; context.quadraticCurveTo(lowerControlPointPosition[0], - lowerControlPointPosition[1], + -lowerControlPointPosition[1], lowerLeftPosition[0], - lowerLeftPosition[1]); + -lowerLeftPosition[1]); } else { context.strokeStyle = LINE_STROKE_STYLE; - context.lineTo(lowerLeftPosition[0], lowerLeftPosition[1]); + context.lineTo(lowerLeftPosition[0], -lowerLeftPosition[1]); } context.stroke(); context.strokeStyle = LIGHT_STROKE_STYLE; context.beginPath(); - context.moveTo(lowerLeftPosition[0], lowerLeftPosition[1]); - context.lineTo(upperLeftPosition[0], upperLeftPosition[1]); + context.moveTo(lowerLeftPosition[0], -lowerLeftPosition[1]); + context.lineTo(upperLeftPosition[0], -upperLeftPosition[1]); context.stroke(); + + // Draw B-quad normals. + const lowerLeftNormal = bVertexNormals[lowerLeftIndex]; + const lowerRightNormal = bVertexNormals[lowerRightIndex]; + const upperLeftNormal = bVertexNormals[upperLeftIndex]; + const upperRightNormal = bVertexNormals[upperRightIndex]; + drawNormal(context, lowerLeftPosition, lowerLeftNormal, invScaleFactor, 'bVertex'); + drawNormal(context, lowerRightPosition, lowerRightNormal, invScaleFactor, 'bVertex'); + drawNormal(context, upperLeftPosition, upperLeftNormal, invScaleFactor, 'bVertex'); + drawNormal(context, upperRightPosition, upperRightNormal, invScaleFactor, 'bVertex'); } // Draw segments. @@ -363,10 +389,10 @@ class MeshDebuggerView extends PathfinderView { } } -function getPosition(positions: Float32Array, vertexIndex: number): Float32Array | null { +function getPosition(positions: Float32Array, vertexIndex: number): glmatrix.vec2 | null { if (vertexIndex === UINT32_MAX) return null; - return new Float32Array([positions[vertexIndex * 2 + 0], -positions[vertexIndex * 2 + 1]]); + return glmatrix.vec2.clone([positions[vertexIndex * 2 + 0], positions[vertexIndex * 2 + 1]]); } function getNormals(normals: NormalsTable, @@ -426,10 +452,10 @@ function drawSegmentVertices(context: CanvasRenderingContext2D, if (controlPoint != null) drawSegmentControlPoint(context, controlPoint, invScaleFactor); - drawNormal(context, position0, normal0, invScaleFactor); - drawNormal(context, position1, normal1, invScaleFactor); + drawNormal(context, position0, normal0, invScaleFactor, 'edge'); + drawNormal(context, position1, normal1, invScaleFactor, 'edge'); if (controlPoint != null && normalControlPoint != null) - drawNormal(context, controlPoint, normalControlPoint, invScaleFactor); + drawNormal(context, controlPoint, normalControlPoint, invScaleFactor, 'edge'); } } @@ -443,15 +469,15 @@ function drawVertexIfNecessary(context: CanvasRenderingContext2D, markedVertices[vertexIndex] = true; context.beginPath(); - context.moveTo(position[0], position[1]); - context.arc(position[0], position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI); + context.moveTo(position[0], -position[1]); + context.arc(position[0], -position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI); context.fill(); context.save(); context.scale(invScaleFactor, invScaleFactor); context.fillText("" + vertexIndex, position[0] / invScaleFactor + POINT_LABEL_OFFSET[0], - position[1] / invScaleFactor + POINT_LABEL_OFFSET[1]); + -position[1] / invScaleFactor + POINT_LABEL_OFFSET[1]); context.restore(); } @@ -490,14 +516,15 @@ function drawSegmentControlPoint(context: CanvasRenderingContext2D, function drawNormal(context: CanvasRenderingContext2D, position: glmatrix.vec2, normalAngle: number, - invScaleFactor: number) { - const length = invScaleFactor * NORMAL_LENGTH; + invScaleFactor: number, + normalType: NormalType) { + const length = invScaleFactor * NORMAL_LENGTHS[normalType]; const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH; const endpoint = glmatrix.vec2.clone([position[0] + length * Math.cos(normalAngle), -position[1] + length * Math.sin(normalAngle)]); context.save(); - context.strokeStyle = NORMAL_STROKE_STYLE; + context.strokeStyle = NORMAL_STROKE_STYLES[normalType]; context.beginPath(); context.moveTo(position[0], -position[1]); context.lineTo(endpoint[0], endpoint[1]); diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index f7f283bb..f8701679 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -65,6 +65,7 @@ const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE; const MESH_TYPES: Meshes = { bQuads: { type: 'Uint32', size: B_QUAD_FIELD_COUNT }, bVertexLoopBlinnData: { type: 'Uint32', size: 1 }, + bVertexNormals: { type: 'Float32', size: 1 }, bVertexPathIDs: { type: 'Uint16', size: 1 }, bVertexPositions: { type: 'Float32', size: 2 }, coverCurveIndices: { type: 'Uint32', size: 1 }, @@ -90,6 +91,7 @@ const MESH_TYPES: Meshes = { const BUFFER_TYPES: Meshes = { bQuads: 'ARRAY_BUFFER', bVertexLoopBlinnData: 'ARRAY_BUFFER', + bVertexNormals: 'ARRAY_BUFFER', bVertexPathIDs: 'ARRAY_BUFFER', bVertexPositions: 'ARRAY_BUFFER', coverCurveIndices: 'ELEMENT_ARRAY_BUFFER', @@ -122,6 +124,7 @@ const MESH_LIBRARY_FOURCC: string = 'PFML'; const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { bqua: 'bQuads', bvlb: 'bVertexLoopBlinnData', + bvno: 'bVertexNormals', bvpi: 'bVertexPathIDs', bvpo: 'bVertexPositions', cvci: 'coverCurveIndices', @@ -151,6 +154,7 @@ export interface Meshes { readonly bVertexPositions: T; readonly bVertexPathIDs: T; readonly bVertexLoopBlinnData: T; + readonly bVertexNormals: T; readonly coverInteriorIndices: T; readonly coverCurveIndices: T; readonly edgeBoundingBoxPathIDs: T; @@ -176,6 +180,7 @@ export class PathfinderMeshData implements Meshes { readonly bVertexPositions: ArrayBuffer; readonly bVertexPathIDs: ArrayBuffer; readonly bVertexLoopBlinnData: ArrayBuffer; + readonly bVertexNormals: ArrayBuffer; readonly coverInteriorIndices: ArrayBuffer; readonly coverCurveIndices: ArrayBuffer; readonly edgeBoundingBoxPathIDs: ArrayBuffer; @@ -357,6 +362,7 @@ export class PathfinderMeshBuffers implements Meshes { readonly bVertexPositions: WebGLBuffer; readonly bVertexPathIDs: WebGLBuffer; readonly bVertexLoopBlinnData: WebGLBuffer; + readonly bVertexNormals: WebGLBuffer; readonly coverInteriorIndices: WebGLBuffer; readonly coverCurveIndices: WebGLBuffer; readonly edgeBoundingBoxPathIDs: WebGLBuffer; diff --git a/partitioner/src/lib.rs b/partitioner/src/lib.rs index aa5e52f2..7cc8a0fb 100644 --- a/partitioner/src/lib.rs +++ b/partitioner/src/lib.rs @@ -29,7 +29,7 @@ pub mod capi; pub mod mesh_library; pub mod partitioner; -mod bold; +mod normal; #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] diff --git a/partitioner/src/mesh_library.rs b/partitioner/src/mesh_library.rs index 726556b2..233f5d7c 100644 --- a/partitioner/src/mesh_library.rs +++ b/partitioner/src/mesh_library.rs @@ -17,7 +17,7 @@ use std::io::{self, ErrorKind, Seek, SeekFrom, Write}; use std::ops::Range; use std::u32; -use bold; +use normal; use {BQuad, BVertexLoopBlinnData}; #[derive(Debug, Clone)] @@ -26,6 +26,7 @@ pub struct MeshLibrary { pub b_vertex_positions: Vec>, pub b_vertex_path_ids: Vec, pub b_vertex_loop_blinn_data: Vec, + pub b_vertex_normals: Vec, pub cover_indices: MeshLibraryCoverIndices, pub edge_data: MeshLibraryEdgeData, pub segments: MeshLibrarySegments, @@ -40,6 +41,7 @@ impl MeshLibrary { b_vertex_positions: vec![], b_vertex_path_ids: vec![], b_vertex_loop_blinn_data: vec![], + b_vertex_normals: vec![], cover_indices: MeshLibraryCoverIndices::new(), edge_data: MeshLibraryEdgeData::new(), segments: MeshLibrarySegments::new(), @@ -52,12 +54,24 @@ impl MeshLibrary { self.b_vertex_positions.clear(); self.b_vertex_path_ids.clear(); self.b_vertex_loop_blinn_data.clear(); + self.b_vertex_normals.clear(); self.cover_indices.clear(); self.edge_data.clear(); self.segments.clear(); self.segment_normals.clear(); } + pub(crate) fn add_b_vertex(&mut self, + position: &Point2D, + path_id: u16, + loop_blinn_data: &BVertexLoopBlinnData, + normal: f32) { + self.b_vertex_positions.push(*position); + self.b_vertex_path_ids.push(path_id); + self.b_vertex_loop_blinn_data.push(*loop_blinn_data); + self.b_vertex_normals.push(normal); + } + pub(crate) fn add_b_quad(&mut self, b_quad: &BQuad) { self.b_quads.push(*b_quad); @@ -176,7 +190,7 @@ impl MeshLibrary { /// Computes vertex normals necessary for emboldening and/or stem darkening. pub fn push_normals(&mut self, stream: I) where I: Iterator { - bold::push_normals(self, stream) + normal::push_normals(self, stream) } /// Writes this mesh library to a RIFF file. @@ -195,6 +209,7 @@ impl MeshLibrary { try!(write_chunk(writer, b"bvpo", &self.b_vertex_positions)); try!(write_chunk(writer, b"bvpi", &self.b_vertex_path_ids)); try!(write_chunk(writer, b"bvlb", &self.b_vertex_loop_blinn_data)); + try!(write_chunk(writer, b"bvno", &self.b_vertex_normals)); try!(write_chunk(writer, b"cvii", &self.cover_indices.interior_indices)); try!(write_chunk(writer, b"cvci", &self.cover_indices.curve_indices)); try!(write_chunk(writer, b"ebbv", &self.edge_data.bounding_box_vertex_positions)); diff --git a/partitioner/src/bold.rs b/partitioner/src/normal.rs similarity index 66% rename from partitioner/src/bold.rs rename to partitioner/src/normal.rs index 6083e0e1..5ab0ac4a 100644 --- a/partitioner/src/bold.rs +++ b/partitioner/src/normal.rs @@ -1,4 +1,4 @@ -// pathfinder/partitioner/src/bold.rs +// pathfinder/partitioner/src/normal.rs // // Copyright © 2017 The Pathfinder Project Developers. // @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Infrastructure to enable on-GPU emboldening and stem darkening. +//! Utility functions for vertex normals. use euclid::{Point2D, Vector2D}; use pathfinder_path_utils::{PathCommand, PathSegment, PathSegmentStream}; @@ -48,20 +48,20 @@ pub fn push_normals(library: &mut MeshLibrary, stream: I) }; } - let prev_vector = match prev_segment { - PathSegment::Line(endpoint_0, endpoint_1) => endpoint_1 - endpoint_0, - PathSegment::Curve(_, control_point, endpoint_1) => endpoint_1 - control_point, - }; - let next_vector = match next_segment { - PathSegment::Line(endpoint_0, endpoint_1) => endpoint_1 - endpoint_0, - PathSegment::Curve(endpoint_0, control_point, _) => control_point - endpoint_0, + let next_vertex_normal = match (&prev_segment, &next_segment) { + (&PathSegment::Line(ref prev_endpoint, ref vertex_endpoint), + &PathSegment::Line(_, ref next_endpoint)) | + (&PathSegment::Curve(_, ref prev_endpoint, ref vertex_endpoint), + &PathSegment::Line(_, ref next_endpoint)) | + (&PathSegment::Line(ref prev_endpoint, ref vertex_endpoint), + &PathSegment::Curve(_, ref next_endpoint, _)) | + (&PathSegment::Curve(_, ref prev_endpoint, ref vertex_endpoint), + &PathSegment::Curve(_, ref next_endpoint, _)) => { + calculate_vertex_normal(prev_endpoint, vertex_endpoint, next_endpoint) + } }; - let prev_edge_normal = Vector2D::new(-prev_vector.y, prev_vector.x).normalize(); - let next_edge_normal = Vector2D::new(-next_vector.y, next_vector.x).normalize(); - - let next_vertex_normal = (prev_edge_normal + next_edge_normal) * 0.5; - let next_vertex_angle = (-next_vertex_normal.y).atan2(next_vertex_normal.x); + let next_vertex_angle = calculate_normal_angle(&next_vertex_normal); let prev_vertex_angle = if !is_first_segment { match index_of_prev_segment.unwrap() { @@ -86,14 +86,12 @@ pub fn push_normals(library: &mut MeshLibrary, stream: I) endpoint_1: next_vertex_angle, }); } - PathSegment::Curve(endpoint_0, control_point, _) => { - let prev_prev_vector = control_point - endpoint_0; - let prev_prev_edge_normal = Vector2D::new(-prev_prev_vector.y, - prev_prev_vector.x).normalize(); - - let control_point_vertex_normal = (prev_prev_edge_normal + prev_edge_normal) * 0.5; + PathSegment::Curve(endpoint_0, control_point, endpoint_1) => { + let control_point_vertex_normal = calculate_vertex_normal(&endpoint_0, + &control_point, + &endpoint_1); let control_point_vertex_angle = - (-control_point_vertex_normal.y).atan2(control_point_vertex_normal.x); + calculate_normal_angle(&control_point_vertex_normal); index_of_prev_segment = Some(SegmentIndex::Curve(library.segment_normals.curve_normals.len())); @@ -120,6 +118,23 @@ pub fn push_normals(library: &mut MeshLibrary, stream: I) } } +pub fn calculate_vertex_normal(prev_position: &Point2D, + vertex_position: &Point2D, + next_position: &Point2D) + -> Vector2D { + let prev_edge_vector = *vertex_position - *prev_position; + let next_edge_vector = *next_position - *vertex_position; + + let prev_edge_normal = Vector2D::new(-prev_edge_vector.y, prev_edge_vector.x).normalize(); + let next_edge_normal = Vector2D::new(-next_edge_vector.y, next_edge_vector.x).normalize(); + + (prev_edge_normal + next_edge_normal) * 0.5 +} + +pub fn calculate_normal_angle(normal: &Vector2D) -> f32 { + (-normal.y).atan2(normal.x) +} + #[derive(Clone, Copy, Debug)] enum SegmentIndex { Line(usize), diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index 594c8c04..09ffb2cb 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -10,7 +10,7 @@ use bit_vec::BitVec; use euclid::approxeq::ApproxEq; -use euclid::Point2D; +use euclid::{Point2D, Vector2D}; use log::LogLevel; use pathfinder_path_utils::PathBuffer; use pathfinder_path_utils::curve::Curve; @@ -19,9 +19,11 @@ use std::collections::BinaryHeap; use std::cmp::Ordering; use std::f32; use std::iter; +use std::ops::{Add, AddAssign}; use std::u32; use mesh_library::{MeshLibrary, MeshLibraryIndexRanges}; +use normal; use {BQuad, BVertexLoopBlinnData, BVertexKind, Endpoint, FillRule, Subpath}; const MAX_B_QUAD_SUBDIVISIONS: u8 = 8; @@ -38,6 +40,7 @@ pub struct Partitioner<'a> { heap: BinaryHeap, visited_points: BitVec, active_edges: Vec, + vertex_normals: Vec, path_id: u16, } @@ -56,6 +59,7 @@ impl<'a> Partitioner<'a> { heap: BinaryHeap::new(), visited_points: BitVec::new(), active_edges: vec![], + vertex_normals: vec![], path_id: 0, } } @@ -112,10 +116,14 @@ impl<'a> Partitioner<'a> { while self.process_next_point() {} - debug_assert!(self.library.b_vertex_loop_blinn_data.len() == - self.library.b_vertex_path_ids.len()); - debug_assert!(self.library.b_vertex_loop_blinn_data.len() == - self.library.b_vertex_positions.len()); + self.write_normals_to_library(); + + debug_assert_eq!(self.library.b_vertex_loop_blinn_data.len(), + self.library.b_vertex_path_ids.len()); + debug_assert_eq!(self.library.b_vertex_loop_blinn_data.len(), + self.library.b_vertex_positions.len()); + debug_assert_eq!(self.library.b_vertex_loop_blinn_data.len(), + self.library.b_vertex_normals.len()); let end_lengths = self.library.snapshot_lengths(); MeshLibraryIndexRanges::new(&start_lengths, &end_lengths) @@ -209,10 +217,11 @@ impl<'a> Partitioner<'a> { active_edge.left_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; active_edge.control_point_vertex_index = active_edge.left_vertex_index + 1; - self.library.b_vertex_positions.push(endpoint_position); - self.library.b_vertex_path_ids.push(self.path_id); - self.library.b_vertex_loop_blinn_data.push(BVertexLoopBlinnData::new( - active_edge.endpoint_kind())); + // FIXME(pcwalton): Normal + self.library.add_b_vertex(&endpoint_position, + self.path_id, + &BVertexLoopBlinnData::new(active_edge.endpoint_kind()), + 0.0); active_edge.toggle_parity(); } @@ -251,9 +260,12 @@ impl<'a> Partitioner<'a> { &control_point_position, &new_point.position, bottom); - self.library.b_vertex_positions.push(*control_point_position); - self.library.b_vertex_path_ids.push(self.path_id); - self.library.b_vertex_loop_blinn_data.push(control_point_b_vertex_loop_blinn_data); + + // FIXME(pcwalton): Normal + self.library.add_b_vertex(control_point_position, + self.path_id, + &control_point_b_vertex_loop_blinn_data, + 0.0); } } } @@ -315,6 +327,25 @@ impl<'a> Partitioner<'a> { } } + fn write_normals_to_library(&mut self) { + for (b_vertex_index, vertex_normal) in self.vertex_normals.iter().enumerate() { + debug_assert!(b_vertex_index <= self.library.b_vertex_normals.len()); + + let angle = vertex_normal.angle(); + if b_vertex_index == self.library.b_vertex_normals.len() { + self.library.b_vertex_normals.push(angle) + } else { + self.library.b_vertex_normals[b_vertex_index as usize] = angle + } + } + + let remaining_b_vertex_count = self.library.b_vertex_positions.len() - + self.library.b_vertex_normals.len(); + if remaining_b_vertex_count > 0 { + self.library.b_vertex_normals.extend(iter::repeat(0.0).take(remaining_b_vertex_count)) + } + } + fn add_new_edges_for_min_point(&mut self, endpoint_index: u32, next_active_edge_index: u32) { // FIXME(pcwalton): This is twice as slow as it needs to be. self.active_edges.insert(next_active_edge_index as usize, ActiveEdge::default()); @@ -330,11 +361,12 @@ impl<'a> Partitioner<'a> { new_active_edges[0].left_vertex_index = left_vertex_index; new_active_edges[1].left_vertex_index = left_vertex_index; + // FIXME(pcwalton): Normal let position = self.endpoints[endpoint_index as usize].position; - self.library.b_vertex_positions.push(position); - self.library.b_vertex_path_ids.push(self.path_id); - self.library.b_vertex_loop_blinn_data - .push(BVertexLoopBlinnData::new(BVertexKind::Endpoint0)); + self.library.add_b_vertex(&position, + self.path_id, + &BVertexLoopBlinnData::new(BVertexKind::Endpoint0), + 0.0); new_active_edges[0].toggle_parity(); new_active_edges[1].toggle_parity(); @@ -382,9 +414,12 @@ impl<'a> Partitioner<'a> { &control_point_position, &right_vertex_position, false); - self.library.b_vertex_positions.push(control_point_position); - self.library.b_vertex_path_ids.push(self.path_id); - self.library.b_vertex_loop_blinn_data.push(control_point_b_vertex_loop_blinn_data); + + // FIXME(pcwalton): Normal + self.library.add_b_vertex(&control_point_position, + self.path_id, + &control_point_b_vertex_loop_blinn_data, + 0.0) } } @@ -403,9 +438,12 @@ impl<'a> Partitioner<'a> { &control_point_position, &right_vertex_position, true); - self.library.b_vertex_positions.push(control_point_position); - self.library.b_vertex_path_ids.push(self.path_id); - self.library.b_vertex_loop_blinn_data.push(control_point_b_vertex_loop_blinn_data); + + // FIXME(pcwalton): Normal + self.library.add_b_vertex(&control_point_position, + self.path_id, + &control_point_b_vertex_loop_blinn_data, + 0.0) } } } @@ -737,12 +775,16 @@ impl<'a> Partitioner<'a> { } } - self.library.add_b_quad(&BQuad::new(upper_subdivision.left_curve_left, - upper_subdivision.left_curve_control_point, - upper_subdivision.middle_point, - lower_subdivision.left_curve_left, - lower_subdivision.left_curve_control_point, - lower_subdivision.middle_point)) + let b_quad = BQuad::new(upper_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + lower_subdivision.left_curve_left, + lower_subdivision.left_curve_control_point, + lower_subdivision.middle_point); + + self.update_vertex_normals_for_new_b_quad(&b_quad); + + self.library.add_b_quad(&b_quad); } fn subdivide_active_edge_again_at_t(&mut self, @@ -779,6 +821,8 @@ impl<'a> Partitioner<'a> { bottom), ].into_iter()); + // FIXME(pcwalton): Normal + (SubdividedActiveEdge { left_curve_left: subdivision.left_curve_left, left_curve_control_point: left_control_point_index, @@ -1001,14 +1045,15 @@ impl<'a> Partitioner<'a> { let path_id = self.library.b_vertex_path_ids[left_curve_left as usize]; let right_point = self.endpoints[active_edge.right_endpoint_index as usize] .position; - let middle_point = left_point_position.to_vector().lerp(right_point.to_vector(), t); + let middle_point = left_point_position.to_vector() + .lerp(right_point.to_vector(), t); + // FIXME(pcwalton): Normal active_edge.left_vertex_index = self.library.b_vertex_loop_blinn_data.len() as u32; - self.library.b_vertex_positions.push(middle_point.to_point()); - self.library.b_vertex_path_ids.push(path_id); - self.library - .b_vertex_loop_blinn_data - .push(BVertexLoopBlinnData::new(active_edge.endpoint_kind())); + self.library.add_b_vertex(&middle_point.to_point(), + path_id, + &BVertexLoopBlinnData::new(active_edge.endpoint_kind()), + 0.0); left_curve_control_point_vertex_index = u32::MAX; } @@ -1030,23 +1075,27 @@ impl<'a> Partitioner<'a> { active_edge.left_vertex_index = left_curve_control_point_vertex_index + 1; active_edge.control_point_vertex_index = left_curve_control_point_vertex_index + 2; - self.library.b_vertex_positions.extend([ - left_curve.control_point, - left_curve.endpoints[1], - right_curve.control_point, - ].into_iter()); - self.library.b_vertex_path_ids.extend(iter::repeat(self.path_id).take(3)); - self.library.b_vertex_loop_blinn_data.extend([ - BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], - &left_curve.control_point, - &left_curve.endpoints[1], - bottom), - BVertexLoopBlinnData::new(active_edge.endpoint_kind()), - BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], - &right_curve.control_point, - &right_curve.endpoints[1], - bottom), - ].into_iter()); + // FIXME(pcwalton): Normals + self.library + .add_b_vertex(&left_curve.control_point, + self.path_id, + &BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], + &left_curve.control_point, + &left_curve.endpoints[1], + bottom), + 0.0); + self.library.add_b_vertex(&left_curve.endpoints[1], + self.path_id, + &BVertexLoopBlinnData::new(active_edge.endpoint_kind()), + 0.0); + self.library + .add_b_vertex(&right_curve.control_point, + self.path_id, + &BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], + &right_curve.control_point, + &right_curve.endpoints[1], + bottom), + 0.0); } } @@ -1057,6 +1106,55 @@ impl<'a> Partitioner<'a> { } } + fn update_vertex_normals_for_new_b_quad(&mut self, b_quad: &BQuad) { + self.update_vertex_normal_for_b_quad_edge(b_quad.upper_left_vertex_index, + b_quad.upper_control_point_vertex_index, + b_quad.upper_right_vertex_index); + self.update_vertex_normal_for_b_quad_edge(b_quad.lower_right_vertex_index, + b_quad.lower_control_point_vertex_index, + b_quad.lower_left_vertex_index); + } + + fn update_vertex_normal_for_b_quad_edge(&mut self, + prev_vertex_index: u32, + control_point_vertex_index: u32, + next_vertex_index: u32) { + if control_point_vertex_index == u32::MAX { + let normal_vector = self.calculate_normal_for_edge(prev_vertex_index, + next_vertex_index); + self.update_normal_for_vertex(prev_vertex_index, &normal_vector); + self.update_normal_for_vertex(next_vertex_index, &normal_vector); + return + } + + let prev_normal_vector = self.calculate_normal_for_edge(prev_vertex_index, + control_point_vertex_index); + let next_normal_vector = self.calculate_normal_for_edge(control_point_vertex_index, + next_vertex_index); + self.update_normal_for_vertex(prev_vertex_index, &prev_normal_vector); + self.update_normal_for_vertex(control_point_vertex_index, &prev_normal_vector); + self.update_normal_for_vertex(control_point_vertex_index, &next_normal_vector); + self.update_normal_for_vertex(next_vertex_index, &next_normal_vector); + } + + fn update_normal_for_vertex(&mut self, vertex_index: u32, normal_vector: &VertexNormal) { + let vertex_normal_count = self.vertex_normals.len(); + if vertex_index as usize >= vertex_normal_count { + let new_vertex_normal_count = vertex_index as usize - vertex_normal_count + 1; + self.vertex_normals + .extend(iter::repeat(VertexNormal::zero()).take(new_vertex_normal_count)); + } + + self.vertex_normals[vertex_index as usize] += *normal_vector + } + + fn calculate_normal_for_edge(&self, left_vertex_index: u32, right_vertex_index: u32) + -> VertexNormal { + let left_vertex_position = &self.library.b_vertex_positions[left_vertex_index as usize]; + let right_vertex_position = &self.library.b_vertex_positions[right_vertex_index as usize]; + VertexNormal::new(left_vertex_position, right_vertex_position) + } + fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 { let endpoint = &self.endpoints[endpoint_index as usize]; let subpath = &self.subpaths[endpoint.subpath_index as usize]; @@ -1257,3 +1355,44 @@ enum SubdivisionType { Inside, Lower, } + +/// TODO(pcwalton): This could possibly be improved: +/// https://en.wikipedia.org/wiki/Mean_of_circular_quantities +#[derive(Debug, Clone, Copy)] +struct VertexNormal { + sum: Vector2D, +} + +impl VertexNormal { + fn new(vertex_a: &Point2D, vertex_b: &Point2D) -> VertexNormal { + let vector = *vertex_a - *vertex_b; + VertexNormal { + sum: Vector2D::new(-vector.y, vector.x).normalize(), + } + } + + fn zero() -> VertexNormal { + VertexNormal { + sum: Vector2D::zero(), + } + } + + fn angle(&self) -> f32 { + normal::calculate_normal_angle(&self.sum) + } +} + +impl Add for VertexNormal { + type Output = VertexNormal; + fn add(self, rhs: VertexNormal) -> VertexNormal { + VertexNormal { + sum: self.sum + rhs.sum, + } + } +} + +impl AddAssign for VertexNormal { + fn add_assign(&mut self, rhs: VertexNormal) { + *self = *self + rhs + } +}