Varied node radius for CurveNetwork.
Coming back to this issue from last year: https://github.com/nmwsharp/polyscope/issues/139
I copied the same approach from PointCloud and add a function to allow varying the node radius of CurveNetwork.
The radius can be set using:
polyscope::getCurveNetwork(name_crv)
->addNodeScalarQuantity("nodeR", varyingR);
polyscope::getCurveNetwork(name_crv)
->setNodeRadiusQuantity("nodeR");
However, the edges currently cannot be set correctly. Based on the discussion, I think it is good to blend the curve radius to the radius of two nodes, instead of managing two sets of radii (node + edge) -- it makes things cleaner.
However, this requires a new "truncated cone" shader so that the two radii of the cylinder on the two ends can be different. My openGL knowledge is a bit limited, can have difficulty doing that.
The current effect is shown below:

P.S. The PR contains some commented PR (what I started by allowing also managing edge radius, but later decide not to). Please feel free to use it or delete it.
OK. I learned a bit of GSGL language and modified the shader.
The current scheme is to get the two radii (tip, tail) of the cylinder from the nodes, so that the network is still rendered geometrically smoothly.
Changes:
- cylinder shader, use
tipRadiusandtailRadiusto replace the case of uniform radius. - Modified corresponding functions.
- add a replacementRule for
cylinder_shaders:CYLINDER_VARIABLE_SIZE - add functions to set node radii from scalar arrays.
Currently, the updated viewer works fine for me:

