Skip to main content

Release notes *2.6.1*

· 3 min read

This release enhances 3D rendering with GraphicsComplex now supporting VertexNormals for improved lighting accuracy and dynamic geometry options in Polygon for creating complex shapes. Developer tools are improved with shared JavaScript libraries, a global CLI for easy notebook access, and streamlined cell evaluation and management features.

Download original notebook

const balloonContainer = document.getElementById("balloon-container");

function random(num) {
  return Math.floor(Math.random() * num);
}

function getRandomStyles() {
  var r = random(255);
  var g = random(255);
  var b = random(255);
  var mt = random(200);
  var ml = random(50);
  var dur = random(5) + 5;
  return `
  background-color: rgba(${r},${g},${b},0.7);
  color: rgba(${r},${g},${b},0.7); 
  box-shadow: inset -7px -3px 10px rgba(${r - 10},${g - 10},${b - 10},0.7);
  margin: ${mt}px 0 0 ${ml}px;
  animation: float ${dur}s ease-in infinite
  `;
}

function createBalloons(num) {
  for (var i = num; i > 0; i--) {
    var balloon = document.createElement("div");
    balloon.className = "balloon";
    balloon.style.cssText = getRandomStyles();
    balloonContainer.append(balloon);
  }
}

function removeBalloons() {
  balloonContainer.style.opacity = 0;
  setTimeout(() => {
    balloonContainer.remove()
  }, 500)
}

createBalloons(10);
setTimeout(removeBalloons, 15000);

return '';

GraphicsComplex now supports VertexNormals

It was an old bug, that provided normals were ignored, which leaded to some visual artifacts in terms of lighting. Looks at the differece now

