OpenVectorFormat icon indicating copy to clipboard operation
OpenVectorFormat copied to clipboard

Added Cubic and PolyBezier Curves

Open Samtanner12 opened this issue 2 months ago • 7 comments

Added Cubic and PolyBezier Curves to the proto definition. Polybezier curves allow for a quicker curve calculation and more accurate position on the curve facilitating a smoother surface finish. This change will allow us to use OVF with Dyndrite for our additive solution.

Samtanner12 avatar Oct 15 '25 20:10 Samtanner12

Hello Mr. Tanner,

thank you for your Pull Request. In general, we endorse the integration of additional geometric spline types, especially if there is industry need. Because OVF is based on Protocol Buffers (protobuf) and code generators, we can roll out format additions quickly.

The rules for protobuf forwards-/ backwards compatibility

  • We can add new tags without breaking anything. Parsers using older versions will read the added field in new files, add it to unknown fields and then continue parsing known fields. New parsers will read older files and the new field will be empty. In the case of vector block one of, the new cases will simply never be used.

  • We cannot remove a field or change its definition; this would break all applications using the previous definition.

  • It is possible to deprecate fields ([obsolete], reserves tag to prevent reuse) in protobuf. Up to now we have not done so and would like to avoid it. These types of changes need time to trickle down to all software using OVF

Because we cannot remove or rename a field after adding, I would like to discuss a few points before merging.

Observations

  1. Your suggestion uses a flattened control point form, not a coefficient form. I agree because this is consistent with the linear (Hatches, Polylines) definitions, which is also stored in end point coordinate from, not as angle and length.

  2. For linear segments, OVF (and all other AM formats I know) offer 2 definitions

    1. Polylines form a continuous line. There are no jumps between segments, consecutive segments share the same end point. In OVF a closed polyline (polygon) is represented by the first and last point having identical coordinates.
    2. Hatches form discontinuous lines. Jumps are inserted between segments.
      1. We do not store each hatch in its own vector block as a Polyline because that causes significant overhead for vector block (meta-) data (marking_params_key, laser_index etc.)
      2. A common source of wasteful storage and bugs is storing polylines in a hatches block by duplicating endpoints, so they are coincident. Because hatches are often handled differently by machine controllers (no polydelays/angle-based corner cutting), this can result in unexpected skywriting behaviour and build time degradation. Zero length jump commands can also cause delays if they are not filtered properly.
  3. To me it is not completely clear from your definition if it is representing multiple Bezier segments with jumps between them or a continuous spline. Because of the similar structure and problems in linear segments, I would propose offering either both definitions or one precise definition. Example:

    1. CubicBezierSpline: continuous, control points xy are shared between adjacent segments
    2. CubicBezierHatches: discontinuous, jumps are inserted between segments, control points xy exist for each segment for start and end.

Discussion Points Summary

  1. Do you plan to use the Bezier segments as continuous splines or as hatches or both?
  2. If your definition is meant for splines, does it cause any problems for you to compact the layout by collapsing identical x/y points? This e.g. results in 4 floats per segment in QuadraticBezier [x0, y0, cx0, cy0, x1, y1, cx2, cy2, x3, y3, cx3, cy3, …]. I suggest the nth tail segment to be shortened to only contain [xn, yn] instead of [xn, yn, cxn, cyn] with the last 2 control points unused. Reasoning: This enables memory casting to the expanded definition (with start and end point in one struct) by using a stride pattern.
  3. Do you have any suggestions to improve the precision of the naming? My suggestions are BezierSpline and BezierHatches to convey the type of connection between segments (jump or continuous).
  4. Do you see need for any other types of splines, e.g. NURBS?

SebastianDirks avatar Oct 17 '25 10:10 SebastianDirks

Hello Mr Dirks,

Thank you for getting back with me so quickly. Our specific use case Includes a jump to a specific start point followed by a sequence of curves following the format control point, target point. [Cx0,Cy0,Tx0,Tx0,Cx1,Cy1,Tx1…]. For Quadratic Bezier this involves two control points. In the future we may also utilize the hatching feature for infill on our parts.

I agree that compressing identical points is a good idea, however dropping the control points at the nth tail segment causes issues for our use case. We are drawing a curve relative to our current position and those control points define the curve to our target.

I think one solution that works well with our use case would be

