How to turn needle copies slightly towards the branch end?

   3625   18   3
User Avatar
Member
57 posts
Joined: April 2018
Offline
Hi!

I found some related posts here in the forum but they were different cases, did not seem to help with this.
This is partially related to Clarisse but the math is done in Houdini so even if you are not familiar with Clarisse, please check if you could help me
Sorry about longish post but I wanted to give all info so you guys won't need to guess anything about what I am trying to do

I have a branch geometry (triangles, made by remesh) and then I want to copy needles on the surface but so that they slightly turn towards the end of the branch. Just simply imagine how fir or pine branch looks like, the needles are not sticking directly outwards, they turn to the end of branch. That is my goal.

But I will not do the copying in Houdini, I want to generate attribute(s) that can drive Clarisse scattering to rotate the needle instances towards the branch ends. But for that the math is still needed of course.
See the attached image, that is what I get without this in Clarisse. Not bad but too ‘messy’.

In theory I think (one) solution would be to modify the surface point normals (and create new attribute from that) so that they somehow are affected by the surface direction but I cannot figure out that ‘somehow’ part
The problem is that branches are not straight tubes, otherwise the direction would be towards the farthest point or something like that.
I have done some VEX but I'm still quite newbie with it so I hope this is even easy or at least doable. I'm still pretty sure VEX will be needed even though there are amazing nodes in Houdini.

I have attached a hip that has simple setup so ‘only’ thing needed is the turning of needles towards the branch end

Just for info:
This is part of my quite huge project where I make a Houdini batch tool that takes OnyxTree (great software and biologically accurate but old so cannot produce 15+ million poly trees and all branch levels are separate connected pieces) trees and rebuilds the geometry and UVs so that all branch joints are smooth so they can be watched quite closely. In addition to that I want to remove needles and leaves and instead have quaternion/vector attribute in *branch* geometry that I can use in Clarisse to drive scattering rotation so there will be no limits to number of needles/leaves and they can be adjusted dynamically there.
Best would be attribute that gives rotation for the instances so that they are otherwise sticking directly outwards the branch surface but in some angle towards the branch end. It sounds easy but at least for me it seems not.

The final result will be Alembic file where the branches have attributes for rotation of needle instances. Normals may be enough but if possible, maybe quaternion attribute would be better or both if possible
Note: The points the Scatter here produces are *not exported*, they are only used to create the attribute. The main problem is the math.

In Houdini process I have each branch part as separate connected piece (but not straight tube) and they have UV towards the end so that might be used to determine the direction somehow. And I have also calculated relative distance from Trunk for each branch (Bough, Branches1,2,3 to Twig tips, to control needle distribution in Clarisse) so that too could be used in this at least to know the direction somehow.

In final geometry the branch parts will be remeshed together but then Attribute Transfer can get those attributes back so for this it is enough to think each part as curvy tube, no branches inside a connected piece if that helps.

Also please make the solution as fast as possible, there are around 30000 branch parts to process in a tree

Can anyone please help me or is this too complicated to do even for Houdini?

Thank you very much!

Antti
Edited by atnreg - March 3, 2020 10:20:49

Attachments:
pine_needles_clarisse.png (1.2 MB)
needle_instance_rotation.hiplc (94.3 KB)

My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
406 posts
Joined: April 2017
Offline
I don't know shit about Clarisse, but I assume the instancing method is the same (you need to provide a normal and up vector for orienting each copy).

Really what you want to do is bias the normal (+Z) for each copy towards the tip of the branch. If you were doing this in VEX, what you'd do is compute the vector between the tip of the branch (B) and the root of each needle (R), normalize that difference, and then lerp between that vector and the current normal vector. So,
N = lerp(N, normalize(B-R), bias);
, where bias is a number between 0-1 that determines how much you want to bend each needle towards the tip.
Edited by toadstorm - March 3, 2020 20:25:24
MOPs (Motion Operators for Houdini): http://www.motionoperators.com [www.motionoperators.com]
User Avatar
Member
205 posts
Joined: Jan. 2014
Offline
this post should be shorter)
as toadstorm said, also you can use “orient” on your points, clarisse understands that too.
this function should help with conversion from you instance direction to the branch direction.
https://www.sidefx.com/docs/houdini/vex/functions/dihedral [www.sidefx.com]
I contribute to the beauty of this world
https://houdininotes.ivanlarinin.com/ [houdininotes.ivanlarinin.com]
User Avatar
Member
57 posts
Joined: April 2018
Offline
Hi!

