blob: 9d1f1c61d243ee93e149abf78175792e4253b65c [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{
component_dirt::ComponentDirt,
core::{Core, CoreContext, Object, ObjectRef, OnAdded, Property},
dyn_vec::DynVec,
math::{self, Mat},
node::Node,
option_cell::OptionCell,
shapes::{
command_path::CommandPathBuilder, CommandPath, CubicVertex, PathVertex, PointsPath, Shape,
StraightVertex,
},
status_code::StatusCode,
Component, TransformComponent,
};
#[derive(Debug, Default)]
pub struct Path {
node: Node,
path_flags: Property<u64>,
shape: OptionCell<Object<Shape>>,
pub(crate) vertices: DynVec<Object<PathVertex>>,
command_path: OptionCell<CommandPath>,
}
impl ObjectRef<'_, Path> {
pub fn path_flags(&self) -> u64 {
self.path_flags.get()
}
pub fn set_path_flags(&self, path_flags: u64) {
self.path_flags.set(path_flags);
}
}
impl ObjectRef<'_, Path> {
pub fn shape(&self) -> Option<Object<Shape>> {
self.shape.get()
}
pub fn transform(&self) -> Mat {
if let Some(points_path) = self.try_cast::<PointsPath>() {
return points_path.transform();
}
self.cast::<TransformComponent>().world_transform()
}
pub fn push_vertex(&self, path_vertex: Object<PathVertex>) {
self.vertices.push(path_vertex);
}
pub(crate) fn vertices(&self) -> impl Iterator<Item = Object<PathVertex>> + '_ {
self.vertices.iter()
}
pub(crate) fn with_command_path(&self, f: impl FnMut(Option<&CommandPath>)) {
self.command_path.with(f);
}
pub fn mark_path_dirty(&self) {
if let Some(points_path) = self.try_cast::<PointsPath>() {
points_path.mark_path_dirty();
}
self.cast::<Component>().add_dirt(ComponentDirt::PATH, false);
if let Some(shape) = self.shape() {
shape.as_ref().path_changed();
}
}
pub fn is_path_closed(&self) -> bool {
if let Some(points_path) = self.try_cast::<PointsPath>() {
return points_path.is_path_closed();
}
true
}
pub fn build_dependencies(&self) {
self.cast::<TransformComponent>().build_dependencies();
}
pub fn on_dirty(&self, dirt: ComponentDirt) {
if let Some(shape) = self.shape() {
if Component::value_has_dirt(dirt, ComponentDirt::WORLD_TRANSFORM) {
shape.as_ref().path_changed();
}
}
}
pub fn update(&self, value: ComponentDirt) {
self.cast::<TransformComponent>().update(value);
if Component::value_has_dirt(value, ComponentDirt::PATH) {
self.command_path.set(Some(self.build_path()));
}
}
fn build_path(&self) -> CommandPath {
let mut builder = CommandPathBuilder::new();
if self.vertices.len() < 2 {
return builder.build();
}
enum Dir {
In,
Out,
}
fn path_vertex_get_or_translation(
path_vertex: ObjectRef<'_, PathVertex>,
dir: Dir,
) -> math::Vec {
path_vertex
.try_cast::<CubicVertex>()
.map(|cubic| match dir {
Dir::In => cubic.render_in(),
Dir::Out => cubic.render_out(),
})
.unwrap_or_else(|| path_vertex.render_translation())
}
let first_point = self.vertices.index(0);
let mut out;
let mut prev_is_cubic;
let start;
let start_in;
let start_is_cubic;
if let Some(cubic) = first_point.try_cast::<CubicVertex>() {
let cubic = cubic.as_ref();
prev_is_cubic = true;
start_is_cubic = true;
start_in = cubic.render_in();
out = cubic.render_out();
start = cubic.cast::<PathVertex>().render_translation();
builder.move_to(start);
} else {
prev_is_cubic = false;
start_is_cubic = false;
let point = first_point.cast::<StraightVertex>();
let point = point.as_ref();
let radius = point.radius();
if radius > 0.0 {
let prev = self.vertices.index(self.vertices.len() - 1);
let pos = point.cast::<PathVertex>().render_translation();
let to_prev = path_vertex_get_or_translation(prev.as_ref(), Dir::Out) - pos;
let to_prev_length = to_prev.length();
let to_prev = to_prev * to_prev_length.recip();
let next = self.vertices.index(1);
let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos;
let to_next_length = to_next.length();
let to_next = to_next * to_next_length.recip();
let render_radius = to_prev_length.min(to_next_length.min(radius));
let translation = pos + to_prev * render_radius;
start = translation;
start_in = translation;
builder.move_to(translation);
let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
out = pos + to_next * render_radius;
builder.cubic_to(out_point, in_point, out);
} else {
let translation = point.cast::<PathVertex>().render_translation();
start = translation;
start_in = translation;
out = translation;
builder.move_to(translation);
}
}
for (i, vertex) in self.vertices.iter().enumerate().skip(1) {
let vertex = vertex.as_ref();
if let Some(cubic) = vertex.try_cast::<CubicVertex>() {
let in_point = cubic.render_in();
let translation = vertex.render_translation();
builder.cubic_to(out, in_point, translation);
prev_is_cubic = true;
out = cubic.render_out();
} else {
let point = vertex.cast::<StraightVertex>();
let pos = vertex.render_translation();
let radius = point.radius();
if radius > 0.0 {
let to_prev = out - pos;
let to_prev_length = to_prev.length();
let to_prev = to_prev * to_prev_length.recip();
let next = self.vertices.index((i + 1) % self.vertices.len());
let to_next = path_vertex_get_or_translation(next.as_ref(), Dir::In) - pos;
let to_next_length = to_next.length();
let to_next = to_next * to_next_length.recip();
let render_radius = to_prev_length.min(to_next_length.min(radius));
let translation = pos + to_prev * render_radius;
if prev_is_cubic {
builder.cubic_to(out, translation, translation);
} else {
builder.line_to(translation);
}
let out_point = pos + to_prev * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
let in_point = pos + to_next * (1.0 - math::CIRCLE_CONSTANT) * render_radius;
out = pos + to_next * render_radius;
builder.cubic_to(out_point, in_point, out);
prev_is_cubic = false;
} else if prev_is_cubic {
builder.cubic_to(out, pos, pos);
prev_is_cubic = false;
out = pos;
} else {
builder.line_to(pos);
out = pos;
}
}
}
if self.is_path_closed() {
if prev_is_cubic || start_is_cubic {
builder.cubic_to(out, start_in, start);
}
builder.close();
}
builder.build()
}
}
impl Core for Path {
parent_types![(node, Node)];
properties![(128, path_flags, set_path_flags), node];
}
impl OnAdded for ObjectRef<'_, Path> {
on_added!([on_added_dirty, import], Node);
fn on_added_clean(&self, context: &dyn CoreContext) -> StatusCode {
let code = self.cast::<Node>().on_added_clean(context);
if code != StatusCode::Ok {
return code;
}
for parent in self.cast::<Component>().parents() {
if let Some(shape) = parent.try_cast::<Shape>() {
self.shape.set(Some(shape.clone()));
shape.as_ref().push_path(self.as_object());
return StatusCode::Ok;
}
}
StatusCode::MissingObject
}
}