` //Quadratic Bézier segments. //Each segment is (x0, y0, cx, cy, x1, y1) => 6 floats per segment. //Pack N segments as: [x0,y0,cx,cy,x1,y1, x0,y0,cx,cy,x1,y1, ...] message QuadraticBezierHatches { repeated float segments = 1; }

//Cubic Bézier segments. //Each segment is (x0, y0, c1x, c1y, c2x, c2y, x1, y1) => 8 floats per segment. //Pack N segments as: [x0,y0,c1x,c1y,c2x,c2y,x1,y1, ...] message CubicBezierHatches { repeated float segments = 1; }

//Linked Quadratic Bézier segments. //Initial segment contains start point (Ix, Iy) //Each segment is (cx0, cy0, x0, y0) in the format control, target //=> 2 for start then 4 per segment. message QuadraticBezierSpline{ repeated float data = 1; }

//Linked Cubic Bézier segments. //Initial segment contains start point (Ix, Iy) //Each segment is (c1x0, c1y0, c2x0, c2y0, x0, y0) in the format control, control, target //=> 2 floats for start then 6 per segment. message CubicBezierSpline { repeated float data = 1; }`

As for NURBS and other curve types I do not see an urgent need at them moment. With the flexibility provided by the protobuf format OVF can adapt with the industry.

Thank you so much and I look forward to your feedback.

Samtanner12 avatar Oct 17 '25 14:10 Samtanner12

Hello Mr. Tanner,

thank you for the update. We have requested a review from Dyndrite as well for this PR. This should be done in the next few days, afterwards I will merge. I have other additions for lookahead based scanners on a branch that I will also merge. Then I will put together a new release version of OVF.

Best Sebastian

SebastianDirks avatar Oct 21 '25 07:10 SebastianDirks

Hello Mr. Dirks,

Thank you. I look forward to their feedback and to the next release version. Thank you for your help.

Respectfully, Sam Tanner

Samtanner12 avatar Oct 21 '25 13:10 Samtanner12

Thank you so much for your timely response, I understand what you are attempting to accomplish, but I am unsure of the practicality and usefulness of such a change.

Supporting arbitrary-degree Béziers forces variable-degree evaluation leading to an exponential compute time (de Casteljau ~O(dn²) per sample). In practice our controllers accelerate quad/cubic only and higher degrees will get flattened anyway, adding cost without quality gain. Fixed length bezier curves keeps streaming lightweight and predictable in my opinion.

Switching to general, nested messages increases payload size and decoder complexity, and would require all readers to implement flattening. I think keep quad/cubic as the primary wire format.

I agree, documenting expected behavior from a misaligned float does pose a risk, however I hesitate to depart from convention without a compelling reason.

For our current use case repeated float segments keep it as lightweight and predictable as possible, however I might be blind to the needs of the industry outside of our own implementation. I appreciate any feedback you may have for me.

Thank you.

Respectfully, Sam Tanner

Samtanner12 avatar Nov 06 '25 18:11 Samtanner12

Thank you both for your detailed feedback. I want to add details on the previous design choices for OVF that lead to the repeated float design for Polyline types, and add my toughts on the different proposals.

OVF perf: why unstructured repeated float?

@dyn-sdunn The choice of using unstructured repeated float in OVF is very deliberate and the reasoning is performance. Approx. 99% of the data volume in OVF is the coordinate data. Let me use the example of Polyline2d. What we do in our C# library is reinterpreting the float data as an array of structs using the C# equivalent of memcast. This lets us use many different definitions of point structs without allocating again or copying the data (e.g. for different libraries, to use JIT intrinsics for SIMD and other algorithms). Zero copy in place is the minimal overhead possible of any solution. This general idea is also possible in C++. All geometry libraries I work with define 2d or 3d coordinate vectors as fixed size structs instead of dynamically sized types like List (C#) or Vector (C++).

wire format consequences

Encoding the Point structured in protobuf would lead to a different encoding "on the wire". Each message of a repeated embedded message field needs its own tag for the message (min 1 byte), length of the message (min 1 byte) and a tag for each field (1 byte for x, 1 byte for y). What we have now is a single tag and length for ALL floats, which is neglectable serialization overhead for typical block sizes. The repeated float field is "parsed" with a memcopy based on the length from the network buffer/file stream buffer to the proto object float array. The structured embedded message adds 4 bytes of overhead per 2 floats. This would increase file size by at least 50%. Even worse, each length and tag is a varint and has to be parsed with branch heavy code on a single byte level with bit masks instead of a bulk memcopy. So the general OVF design idea is to document the form of structs on the lowest level and chose the more efficient definition.