(*GB[*){{(*VB[*)(FrontEndRef["f9b15308-6ceb-467a-a96d-df7639cc80ad"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKp1kmGZoaG1jomiWnJumamJkn6iZamqXopqSZmxlbJidbGCSmAACH2hYR"*)(*]VB*)(*|*),(*|*)(*VB[*)(Graphics[Arrow[{{0, 0}, {1, 0}}], ImageSize -> 50, ImagePadding -> None])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KWnMIB4HkHAvSizIyEwuhsizAgnHoqL88jQmmHKfzOISVF4mkGYAE2jijJjiQaU5qcWcQIZnbmJ6anBmVWqmEaYCHpiCgMSUlMy8dLCMX35eKgCWRCRi"*)(*]VB*)(*|*),(*|*)(*VB[*)(FrontEndRef["37227be2-25df-4d3c-a000-35796369ef66"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKG5sbGZknpRrpGpmmpOmapBgn6yYaGBjoGpuaW5oZm1mmppmZAQB7PxUY"*)(*]VB*)}}(*]GB*)

Dynamic indexed & non-indexed geometry for Polygon

There is no such thing defined in standard library of Wolfram Language, but we did bring it to you from WebGL world, since it may come handy for complex dynamic 3D geometry.

Non-indexed

The idea now, it that you can define starting and ending index for all your triangles using Polygon inside GraphicsComplex

maxIndex = 312;
EventHandler[InputRange[1, 312, 1, 312], (maxIndex = #) &]

With[{vertices = (*VB[*)(Get[FileNameJoin[{".iconized", "iconized-3957.wl"}]])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KWlMIB4fkPBMzs/LrEp1y8xJdcqvyCyQY2CASIKUBpXmpAazAhk+iUmpOcEcQFZYalFJZnJqMQADMRNT"*)(*]VB*)},
  GraphicsComplex[vertices, {
    Polygon[1, maxIndex // Offload]
  }] // Graphics3D 
]
(*VB[*)(EventObject[<|"Id" -> "1febcf3d-7f14-4e31-a718-4d1768d67879", "Initial" -> 312, "View" -> "78b77dc8-871b-480a-a4a7-44576564267a"|>])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKm1skmZunJFvoWpgbJumaWBgk6iaaJJrrmpiYmpuZmpkYmZknAgB9RhT2"*)(*]VB*)
(*VB[*)(FrontEndRef["83dd6324-65b2-4296-814d-1d9ccb12fd8d"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKWxinpJgZG5nompkmGemaGFma6VoYmqToGqZYJicnGRqlpVikAAB30xV5"*)(*]VB*)

It means you can specify only the range of your geometry, without indexes. The benifit of this approach, you can use fixed length buffer for vertices and limit your drawing range using two arguments of Polygon.

Indexed geometry

This is more a traditional way of defining 3D shapes. One can morph one complicated shape into another!

Have a look at this dynamic parameteric plot

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 make a scene *)

LeakyModule[{
    vertices, normals, indices
  },
    {
      EventHandler[InputRange[0,1,0.1,0], Function[value,
        With[{res  = sample[value]},
          normals = res[[3]];
          indices = res[[2]];
          vertices = res[[1]];
        ];
      ]],

      {vertices, indices, normals} = sample[0];
      
      Graphics3D[{
        MeshMaterial[MeshToonMaterial[]], Gray, 
        SpotLight[Red, 5 {1,1,1}], SpotLight[Blue, 5 {-1,-1,1}], 
        SpotLight[Green, 5 {1,-1,1}], 
        PointLight[Magenta, {10,10,10}],
        
        GraphicsComplex[vertices // Offload, {
          Polygon[indices // Offload]
        }, VertexNormals->Offload[normals, "Static"->True]]
        
      }, Lighting->None]
    } // Column // Panel 
]
(*BB[*)(Panel[(*GB[*){{(*VB[*)(EventObject[<|"Id" -> "83d20c8f-e5b4-4fbb-a44d-d015185217fe", "Initial" -> 0, "View" -> "bf44ef25-0eee-4d64-8a64-03abf5de810a"|>])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKJ6WZmKSmGZnqGqSmpuqapJiZ6FokAgkD48SkNNOUVAtDg0QAkN8WLQ=="*)(*]VB*)}(*||*),(*||*){(*VB[*)(Graphics3D[{MeshMaterial[MeshToonMaterial[]], GrayLevel[0.5], SpotLight[RGBColor[1, 0, 0], {5, 5, 5}], SpotLight[RGBColor[0, 0, 1], {-5, -5, 5}], SpotLight[RGBColor[0, 1, 0], {5, -5, 5}], PointLight[RGBColor[1, 0, 1], {10, 10, 10}], GraphicsComplex[Offload[vertices$2642384], {Polygon[Offload[indices$2642384]]}, VertexNormals -> Offload[normals$2642384, "Static" -> True]]}, Lighting -> None])(*,*)(*"1:eJyVkd9OwjAUxgv+AY1GX8HEJwBivDMRE7wYYAbxvrJ2a1J6lrYQeFvfw4vZ07pNBjHIxRf2nbPf+XbO3QfEvEUIMadOXkEmvI1Pl05GmuaZWJj+C++UHZEwNvRfORkzk42pZVpQyQm6tz/uHEBVFd9/EYjbiK2Z1MT/Pp/CNKzNcrCRSDPLT9DqOolHz0OQoAUCBCklNJRpxBn6lRwHrFge3QB+FUUR5J/A1l8JG0Bc8BsIdcQ3H4iIb9cSije/TjaEZS7ZJmwejzflXAJN/H3WTFuxYOa+9zDo9R8H9f3r63Z8PLlNQe1DcJJQyQ6jXTLilWTm2v15d2PYZgJ6SaUJ9SZEheJhyOwcd26py+q9uV6xxpiuz+z2J1Tq3Qko9g0K1J5I"*)(*]VB*)}}(*]GB*)])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KWlMIB4/kAgoyi/LTElN8S8oyczPK05jAElwgCQS81JznPIrIEpBGoNKc1KDwSakJqYEs8LUAADiShU/"*)(*]BB*)

See new examples!

Shared JS libraries

D3.js, THREE.js, KaTeX.js, Marked.js used by many modules of WLJS system are now available as sort of shared libraries. It reduces the size of Javascript code for different plugins as well as gives an access to them from JS cells (you to play with them)

.js
const dom = document.createElement('div');
let animation;

async function buildScene() {
  await interpretate.shared.THREE.load(); //here
  const THREE = interpretate.shared.THREE.THREE;

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.1, 1000 );

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize( 400, 300);

  dom.appendChild( renderer.domElement );

  const geometry = new THREE.BoxGeometry( 1, 1, 1 );
  const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
  const cube = new THREE.Mesh( geometry, material );
  scene.add( cube );

  camera.position.z = 5 

  function animate() {

	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;

	renderer.render( scene, camera );
    animation = requestAnimationFrame(animate);

  } 

  animate();
}

this.ondestroy = () => {
  cancelAnimationFrame(animation);
  console.log('removed');
}

buildScene();


return dom;

const dom = document.createElement('div');
let animation;

async function buildScene() {
  await interpretate.shared.THREE.load();
  const THREE = interpretate.shared.THREE.THREE;

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.1, 1000 );

  const renderer = new THREE.WebGLRenderer();
  renderer.setSize( 400, 300);

  dom.appendChild( renderer.domElement );

  const geometry = new THREE.BoxGeometry( 1, 1, 1 );
  const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
  const cube = new THREE.Mesh( geometry, material );
  scene.add( cube );

  camera.position.z = 5 

  function animate() {

	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;

	renderer.render( scene, camera );
    animation = requestAnimationFrame(animate);

  } 

  animate();
}

this.ondestroy = () => {
  cancelAnimationFrame(animation);
  console.log('removed');
}

buildScene();


return dom;

It won't cause double loading, shared libraries manager will take care about loading process

Custom shaders

Using Javascript one can define custom vertex/fragment shader materials

.js
function vertexShader() {
  return `
    varying vec3 vUv; 

    void main() {
      vUv = position; 

      vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * modelViewPosition; 
    }
  `;
}

function fragmentShader()  {
  return `
      uniform vec3 colorA; 
      uniform vec3 colorB; 
      varying vec3 vUv;

      void main() {
        gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
      }
  `;
}

let THREE;
interpretate.shared.THREE.load().then(() => {
  THREE = interpretate.shared.THREE.THREE;
})

core.CustomMaterial = async (args, env) => {
  let uniforms = {
    colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
    colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
  }

  return (function() {
    return new THREE.ShaderMaterial({
      uniforms: uniforms,
      fragmentShader: fragmentShader(),
      vertexShader: vertexShader(),
    });
  })
}
function vertexShader() {
  return `
    varying vec3 vUv; 

    void main() {
      vUv = position; 

      vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
      gl_Position = projectionMatrix * modelViewPosition; 
    }
  `;
}

function fragmentShader()  {
  return `
      uniform vec3 colorA; 
      uniform vec3 colorB; 
      varying vec3 vUv;

      void main() {
        gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
      }
  `;
}

let THREE;
interpretate.shared.THREE.load().then(() => {
  THREE = interpretate.shared.THREE.THREE;
})

core.CustomMaterial = async (args, env) => {
  let uniforms = {
    colorB: {type: 'vec3', value: new THREE.Color(0xACB6E5)},
    colorA: {type: 'vec3', value: new THREE.Color(0x74ebd5)}
  }

  return (function() {
    return new THREE.ShaderMaterial({
      uniforms: uniforms,
      fragmentShader: fragmentShader(),
      vertexShader: vertexShader(),
    });
  })
}

a as it material normal now use

Graphics3D[{
  Translate[PolyhedronData["Dodecahedron"][[1]]//N , {-2,-3,0}],
  MeshMaterial[CustomMaterial[]],
  Translate[PolyhedronData["Dodecahedron"][[1]]//N , {2,3,0}]
}]
(*VB[*)(FrontEndRef["b69687c3-703f-4f4d-80dc-f117c1e6f420"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKJ5lZmlmYJxvrmhsYp+mapJmk6FoYpCTrphkamicbppqlmRgZAACATxV6"*)(*]VB*)

Support for Image3D preview

We use MarchingCubes method to preview 3D images

Image3D[#] & /@
  CellularAutomaton[{14, {2, 1}, {1, 1, 1}}, {{{{1}}}, 0}, 3];
Partition[%, 2] // Grid 
(*GB[*){{(*VB[*)(Image3DDump42)(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKm1ikGFlYJCbqGpqnpeiapFkk6SaZJlroppmkppkZpKSkGhgaAwCNNhY5"*)(*]VB*)(*|*),(*|*)(*VB[*)(Image3DDump43)(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKpySaJ5obWCbpmhonp+qaGKWZ6lqkGiXqGpmaJxkkp6QkGhkZAQCMUxX1"*)(*]VB*)}(*||*),(*||*){(*VB[*)(Image3DDump44)(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKWyYbmRsbmZjpJqaYm+qaJJom6lqkmVvomhknmZonJ1saGKWZAQB8FRVU"*)(*]VB*)(*|*),(*|*)(*VB[*)(Image3DDump45)(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKG5gZmVqYWSTrGhgYmOuaGJsZ6Saam1voJpqmJSabpFqmmaaaAwBtjhUf"*)(*]VB*)}}(*]GB*)

CLI

After 2.6.0 version WLJS Notebook Desktop application is available globally from the terminal. Simply type

wljs .

like on the screenshot here

to open the an app in the current directory

Inbox

To show all accumulated messages (prints, warnings, errors) click on a inbox button

EvaluateCell

There is a programmatic way of evaluating printed cells

cell = CellPrint["Red", "Type"->"Input"];
EvaluateCell[cell];
Red
(*VB[*)(RGBColor[1, 0, 0])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeGJAIcndyzs/JLwouTyxJzghJzS3ISSxJTWMGyXMgyRcxgMEHeyiDgQHOAAALpBNd"*)(*]VB*)

CellPrint to a new window

There is a new option of CellPrint to specify the target

cell = CellPrint[ToString[Plot[x,{x,0,1}], StandardForm], 
  "Target"->_
];

Then you can delete it as normal cell, which will cause closing of a window.

Delete[cell];

Ballon animation by Jemima (codepen)