Thank you very much for your fast and helpful replies, both gave me keywords to work on (never heard of either of these before)

The only problem still remaining is that as I said the branches are not straight tubes so finding the ‘tip’ point is not that easy. In fact, it would be best to find the direction of the actual branch at any point (or slightly ‘ahead’ towards the tip of the point which we are processing) and then use those functions to calculate the new @orient.

The question is, how can we find that ‘ahead’ when it is not in any axis direction and there are no edgeloops?

Of course using branch parts gives quite good results but if the branch is very curved, the ‘tip’ point leads to somewhat wrong direction.

Any ideas for this?

Thank you very much!

Antti
Edited by atnreg - March 4, 2020 06:04:24
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
57 posts
Joined: April 2018
Offline
Actually there ARE edgeloops in original geometry (I do remesh later) so I ‘only’ need to find ‘next’ edgeloop in the geometry (starting from ‘bottom’ polygon of the branch). I have been reading and searching but so far I haven't found way to do for-loop for edgeloops or something like that, on VEX or nodes. I will continue the research of course but if someone can give ideas, please do
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
57 posts
Joined: April 2018
Offline
I tested it and it works great on straight or almost straight branch parts but curvy goes crazy.
So I really need some way to calculate the direction vector in pieces, still no idea how to do that, I've been reading and watching tutorials and tips but no luck so far

So how to do for-loop or make separate pieces of polyloops of normal tube with regular polygon structure with edgeloops?
Or select ‘next’ edgeloop?

Or actually it might be better to select edge ring (polyloop) instead, that way there is both ‘bottom’ and ‘tip’ for each. But how to do that, I have no idea

The search goes on…any ideas are highly welcome!

Antti
Edited by atnreg - March 4, 2020 10:24:09
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
504 posts
Joined: July 2005
Offline
Hi,

if your geometry is connected you can define a group (containing a few points) at the root/base. There is a new node called “distance along geometry” and there is also a VEX function, which can be used to calculate an approximation of the geodesic distance for each point to this base group. Once you have the distance, you can use it to calculate something like the gradient for each point using its neighbours. The neighbours on the side, where distance is bigger (than of the point) should dominate the direction to their side (to the end of the branches) and the neighbours from the other side (where the distance is smaller), should push away the direction from their side, which is also the direction to the end of the branches.

Here is an example
Edited by Aizatulin - March 4, 2020 13:25:02

Attachments:
needle_instance_rotationDist.hipnc (138.2 KB)

User Avatar
Member
57 posts
Joined: April 2018
Offline
WOW! It looks great!
I will study it thoroughly and will test it on real geometry tomorrow.

Thank you especially for the working demo, by text I would not have figured it out how to do it

I started testing method where I first find out the number of points in the ‘bottom’ edge loop and then use that to loop every that many points but it is risky as the original geometry may have irregularities.

So this really looks perfect!

Thank you VERY much

Antti
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
57 posts
Joined: April 2018
Offline
(edited to only show the question as my ideas were wrong, using lerp())

This method works quite nicely but I think it would be even better (and more adjustable) if the attribute was quaternion. How can I do that?

Antti
Edited by atnreg - March 12, 2020 02:35:39
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
406 posts
Joined: April 2017
Offline
You're gonna have to brush up on a little math for this one.