vector blocks and machine controller capabilities

The Vector Block types serve as an extension point in OVF that represent controller capabilities. The intended design for OVF machine controllers is basically a switch statement on the vector block type to execute the desired exposure. It is not necessarily desirable to make the Vector Block design the most general possible, because as Mr. Tanner noted that also requires the machine controller to implement the most general form although it might never be used.

the pros and cons of generality

I would also prefer a more straightforward definition that can be memcast to common structs used in the geometry libraries. If the need for higher degree Bezier (or other Spline types) arises, this can simply be implemented with another vector block type, which is an optional capability. Converting complex and general definitions to primitive forms on the machine controller can be efficient if it reduces data volume and reduces discretization errors, but I think for Bezier it would just add a nested swich statement in the handling of the vector block type. Two examples for complexity vs. performance: Pro: Discretizing a circle or a Bezier spline to straight line segment micro vectors on the embedded machine controller (optimized for the hardware cycle time) is easy to calculate and saves data. Con: Performing polygon offsets to calculate hatch fillings on the machine controller is not a good idea because the operation is expensive (CPU time) and requires careful handling of float rounding errors and edge cases.

representation of initial segments and strided memory access

I also want to elaborate on the initial segments comment by Samuel. On the one hand this is a valid point because this deviates from the other Vector Block types. The repeated float fields are intended to be treated as an array of structs. Partial structs are not intended and their handling is undefined by the format, our C# library and format checker throws an error in that case. Per the previous discussion, in case of the spline types what makes them a spline is that 2 adjacent segments have a common start/end point. This could also be represented with the BezierHatches definition, but that not only stores duplicate data but also gives rise to wired edge cases where parts of a hatch vector block form a spline that is then interrupted. My idea to enforce this on the format level was to memcast the spline definition into the same 6 float segments (for 2d quadratic) with a “stride” of 2 floats, which is an overlap between the adjacent structs. . I know the “stride” concept from GPU vertex buffers in graphics pipelines. But it might be clearer and easier to define the struct as having 4 floats, and 2 floats as extra fields. A stride requires pointer arithmetic in the indexer to access the “array of structs”. This is possible in C++ of course and in C# with Span, but it is uncommon and would at least need more comprehensive documentation. The “struct of 4” + 2 extra floats is closer to what we already use.

useful feedback to move forward

@Samtanner12 as far as I understood your software uses the full struct (with start and end point) for splines. The "array of structs with stride" pattern can also be used with the definition proposed by Samuel, if the 2 floats from the extra fields are appended to the Vector after protobuf has parsed the Vector Block Message. I would then propose to define the last point (end point) instead of the start point in extra fields. This avoids a memmove of the whole backing array of the Vector because we have to insert at the front. This would give e.g. layout (x0, y0, cx0, cy0) for quadratic Bezier struct. Would that work for you, or would you prefer the current proposal with more comprehensive documentation?

@dyn-sdunn @dyndrite-gabriel is Dyndrite currently already internally using any fixed size structs for quadratic or cubic Bezier spline segments, or uses libraries that define such structs? If yes, what is their memory layout? Is it directly compatible with one of the proposed definitions or would require transformations?

SebastianDirks avatar Nov 07 '25 10:11 SebastianDirks

@SebastianDirks I appreciate your comprehensive breakdown of the various proposals.

My current implementation and firmware limitations does require me to use a full struct format.

I had not considered the cost of the front insert, and I understand the value of your proposed change to an endpoint definition. I see no reason to not move forward with the proposed definition as far as I am concerned. I look forward to answering any other questions that might be relevant. Thank you.

Samtanner12 avatar Nov 13 '25 22:11 Samtanner12

Hi @SebastianDirks @dyndrite-gabriel @dyndrite-gabriel I’ve pushed the proposed updates:

Hatches: Quadratic/Cubic (stride 6/8); 3D variants (9/12).

Splines: start+control(s) with tail end point; 3D variants included.

Kept packed repeated float convention;

Let me know if you have any questions or concerns. thank you.

Samtanner12 avatar Dec 02 '25 16:12 Samtanner12

@Samtanner12 thank you for the update and the contribution. Excuse my delayed reply, I have been on vacation. I will now merge this pull request with the feedback. I have to tidy up and merge another branch then before the next release, will should follow in the coming days.

SebastianDirks avatar Dec 18 '25 09:12 SebastianDirks