17
$\begingroup$

This question is a duplicate of a question on stack overflow But since the answer is 6 years old, things may have changed somewhat.

Question

I would like to render Mathematica Graphics3D in three.js.

Attempt

So far I have used

 Export["test.3ds", pl, "3DS"]

on this Mathematica plot.

Mathematica graphics

and I get this (click to animate) Mathematica graphics

Together with the 3ds loader from three.js this only works for mesh like objects, not lines or points for instance and it does not scale very well in terms of performance.

Another possibility seems to be the x3d format which may be loadable using this

We have written a specific three.js code which produces this:

Mathematica graphics

Any other efficient alternatives?

It would be great for instance to be able to export mathematica meshes.

Post Scriptum

Since I have been asked how these lines correspond to the cosmic web, i.e. where galaxies live in the universe cf this simulation.

Mathematica graphics

$\endgroup$
4
  • 1
    $\begingroup$ I am curious what this plot shows (but maybe chat is better for that). $\endgroup$ – Szabolcs Mar 21 '18 at 12:32
  • $\begingroup$ Maybe it will work if you replace Line by Tube and Point by Sphere (with appropriate radii). As far as I remember, three.js supports also OBJ format and you can export directly to it from Mathematica. $\endgroup$ – Henrik Schumacher Mar 28 '18 at 8:20
  • $\begingroup$ I have also quite efficient code for turning a Line into a GraphicsComplex consisting of triangles (with vertex normals). Would that help? $\endgroup$ – Henrik Schumacher Mar 28 '18 at 9:28
  • $\begingroup$ I wasn't really satisfied with it since it does not really answer the question and it felt pretty much like a code dump... But maybe somebody might find this useful. $\endgroup$ – Henrik Schumacher Mar 31 '18 at 16:03
6
+100
$\begingroup$

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

$\endgroup$
9
  • $\begingroup$ it looks like this online: 3dviewer.horizon-simulation.org/3ds/fast-knot.html $\endgroup$ – chris Mar 31 '18 at 16:33
  • $\begingroup$ I added, cCross3. Looks as if the three.js uses the opposite vertex normals: it renders only the inward side of the faces, not the outward ones... Or maybe something with the export went wrong. The built-in 3D exports are actually rather buggy. $\endgroup$ – Henrik Schumacher Mar 31 '18 at 16:42
  • $\begingroup$ Thanks, @chris. I guess this is not exactly what you were looking for... =/ $\endgroup$ – Henrik Schumacher Apr 1 '18 at 14:48
  • $\begingroup$ Well I was hoping to stimulate a generic approach for the sake of this site, but as far as my needs were concerned I was after an efficient tube like solution. $\endgroup$ – chris Apr 1 '18 at 15:54
  • 1
    $\begingroup$ @ChipHurst Thank you for pointing that out! At least the two errors should be fixed now. $\endgroup$ – Henrik Schumacher Mar 22 '19 at 16:52
4
$\begingroup$

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>

$\endgroup$
4
$\begingroup$

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.

$\endgroup$
2
  • $\begingroup$ Thanks for this. This is the closest answer to my original question! $\endgroup$ – chris Feb 27 '20 at 8:16
  • $\begingroup$ You are welcome, @chris! Hope it will help. $\endgroup$ – Кирилл Васин Feb 28 '20 at 13:07
1
$\begingroup$

As an alternative, one can also use the stl format for other classes of objects (which seems to be the default for 3D Printing in Mathematica 11)

Mathematica graphics

Mathematica graphics

 Export["contour.3ds", pl]

Mathematica graphics

Also

model = ExampleData[{"Geometry3D", "Triceratops"}, "Region"]

Mathematica graphics

Export["contour.3ds", model]

Mathematica graphics

An example of implementation in three.js is shown here


A drawback of sts or 3ds is that they loose colours.

For instance

LeftRightTorus[i_, j_, k_] := 
 0.1 {i + Sin[t] (3.9 + Cos[r]), j + Cos[t] (3.9 + Cos[r]), k + Sin[r]}
FrontBackTorus[i_, j_, k_] := 
 0.1 {i + Sin[r], j + (3.9 + Cos[r]) Sin[t], k + (3.9 + Cos[r]) Cos[t]}
TopBottomTorus[i_, j_, k_] := 
 0.1 {i + (3.9 + Cos[r]) Cos[t], j + Sin[r], k + (3.9 + Cos[r]) Sin[t]}
pl = ParametricPlot3D[
  Evaluate[{FrontBackTorus[6, 0, 6], FrontBackTorus[12, 6, 12], 
    FrontBackTorus[12, 6, 0], FrontBackTorus[18, 0, 6], 
    FrontBackTorus[6, 12, 6], FrontBackTorus[18, 12, 6], 
    LeftRightTorus[12, 0, 0], LeftRightTorus[12, 12, 12], 
    LeftRightTorus[6, 6, 6], LeftRightTorus[18, 6, 6], 
    LeftRightTorus[12, 0, 12], LeftRightTorus[12, 12, 0], 
    TopBottomTorus[6, 0, 0], TopBottomTorus[18, 0, 0], 
    TopBottomTorus[6, 0, 12], TopBottomTorus[18, 0, 12], 
    TopBottomTorus[6, 12, 12], TopBottomTorus[18, 12, 12], 
    TopBottomTorus[6, 12, 0], TopBottomTorus[18, 12, 0], 
    TopBottomTorus[12, 6, 6]}], {t, 0, 2 \[Pi]}, {r, 0, 2 \[Pi]}, 
  Mesh -> None, Boxed -> False, Axes -> False
  , PlotStyle -> ColorData[10] /@ Range[10]]

Mathematica graphics

Then

 Export["knot.st", pl]

looks like this:

Mathematica graphics

i.e. it has lost the colour information.

$\endgroup$
1
  • $\begingroup$ I am afraid, exporting color information correctly in a universal way is rather nontrivial due to the very differering ways color is handled in various languages... $\endgroup$ – Henrik Schumacher Apr 1 '18 at 14:51
-3
$\begingroup$

You can try to use A3dsViewer. Export to the html5 (three.js format)

$\endgroup$
2
  • 3
    $\begingroup$ Can you give an example of how to use it with a Mathematica object? $\endgroup$ – J. M.'s torpor Mar 22 '18 at 12:26
  • $\begingroup$ if you can export/get *.3ds file from the Mathematica object. Simply do load 3ds file in the A3dsViewer and export to html5(three.js format), it should work. $\endgroup$ – Slider Mar 23 '18 at 12:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.