WLJS LogoWLJS Notebook

GraphicsComplex

Supports Offload
GraphicsComplex[data_List, primitives_, opts___]

represents an efficient graphics structure for drawing complex 3D objects (or 2D - see GraphicsComplex) storing vertices data in data variable. It replaces indexes found in primitives (can be nested) with a corresponding vertices and colors (if specified)

Most plotting functions such as ListPlot3D and others use this way showing 3D graphics.

The implementation of GraphicsComplex is based on a low-level THREE.js buffer position attribute directly written to a GPU memory.

Supported primitives

Line

No restrictions

v = PolyhedronData["Dodecahedron", "Vertices"] // N;
i = PolyhedronData["Dodecahedron", "FaceIndices"];
GraphicsComplex[v, {Black, Line[i]}] // Graphics3D 

Polygon

Triangles works faster than quads or pentagons

GraphicsComplex[v, Polygon[i]] // Graphics3D 

Non-indexed geometry

One can provide only the ranges for the triangles to be rendered

GraphicsComplex[v, Polygon[1, Length[v]]] // Graphics3D 

it assumes you are using triangles

Point

Sphere

Tube

Options

VertexColors

Defines sets of colors used for shading vertices:

v = {{1, 0, 0}, {1, 1, 1}, {0, 0, 1}};
Graphics3D[{Yellow, GraphicsComplex[v, Polygon[{1,2,3}], VertexColors -> {Red, Blue, Green}]}, ViewPoint->{1,1,1}]

We recommend to provide VertexColors as a plain list which should have the following form

"VertexColors" ->{{r1,g1,b1}, {r2,g2,b2}, ...}

This will avoid high memory and CPU load on many polygons count.

VertexNormals

Defines sets of normals used for shading.

If it is undefined, 3D renderer will use automatically calcualte them based on the polygon normal. It results is so-called flat-shading:

v = {{1, 0, 0}, {1, 1, 1}, {0, 0, 1}};
Graphics3D[{Yellow, GraphicsComplex[v, Polygon[{1,2,3}]]}, ViewPoint->{1,1,1}]

Using a different normal for one of the triangle's vertices changes the effects of lighting:

v = {{1, 0, 0}, {1, 1, 1}, {0, 0, 1}};
n = {1, -1, 1};
n2 = {-1, 0, -1};
Graphics3D[{Yellow, GraphicsComplex[v, Polygon[{1,2,3}], VertexNormals -> {n2, n, n}]}, ViewPoint->{1,1,1}]

VertexTextureCoordinates

Defines sets of UV coordinates used for mapping Texture.

img = ExampleData[{"TestImage", "Lena"}];
Graphics3D[{
  Texture[img],
  GraphicsComplex[
    {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}},
    Polygon[{1, 2, 3, 4}],
    VertexTextureCoordinates -> {{0, 1}, {0, 0}, {1, 0}, {1, 1}}
  ]
}]

"VertexFence"

Defaults to False. When enabled, it sets an update barrier for all dependent primitives, ensuring they update their data only after the vertex data has been updated.

This helps prevent desynchronization issues where the vertex buffer updates lag behind polygon or line indices (or vice versa), which can lead to visual artifacts and WebGL buffer errors.

Pack normals, vertices and colors to a single symbol. This will help to avoid data racing. "VertexFence" indeed eliminates races with indices, but it is helpless against the mutations of GraphicsComplex attributes.

How to update things

Fixed indexes, variable vertices

Use Offload wrapper:

vertices = Flatten[Table[{i,j, 0}, {i,1.0,100.0,1.0}, {j, 1.0,100.0,1.0}],1];
idx[i_, j_] := (i - 1) 100 + j;
tris = Flatten[
  Table[
    {
      {idx[i, j],     idx[i + 1, j], idx[i, j + 1]},
      {idx[i + 1, j], idx[i + 1, j + 1], idx[i, j + 1]}
    },
    {i, 1, 100 - 1},
    {j, 1, 100 - 1}
  ],
  2
];

GraphicsComplex[vertices//Offload, Polygon[tris]]//Graphics3D 

Now we can animate vertices:

gen := Module[
  {t = AbsoluteTime[], s = 0.13, cx, cy},
  cx = 50 + 20 Cos[0.5 t];
  cy = 50 + 20 Sin[0.7 t];
  vertices = Flatten[
    Table[
      {
        i, j,
         5 Sin[s i + t] +
         5 Sin[s j - 1.3 t] +
        3 12 Exp[-((i - cx)^2 + (j - cy)^2)/120.]
      },
      {i, 1., 100.}, {j, 1., 100.}
    ],
    1
  ]
];

Do[gen; Pause[1/30.0], {500}];

Fixed indexes, variable vertices and colors

Taking the previous example, we can add dynamic color:

idx[i_, j_] := (i - 1) 100 + j;
tris = Flatten[
  Table[
    {
      {idx[i, j],     idx[i + 1, j], idx[i, j + 1]},
      {idx[i + 1, j], idx[i + 1, j + 1], idx[i, j + 1]}
    },
    {i, 1, 100 - 1},
    {j, 1, 100 - 1}
  ],
  2
];

cf = ColorData["DarkRainbow"];

genData := Module[
  {t = AbsoluteTime[], s = 0.12, verts, z, cols},

  verts = Flatten[
    Table[
      {
        i, j,
        6 Sin[s i + 1.4 t] +
        5 Sin[s j - 1.1 t] +
        3 Sin[s (i + j) + 0.6 t] +
        8 Cos[0.5 s Sqrt[(i - 50.)^2 + (j - 50.)^2] - 1.8 t]
      },
      {i, 1., 100.}, {j, 1., 100.}
    ],
    1
  ];

  z = verts[[All, 3]];

  (* fixed range avoids color flicker *)
  cols = (List@@cf[#]) &/@ Rescale[z, {-18, 18}];

  data = {verts, cols};
];

genData;

GraphicsComplex[data[[1]]//Offload, Polygon[tris], VertexColors->Offload[data[[2]]]]//Graphics3D 

Do[genData; Pause[1/30.0];, {500}];

Here is another beautiful example:

(* Grid dimensions *)
nx = 50; ny = 50;

(* Triangle indices — generated once *)
indices = Flatten[Table[
  With[{v = (i - 1) ny + j},
    {{v, v + 1, v + ny + 1}, {v, v + ny + 1, v + ny}}
  ],
  {i, nx - 1}, {j, ny - 1}
], 2];

(* Generates {vertices, vertexColors} at time t *)
generateFrame[t_] := Module[{verts, zVals, mn, mx},
  verts = Flatten[Table[
    With[{r = Sqrt[x^2 + y^2]},
      {x, y,
        Sin[4 r - 2 t] Exp[-0.3 r] +
        0.3 Sin[2 x - t] Cos[2 y + 0.5 t]}
    ],
    {x, Subdivide[-2., 2., nx - 1]},
    {y, Subdivide[-2., 2., ny - 1]}
  ], 1];

  zVals = verts[[All, 3]];
  {mn, mx} = MinMax[zVals];

  {verts, Map[
    With[{s = Rescale[#[[3]], {mn, mx}]},
      List@@RGBColor[s, 0.15 + 0.35 s, 1. - 0.8 s]
    ]&, verts
  ]}
];

(* Initialize *)
complex = generateFrame[0.];

(* Display *)
Graphics3D[{GraphicsComplex[
  Offload[complex[[1]]],
  {Polygon[indices]},
  VertexColors -> Offload[complex[[2]]]
], EventHandler[AnimationFrameListener[complex//Offload], Function[Null,
  complex = generateFrame[AbsoluteTime[]];
]]}]

Update everyhting

Now we consider a more eralistic case, when both indices and vertices / other attributes are changing:

(* Grid dimensions *)
nx = 50; ny = 50;

(* All triangle indices *)
allIndices = Flatten[Table[
  With[{v = (i - 1) ny + j},
    {{v, v + 1, v + ny + 1}, {v, v + ny + 1, v + ny}}
  ],
  {i, nx - 1}, {j, ny - 1}
], 2];

xs = Subdivide[-2., 2., nx - 1];
ys = Subdivide[-2., 2., ny - 1];

(* Precompute centroid XY of each triangle for fast filtering *)
gridXY = Flatten[Table[{x, y}, {x, xs}, {y, ys}], 1];
triCX = (gridXY[[#[[1]], 1]] + gridXY[[#[[2]], 1]] + gridXY[[#[[3]], 1]]) / 3. & /@ allIndices;
triCY = (gridXY[[#[[1]], 2]] + gridXY[[#[[2]], 2]] + gridXY[[#[[3]], 2]]) / 3. & /@ allIndices;

generateFrame[t_] := Module[{verts, zVals, mn, mx, h1, h2, d1, d2, mask},
  verts = Flatten[Table[
    With[{r = Sqrt[x^2 + y^2]},
      {x, y,
        Sin[4 r - 2 t] Exp[-0.3 r] +
        0.3 Sin[2 x - t] Cos[2 y + 0.5 t]}
    ],
    {x, xs}, {y, ys}
  ], 1];

  zVals = verts[[All, 3]];
  {mn, mx} = MinMax[zVals];

  (* Two holes orbiting the center *)
  h1 = {1.3 Cos[0.8 t], 1.3 Sin[0.8 t]};
  h2 = {1.3 Cos[0.8 t + Pi], 1.3 Sin[0.8 t + Pi]};

  (* Vectorized distance computation *)
  d1 = Sqrt[(triCX - h1[[1]])^2 + (triCY - h1[[2]])^2];
  d2 = Sqrt[(triCX - h2[[1]])^2 + (triCY - h2[[2]])^2];

  (* Keep triangles outside both holes *)
  mask = UnitStep[d1 - 0.55] UnitStep[d2 - 0.55];

  {Pick[allIndices, mask, 1], {verts,
   Map[With[{s = Rescale[#[[3]], {mn, mx}]},
     List @@ RGBColor[s, 0.15 + 0.35 s, 1. - 0.8 s]] &, verts]}}
];

(* Initialize *)
{indices, complex} = generateFrame[0.];

(* Display — note Offload on complex[[3]] for dynamic indices *)
Graphics3D[{GraphicsComplex[
  Offload[complex[[1]]],
  {Polygon[Offload[indices]]},
  VertexColors -> Offload[complex[[2]]],
  "VertexFence" -> True
], EventHandler[AnimationFrameListener[complex // Offload], Function[Null,
  {indices, complex} = generateFrame[AbsoluteTime[]];
]]}]

Use other plot functions as generators

both. Here is an example with dynamic adapter for ParametericPlot3D

define shapes

cell 1
sample[t_] := With[{
   complex = ParametricPlot3D[
     (1 - t) * {
       (2 + Cos[v]) * Cos[u],
       (2 + Cos[v]) * Sin[u],
       Sin[v]
     } + t * {
       1.16^v * Cos[v] * (1 + Cos[u]),
       -1.16^v * Sin[v] * (1 + Cos[u]),
       -2 * 1.16^v * (1 + Sin[u]) + 1.0
     },
     {u, 0, 2\[Pi]},
     {v, -\[Pi], \[Pi]},
     MaxRecursion -> 2,
     Mesh -> None
   ][[1, 1]]
   },
  {
   complex[[1]],
   Cases[complex[[2]], _Polygon, 6] // First // First,
   complex[[3, 2]]
  }
]

now construct the scene

Module[{
    VerticesAndNormals = {{}, {}}, indices = {}
  },
    {
      EventHandler[InputRange[0,1,0.1,0], Function[value,
        With[{res  = sample[value]},

          
          indices = res[[2]];
          VerticesAndNormals = {res[[1]], res[[3]]};
          
        ];
      ]],

      With[{res  = sample[0.0]},

          
          indices = res[[2]];
          VerticesAndNormals = {res[[1]], res[[3]]};
          
      ];
      
      Graphics3D[{
        SpotLight[Red, 5 {1,1,1}], SpotLight[Blue, 5 {-1,-1,1}], 
        SpotLight[Green, 5 {1,-1,1}], 
        PointLight[Magenta, {10,10,10}],
        
        GraphicsComplex[Offload[VerticesAndNormals[[1]]], {
          Polygon[Offload[indices]]
        }, VertexNormals->Offload[VerticesAndNormals[[2]]], "VertexFence"->True]
        
      }, Lighting->None]
    } // Column // Panel 
]

Non-indexed geometry

This is a another mode of working with Non-indexed geometry in Polygon. The benefit of this approach, you can use fixed length buffer for vertices and limit your drawing range using two arguments of Polygon.

On this page