SplineHelp: unexpected results [SOLVED]
-
On 27/01/2015 at 13:17, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R16
Platform: Windows ;
Language(s) : C++ ;---------
Hello Forum,I'm working on a plugin that places objects along a spline. SplineHelp::GetLinePointSegment() is returning results I did not expect(probably more accurately(I do not understand)). I have created a simple CommandData plugin to demonstrate:
// main.cpp #include "c4d.h" #include "splinehelp_test.h" Bool PluginStart( void ) { if ( !RegisterSplineHelpTest() ) { return false; } return true; } void PluginEnd( void ) { } Bool PluginMessage( Int32 id, void* data ) { return false; }
// splinehelp_test.h #ifndef SPLINEHELP_TEST_H_ #define SPLINEHELP_TEST_H_ #include "c4d.h" class SplineHelpTest : public CommandData { public: virtual Bool Execute( BaseDocument* doc ); static SplineHelpTest* Alloc() { return NewObjClear( SplineHelpTest ); } }; Bool RegisterSplineHelpTest(); #endif // SPLINEHELP_TEST_H_
// splinehelp_test.cpp #include "lib_splinehelp.h" #include "splinehelp_test.h" #define PID_SPLINEHELP_TEST 1034539 //unique from plugincafe.com Bool SplineHelpTest::Execute( BaseDocument* doc ) { BaseObject* op = doc->GetActiveObject(); if ( ! op ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "No object selected.\n" "Execution stopping.", GEMB_OK ); return true; } if ( op->GetType() != Ospline ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Selected object is not Ospline.\n" "Execution stopping.", GEMB_OK ); return true; } SplineHelp* spline_help = SplineHelp::Alloc(); if ( ! spline_help ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Could not allocate SplineHelp.\n" "Execution stopping.", GEMB_OK ); return true; } Bool init_ok = spline_help->InitSpline( op, Vector(), nullptr, false, false, true, true ); if ( ! init_ok ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Could not Initialize SplineHelp.\n" "Execution stopping.", GEMB_OK ); return true; } Int32 pcnt = static_cast<PointObject*>( op )->GetPointCount(); for ( Int32 i = 0; i < pcnt; i++ ) { GePrint( "------------------------" ); GePrint( "spline point idx: " + String::IntToString( i ) ); Int32 line_point_idx = spline_help->SplineToLineIndex( i ); GePrint( "line point idx: " + String::IntToString( line_point_idx ) ); Float64 offset; Int32 local_idx; Int32 segment_idx; spline_help->GetLinePointSegment( line_point_idx, &offset, &local_idx, &segment_idx ); GePrint( "offset: " + String::FloatToString( offset ) ); } return true; } Bool RegisterSplineHelpTest() { return RegisterCommandPlugin( PID_SPLINEHELP_TEST, String( "SplineHelp Test" ), 0, nullptr, String( "SplineHelp Test Help" ), SplineHelpTest::Alloc() ); }
I created a curved, 2-point bezier spline and ran this plugin with the following results:
------------------------ spline point idx: 0 line point idx: 0 offset: 0 ------------------------ spline point idx: 1 line point idx: 33 offset: 0.971
I expected the offset of the second spline point to be 1.0.
Next I made a 3-point bezier spline where all of the points are in a straight line, all of the tangent values are zero and the 2nd point is exactly in the center of the first and last points. Here are the results:
------------------------ spline point idx: 0 line point idx: 0 offset: 0 ------------------------ spline point idx: 1 line point idx: 1 offset: 0.333 ------------------------ spline point idx: 2 line point idx: 2 offset: 0.667
I expected the 2nd point's offset to be 0.5 and the last point's offset to be 1.0.
Next I made the interpolation of all points soft and twisted the tangents around to make a curvy, 3-point spline. Here are the results:
------------------------ spline point idx: 0 line point idx: 0 offset: 0 ------------------------ spline point idx: 1 line point idx: 28 offset: 0.509 ------------------------ spline point idx: 2 line point idx: 54 offset: 0.982
I had no expectation for the 2nd point, but did expect the 3rd point have an offset of 1.0.
If anyone understands how SplineHelp::GetLinePointSegment() works, an explanation would be greatly appreciated by me.
Thanks for your time,
Joe
-
On 29/01/2015 at 10:36, xxxxxxxx wrote:
Well, it certainly seems the offset is calculated by using the line point count:
(1/linepointcount) * linepointindex = offset
Because taking your first example:
(1/34) * 33 = 0.971
And also in your second example:
(1/3) * 2 = 0.667Makes it pretty obvious. Did you check the lineobject count? Is it indeed 34 and 3? In that case I also would assume the offset should be calculated zero-indexed by splinehelp (if spline is open) and as splinepoint index 1 does not return 0.0 the functions definetly return zero indexed style offsets:
(1/(linepointcount-1)) * linepointindex
which would give you the expected offset. However, only maxon knows the code and what reason is behind this.
-
On 29/01/2015 at 10:41, xxxxxxxx wrote:
Hi Joe,
You'll never get an offset of 1.0, because the offset refers to the start point of every line across the segment in question. That's why the the first line is at index 0 and will always return an offset of zero. The end point of the last line is 1.0 across the segment. Confusingly, some of your spline examples have one point more in the segment than you report, as the calculations in C4D give the following results:
The index you get back is calculated as: 'index you give' - 'the segment start index'
The offset you get back is the index calculated above divided by the float converted value of the line segment point count. Since the index values go from 0 to the amount of points minus 1, you can only get to one increment before 100% (ie what you mean by 1.0).That means from your examples:
"curved, 2-point bezier spline" has 34 line points.
line point idx: 33
offset: 33 / 34 points = 0.9713-point bezier spline where all of the points are in a straight line, all of the tangent values are zero and the 2nd point is exactly in the center of the first and last points. This one has 3 line points.
line point idx: 1
offset: 1 / 3 points = 0.333line point idx: 2
offset: 2 / 3 points = 0.667the interpolation of all points soft and twisted the tangents around to make a curvy, 3-point spline. It has 55 line points.
line point idx: 28
offset: 28 / 55 = 0.509line point idx: 54
offset: 54 / 55 = 0.982I hope that helps!
Joey Gaspe
SDK Support Engineer -
On 30/01/2015 at 00:04, xxxxxxxx wrote:
@Katachi and Joey
Thanks for taking the time to look at this for me. Appreciated!Originally posted by xxxxxxxx
You'll never get an offset of 1.0, because the offset refers to the start point of every line across the segment in question.
I understand what you are saying, but most of the calculations in SplineHelp appear to deal with how a spline is calculated, not drawn, so that is why I am confused. The other methods in SplineHelp that deal with offsets use a range from 0.0 to 1.0. So it is odd to me that GetSplinePointSegment and GetLinePointSegment do not. I use splines a lot, so I'm curious as to where their results would be useful?
Originally posted by xxxxxxxx
Confusingly, some of your spline examples have one point more in the segment than you report
Sorry for the confusion. In my humble opinion, the only spline you should have been able to reproduce in a relative fashion is example 2, where the points are in a straight line(assuming you and I were both using the same interpolation type and resolution). The other 2 examples should have varying line vertex counts depending on point locations, tangent locations, interpolation type and resolution. I was rushing and should have been more clear on what I was trying to demonstrate.
My main goal is to find the offset of a spline point along a segment. I have created a function to help solve this problem that I would like to share and discuss. Below is a new version of the previously posted CommandData plugin. It has been revised to test the functions I have created.
// main.cpp #include "c4d.h" #include "splinehelp_test.h" Bool PluginStart( void ) { if ( !RegisterSplineHelpTest() ) { return false; } return true; } void PluginEnd( void ) { } Bool PluginMessage( Int32 id, void* data ) { return false; }
//---------------------------------------------------------------------------------------- /// splinehelptest.h /// A CommandData plugin to test functions located in splinehelphelpers.h. /// @author Joe Buck /// @version 2.0 01/29/15 //---------------------------------------------------------------------------------------- #ifndef SPLINEHELP_TEST_H_ #define SPLINEHELP_TEST_H_ #include "c4d.h" class SplineHelpTest : public CommandData { public: //---------------------------------------------------------------------------------------- /// Prints information to the console about selected points /// in a SpineObject that is selected in the Object Manager. //---------------------------------------------------------------------------------------- virtual Bool Execute( BaseDocument* doc ); static SplineHelpTest* Alloc() { return NewObjClear( SplineHelpTest ); } }; Bool RegisterSplineHelpTest(); #endif // SPLINEHELP_TEST_H_
// splinehelp_test.cpp #include "splinehelp_test.h" #include "lib_splinehelp.h" #include "splinehelphelpers.h" #define PID_SPLINEHELP_TEST 1034539 //unique from plugincafe.com Bool SplineHelpTest::Execute( BaseDocument* doc ) { BaseObject* op = doc->GetActiveObject(); if ( ! op ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "No object selected.\n" "Execution stopping.", GEMB_OK ); return true; } if ( op->GetType() != Ospline ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Selected object is not Ospline.\n" "Execution stopping.", GEMB_OK ); return true; } Int32 spline_pcnt = static_cast<PointObject*>( op )->GetPointCount(); if ( spline_pcnt < 1 ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Zero point spline selected.\n" "Execution stopping.", GEMB_OK ); return true; } SplineHelp* spline_help = SplineHelp::Alloc(); if ( ! spline_help ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Could not allocate SplineHelp.\n" "Execution stopping.", GEMB_OK ); return true; } Bool init_ok = spline_help->InitSpline( op, Vector(), nullptr, false, false, true, true ); if ( ! init_ok ) { GeOutString( "SplineHelp Test\n" "Plugin Error:\n" "Could not Initialize SplineHelp.\n" "Execution stopping.", GEMB_OK ); return true; } BaseSelect* bs = static_cast<PointObject*>( op )->GetPointS(); GePrint( "------------------------" ); for( Int32 i = 0; i < spline_pcnt; i++ ) { if( bs->IsSelected( i ) ) { Int32 segment_idx; Int32 local_point_idx; GetSplinePointSegment( op, i, spline_help, &segment_idx, &local_point_idx ); GePrint( "Selected point index: " + String::IntToString( i ) ); GePrint( "Segment index: " + String::IntToString( segment_idx ) ); GePrint( "Local point index: " + String::IntToString( local_point_idx ) ); Float64 offset = GetSplinePointOffset( op, i, spline_help, doc ); GePrint( "Local offset: " + String::FloatToString( offset ) ); GePrint( "------------------------" ); } } SplineHelp::Free( spline_help ); return true; } Bool RegisterSplineHelpTest() { return RegisterCommandPlugin( PID_SPLINEHELP_TEST, String( "SplineHelp Test" ), 0, nullptr, String( "SplineHelp Test Help" ), SplineHelpTest::Alloc() ); }
//---------------------------------------------------------------------------------------- /// splinehelphelpers.h /// Helper functions for use with SplineHelp to get additional information about a SplineObject. /// @author Joe Buck /// @version 1.0 01/29/15 //---------------------------------------------------------------------------------------- #ifndef SPLINEHELPHELPERS_H_ #define SPLINEHELPHELPERS_H_ #include "c4d.h" #include "lib_splinehelp.h" //---------------------------------------------------------------------------------------- /// Returns the spline space offset of a spline point from the beginning of the point's containing segment. /// @param[in] op Search Object. Should be same SplineObject used to initialize SplineHelp. /// @param[in] spline_point_idx The spline point index to find and offset for. /// @param[in] spline_help Pointer to an allocated and initialized instance of SplineHelp. /// @param[in] doc Pointer to current BaseDocument. /// @return Offset(0.0 to 1.0) from beginning of containing segment in spline space. //---------------------------------------------------------------------------------------- Float64 GetSplinePointOffset( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, BaseDocument* doc ); //---------------------------------------------------------------------------------------- /// Find the index of a segment containing a specific spline point and the local index of that point /// @param[in] opSearch Object. Should be same SplineObject used to initialize SplineHelp. /// @param[in] spline_point_index Spline point index to search for. /// @param[in] spline_help Pointer to an allocated and initialized instance of SplineHelp. /// @param[out] segment_idx Assigned the index of the segment containing the spline point or NOTOK. /// @param[out] local_point_idx Assigned a local index where the first point in the segment will be 0 or NOTOK. //---------------------------------------------------------------------------------------- void GetSplinePointSegment( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, Int32* segment_idx, Int32* local_point_idx ); #endif // SPLINEHELPHELPERS_H_
// splinehelphelpers.cpp #include "splinehelphelpers.h" Float64 GetSplinePointOffset( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, BaseDocument* doc ) { LineObject* line_object = static_cast<SplineObject*>( op )->GetLineObject( doc, 1.0, nullptr ); Int32 line_vertex_idx = spline_help->SplineToLineIndex( spline_point_idx ); const CLine* ladr = line_object->GetLineR(); ladr += line_vertex_idx; return ladr->pos; } void GetSplinePointSegment( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, Int32* segment_idx, Int32* local_point_idx ) { Int32 scnt = spline_help->GetSegmentCount(); if( scnt == 0 ) { *segment_idx = NOTOK; *local_point_idx = NOTOK; return; } else if( scnt == 1 ) { *segment_idx = 0; *local_point_idx = spline_point_idx; return; } else { const Segment* sadr = static_cast<SplineObject*>( op )->GetSegmentR(); Int32 point_idx_counter = 0; for ( Int32 i = 0; i < scnt; i++, sadr++ ) { for( Int32 j = 0; j < sadr->cnt; j++, point_idx_counter++ ) { if( point_idx_counter == spline_point_idx ) { *segment_idx = i; *local_point_idx = j; return; } } } } *segment_idx = NOTOK; *local_point_idx = NOTOK; }
@Joey
I still have a few questions if you have time to answer:- In GetSplinePointOffset(), I retrieve a pointer to a LineObject. Is this considered an expensive operation?
- SplineObject::GetLineObject() has a parameter of L.O.D. Where can I find more information about its meaning.
- Is there a faster way than my function GetSplinePointSegment() to find the segment a point is?
- Is it possible to extend SplineHelp with a subclass?
Again, thanks for your time and support.
Joe Buck
-
On 30/01/2015 at 13:26, xxxxxxxx wrote:
Hi Joe Buck,
No problem, I'm here to help! I see Katachi derived the essence of my answer on his own, good stuff.
Question:
"I understand what you are saying, but most of the calculations in SplineHelp appear to deal with how a spline is calculated, not drawn, so that is why I am confused. The other methods in SplineHelp that deal with offsets use a range from 0.0 to 1.0. So it is odd to me that GetSplinePointSegment and GetLinePointSegment do not. I use splines a lot, so I'm curious as to where their results would be useful?"
Answer:
That's a good question. They get you to the 'segment' / 'point on the segment' combination of values, and not any more precise. It's probably to find the point at which to change its structure, or to cut it up, for instance. I believe you may be trying to find a more precise position along the spline than the segments / points used to draw it can provide? If that's the case, you'll have to use more segments and/or points, as you're trying to find a 'sub-point' that doesn't really exist as anything but perhaps one or more pixels when drawn. Precision vs. efficiency are the trade offs being balanced here.
Question set:
"My main goal is to find the offset of a spline point along a segment. I have created a function to help solve this problem that I would like to share and discuss. Below is a new version of the previously posted CommandData plugin. It has been revised to test the functions I have created."
"I still have a few questions if you have time to answer:"
1. "In GetSplinePointOffset(), I retrieve a pointer to a LineObject. Is this considered an expensive operation?"
It depends on what you mean by expensive. First make sure you're getting the results you expect by tweaking your data set, the parameters, etc. and then benchmark your code. I assume you mean in terms of relative speed, considering all the factors (the parameters you set, amount of iterations across all your splines, etc.). I'm saying this because what I see in C4D's source code is certainly not short, but is meant to be as efficient as possible.
2. "SplineObject::GetLineObject() has a parameter of L.O.D. Where can I find more information about its meaning?"
The docs state "lod : The level of detail to create the line." which I know isn't clear in practical terms, so I analyzed the code and found it affects various low level values. There's some differences in the calculations between values at or below 1.0 and above 1.0. I believe you'll have to try different values to fine tune your results, as there is no way to derive a practical explanation due to code complexity in this case.
3. "Is there a faster way than my function GetSplinePointSegment() to find the segment a point is on?"
Yes, your inner for loop can be eliminated if you instead check that your point index counter and the current segment's amount of points added together is more than the spline point index you're looking for, as it means the current segment contains the point if true. The segment index would be assigned the same (ie to i), but the local point index would be assigned the difference between the spline point index and the point index counter, and then return. Otherwise if not yet in range, you add the point count for that segment to the point index counter and loop to the next segment.
You may have to tweak what I describe to make sure the range is correct, but it's the general idea on how to optimize your code. After you write it, you can post it if you want me to double check its correctness.
Pseudo code:
for loop for the segments { if (point index counter and current segment point count combined is beyond the spline point index value) { *segment_idx = i; *local_point_idx = the difference between the spline point index and the point index counter; return; } add the point count for that segment to the point index counter; }
4. "Is it possible to extend SplineHelp with a subclass?"
It's probably not possible, and I'd at least not recommend it, for various reasons, especially unknown future changes that may be made in the SDK that could affect it. It's best that you use it within a class that provides extended capability.
I'm glad to help!
Joey Gaspe
SDK Support Engineer -
On 01/02/2015 at 20:14, xxxxxxxx wrote:
@Professor Gaspe
3. Thanks for helping me think about coding in a different way. I'm a motion-graphics artist and do not get to talk to people about c++ very often. Here is the revised code://---------------------------------------------------------------------------------------- /// splinehelphelpers.h /// Helper functions for use with SplineHelp to get additional information about a SplineObject. /// @author Joe Buck /// @version 1.1 02/01/15 /// revised GetSplinePointSegment() to be more efficient //---------------------------------------------------------------------------------------- #ifndef SPLINEHELPHELPERS_H_ #define SPLINEHELPHELPERS_H_ #include "c4d.h" #include "lib_splinehelp.h" //---------------------------------------------------------------------------------------- /// Returns the spline space offset of a spline point from the beginning of the point's containing segment. /// @param[in] op Search Object. Should be same SplineObject used to initialize SplineHelp. /// @param[in] spline_point_idx The spline point index to find and offset for. /// @param[in] spline_help Pointer to an allocated and initialized instance of SplineHelp. /// @param[in] doc Pointer to current BaseDocument. /// @return Offset(0.0 to 1.0) from beginning of containing segment in spline space. //---------------------------------------------------------------------------------------- Float64 GetSplinePointOffset( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, BaseDocument* doc ); //---------------------------------------------------------------------------------------- /// Find the index of a segment containing a specific spline point and the local index of that point /// @param[in] op Search Object. Should be same SplineObject used to initialize SplineHelp. /// @param[in] spline_point_index Spline point index to search for. /// @param[in] spline_help Pointer to an allocated and initialized instance of SplineHelp. /// @param[out] segment_idx Assigned the index of the segment containing the spline point or NOTOK. /// @param[out] local_point_idx Assigned a local index where the first point in the segment will be 0 or NOTOK. //---------------------------------------------------------------------------------------- void GetSplinePointSegment( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, Int32* segment_idx, Int32* local_point_idx ); #endif // SPLINEHELPHELPERS_H_
// splinehelphelpers.cpp #include "splinehelphelpers.h" Float64 GetSplinePointOffset( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, BaseDocument* doc ) { LineObject* line_object = static_cast<SplineObject*>( op )->GetLineObject( doc, 1.0, nullptr ); Int32 line_vertex_idx = spline_help->SplineToLineIndex( spline_point_idx ); const CLine* ladr = line_object->GetLineR(); ladr += line_vertex_idx; return ladr->pos; } void GetSplinePointSegment( BaseObject* op, Int32 spline_point_idx, SplineHelp* spline_help, Int32* segment_idx, Int32* local_point_idx ) { Int32 scnt = spline_help->GetSegmentCount(); if( scnt == 0 ) { *segment_idx = NOTOK; *local_point_idx = NOTOK; return; } else if( scnt == 1 ) { *segment_idx = 0; *local_point_idx = spline_point_idx; return; } else { const Segment* sadr = static_cast<SplineObject*>( op )->GetSegmentR(); for ( Int32 point_idx_counter = 0, segment_idx_counter = 0; point_idx_counter <= spline_point_idx; point_idx_counter += sadr->cnt, sadr++, segment_idx_counter++ ) { *segment_idx = segment_idx_counter; *local_point_idx = spline_point_idx - point_idx_counter; } return; } }
1. I asked because Niklas mentioned getting the LineObject with GetCache() in this post:
https://developers.maxon.net/forum/topic/7511/9397_percentage-offsets-of-interpolation-points
I will continue testing.2. Fair enough. I will continue testing.
4. Ah yes. Rookie question.
@Joey
Unless you have any more wisdom you would like to share, I think we can consider this topic solved. Do I mark it or do you?Thanks for your expert tutelage and professional support.
Joe Buck
-
On 02/02/2015 at 11:57, xxxxxxxx wrote:
Hi Joe Buck,
Thanks for letting us know you appreciate our help. The Cinema 4D SDK support team is at your service to help you with your plugin questions!
I looked over your code, and it appears to be correct. Just a recommendation to make: The convention is to not put too much code in the for loop control section, and more in the body of the for loop. It makes it a bit harder to read, but if you've tested it and found it to work, it's best to follow the 'if it isn't broken, don't fix it' rule of thumb.
Since you state your questions are covered, I'll close the topic as solved.
Joey Gaspe
SDK Support Engineer