-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor vertex functions to enable composite paths #6560
Comments
I would like to work on this issue. Just a little hiccup, i can start in the next 2 weeks, so if someone wants to do it before that they can, but after that i would like to work on this issue. |
Hi @Gaurav-1306! It's great that you want to work on this! We may want to break this up into smaller tasks that multiple people can work on. Maybe the first thing is to make a list of people who are interested. Maybe one person can take the lead by dividing up tasks. When I get a chance, I can think more about that, but I'd love to hear from others on this. |
I would like work on this as well!! |
Great write up @GregStanton! I like the idea of having internal classes for each type of vertices and it also makes it possible for addon libraries to implement their own unique vertices that can work with beginShape() and endShape(). As a start can we work out a common interface that each of the classes should adhere to? ie. What properties and methods should they all have? What are the signatures of the methods? After that or at the same time we can figure out how the internals call the methods/interface. For anyone interested in working on this, please work out a proposal in this issue first. You can test out code implementation in the web editor as @GregStanton has done with the proof of concept. Thanks! |
Thanks for the kind words @limzykenneth, and for helping to direct the effort! I'm really glad you like the idea. I'm not sure if you noticed, but the code that makes my proof of concept work is all located in shape.js, inside the project's file directory. I think it may provide initial answers to your questions. For example, the vertex classes all have the following interface:
Possible next steps/questions
Edit: Maybe @Gaurav-1306 or @Jaivignesh-afk can divide up points 1. and 2. above, regarding the 3D case and contours? |
We don't necessarily have to to figure this out at this point. Once we have the implementation working with the flexibility we want, we can revisit at a later point. That is to say it's a nice to have but not must have. |
For 3D support: in WebGL, each path/contour ends up just as an array of vertices, so each segment would need to be able to convert itself into a polyline. This will also depend on the level of detail, so its interface for WebGL would have to look something like: interface Segment {
first: boolean
addToCanvasPath(ctx: CanvasRenderingContext2D): void
addToPolyline(polyline: Array<p5.Vector>, curveDetail: number): void
} Here I've also made this a void function that mutates the array to be consistent with the way the 2D one works, but outputting an array of vertices which we then concatenate to a big list would also work. I've also added a To support contours, if we have: type CompositePath = Array<Segment> ...then a full shape would be: type Shape = Array<CompositePath> where |
Replying to #6560 (comment) by @davepagurek: Thanks! This is very helpful. There's some tension between the idea of vertex classes and segment classes; I think you were right to switch over to segment classes. The deciding factor for me is that it seems natural to put all the Catmull-Rom vertices in a single object, and it doesn't make sense to think of that as a singular "vertex" object. Below, I propose a few tweaks that combine (and hopefully refine) the interfaces we each suggested. I'm using a TypeScript-like syntax as an informal shorthand (e.g. Proposed interfacesIf these sound good, we can test them by incorporating them into a new version of the proof of concept. (That's good because I don't have the extra time right now to think this through completely! I think it makes sense, though.)
Notes
|
I would also like to work om this issue! |
Thanks @GregStanton! I took some time to think through the implementation some more and have some more refinement to propose (or at least spelling out some of the separation of concerns more explicitly for implementers.) Ideally For easier testing and to keep the code clean, I'd ideally like to keep Shape/Path/PathSegment as decoupled as possible. There's a bit of mixing right now between what the data of these classes contain, and the algorithm of how they get constructed. Both are important, but I'd ideally like to have some invariants so that there's less confusing state management. How do you feel about these as some things to try to keep true during construction/drawing:
With that in mind, here are some more thoughts for public interfaces (omitting internal state for now as an implementation detail). interface Shape {
// To be called by `p5.Renderer` from public drawing API methods:
addVertex(v: VertexInfo)
addCurveVertex(v: VertexInfo)
addArcVertex(v: VertexInfo, options: ArcVertexOptions)
addQuadraticVertex(v1: VertexInfo, v2: VertexInfo)
addBezierVertex(v1: VertexInfo, v2: VertexInfo, v3: VertexInfo)
startPath()
endPath()
closePath()
// To be called from `p5.RendererGL` and `p5.Renderer2D` to draw to the screen
toVertices(curveDetail: number): Array<Array<VertexInfo>> // Array of arrays for each contour
drawToCanvas(ctx: CanvasRenderingContext2D)
}
interface VertexInfo {
position: p5.Vector
// WebGL-specific data per vertex or control point:
texCoord?: [number, number]
fill?: [number, number, number, number]
} Not necessary for a proof-of-concept, but just as a heads up, WebGL mode has more info than just position data for vertices, so I've made an interface for everything that might include. I think you can generally substitute I've also only opted to include So then interface Path {
addVertex(v: VertexInfo)
addCurveVertex(v: VertexInfo)
addArcVertex(v: VertexInfo, options: ArcVertexOptions)
addQuadraticVertex(v1: VertexInfo, v2: VertexInfo)
addBezierVertex(v1: VertexInfo, v2: VertexInfo, v3: VertexInfo)
closePath()
toVertices(curveDetail: number): Array<VertexInfo> // Just one array now at this level
drawToCanvas(ctx: CanvasRenderingContext2D)
firstVertex(): VertexInfo // For segments, if needed
} The vertex adder methods on Since I'm just documenting the public API, I've omitted things like the current vertex. The idea is that the path should know its own current vertex (or can be derived by getting the last vertex of its last segment), but nothing externally needs to know that. Following @GregStanton's idea, if segments don't know their index, paths would create an array containing the first vertex of the first segment, and then calling interface PathSegment {
type: string
index: number
startVertex(): VertexInfo
endVertex(): VertexInfo
drawToCanvas(ctx: CanvasRenderingContext2D)
addToVertexPath(shape: Shape, curveDetail: number)
} |
Replying to #6560 (comment) by @davepagurek: Thank you for all this work! One of my favorite things about software development is iterating like this. My previous proposal needed to be adjusted to improve encapsulation, and it looks like you've taken the next steps. Instead of state changes coming from anywhere, we now have objects being responsible for modifying themselves. I see how removing internal state from the interfaces (like the Eventually, we may want to specify the interfaces, the internal state for classes that implement them, and as @limzykenneth suggested, the expected call sites for the various methods. That could be a guide for implementers. But first, I think I'll be able to provide better feedback on the interface details after I have a few questions answered. Questions2D vs. 3DI'm now thinking we really have two shape models:
If we follow the single-responsibility principle, it seems like these should be implemented in separate classes. People interested in the 2D functionality may think the vertex-related code is relevant to them, when in fact it's not. Maybe we could specify a common Shape interface that abstracts out anything common to the two cases, and then we could extend it with Shape2D and Shape3D interfaces? I'd need to spend more time to figure out what this would look like, so I figure it's good to float the general idea first. Did I break it up too much?Conceptually, our shapes are built from paths, and our paths are built from segments or vertices. It seems nice to organize our code to reflect this. On the other hand, breaking it up means a single function call turns into three. If we want to draw a shape, we now call a drawing method on the shape, and that calls a drawing method on a path, and that calls a drawing method on a segment. It's not currently clear to me if this is a step backward or a step forward. Maybe we should focus on breaking up the code horizontally (between 2D and 3D cases) rather than vertically (shapes, paths, and path segments)? Is it possible that doing both would mean breaking things up too much? Maybe we could just use a Path type in our specification, rather than a full interface, like you had originally? Boundary vs. contours
I think you're right about combining "boundary" and "contours." The original idea is to make Do you see any reason why we couldn't treat |
I wonder if having the ability to get vertices out of paths in general might be something we want once we have a format that makes it easy to do so, to get functionality like
That also works as long as we can still break out the common functionality between 2D and 3D mode. I think part of the problem right now is that because the data is handled differently across both modes, we end up with different behaviours between both. I think if the functionality to build up the path is common to both, then that fully addresses that concern for me. Continuing to think about the visitor pattern sort of idea: if we make these as their own separate objects rather than tying it directly to
I think that should be ok! It's currently only hard because contours are special cased in the implementation, but if we decide to treat them uniformly internally, then I think we can expose that new functionality without breaking any backwards compatibility. |
Replying to #6560 (comment) by @davepagurek: Next iterationI put together a new iteration of the interface to help us move forward. The main changes are a separation of the 2D and 3D cases, the addition of a visitor interface for adding new functionality, and a return to the original idea of public shape data. To add new vertex types (e.g. smooth quadratic and Bézier vertices), it should only be necessary to (a) create a new user-facing vertex function, (b) create a new segment class in 2D and 3D, and (c) add a new method to any existing visitor classes. To add a new feature, it should only be necessary to add a new visitor class and decide how the client code will make use of it; a great trial feature might be something akin to SVG's getPointAtLength(). Note: This comment contains significant edits. I'm hoping this latest iteration is better, but it may still need work. If anyone wants to help improve it or else submit an alternative proposal, that would be great!
Notes
ReferenceFor anyone following along, this seems to be a good introduction to the visitor pattern: https://refactoring.guru/design-patterns/visitor UpdateTo be clear, I'm proposing that we make no distinction between vertex functions called directly inside But... I didn't realize contours in p5 are closed by default. If we require Another updateI removed the |
This is looking really promising! Replies to your notes:
I was thinking about this some more, and it seems like there will always be some step where we have to manually parse the arguments into either 2D or 3D data, because even in WebGL mode, you can still call
This is a really good point! Currently, primitives like If so, then maybe we need something like a command queue, and a strategy for interpreting the queue. Maybe the default strategy always creates new segments, but we swap out the strategy when you change shape mode, so the class QuadStripMode {
constructor() {
this.lastEdge = null;
}
handleCommands(queue, shape) {
if (this.lastEdge) {
if (queue.length !== 2) return;
shape.contours.push([
this.lastEdge[0],
this.lastEdge[1],
queue[1],
queue[0],
]);
this.lastEdge = [queue.shift(), queue.shift()];
} else {
if (queue.length !== 4) return;
shape.contours.push([queue[0], queue[1], queue[3], queue[2]]);
queue.shift();
queue.shift();
this.lastEdge = [queue.shift(), queue.shift()];
}
}
} Also, curves probably don't make sense when combined with a mode like One more thought: right now the shape mode is only specified on the whole shape. In theory, this could be different for just one part of a shape (one |
Replying to #6560 (comment) by @davepagurek: Thanks! You make a good point about creating a single construction process, and the queueing idea opens up a new possibility. Overall, it sounds like our next job is to figure out how to handle two cases: dimensions (2D or 3D) and kinds ( MotivationLet's break it down and consider the dimension problem first. Constructing a shape in 2D and 3D involves nearly the same process, and we'd like to avoid duplicate code. Since the only apparent difference is that different objects are created, we might try a creational design pattern: rather than merging the objects themselves—which leads to inapplicable object properties and extra complexity for 2D developers—we can merge the interfaces through which they're created. As it turns out, this also provides us with a way to handle the kind problem. Handling dimensionWe start by defining 2D and 3D factories. In our case, these are classes whose methods create objects, so the methods have names like Handling kindNow let's consider if this will still work when we add First let’s consider how many different kinds of shape primitives each vertex function may need to create. The existing non-default kinds only work with Now let’s consider how each vertex function might manage to create an unforeseen variety of shape primitives. That will work if the vertex functions can retrieve the right creator method based on the vertex kind and shape kind. We could try to extend our factory classes, but a possibly simpler option might be to replace the factories with Organizing construction and renderingOnce the vertex functions create the primitives, they need to add them to the shape, and once the shape is complete, it needs to be rendered. For these purposes, we could give each primitive object This way, if a user is reading the p5 website’s reference pages and clicks to see the source code, it should be much easier for them to navigate it and to understand the parts they’re interested in. When they look up the source for the user-facing shape and vertex functions, they'll see that these direct the construction and rendering of 2D and 3D shapes from one place, without Implementing construction and renderingI'm assuming for now that we're making the shape accessible to the primitive shape classes. In that case, as soon as the user input is encountered, we can push it through the appropriate For example, a new Summary
Questions
Future considerations
Good point! Do you have any use cases in mind? If greater generality can be achieved without increasing complexity of one form or another, then we almost don’t even need to come up with use cases. But if it does, it’d be nice to know some concrete benefits to point to. I have some initial ideas but don’t want this comment to get too long, so I’ll defer this for now. We may also want to consider if certain combinations wouldn’t make sense, and if so, how to handle that. Once we settle on an overall approach, I guess the next step is to make the necessary adjustments to the previous iterations of the interface that we discussed. Update: Changed Footnotes
|
2D and 3D segment interfaces
Right, I'm not confident that we'd able to have a common Thinking through the implications of this, I feel like it's conceptually simpler to be able to have methods that convert to different output formats: If we had a common I personally am not opposed to having just one dimensional type per segment type, which implements both output formats in one spot. That would look something like: // Probably will extend a base class that handles some construction stuff later
class LineSegment {
shape: Shape;
index: number;
vertex: Vertex;
constructor(shape: Shape, index: number, vertex: Vertex) {
this.shape = shape;
this.index = index;
this.vertex = v;
}
toVertices() {
return [this.vertex];
}
toContext2D(ctx: CanvasRenderingContext2D) {
if (this.index === 0) {
ctx.moveTo(this.vertex.position.x, this.vertex.position.y);
} else {
ctx.lineTo(this.vertex.position.x, this.vertex.position.y);
}
}
} Downsides:
The benefits of this are:
Handling kindI think a map is a good idea. Maybe rather than putting both the vertex and kind in the key, which means having to enumerate a lot of possibilities, we can just have two maps? The kind function could then look up the vertex type in the map if it wants the default behaviour, or override it with custom behaviour (e.g. a
|
Hi all! We're going to have an online video call to hash out an approach that will allow us to accommodate composite paths, contours, the 2D and 3D cases, shape kinds, and visitors; @davepagurek and I will be there, and anyone who is interested is welcome to join! Date: Thursday, December 7, 2023 The BitPaper platform we're using includes a shared online whiteboard, and it runs in the browser, so no installation is required. You can use the link to visit the meeting room and take a look at the agenda, but the pages are locked until the meeting goes live, so no edits will be possible until then. In case anyone is interested and can't make it, we'll post meeting notes here. If you're interested, we'd love it if you could comment here, so that we know how many people to expect. But, if you can't let us know in advance, you're still welcome to attend. Thanks! Update: When the meeting opens, you may need to sign in to BitPaper. You should be able to join with a Google or Meta account, or with an email and a password. It's free for you to use. |
I'd love to be a fly on the wall if you'd have me @GregStanton 👀 |
Of course @xanderjl! Looking forward to it. I just updated the meeting announcement with the actual link to the meeting room. |
i would also i to join the meet. |
Hi all! I put together a new approach based on all the discussion so far, and I updated it with feedback based on the meeting. FeaturesThis plan supports the following features:
GoalsWe can check any design changes against the following goals:
Design overviewAfter Each vertex method attached to
Then
Shape construction: Shape -> Contours -> Primitives -> VerticesA shape consists of contours (subshapes), which themselves consist of shape primitives. While the shape primitives are the most basic objects that can be rendered, they may be composed of one or more vertices. The outline below indicates how the design will look with the planned API for custom shapes in p5.js 2.0.
Notes:
Path primitivesPaths are created when the contour AnchorsAn Anchor is the starting point for the segments in a path. It's created when a contour of the
In the typical case, a path segment will begin at the end of the preceding segment. However, the very first segment lacks a predecessor; the anchor fills this role. (For example, SegmentsSegments are equivalent to subpaths in the native canvas API and SVG.
Isolated primitivesIsolated primitives aren't connected to their neighbors. They're created when the contour
Since isolated primitives aren't connected to their neighbors, these primitive classes are self contained. For example, Tessellation primitivesTessellations consist of shapes that cover a region in 2D or a surface in 3D, with no overlaps and no gaps. They're created when the contour
Note that the classes have names like Future primitivesThere are other combinations of vertex kinds and shape kinds that lead to meaningful primitives. For example, in a contour with a Shape handling: Visitor classesA primitive visitor can visit different primitives and use their data, for example to produce native Canvas API drawing commands. Putting such functionality inside a separate visitor class means that a 3D primitive doesn't require a method for the 2D Canvas API, for example. Visitor classes are also independent of the renderer, which makes it easier to add new features that could potentially work with multiple renderers. A few examples of classes that might implement the Class:
Class:
Class:
Note: If a particular visitor class doesn't apply to a certain kind of primitive, it could either omit the method corresponding to that class, or it could throw an error. Updates:
|
That update looks great @GregStanton, it looks like it will handle all the situations we've though of so far! On the call today we can go over this all again and answer any questions and clarifications other contributors might have. |
Updates from the meeting on December 7, 2023:Thanks so much to everyone for attending! During the meeting, we adjusted the latest approach and cleared up our main questions. I went ahead and incorporated the adjustments into my previous comment. It looks like we're ready to go ahead with the approach described there. I'll summarize the key points below. I'm filling in some details myself, so if anyone notices any errors, please let me know! Summary of questions
Summary of decisionsIt looks like we're going to go ahead with this interface with a few adjustments. Adjustment 1: Based on the discussion, it was determined that the tessellation primitives can implement the Adjustment 2: In the 2D case, we may want to output commands to the Next stepsWill it be helpful to break up the refactoring into smaller tasks, and then possibly assign those to individual contributors? Or would it make sense for someone to take on the whole thing? Other than this project management aspect, it seems that we're ready to start on the implementation. After that, since it will be a big change to the codebase, we'd like to use the new visual testing system (once it's ready), to prevent regressions. |
Hi everyone, Apologies for the delay in updates. Here's the progress so far: I've implemented the vertex functions for both 2D and 3D, along with visitors and contours, and initiated work on shapekinds. If possible I'll aim to complete shape kinds and create a PR for comprehensive review or create one with whatever done so far, soon. |
Hi @capGoblin! No problem at all, and thanks for all your work! Did you see the update in the body of this issue about the new approach, the task list, and the new branch? |
Thanks @limzykenneth! |
@GregStanton Not sure what point you are at with this feature but I'm thinking of moving this to be a proposal in the 2.0 RFC to give it a bit more flexibility in terms of not having to consider breaking changes as much. Let me know if that sounds good to you, you can summarize what you have here as the proposal and no need to write something from scratch. |
Thanks @limzykenneth! There's been some good progress so far. I've sorted out a number of the smaller tasks in the task list, and we've worked out the overall implementation. However, there are some major updates that I'd like to make, in light of the move to p5.js 2.0.
In short, I think this issue definitely satisfies our criteria for the 2.0 proposals, but I need to figure out how to incorporate the two updates above. Also, the comments on the current issue contain a long brainstorming discussion that led to the implementation design, but newcomers shouldn't need to read through all that. So, it might make sense for me to submit a clean, updated issue using the new 2.0 template. (I could tag everyone who was originally interested in this issue and link back to it for context.) Do you mind if I spend some time this week to figure out a good approach? |
Okay, I'm ready for this issue to be listed as an RFC proposal, @limzykenneth. I think this should be separate from the proposal for the new vertex API, since the refactoring could be done for either API. |
Some thoughts about an explicit name for the default shape kind in 2D and 3D are below. Any feedback would be great! Current situation: Solution? Here's why I'm proposing
|
@jules-kris, you're looking into explicit names for default values right now, let me know if you have any feedback on the name for this one! I think |
@davepagurek: Thanks Dave! I appreciate the brainstorm! My initial feeling about "outline" and "silhouette" is that these both seem to refer to marking the boundary of a 2D shape, which is typically a closed curve. To be fair, But it's great to come up with lots of ideas. The more the better. I'll let your ideas percolate in my brain and see if they inspire something new! |
Oh wait. How about Paths in related contexts:
Does anyone see any downsides to this? Perhaps we'd want to use "path" to mean something else in the future? I doubt that's a concern, though. The default shape kind is the closest thing in p5 to the concept of a path. In fact, I just noticed that I referred to these default shapes as "composite paths" in the title of this issue haha. I also previously described our concept of segments as "equivalent to subpaths in the native canvas API and SVG." Instead of classes for |
Path sounds pretty good to me, and it's definitely easier to spell! I suppose its a little overloaded in the graphics space because e.g. in SVGs and in the native canvas context, they have objects called paths rather than modes, but p5 doesn't have any objects or other functions using that name, and this shape mode works pretty similarly to the SVG/native canvas path drawing APIs. |
Thanks @davepagurek! Good point about objects vs. modes. Hopefully that doesn't cause too much confusion. For what it's worth, we will have path objects under the hood ( |
One more idea for the name of the default shape kind: If a future version of p5 were to allow all shape kinds to work with all vertices, then we'd be forced to interpret |
Update: After a quick real-time discussion with @davepagurek , it looks like @davepagurek: After writing down our observations (see below), I'm now leaning toward @jules-kris: If you have any feedback, that would be great! If you want to skip the foregoing discussion and just focus on this comment, it should summarize the important things. General criteria:
Assessment of
Notes:
Assessment of
Notes:
Plan for now: |
I was about to file a new issue, then found this one. A live version of this demo is here:
|
Thanks for sharing @golanlevin! It's great to hear that this project may help with the library you're working on! Would you be interested in doing some user testing with your library once everything is ready? If so, you may want to check out the new vertex-function API for p5.js 2.0, since it looks like we're going to be switching to that. |
@GregStanton Thanks for pointing me to the new vertex API for p5 v.2! |
@golanlevin Sure thing! I'm glad that was helpful :) |
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?
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.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:
endShape()
method is an example of a specific function with room for improvement. It has too many responsibilities, as it contains rendering code for all vertex types. It also directly includes a mathematical conversion from Catmull-Rom splines to Bezier curves that partly duplicates work done by a separate function in p5.Font.js, and it contains dead code (it repeatedly checks if shapeKind is equal to constants.POLYGON, but no such constant is defined in constants.js).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. WhenendShape()
is called, it simply loops over the vertex objects, and they add themselves to the canvas;endShape()
doesn't need to know thatcurveVertex()
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'sbeginShape()
/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:
normal()
*SEGMENTS
or leave it asnull
*Shape
,Contour
, and primitive shape classescreatePoint()
for() => new Point(\*data*\)
)PrimitiveShapeCreators
(a JavaScriptMap
object)*drawShape()
on 2D and 3D renderers and refactor user-facing functionsPointAtLengthGetter
***
: 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!
The text was updated successfully, but these errors were encountered: