Skip to main content

Triangulation and basics of dynamic polygons

· 2 min read
Kirill Vasin

It is quite tricky to make an efficient function for plotting dynamic 3D surfaces, which change with time using a high-level code such as Wolfram Language.

However, graphics library provides a low-level access to a GPU, that operates with vertices and triangles. The last one is our way to go

Download original notebook

TLDR

Download this notebook to see the result


How to draw fast

There is a special structure for that

GraphicsComplex[vertices_, primitives_, "VertexColors"->colors_]

where vertices will be send directly to the GPU and can be efficiently updates as well. The only drawback is that the number of points has to be the same

Therfore we need to define the working mesh and triangulate it

Random point distribution

Putting random numbers on 2D plane is not an optimal way, we need something more sophisticated

proc = HardcorePointProcess[50, 0.9, 2];
reg = Rectangle[{-10, -10}, {10, 10}];
samples = RandomPointConfiguration[proc, reg]["Points"];
ListPlot[samples]
(*VB[*)(FrontEndRef["694ad5f2-7a1b-47b9-9e11-ce34dd596715"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKm1maJKaYphnpmicaJumamCdZ6lqmGhrqJqcam6SkmFqamRuaAgCFPRWB"*)(*]VB*)

here is models a set of hard-core particles, which has a certain potential that keeps them apart.

Triangulation

We will use Delaunay method for that with some post-processing

Needs["ComputationalGeometry`"];
triangles2[points_] := Module[{tr, triples},
  tr = DelaunayTriangulation[points];
  triples = Flatten[Function[{v, list},
      Switch[Length[list],
        (* account for nodes with connectivity 2 or less *)
        1, {},
        2, {Flatten[{v, list}]}, 
        _, {v, ##} & @@@ Partition[list, 2, 1, {1, 1}]
      ]
    ] @@@ tr, 1];
  Cases[GatherBy[triples, Sort], a_ /; Length[a] == 3 :> a[[1]]]]

now apply it to our data

triangles = triangles2[samples];
ListPlot[samples, Prolog->{EdgeForm[Black], Yellow, Opacity[0.5], GraphicsComplex[samples, Polygon[triangles]]}, PlotStyle->{Red, PointSize[0.03]}]
(*VB[*)(FrontEndRef["4def7292-250f-4955-9beb-482692c0d671"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKm6SkppkbWRrpGpkapOmaWJqa6lompSbpmlgYmVkaJRukmJkbAgB+uRUk"*)(*]VB*)

For the demonstration purpose we kept the number of point very low. However, it can be increased drastically.

Sampling our function in 3D

Let us have an example function - a Gaussian distribution. We can sample it using our mesh and then plot it using polygons

(* sample function *)
f[p_, {x_,y_,z_}] := z Exp[-(*FB[*)(((*SpB[*)Power[Norm[p - {x,y}](*|*),(*|*)2](*]SpB*))(*,*)/(*,*)(2.))(*]FB*)]

probe = {#[[1]], #[[2]], f[#, {10, 0, 0}]} &/@ samples // Chop;
colors = With[{mm = MinMax[probe[[All,3]]]},
      (Blend[{{mm[[1]], Blue}, {mm[[2]], Red}}, #[[3]]] )&/@ probe /. {RGBColor -> List} // Chop];

Make a static plot

Graphics3D[{
  GraphicsComplex[probe, {Polygon[triangles]}, "VertexColors"->colors]
}]
(*VB[*)(FrontEndRef["18250678-2582-4554-b34a-6986be55867e"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKG1oYmRqYmVvoGplaGOmamJqa6CYZmyTqmllamCWlmppamJmnAgBlMhRt"*)(*]VB*)

Dynamic plot

Now we need only to add Offload wrappers in order to make our data alive as well as some elements of control to manipulate the data

evaluate initialization cells, before running this one

Graphics3D[{
  GraphicsComplex[probe // Offload, {Polygon[triangles]}, "VertexColors"->Offload[colors, "Static"->True]],

  EventHandler[Sphere[{0,0,0}, 0.1], {"transform"->Function[data, With[{pos = data["position"]},
    probe = {#[[1]], #[[2]], f[#, pos]} &/@ samples // Chop;
    colors = With[{mm = MinMax[probe[[All,3]]]},
      (Blend[{{mm[[1]], Blue}, {mm[[2]], Red}}, #[[3]]] )&/@ probe /. {RGBColor -> List} // Chop];
  ]]}]
}]

Or variation with a light-source

light = {0,0,0};
Graphics3D[{
  GraphicsComplex[probe // Offload, {Polygon[triangles]}],
  PointLight[Red, light // Offload],

  EventHandler[Sphere[{0,0,0}, 0.1], {"transform"->Function[data, With[{pos = data["position"]},
    probe = {#[[1]], #[[2]], f[#, pos]} &/@ samples // Chop;
    light = pos;
  ]]}]
}]