Skip to content

Refactor vertex functions to enable composite paths #6560

Closed
@GregStanton

Description

@GregStanton

Increasing Access

This enhancement will provide greater access to individuals in multiple circumstances.

Code contributors:
For volunteers who have less experience with large codebases or less free time to navigate them, it will become feasible to contribute to vertex-related features. For example, there is community support for a new arcVertex() function, but the complexity of the current codebase has impeded progress.

Users:
For users who want to make composite shapes, the existing p5 API will work as they expect it to, so they will face less confusion. A knowledge barrier will also be removed, since they won't need to learn the native canvas API or SVG in order to make composite shapes.

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build Process
  • Unit Testing
  • Internalization
  • Friendly Errors
  • Other (specify if possible)

Feature enhancement details

Problem

Mixing path primitives

Users expect to be able to create composite paths with a mix of path primitives (line segments, quadratic Bézier curves, cubic Bézier curves, and Catmull-Rom splines). The p5 reference does not indicate that general mixing is not possible, and it's already possible with native canvas paths and SVG paths.

However, the current p5 implementation assumes shapes include at most one type of nonlinear path. For example, see the 2D renderer's endShape() method. This limitation leads to unexpected behavior in a range of cases. A couple of these are illustrated below.

Expected shape Actual result
Quadratic and cubic Béziers
Lines and Catmull-Rom splines

Complexity of the codebase

With the current implementation, it will be difficult to support mixing vertex types (e.g. #906), to track down bugs (e.g. #6555), and to implement new features (e.g. #6459). The difficulty arises from specific functions as well as broader code organization:

Solution

Separation of concerns

We can solve many of these problems at once by separating concerns. The idea is to create classes for the different vertex types, each with their own data and their own methods that add them to the canvas. Then vertex(), quadraticVertex(), etc. just push vertex instances of the appropriate type into a path array. When endShape() is called, it simply loops over the vertex objects, and they add themselves to the canvas; endShape() doesn't need to know that curveVertex() requires special logic, for example. By refactoring in this way, mixing path primitives is essentially automatic, with no change to the public p5 API.

Proof of concept

I put together a proof of concept in the p5 Web Editor; it includes an implementation of arcVertex() as proposed in issue #6459. A separate sketch shows how it fixes unexpected mixing behavior. With these two enhancements together (arcVertex() and composite paths), p5's beginShape()/endShape() will be able to produce composite paths with all the basic primitives of the native canvas API and SVG, plus Catmull-Rom splines. Based on some discussion, it looks like it will also help with the bug observed in issue #6555. Although the proof of concept focuses on the 2D case, the goal is to accommodate the 3D case as well, with some adjustments.

Update: Task list

Calling all volunteers!

@davepagurek, @limzykenneth, @xanderjl, @Gaurav-1306, @Jaivignesh-afk, @sudhanshuv1, @capGoblin, @lindapaiste

Based on discussions here, in the online meeting, and on Discord, we're going to use this comment as an overall implementation guide, and we've decided to organize the work according to the following tasks:

  • Update reference to explicitly describe the new behaviors
  • Create snapshots of existing behavior, for the visual tests
  • Decide how to refactor normal()*
  • Decide whether to rename default contour kind to SEGMENTS or leave it as null*
  • Increase consistency in usage and documentation of default values
  • Decide on the file structure*
  • Implement Shape, Contour, and primitive shape classes
  • Implement creator functions*
    • Decide if we want wrappers (e.g. createPoint() for () => new Point(\*data*\))
    • Implement wrappers or do nothing if we decide to omit them
  • Implement PrimitiveShapeCreators (a JavaScript Map object)*
  • Implement 2D and 3D output visitor classes
  • Implement drawShape() on 2D and 3D renderers and refactor user-facing functions
  • Develop PointAtLengthGetter**
  • Consider new primitives based on existing combinations of vertex and shape kinds**

*: A separate GitHub Issue does not need to be created.
**: A separate GitHub Issue should be created, but not yet.

For all the unmarked tasks, I'll set up separate issues with more details as soon as I get a chance, and the task list will link to them. Any code should be committed to the new vertex-refactor branch @davepagurek set up, and we'll merge it into the main branch once the time is right. For right now, we can get started by discussing the tasks marked with a single asterisk!

Metadata

Metadata

Type

No type

Projects

Status

Completed

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions