Graphics3D to three.js converter?

So, I just dump my code for the function MyTube. The function creates a tube around a polygonal line.

This does not really answer the OP's question, but the resulting GraphicsComplex 1.) gets rendered more fluently in Mathematica and 2.) can be exported by standard means to other mesh formats (e.g., 3ds, obj, stl...). So, it might become a piece in a more complex export pipeline.

The function MyTube employs a (discrete) Bishop frame along the polygonal curve (slightly twisted in case the option "Closed" is set to True in order to produce a water-tight surface). The code doesn't look very elegant but it does the job and it has decent performance.

ClearAll[MyTube]
MyTube[pts_, OptionsPattern[{
    "Radius" -> 0.025,
    "Closed" -> False,
    "InitialVector" -> Automatic,
    Mesh -> 32
    }]] :=

 Module[{p, ν, nn, mm, dp, τ, b, e, ϕ, w, , u, 
   v, τ0, e0, A, a, q, u0, v0, normals, fflist, radius, Alist, 
   closedQ, angles, λ, ω, mostτ, restτ},
  p = pts;
  closedQ = OptionValue["Closed"];
  radius = OptionValue["Radius"];
  nn = OptionValue["Mesh"];
  ν = Transpose[{Cos[#], Sin[#]} &[Most[Subdivide[0., 2. Pi, nn]]]];
  If[closedQ, p = Join[{p[[-2]]}, p, {p[[2]]}], p = p;];
  dp = Differences[p];
  τ = cNormalize3[dp];
  mostτ = Most[τ];
  restτ = Rest[τ];
  If[Length[τ] > 1,
   b = cNormalize3[cCross3[mostτ, restτ]];
   e = cCross3[b, Most[τ]];
   ϕ = cTripleAngle3[mostτ, restτ, b]
   ,
   b = {}; e = {}; ϕ = {};
   ];
  w = NDSolve`FEM`MapThreadDot[
    rotationMatrix3DAngleVector[0.5 ϕ, b], e/Cos[0.5 ϕ]];
  τ0 = τ[[1]] ;
  u0 = OptionValue["InitialVector"];
  If[u0 === Automatic, u0 = N[IdentityMatrix[3]][[Ordering[Abs[τ[[1]]], 1][[1]]]]; ];
  u0 = Normalize[u0 - τ0 τ0.u0]; v0 = Cross[τ0, u0];
  Alist = If[Length[τ] > 1, rotationMatrix3DAngleVector[ϕ, b], {}];
  If[Length[Alist] >= 1,
   {u, v} = Compile[{{u0, _Real, 1}, {A, _Real, 3}},
      Block[{U = u0}, Join[{u0}, Table[U = A[[i]].U, {i, 1, Length[A]}]]],
      RuntimeAttributes -> {Listable},
      Parallelization -> True
      ][{u0, v0}, Alist];
   (* {u,v} is now a Bishop frame. If the curve is closed, 
   we have to twist it a bit in order to get a continuous frame. *)      
    If[closedQ,(*Then*)
    λ = Sqrt[Dot[dp^2, ConstantArray[1., 3]]];
    λ = 0.5 (Most[λ] + Rest[λ]);
    ω = cTripleAngle3[u[[1]], u[[-2]], τ[[1]]];
    angles = ω Join[ConstantArray[0., 1], Accumulate[λ]/Total[Most[λ]]];
    {u, v} = {Cos[angles] u - Sin[angles] v, Sin[angles] u + Cos[angles] v};
    ];
   A = With[{Part = Compile`GetElement},
     Compile[{{u, _Real, 1}, {v, _Real, 1}, {w, _Real, 1}, {e, _Real, 1}, {b, _Real, 1}, {ϕ, _Real}},
       If[ϕ < 10^(-12),
        {u, v},
        {u, v}.Table[e[[i]] w[[j]] + b[[i]] b[[j]], {i, 1, 3}, {j, 1, 3}] ],
       RuntimeAttributes -> {Listable},
       Parallelization -> True
       ][u[[1 ;; Length[ϕ]]], v[[1 ;; Length[ϕ]]], 
      w[[1 ;; Length[ϕ]]], e[[1 ;; Length[ϕ]]], 
      b[[1 ;; Length[ϕ]]], ϕ]
     ];
   ,
   {u, v} = Developer`ToPackedArray[{{u0, u0}, {v0, v0}}];
   A = {u, v};
   ];
  normals = Flatten[Table[ν.A[[i]], {i, 1, Length[ϕ]}], 1];
  a = -radius ν;
  q = Flatten[Table[ConstantArray[p[[i + 1]], nn] + a.A[[i]], {i, 1, Length[ϕ]}], 1];
  If[Length[p] == 2, q = {}; u = {u0}; v = {v0}];
  If[closedQ,
   mm = Length[ϕ] - 1;
   q = q[[1 ;; -1 - nn]];
   normals = normals[[1 ;; -2]];
   fflist = Join[
     getOpenTubeFaces[mm, nn],
     ReplaceAll[
      ReplaceAll[
       getOpenTubeFaces[2, nn],
       Dispatch[ Thread[Range[nn] -> Range[1 + (mm - 1) nn, nn + (mm - 1) nn]]]],
      Dispatch[Thread[Range[nn + 1, 2 nn] -> Range[nn]]]
      ]
     ];
   ,
   mm = Length[ϕ] + 2;
   q = Join[ConstantArray[p[[1]], nn] + a.{u[[1]], v[[1]]}, q, ConstantArray[p[[-1]], nn] + a.{u[[-1]], v[[-1]]}];
   normals = Join[ν.{u[[1]], v[[1]]}, normals, ν.{u[[-1]], v[[-1]]}];
   fflist = getOpenTubeFaces[mm, nn];
   ];
  GraphicsComplex[q, Polygon[fflist], VertexNormals -> normals]
  ]

Block[{u, uu, v, vv, w, ww, angle},
  uu = Table[Compile`GetElement[u, i], {i, 1, 3}];
  vv = Table[Compile`GetElement[v, i], {i, 1, 3}];
  ww = Table[Compile`GetElement[w, i], {i, 1, 3}];

  cNormalize3 = With[{code = Sqrt[Total[uu^2]], ϵ = 10^5 $MachineEpsilon},
    Compile[{{u, _Real, 1}},
     Block[{l = code},
      If[l < ϵ, u 0., u/l]
      ],
     CompilationTarget -> "C",
     RuntimeAttributes -> Listable,
     Parallelization -> True,
     RuntimeOptions -> "Speed"
     ]
    ];

  cTripleAngle3 = With[{code = ArcTan[uu.vv, Det[{uu, vv, ww}]]},
    Compile[{{u, _Real, 1}, {v, _Real, 1}, {w, _Real, 1}},
     code,
     CompilationTarget -> "C",
     RuntimeAttributes -> {Listable},
     Parallelization -> True,
     RuntimeOptions -> "Speed"
     ]
    ];

  rotationMatrix3DAngleVector = With[{
      ϵ = 1. 10^-14,
      r2 = uu.uu,
      id = N[IdentityMatrix[3]],
      code = N[Simplify[ComplexExpand[RotationMatrix[angle, uu]], Compile`GetElement[u, 1] ∈ Reals]] /. Part -> Compile`GetElement
      },
     Compile[{{angle, _Real}, {u, _Real, 1}},
      If[
       Abs[angle] < ϵ || r2 < ϵ,
       id,
       code
       ],
      CompilationTarget -> "C",
      RuntimeAttributes -> {Listable},
      Parallelization -> True,
      RuntimeOptions -> "Speed"
      ]
     ];
 ];

cCross3 = Compile[{{X, _Real, 1}, {Y, _Real, 1}},
  {
   -Compile`GetElement[X, 3] Compile`GetElement[Y, 2] + Compile`GetElement[X, 2] Compile`GetElement[Y, 3],  Compile`GetElement[X, 3] Compile`GetElement[Y, 1] - Compile`GetElement[X, 1] Compile`GetElement[Y, 3], -Compile`GetElement[X, 2] Compile`GetElement[Y, 1] +  Compile`GetElement[X, 1] Compile`GetElement[Y, 2]
   },
  CompilationTarget -> "C",
  RuntimeAttributes -> Listable,
  Parallelization -> True,
  RuntimeOptions -> "Speed"
  ];

getOpenTubeFaces = Compile[{{mm, _Integer}, {nn, _Integer}},
   Join[
    Flatten[Join[
      Table[
       {{i + 1 + nn (j - 1), i + nn (j - 1), i + nn j}, {i + 1 + nn (j - 1), i + nn j, i + 1 + nn j}},
       {i, 1, nn - 1}, {j, 1, mm - 1}],
      {Table[{{1 + nn (j - 1), nn + nn (j - 1), nn + nn j}, {1 + nn (j - 1), nn + nn j, 1 + nn j}}, {j, 1, mm - 1}]}
      ], 2]
    ],
   CompilationTarget -> "C",
   RuntimeOptions -> "Speed"
   ];

Here is the obligatory usage example:

pts = KnotData["FigureEight", "SpaceCurve"] /@ Subdivide[0., 2. Pi, 2000];
gc = MyTube[pts, "Closed" -> True, "Radius" -> 0.1]; // AbsoluteTiming // First
Length @@@ Cases[gc, _Polygon, All]
Graphics3D[{Orange, EdgeForm[], Specularity[White, 30], gc}, Lighting -> "Neutral"]

0.021071

{128000}

enter image description here


As a teaser, and following up on the example given in the question I can follow the documentation

pointcloud = 
  ExampleData[{"Geometry3D", "StanfordBunny"}, "VertexData"];
Graphics3D[Point[RandomSample[pointcloud, 1000]]]

Mathematica graphics

make a 3D ListSurfacePlot3D out of it

pl = ListSurfacePlot3D[pointcloud, MaxPlotPoints -> 50, Axes -> None, 
  Boxed -> False, Mesh -> None, PlotStyle -> Gray]

Mathematica graphics

and export it a a 3ds file

Export["bunny.3ds", pl]

which I can then view in three.js as (click to animate)

Mathematica graphics

But this is just one example for meshes.

The corresponding three.js code read

!DOCTYPE html>
<html lang="en">
        <head>
                <title>3DS</title>
                <meta charset="utf-8">
                <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0,\
 maximum-scale=1.0">
                <style>
                        body {
                                font-family: Monospace;
                                background-color: #000;
                                color: #000;
                                margin: 0px;
                                overflow: hidden;
                        }
                        #info {
                                color: #000;
                                position: absolute;
                                top: 10px;
                                width: 100%;
                                text-align: center;
                                z-index: 100;
                                display:block;
                        }
                        #info a, .button { color: #f00; font-weight: bold; text-decoration: underline; \
cursor: pointer }
                </style>
        </head>

        <body>
                <script src="https://threejs.org/build/three.js"></script>
                <script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
                <script src="https://threejs.org/examples/js/loaders/TDSLoader.js"></script>
 <script>
                        var container, controls;
                        var camera, scene, renderer;
                        init();
                        animate();
                        function init() {
                                container = document.createElement( 'div' );
                                document.body.appendChild( container );
                                camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.in\
nerHeight, 0.1, 10 );
                                camera.position.z = 2;
                                controls = new THREE.TrackballControls( camera );
                                scene = new THREE.Scene();
                                scene.add( new THREE.HemisphereLight() );
                                var directionalLight = new THREE.DirectionalLight( 0xffeedd );
                                directionalLight.position.set( 0, 0, 2 );
                                scene.add( directionalLight );
                                var loader = new THREE.TDSLoader( );
                                loader.setPath( './' );
                                loader.load( './bunny.3ds', function ( object ) {
                                        object.traverse( function ( child ) {
                                        } );
                                        scene.add( object );
                                });
                                renderer = new THREE.WebGLRenderer();
                                renderer.setPixelRatio( window.devicePixelRatio );
                                renderer.setSize( window.innerWidth, window.innerHeight );
                                container.appendChild( renderer.domElement );
                                window.addEventListener( 'resize', resize, false );
                        }
                        function resize() {
                                camera.aspect = window.innerWidth / window.innerHeight;
                                camera.updateProjectionMatrix();
                                renderer.setSize( window.innerWidth, window.innerHeight );
                        }
                        function animate() {
                                controls.update();
                                renderer.render( scene, camera );
                                requestAnimationFrame( animate );
                        }
                </script>

        </body>
</html>


I faced with the same issue. Using Mathematica's function ExportString[…, "ExpressionJSON"] I attempted to export the whole tree of graphical functions and wrote a parser in JS.

It was uploaded to GitHub, here is the link.

There is a primitive construction with a lot of "switch-case" statements. Each of them implements self function like changing the color or applying a matrix to the group of primitives. After parsing it is rendering to the screen using Three.js library and some part of the code from Mathics project.

Example

  1. Plot some graphics (used a low-poly mode for smaller code)
Graphics3D[{
  SphericalPlot3D[
    2 SphericalHarmonicY[2, 0, t, p], {t, 0, Pi}, {p, 0, 2 Pi}, 
    PerformanceGoal -> "Speed"][[1]],
  Opacity[0.6], 
  Tetrahedron[{{1, 1, 1}, {-1, -1, 1}, {1, -1, -1}, {-1, 1, -1}}]
  }]

image

  1. Export as a JSON string
ExportString[%//N, "ExpressionJSON"]
[
    "Graphics3D",
    [
        "List",
        [
            "GraphicsComplex",
            [
                "List",
                ["List",
                    0.0,
                    0.0,
                    1.2615662610100797
                ]
                ,
                ["List",
                    0.0,
                    0.0,
                    1.2615662610100797
                ]
                ,...
  1. Copy and paste it to data.js
\data.js

var JSONThree = [...
  1. Run index.html

enter image description here

Shorter version

I wrote a figure exporter in Export2ThreeJS.nb file. It stores figure and supplementary libraries into a single .html autonomous page. Tell me, please, If someone knowns how to insert this function into the native Mathematica's menu.

Some figures...

1 2

PS: It has been helping me to communicate with my colleagues which do not have wolfram software a lot.