A quaternion describes an orientation, similar to a 3x3 transform matrix (a matrix without position information). You can't just lerp two vectors and get a quaternion out the other side… you're just getting another vector. If what you're trying to do is get a quaternion that describes the rotation necessary to rotate vector A onto vector B, you can use
dihedral(A, B)
in VEX to get that quaternion. Otherwise, you need to create the 3x3 matrix yourself out of two vectors using maketransform(N, up), then converting that to a quaternion:
vector4 q = (quaternion(maketransform(N, up));

Quaternions are 4-dimensional vectors, so a Float32 size 4 should be correct.

Last thing… quaternions don't have a rotation order. Rotation order is only necessary when describing Euler rotations (rotate X, rotate Y, rotate Z). The nice thing about quaternions is that they simply describe an orientation as-is; there's no need to worry about order at all. They're just a lot harder to read by humans. If you want to visualize the orient quaternion in Houdini, just make a little gnomon geometry or even use the default File SOP box geometry, and copy it to a set of points that have your orient attribute.
MOPs (Motion Operators for Houdini): http://www.motionoperators.com [www.motionoperators.com]
User Avatar
Member
57 posts
Joined: April 2018
Offline
First, thank you very much for very fast reply!

You're gonna have to brush up on a little math for this one.
Yes I know, the problem is I'm very new to Houdini (see my sig) and before that I have needed matrices very little on my 20+ years of 3D hobby
I have studied and learned some basics about matrices but they are still quite a mystery to me

A quaternion describes an orientation, similar to a 3x3 transform matrix (a matrix without position information). You can't just lerp two vectors and get a quaternion out the other side… you're just getting another vector.
Oops now I'm embarrassed, it was YOU who explained the lerp function above and somehow I remembered it returns quaternion and I was so sure that I didn't even re-read your post. Sorry!

If what you're trying to do is get a quaternion that describes the rotation necessary to rotate vector A onto vector B, you can use
dihedral(A, B)
in VEX to get that quaternion. Otherwise, you need to create the 3x3 matrix yourself out of two vectors using maketransform(N, up), then converting that to a quaternion:
vector4 q = (quaternion(maketransform(N, up));
Ok, I think I got the idea now. Dihedral (which you also explained above, sorry again! ) seems the right way but how can I have the bias into it? For needles to get correct angle I don't want to rotate them all the way to surface but slightly less, so how to do that? Slerp has bias but it needs two quaternions…aargh…

Quaternions are 4-dimensional vectors, so a Float32 size 4 should be correct.
Good, I thought so too
So @orient shows like that by default (even without prefix) but what if I want to make my own quaternion attribute, how it must be initialized with Attribute Create (vector can't be used, right? Maybe float array of 4?)?

quaternions don't have a rotation order. Rotation order is only necessary when describing Euler rotations (rotate X, rotate Y, rotate Z). The nice thing about quaternions is that they simply describe an orientation as-is; there's no need to worry about order at all. They're just a lot harder to read by humans.
Yes sorry, I need to convert the quaternion to euler angles in Clarisse (there is extract_property node that reads Alembic attributes and it has mode and one of the modes is ‘Quaternion to Euler’). So that's where I need the Houdini rotation order
I think Houdini uses XYZ (it was briefly mentioned in some post) but can you confirm that to make absolutely sure?

If you want to visualize the orient quaternion in Houdini, just make a little gnomon geometry or even use the default File SOP box geometry, and copy it to a set of points that have your orient attribute.
Yes I can do that except that I am only storing the quaternion value into the geometry so simple visualization was nice way. But so there is no way to add visualizer for quaternion, that's what I thought but wanted to make sure there is no trick

Thank you very much for your help and again sorry for not re-reading your earlier posts, of course I needed to know more but I would have saved at least some of your time.

Antti
Edited by atnreg - March 11, 2020 18:02:14
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
406 posts
Joined: April 2017
Offline
Ok, I think I got the idea now. Dihedral (which you also explained above, sorry again! ) seems the right way but how can I have the bias into it? For needles to get correct angle I don't want to rotate them all the way to surface but slightly less, so how to do that? Slerp has bias but it needs two quaternions…aargh…

Create a quaternion that describes your initial orientation. You already have one that describes the orientation you want… the original is whatever orientation the needles started with. If you're starting with N and up vectors, just use
vector4 init_orient = quaternion(maketransform(v@N, v@up));
. Then slerp the two quaternions according to your bias.

So @orient shows like that but what if I want to make my own quaternion attribute, how it must be initialized with Attribute Create (vector can't be used, right? Maybe float array of 4?)?
Assuming you're using VEX, you can bind a quaternion attribute using the prefix p. So,
p@orient = slerp(orientA, orientB, bias);

Yes sorry, I need to convert the quaternion to euler angles in Clarisse (there is extract_property node that reads Alembic attributes and it has mode and one of the modes is ‘Quaternion to Euler’). So that's where I need the Houdini rotation order
I think Houdini uses XYZ (it was briefly mentioned in some post) but can you confirm that to make absolutely sure?
Clarisse can't read an orient attribute? That's surprising. Houdini by default uses XYZ rotation order. If you really need Eulers in a specific rotation order, you can use the cracktransform() VEX function to extract Eulers from a matrix3.
#include <math.h>
matrix3 m = qconvert(p@orient);
vector rotate = cracktransform(XFORM_TRS, XFORM_ZXY, 1, {0,0,0}, m);
v@euler = rotate;

That would bind your Euler rotations, in degrees, to a point attribute v@euler. The XFORM_ZXY is in Clarisse's preferred order, but you could change that to whatever you need.
Edited by toadstorm - March 11, 2020 18:15:08
MOPs (Motion Operators for Houdini): http://www.motionoperators.com [www.motionoperators.com]
User Avatar
Member
57 posts
Joined: April 2018
Offline
WOW you are fast!

Create a quaternion that describes your initial orientation. You already have one that describes the orientation you want… the original is whatever orientation the needles started with. If you're starting with N and up vectors, just use
vector4 init_orient = quaternion(maketransform(v@N, v@up));
. Then slerp the two quaternions according to your bias.
Hmm…so in this case the ‘up’ is the other vector (v@D)? If that works, then yes I can use this method

Assuming you're using VEX, you can bind a quaternion attribute using the prefix p. So,
p@orient = slerp(orientA, orientB, bias);
Oh, p? I didn't even know there was that prefix too

Clarisse can't read an orient attribute?
Sorry I was a bit unclear, Clarisse can't read it directly but using node extract_property and that node has the conversion option to Euler. So the property can be quaternion but Clarisse scatterer (like copy to points with variation handling) instance rotation (Euler) can be taken from that conversion.

Houdini by default uses XYZ rotation order.
Great, thank you, I will use that.

If you really need Eulers in a specific rotation order, you can use the cracktransform() VEX function to extract Eulers from a matrix3.
#include <math.h>
matrix3 m = qconvert(p@orient);
vector rotate = cracktransform(XFORM_TRS, XFORM_ZXY, 1, {0,0,0}, m);
v@euler = rotate;
That would bind your Euler rotations, in degrees, to a point attribute v@euler. The XFORM_ZXY is in Clarisse's preferred order, but you could change that to whatever you need.
Ok, nice to know this is how you do it in Houdini but I don't need it in this case

Thank you very much again, I'll try these in the morning (it's night here now )!

Antti
Edited by atnreg - March 11, 2020 18:40:17
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
57 posts
Joined: April 2018
Offline
I have tried to get this to work and got some progress but the problem is that I don't *fully* understand what the Aizatulin's VEX code does. I understand the idea but not all code.

I think the problem here may be that I am not creating pointcloud but putting attributes in geometry so that Clarisse can read it and then adjust the rotation of scattered needles by that.

There is attr ‘dist’ that is calculated as distance from the start point.
Then there is this:
int nei[];
nei = neighbours(0, @ptnum);

int n = len(nei);
vector Q = {0,0,0};
float dist = point(0, 'dist', @ptnum);
for (int i = 0; i < n; i++)
{
    vector A = point(0, 'P', nei[i]);
    float distA = point(0, 'dist', nei[i]);   
    Q += (distA - dist) * (A - v@P); // what this actually results in?
}
v@D = normalize(Q);
And after that this:
v@N = normalize(v@N + chf('amount') * v@D);
That seems to be same as
v@N=lerp(v@N,v@D,chf('amount'));
so I have understood at least something

I also get quaternion by this:
p@orient=dihedral(v@N,v@D);
It also seems to work in Clarisse but there is no bias adjustment now. I don't know where I could get the second quaternion needed by slerp now

Any ideas?

Antti
Edited by atnreg - March 12, 2020 10:02:30
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
504 posts
Joined: July 2005
Offline
Hi,

Q += (distA - dist) * (A - v@P); // what this actually results in?

distA is the distance value of point A, while dist is the distance value of v@P.
If A is closer to the end of the branches distA is will be greater than dist so (distA - dist) > 0 and the Direction (A - v@P) will be added (as positive value ~ from v@P to A) to Q. In this case the Direction (A - v@P) is more going to the end of the branches.
If v@P is closer to the end of the branches, then (distA - dist) < 0 and (A - v@P) will be added (as negative value ~ from A to v@P) to Q, which means the Direction is going away from A to v@P.

You can use the v@D attribute to create a rotation axis, which can be used to define an v@up vector or quaternion.

here is another version with a rotation.

Attachments:
needle_instance_rotationDistRot.hipnc (140.2 KB)

User Avatar
Member
57 posts
Joined: April 2018
Offline
Thank you very much, now I think I understand it

And also thank you for working demo hip, the change is not big in code but makes a big difference
Ah, that's how I get the up vector!

Now only problem is that if I take that @orient to Clarisse, I get really weird rotations. That of course is not Houdini's problem but do you (or someone else ) happen to be familiar with Clarisse and tell how I must set up the Clarisse extract_property for Scatterer?
I have found some posts in Clarisse forum about Houdini rotation with @orient but none of those work for this
The difference from those is that I don't want to create pointcloud in Houdini, I want to drive Clarisse scattering (and dynamic pointcloud) with this info. I know how to do that in general but when it comes to @orient and Houdini, I haven't found solution.

Anyway, now I know in Houdini end it is correct so unless you have some cool solution to that too, I will try to figure it out myself and if that does not help, bother Clarisse forum with this next

Thank you very much!

Antti
Edited by atnreg - March 12, 2020 10:20:48
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
504 posts
Joined: July 2005
Offline
Sure

but I'm not familiar with Clarisse so I can't help you there.

Only one more thing: p@orient was commented out by default (but if this not the case I have no idea).
User Avatar
Member
57 posts
Joined: April 2018
Offline
but I'm not familiar with Clarisse so I can't help you there.
Ok, too bad but you have already helped a lot (like other people here too of course)

Only one more thing: p@orient was commented out by default (but if this not the case I have no idea).
Yes of course I noticed it

Antti
Edited by atnreg - March 12, 2020 10:38:35
My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
User Avatar
Member
57 posts
Joined: April 2018
Offline
I have been busy with other things but the tree project is progressing, here is latest capture from Clarisse (note the geometry and primitive counts, 300 trees in scene )

Thank you VERY much for your help, guys!

Still a lot to do both in Houdini (twig geometry cleanup) and Clarisse (materials etc.) but this actually looks close to Finnish Pine
Edited by atnreg - March 15, 2020 12:31:44

Attachments:
tmp_capture_01683.png (1.0 MB)

My system:
AMD Ryzen Threadripper 2990 (32-core),64GB RAM,NVIDIA RTX 3090,Win10
UE5.1,Houdini 19.5 Indie(always latest daily),Blender,Clarisse,Onyx,ZBrush...
Clarisse 2016-09, Houdini 2018-05 (seriously 2019-10)
I do all 3D for fun, no business
  • Quick Links