@nmwsharp Will this PR be taken care of at any point?
Hi! Sorry for the delay on this. I took a break from Polyscope development for a while as I was moving and whatnot, but I'm trying to get a new version out now! I haven't forgotten this PR and I'll get it in as soon as I can. Thank you for submitting it :)
Coming back to this issue again after half a year, it seems this PR will never be merged... But anyway, I'm considering expanding the shader to allow drawing lines with different cross-sectional profiles (regular shapes of course, just different number of edges).
Should I include the feature with the same PR or a different one?
OR, if you don't want to have it in the official polyscope, would it be possible to draw the polyscope::network with customized shader?
Hi! To be clear, I definitely do want to get this in to Polyscope, it's a very cool feature, and I appreciate you submitting it.
The hold-up is mainly that for C++ -only PRs, I need to find time to write unit tests, docs, and Python bindings, and that time is hard to come by! It's on my TODO list for the next version, which I'm gradually working through; I got in a bit of dev time in the last month. If you could add some tests or write docs, that would help.
RE the new feature you mention, do you mean rendering curve networks with edges which are triangular prisms, hexagonal prisms, etc? To answer directly, I'd suggest to put that in a different PR so we can merge this one individually sooner rather than later. More broadly, can I ask what the use-case is for rendering like that? Also how would you render the corresponding nodes? I guess we could just disable the nodes.
Custom shaders would be neat, and I've looked in to doing them for e.g. per-fragment color. However, I'm not really sure about the API for more complex custom shaders like edge geometry. As a quick answer, I'd say that's not a great candidate for custom shaders until we've at least tackled the simpler cases like per-fragment color.
@nmwsharp Hey, thanks for the quick respond.
Yes, I can try to find time to write test, even with Python part. I also write Python.
Do you have guidelines for such things? Or any reference? It seems the files in the test folder are not updated recently.
As a side note, I think the cpp part can be faster and have more features than the Python bindings, or do you expect the two have the same features?
As this lib seems mainly used by cpp developers, I don't see why we should postpone adding any feature just because we don't have a corresponding Python binding.
RE the new feature you mention, do you mean rendering curve networks with edges which are triangular prisms, hexagonal prisms, etc? To answer directly, I'd suggest to put that in a different PR so we can merge this one individually sooner rather than later. More broadly, can I ask what the use-case is for rendering like that? Also how would you render the corresponding nodes? I guess we could just disable the nodes.
Yes, we could disable the nodes. Or allow bigger nodes to cover the ends.
I'm developing a software to view some 3d printable micro-structures, where the cross-section shape of linear elements can be defined and make a difference in the performance of the structure. So I need a feature to render the network structure with linear elements beyond the cylinder (circular) shape.
I see in the current cylinder shader, the cross-section seems to be square? How does it get to render to a "circular" shape? I don't know gsgl very well, the code in this PR is just "learn by reading your code". But I'm open to learn to the extent that is enough for such features.
Of course, I can render mesh, but given the amount of linear elements I'm working with is high, shaders should be better.
Custom shaders would be neat, and I've looked in to doing them for e.g. per-fragment color. However, I'm not really sure about the API for more complex custom shaders like edge geometry. As a quick answer, I'd say that's not a great candidate for custom shaders until we've at least tackled the simpler cases like per-fragment color.
If we go shaders, I would say cross-sections with 3-8 sides should be enough (triangle, square, ... octagon). +8 edges will not render so different to circles.
Additionally, if we're aiming high and allow customizable cross-section for linear elements, like https://github.com/EPFL-LGG/UmbrellaMesh_release_v1 (where they can do physical sim with customizable cross-sectional shape), then it is a different story, and I don't know how to do that.
Ah okay, makes sense! (And sounds like a cool project.)
How does it get to render...
Right now the way it works is that the geometry shader constructs a box which is guaranteed to contain the full cylinder. Then afterward, when shading each fragment (aka pixel) of the box, the shader evaluates an analytically-defined ray-cylinder intersection test. If this test says the pixel misses the cylinder, then it gets "discarded" and is not actually drawn, and otherwise we set it to be the appropriate depth/color for the cylinder. So to implement your feature, the initial box can stay the same, and the main work would be to write a corresponding analytical function which evaluates a ray-triangular-prism intersection, or a ray-hexagonal-prism intersection, etc.
Initially I'm a little concerned about such a feature being too niche, and there's not really much advantage vs. writing a helper function to spit out the piecewise-flat geometry of your edge elements. In fact, the explicit geometry version will probably render faster too! However, if you think it makes sense as a built-in feature I'm happy to discuss more, perhaps it could be implemented in a way that doesn't add much complexity elsewhere.
Tests
Adding a test like this for the curve network radius quantity would be awesome https://github.com/nmwsharp/polyscope/blob/master/test/src/basics_test.cpp#L190-L214 . The tests only barely cover the codebase, but they're very helpful for getting things working cross-platform, which is otherwise a huge headache.
Bindings
Mostly adding Python bindings are very simple, just a few wrapper lines like here and here, with test here.
The harder part is probably building/running the Python version locally, and understanding how Pybind11 works if you haven't used it before.
Python
Actually, based on the traffic to the docs pages, significantly more people use Polyscope in Python than C++ (I was surprised too 😄). I think part of the reason is that less people write their own graphics code in Python. And there are just a lot of Python programmers out there these days!
I mainly try to keep C++ & Python up to date and equal whenever possible, because it's one less thing to remember which feature is available where, I can update the docs at the same time, and the Python bindings serve as an extra test for the C++ version. But you're right, it's not strictly necessary to keep them equivalent, and there are indeed a few features available in C++ but not Python because it is not straightforward to write bindings.
In terms of priority, it's: C++ implementation > C++ tests > C++ docs > Python bindings > Python tests > Python docs. I'm willing to merge PRs as long as they have C++ implementation & tests; everything else is very much appreciated, but I can do it later.
Right now the way it works is that the geometry shader constructs a box which is guaranteed to contain the full cylinder. Then afterward, when shading each fragment (aka pixel) of the box, the shader evaluates an analytically-defined ray-cylinder intersection test. If this test says the pixel misses the cylinder, then it gets "discarded" and is not actually drawn, and otherwise we set it to be the appropriate depth/color for the cylinder. So to implement your feature, the initial box can stay the same, and the main work would be to write a corresponding analytical function which evaluates a ray-triangular-prism intersection, or a ray-hexagonal-prism intersection, etc.
Initially I'm a little concerned about such a feature being too niche, and there's not really much advantage vs. writing a helper function to spit out the piecewise-flat geometry of your edge elements. In fact, the explicit geometry version will probably render faster too! However, if you think it makes sense as a built-in feature I'm happy to discuss more, perhaps it could be implemented in a way that doesn't add much complexity elsewhere.
Forgive me that I'm not an expert in shaders... My understanding for shaders is ONLY "to allow you to create more complex geometries, different visual styles, etc. on the GPU side for simpler defined geometries".
From your explanation, If I understand correctly, your cylinder is not something like "triangle-based cylinder that looks like a cylinder" but a true implicit cylinder, isn't it?
If my understanding is correct, then yes, I think for my case, explicit definition should be enough. And if you also make the cylinder explicit (octagon should be enough to render very similar to cylinders), I'm also happy to test.
In fact, I do notice that when loading ~1 million cylinders, the loading time is a bit slow... it might be helpful to compare between/benchmark the two.
(I'll add some test, probably also the python part soon.)
@nmwsharp I add some test in the test folder.
It seems to better wait for the merge before I start the Python part, since the polyscope-py depends on the submodule of this repo for the binding process.
Merged! Thanks for contributing this.
BTW I realized afterward that I think there was a small bug in the autoscale setting, which I corrected here 1623d84a64744d2e0a8f6bd190b0734ca3269ed7. Let me know if that doesn't seem right.
From your explanation, If I understand correctly, your cylinder is not something like "triangle-based cylinder that looks like a cylinder" but a true implicit cylinder, isn't it?
Yep, this is correct. We just use the box to define the region to process, then raycast each pixel against a true implicit cylinder. This looks nice, but can be computationally expensive (which is why sometimes the viewer gets slow if curve networks, spheres, or vectors are taking up a large fraction of your screen).
Great!
I'll find some time this week to take care of the Python side about this PR.
From your explanation, If I understand correctly, your cylinder is not something like "triangle-based cylinder that looks like a cylinder" but a true implicit cylinder, isn't it?
Yep, this is correct. We just use the box to define the region to process, then raycast each pixel against a true implicit cylinder. This looks nice, but can be computationally expensive (which is why sometimes the viewer gets slow if curve networks, spheres, or vectors are taking up a large fraction of your screen).
See. Then I think it might be good to use the cheaper option (if you're fond of the current approach, then perhaps provide an option for the users?) As I mentioned, I'm not very familiar with shader language. But if you can start one such shader using the polygon-prism approach, I think I can help with the rest by following your script.
FYI, I realized that the shader was still not rendering "tapered" cylinders correctly, the new quadratic program wasn't quite right.
I reverted the cylinder shader back to it's old form, and did some derivations for an actual tapered cylinder shader, it's now implemented here https://github.com/nmwsharp/polyscope/blob/master/src/render/opengl/shaders/common.cpp#L264 and used for rendering variable radius curve network edges. I tested it a bit and it seems to work right, but let me know if anything seems off.
Hey, yes you're correct. I tested yours and it looks good to me. All things working on my side. Thanks for checking.
I'm a bit busy last week and will find time for the python part soon.
BTW, do you have plans to create a prism-like cylinder shader as we discussed above?
@nmwsharp I'm working on the python binding of this PR.
It seems the code is merged towards the v2 branch rather than the master. Should I create PR towards the v2 branch?

If yes, the PR is created at: https://github.com/nmwsharp/polyscope-py/pull/27