Babylon JS Day 13

Today I thought of a workaround to the “button anchoring” issue that I mentioned yesterday. Instead of placing the button in the StackPanel with all the other controls, I moved it outside of the StackPanel, then set it’s verticalAlignment to the bottom. Now the button will always be at the bottom of the card.

  var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Read more...");
  button1.width = 1;
  button1.height = "100px";
  button1.color = "white";
  button1.fontSize = 50;
  button1.background = "green";
  button1.paddingBottom = 20;
  button1.paddingLeft = 40;
  button1.paddingRight = 40;
  button1.onPointerUpObservable.add(function () {
    alert("you did it!");
  });
// No need to add the button to the panel
// panel.addControl(button1);
// Instead, add it to the parent texture
  button1.verticalAlignment = 1;
  advancedTexture.addControl(button1);

Then it was time to wrap up the createCard() function. I added some parameters

  • item – the JSON object containing post data
  • positionObject – an object that contains the XYZ values for the position vector.

Side Note: I tried passing the position as a Babylon JS Vector but I was seeing an odd error in the console. Not wanting to spend time on it, this was my work around for now.

function createCard(item, positionObject) {
  const card = BABYLON.MeshBuilder.CreateBox("box", { height: 3, width: 2, depth: 0.2 });
  card.position = new BABYLON.Vector3(positionObject.x, positionObject.y, positionObject.z);
  const plane = BABYLON.MeshBuilder.CreatePlane("plane", { height: 3, width: 2 });
  plane.parent = card;
  plane.position.z = -0.11;

  var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(plane);

  var panel = new BABYLON.GUI.StackPanel();
  panel.verticalAlignment = 0;

  advancedTexture.addControl(panel);

  var title = new BABYLON.GUI.TextBlock(item);
  title.text = item.title;
  title.color = "black";
  title.fontSize = 48;
  title.height = "100px";
  title.textHorizontalAlignment = 0;
  title.textVerticalAlignment = 0;
  title.paddingTop = 40;
  title.paddingLeft = 40;
  title.paddingRight = 40;
  panel.addControl(title);

  var date = new BABYLON.GUI.TextBlock();
  date.text = item.pubDate;
  date.color = "black";
  date.fontSize = 36;
  date.height = "80px";
  date.textHorizontalAlignment = 0;
  date.textVerticalAlignment = 0;
  date.paddingTop = 20;
  date.paddingLeft = 40;
  date.paddingRight = 40;
  panel.addControl(date);

  var note = new BABYLON.GUI.TextBlock();
  note.fontFamily = "Tahoma, sans-serif";
  note.text = item.contentSnippet;
  note.textWrapping = true;
  note.color = "black";
  note.fontSize = 24;
  note.height = "660px";
  note.textHorizontalAlignment = 0;
  note.textVerticalAlignment = 0;
  note.paddingTop = 20;
  note.paddingLeft = 40;
  note.paddingRight = 40;

  panel.addControl(note);

  var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Read more...");
  button1.width = 1;
  button1.height = "100px";
  button1.color = "white";
  button1.fontSize = 50;
  button1.background = "green";
  button1.paddingBottom = 20;
  button1.paddingLeft = 40;
  button1.paddingRight = 40;
  button1.onPointerUpObservable.add(function () {
    alert("you did it!");
  });
  button1.verticalAlignment = 1;
  advancedTexture.addControl(button1);
}

In the future I’ll make a system to place the cards around the scene. For now, I’m placing all the cards in a single row by calculating the position on the X axis while looping over the JSON items.

    let x = -2.2 * i;
    let positionObject = {
      x: x,
      y: 0,
      z: 0
    };
3D Cards
3D Cards
3D Cards
3D Cards

Babylon JS Day 12

Today was all about setting up the layout for my 3D cards. I started by making a short list of what I wanted to display.

  • Title: the name of the card / article / post
  • Date
  • Note: an excerpt from the article
  • Image: a thumbnail pulled from the article

I want to create a 3D object that will list the values above in a simple layout. I started by creating a box object that will represent the card. Then I created a plane object which will hold the AdvancedDynamicTexture on which I draw the content of the card. The plane is positioned just off of the surface of the box.

const card = BABYLON.MeshBuilder.CreateBox("box", { height: 3, width: 2, depth: 0.2 });
  const plane = BABYLON.MeshBuilder.CreatePlane("plane", { height: 3, width: 2 });
  plane.position.z = -0.11;

  var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(plane);

Babylon JS has several controls that I can use for the contents of this texture. I wanted to start with something that would add some structure to the layout. After poking around a bit, I settled on the StackPanel feature of the Babylon JS GUI system. This will let me layout multiple UI controls with some basic rules for how they are placed. It’s not the most sophisticated layout tool, but it will work for the time being.

  var panel = new BABYLON.GUI.StackPanel();
  panel.verticalAlignment = 0;

  advancedTexture.addControl(panel);

I spent the rest of the time today working with text blocks and a simple button. Keep in mind that none of this has seen any design attention yet. I’ll work on styling and general appearance later in the week. For now, think of this card as a prototype.

The only thing I don’t like about the GUI containers in Babylon JS is that they require fixed height values instead of percentages. This leads to a lot of fast iteration as I change padding and height values, constantly refreshing the scene to check slight changes. Not ideal.

Here the controls that I added today. There is a title at the top with a larger font, followed by the date. Then a larger text block in the middle of the card for the excerpt value. I skipped the image for now. At the bottom of the card is a placeholder button. I’m not sure if the final version will have a button or if the entire card will be a button.

A couple of things worth pointing out:

  1. While the StackPanel object has padding properties, I found that setting those messed up the text wrapping on the excerpt text block. I had to remove the padding from the StackPanel and add it to each control.
  2. I couldn’t find mention of any sort of “spacer” control. Something that would let me push the button to the bottom of the card, dynamically resizing based on the content around it. I also could not find a way to anchor only the button to the bottom of the card while anchoring everything else to the top.
 var title = new BABYLON.GUI.TextBlock();
  title.text = "A Month of Babylon JS";
  title.color = "black";
  title.fontSize = 48;
  title.height = "100px";
  title.textHorizontalAlignment = 0;
  title.textVerticalAlignment = 0;
  title.paddingTop = 40;
  title.paddingLeft = 40;
  title.paddingRight = 40;
  panel.addControl(title);

  var date = new BABYLON.GUI.TextBlock();
  date.text = "March 2, 2021";
  date.color = "black";
  date.fontSize = 36;
  date.height = "80px";
  date.textHorizontalAlignment = 0;
  date.textVerticalAlignment = 0;
  date.paddingTop = 20;
  date.paddingLeft = 40;
  date.paddingRight = 40;
  panel.addControl(date);

  var note = new BABYLON.GUI.TextBlock();
  note.fontFamily = "Tahoma, sans-serif";
  note.text =
    "Throughout the month of March, I’m going to learn everything I can about Babylon JS. Over the last year I’ve spent a bit of time with A-Frame, Three JS, and Babylon JS. I explained my reasoning for settling on Babylon JS in the most recent episodes of Project Update...";
  note.textWrapping = true;
  note.color = "black";
  note.fontSize = 24;
  note.height = "660px";
  note.textHorizontalAlignment = 0;
  note.textVerticalAlignment = 0;
  note.paddingTop = 20;
  note.paddingLeft = 40;
  note.paddingRight = 40;

  panel.addControl(note);

  var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Read more...");
  button1.width = 1;
  button1.height = "100px";
  button1.color = "white";
  button1.fontSize = 50;
  button1.background = "green";
  button1.paddingTop = 20;
  button1.paddingBottom = 20;
  button1.paddingLeft = 40;
  button1.paddingRight = 40;
  button1.onPointerUpObservable.add(function () {
    alert("you did it!");
  });
  panel.addControl(button1);

I have a lot of work left to do on this card. I hope to finish it up tomorrow and then make a version that I can clone and populate with data.

The card, as it stands on March 16, 2021
The card, as it stands on March 16, 2021

Babylon JS Day 11

I’m kicking off Week 3 of A Month of Babylon JS by diving into a week-long project to present some data as interactive cards in 3D. My Friday Project this week will involve building some VR interaction for these cards. For now, I’m going to focus on the data and the layout of the cards.

Getting the data

In the interest of saving time, I decided to use the blog posts in this series as the data source for the cards. Rather than spend any time on an API to build this data, I used the XML in my RSS feed. Since my site is built on WordPress, the RSS feed has query parameters built right in. All the posts in this series are tagged with a value of babylon-month so the url to get the data is:

https://radicalappdev.com/feed/?tag=babylon-month

I’m not spending any time on the backend of this project, so I’ll just serve the data as a JSON file. I converted it from the RSS XML to JSON with this site. I know… Lazy Joe. The data is hosted on my WebXR Sandbox site here.

Loading the data

Something that caught my attention last week was a reference to a method called addTextFileTask on the Asset Manager. I can use this to load the JSON file, then parse the text as JSON, and pass it to a function that will iterate over it to create cards in the scene.

 var assetsManager = new BABYLON.AssetsManager(scene);

  const dataPath = "https://webxr.radicalappdev.com/assets/data/babylon-month.json";
  assetsManager.addTextFileTask("babylon-month", dataPath).onSuccess = function (task) {
    const parsed = JSON.parse(task.text);
    createCards(scene, parsed.items); // this function will append 3d card objects to the scene
  };
  assetsManager.load();

3D GUI in Babylon JS

I didn’t have much time left to work on the cards today, but I got a head start on tomorrow by checking out the 3D GUI features of Babylon JS. For now, I just used some sample code from the Plane Panel example. This will create a panel object, then loop over the array of items, creating a HolographicButton object for each one. Not perfect, but good enough for today.

function createCards(scene, items) {
  // Create the 3D UI manager
  var anchor = new BABYLON.TransformNode("");
  var manager = new BABYLON.GUI.GUI3DManager(scene);

  var panel = new BABYLON.GUI.PlanePanel();
  panel.margin = 0.2;

  manager.addControl(panel);
  panel.linkToTransformNode(anchor);
  panel.position.z = -1.5;

  // Let's add some buttons!
  var addButton = function (item) {
    var button = new BABYLON.GUI.HolographicButton("orientation");
    panel.addControl(button);
    button.text = item.title;
  };

  panel.blockLayout = true;
  for (item of items) {
    console.log(item);

    addButton(item);
  }
  panel.blockLayout = false;
}
A Plane Panel with some placeholder objects.

Tomorrow I’ll start working on the 3D card object itself. Then I’ll replace the buttons in the sample code above with the new object.

Babylon JS Day 10: Terrarium

This week for my Friday Project I decided that I wanted to build a simple Terrarium with some low poly assets. My main objectives was to learn about composing a scene in Babylon JS. I started with the table/base that I made on Day 8 and what I learned about asset loading on Day 9.

I’m almost embarrassed to admit that I spent most of the day working with the asset files and trying to get them loaded into the scene. I ran into some issue with obj files where sometimes the related mtl file would just not show up in the scene. Eventually I decided to convert the models to glb file instead. Lucky for me I had a copy of the Blender project for each file, so it was just a matter of opening the Blender project and exporting the model and material as a glb file. I ended up with 13 models that I added to the scene using the assetsManager.

// Asset loading example
 var assetsManager = new BABYLON.AssetsManager(scene);

  const path = "../assets/models/";

  // Trees
  assetsManager.addMeshTask("mesh task", "", path, "Tree1.glb").onSuccess = function (task) {
    task.loadedMeshes[0].position = new BABYLON.Vector3(5, 0, 6);
    task.loadedMeshes[0].rotation = new BABYLON.Vector3(0, 30, 0);

    task.loadedMeshes[0].name = "Tree1";
  };

// Many more files loaded....

assetsManager.load();

One interesting side note: files loaded this way are not named in the scene graph, but I found out that I can set the name property of the loaded mesh. This made it easier to work with these models in the Babylon JS inspector.

I wanted to use some of the models more than once, so I learned how to use the clone() method on a mesh. I created these clones right in the .onSuccess call after loading an asset. For example, I wanted a small stand of trees using the Tree3 model. I used the one imported in the loadedMeshes results array as the first instance in the scene, then cloned it twice for the other instances.

assetsManager.addMeshTask("mesh task", "", path, "Tree3.glb").onSuccess = function (task) {
    task.loadedMeshes[0].position = new BABYLON.Vector3(6.5, 0, -4);
    task.loadedMeshes[0].name = "Tree3";

    tree3Clone1 = task.loadedMeshes[0].clone("Tree3Clone1");
    tree3Clone1.position = new BABYLON.Vector3(5, 0, -1);
    tree3Clone1.rotation = new BABYLON.Vector3(0, -70, 0);
    tree3Clone1.scaling = new BABYLON.Vector3(1.2, 1.2, 1.2);

    tree3Clone2 = task.loadedMeshes[0].clone("Tree3Clone2");
    tree3Clone2.position = new BABYLON.Vector3(6.5, 0, -1.5);
    tree3Clone2.rotation = new BABYLON.Vector3(0, 50, 0);
    tree3Clone2.scaling = new BABYLON.Vector3(0.9, 1.2, 0.9);
  };

I spent more time than I care to admit just deciding where on this small table to place everything. This is what I ended up with for now.

One small touch at the end of the day was adding some animations to the camera. I found an interesting Playground on the Babylon JS forum that gave me some ideas using a timeouts to move the camera around the scene. When the scene loads, these animations will play out over a period of 12 seconds, mainly to demonstrate that you can move the camera around the scene.

When I started out this morning, my plans included many more features. I wanted to add some animals that would move around the scene. I also intended to create a soundtrack and some simple sound effects. Don’t get me started about clouds… Not to mention the dome that is supposed to cover the Terrarium. I just ran out of time. Even with this Friday Project complete, I may keep working on this scene. It could make an interesting place to try new concepts as I learn them.

Try the scene out for yourself here.

Babylon JS Day 9

Today was all about working with assets. The project that I’m going to work on tomorrow involves several models, textures, and audio files. Today I wanted to learn how to create a scene while loading assets from the server.

I got started by revisiting the scene I made yesterday. I cleaned up the code a little and made a Babylon JS Playground out of it. You can check it out here.

I saved this scene as a .babylon file and added it to my project. This was the first attempt at loading it into my scene. I only needed to load the mesh, not the rest of the scene, so I passed the “stand” value as the name of the mesh.

BABYLON.SceneLoader.ImportMeshAsync("stand", "../assets/scenes/", "terrarium-base.babylon");

Then I shifted my attention to loading some third-party assets. I’ve followed a low poly artist named @quaternius for a few years, and I support them on Patreon. One of the benefits I get is a full zip archive of all their models. I’m going to use a few of these tomorrow so I wanted to learn how to load them into my scene.

I spent a bit of time reading the documentation for importing assets. Two pages provided what I was after.

  1. Loading Any File Type has instructions for loading a variety of file types.
  2. Asset Manager is an awesome feature that will let me define loading tasks for all my assets at once. I can even provide some code to execute on success.

Below is an example of the asset manager that I created. For now, this loads the .babylon scene file that I mentioned above, along with an .obj (the .mtl file is imported automatically) and a texture atlas. You can see that the onSuccess call for the texture will assign the texture to a material. When the flower task succeeds, I assign a position and the material. At the end of all of this I can the load() method on the asset manager object, right before the scene loads. As far as I can tell, Babylon JS will load all the assets before starting the scene.

  var assetsManager = new BABYLON.AssetsManager(scene);
  var flowerMat = new BABYLON.StandardMaterial("baseMat", scene);

  var textureTask = assetsManager.addTextureTask("image task", "../assets/models/textures/palette_32x32.png");
  textureTask.onSuccess = function (task) {
    flowerMat.diffuseTexture = task.texture;
  };
  var meshTask = assetsManager.addMeshTask("flower task", "", "../assets/models/", "flower.obj");
  meshTask.onSuccess = function (task) {
    task.loadedMeshes[0].position = BABYLON.Vector3.Zero();
    task.loadedMeshes[0].material = flowerMat;
  };
  meshTask.onError = function (task, message, exception) {
    console.log(message, exception);
  };

  var baseTask = assetsManager.addMeshTask("base task", "", "../assets/scenes/", "terrarium-base.babylon");
  baseTask.onSuccess = function (task) {
    // Nothing to do for now
  };
  baseTask.onError = function (task, message, exception) {
    console.log(message, exception);
  };

  assetsManager.load();

I still have a lot to learn about working with assets in a Babylon JS project, but I now have enough knowledge to move forward with my project tomorrow.

How to export a scene from the Babylon JS Playground

When working with a Babylon JS Playground, you can export your scene as a GLB or BABYLON file. The only issue is that I keep forgetting how do to this :face-palm

Click on the little toolbox icon in the playground to open the inspector.

Open the Babylon JS Inspector on a Playground
Open the Babylon JS Inspector on a Playground

Then click on the wrench icon in the bottom section of the inspector. Scroll down until you see the Scene Export section.

Scene Export
Scene Export

Babylon JS Day 8

Today I broke away from structured reading and gave myself some time to play and create. I already have an idea of what I want to do for the project this Friday, so I spent my time today creating a scene and some basic features that I’ll need later. I’ll go into more detail regarding the project on Friday.

For now, I focused on a few things: camera, lighting, background color, and a basic pedestal to build on.

Camera

I referred to my notes last week to help construct an Arc Rotate camera. This camera will allow a visitor to rotate around a fixed point. Notice the call to upperBetaLimit. I remembered seeing this in the Getting Started guide last week. This will let me stop the camera from moving below a certain point. The scene I’m making is basically on top of a table, so I don’t want the camera to go below the table. The calls to lowerRadiusLimit and upperRadiusLimit allow me to constrain the zoom level of the camera.

  const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0));
  camera.upperBetaLimit = Math.PI / 2.2;
  camera.lowerRadiusLimit = 10;
  camera.upperRadiusLimit = 50;
  camera.setPosition(new BABYLON.Vector3(0, 15, -28));
  camera.attachControl(canvas, true);

Lighting and Background

This is simple for now. I changed the background color of the scene and positioned some lights. I included both a directional light and a hemispheric light.

  scene.clearColor = BABYLON.Color3.FromHexString("#b1d5fa");

  const ambientLight = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 10, 0));
  const directionalLight = new BABYLON.DirectionalLight("DirectionalLight", new BABYLON.Vector3(0, -1, -3), scene);

Pedestal

I throught of several ideas for creating the mesh for the table/pedestal before I settled on the Lathe. I referred to the example from the Getting Started guide where we created a fountain. I did something similar for my pedestal.

  const baseMat = new BABYLON.StandardMaterial("baseMat", scene);
  baseMat.diffuseColor = new BABYLON.Color3.FromHexString("#6e6d7c");
  baseMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);

  const standProfile = [
    new BABYLON.Vector3(0, 0, 0),
    new BABYLON.Vector3(8, 0, 0),
    new BABYLON.Vector3(8, 1, 0),
    new BABYLON.Vector3(7, 1, 0),
    new BABYLON.Vector3(7, 0.5, 0),
    new BABYLON.Vector3(1, 0.5, 0),
    new BABYLON.Vector3(1, 15, 0),
    new BABYLON.Vector3(3, 17, 0),
    new BABYLON.Vector3(10, 18, 0),
    new BABYLON.Vector3(10, 19, 0),
    new BABYLON.Vector3(0, 19, 0)
  ];

  const stand = BABYLON.MeshBuilder.CreateLathe(
    "stand",
    { tessellation: 24, shape: standProfile, sideOrientation: BABYLON.Mesh.DOUBLESIDE },
    scene
  );
  stand.material = baseMat;
  stand.convertToFlatShadedMesh();
  stand.position = new BABYLON.Vector3(0, -17, 0);

At the end of my time today I was left with the building blocks that I’ll use on Friday.

A simple pedestal that I’m going to use for a project

Babylon JS Day 7

I spent my education time today reading the documentation for the GUI system in Babylon JS. This came with a ton of playgrounds to look at, some that included interesting examples of the core features. Highlights include:

  • There are two main GUI modes.
    • Full Screen (screen-space) can be used to create HUDS, overlays, menus
    • Texture Mode can render the contents of a GUI on a texture attached to the face of a mesh. This second option is useful for some of the VR interfaces that I want to build.
  • The core component for GUI is the AdvancedDynamicTexture. This organizes and renders controls that are attached to it.
  • This GUI system comes with a ton of pre-built controls. Some examples are text views, text inputs, password fields, buttons, checkboxes, radio button sets, and sliders. I played around with a few of these and many of them have a decent amount of customization built in.
  • Contains are a way to organize controls in a GUI. Babylon JS has several containers. The one that stands out to me is the StackPanel. If I these can be embedded in other StackPanels then this opens a huge array of layout possibilities.
  • Position, size, padding, etc. all support pixels and percentages. Some containers require the use of pixels.

Overall, it seems like Babylon JS has a lot to offer as far as GUI is concerned. I haven’t even had time to investigate the more interesting 3D GUI. At some point I want to build a GUI for the Breathe demo that I made last week. I’d like to allow guests to set their own timing values and perhaps even include a handful of presets.

Babylon JS Day 6

Today was a bit of a waste of time. When deciding what to learn this week I gave myself two main options.

  1. Pick a handful of topics from the Diving Deeper documentation. Work through these topics with the goal of expanding what I learned last week.
  2. Work through the Create a Game guided learning documentation. This claims to be a step-by-step guild to show some of the common development pattens for making a game in Babylon JS.

I’m not interested in making games, but option two presented a good chance to see how to work with Babylon JS on a larger project, so that is what I spent my time on today.

I didn’t make it far. It turns out that this tutorial is based on TypeScript and involves a ton of project setup and dependencies. I followed along with the documentation as well as I could, but the result was a broken project. As far as I can tell, all modules and dependencies are installed but I can’t build and run the project. I even tried cloning the entire sample project from the provided repo, and that one would not work either.

I’m not going to spend any more time on it. I should have bailed out as soon as I saw the word TypeScript… I know there are a ton of developers who swear by NPM, node modules, webpack, etc. to build their projects, but I just want keep things simple for now. Tomorrow I’m going to start looking at the Diving Deeper documentation that I mentioned above, and I’m going to return to a simple project without all the build tools, config files, and dependencies.

Babylon JS Day 5: Breathe

As I wrote about on Monday, the last day of each week in March I’m taking some time to work on a series of small projects built in Babylon JS. Today I got started with a simple breathing animation. I wanted something similar to the Breathe App on Apple Watch, but I wanted to control the timing animations and add my own sounds.

TL; DR: You can try out the scene on my WebXR Sandbox Site.

If you click the link above, you will see black scene with a sphere in the center. The sphere will grow and shrink over time to indicate the pace of the breathing exercise I’ve been learning. This is based on a pattern of breathing I read about in Breath: The New Science of a Lost Art by James Nestor. Breathe in for 5.5 seconds, breathe out for 5.5 seconds. Pretty simple.

Visuals

This project didn’t get much attention as far as graphics are concerned. I have a simple icosphere that is placed at the world origin. It has a material with an emissiveColor set to #8c1eff. The only light source is a hemisphere light with a diffuse value of #ff2975 and a specular value of #8c1eff. This combination of material and lighting gave me something that I liked so I moved on to the animations.

Expanded breathing sphere

Animations

In the case of this scene, the animations are broken into four segments (five if you count the starting point). These values are hardcoded for now, but in the future, I want to make these something that guests can modify.

0 (start)Base Size
1.5 secondsBase Size, play the wait sound
4 secondsExpanded Size, plan the breathe-in sound
1.5 secondsExpanded Size, play the wait sound
4 secondsBase Size, play the breathe-out sound
Animation sequencing used in the initial Breathe project.

Animations in Babylon JS can only affect a sole property, so I had to create several animations to scale the sphere on all three axes. I set the animation frame rate to 30 and created keyframe for each of the items in the table above. In each keyframe I multiplied the time in the sequence by the frame rate. Babylon JS has an interesting way to call events on a specified frame (maybe I could use this to clean up my redundant code?). I used these animation events to call the play() method on the sound objects. Finally, the animation is set to loop.

Recap

Overall, I’m happy with the result. I have a simple breathing animation that I can use to help myself learn this breathing pace, and I was able to use several things that I learned throughout the week. In the future I’ll expand this to include some other breathing patterns and add some customizable elements.

This weekend I’ll review my notes from this past week and plan for what I should learn next week. Every area that I covered in the Getting Started guide has an expanded section of documentation available, so I don’t think I’ll run out of things to learn anytime soon.

Here is a copy of the source code for the scene. I didn’t spend any time making this code pretty. I used the Babylon JS Playground Template as a starting point and did all the work in the createScene() function.

const canvas = document.getElementById("renderCanvas"); // Get the canvas element
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine

var shapeColor = "#8c1eff";

// Editable time sets for the animation segments
var set1 = 1.5;
var set2 = 4;
var set3 = 1.5;
var set4 = 4;

// Add scene code here
const createScene = () => {
  const scene = new BABYLON.Scene(engine);
  scene.clearColor = BABYLON.Color3.Black();

  const camera = new BABYLON.ArcRotateCamera("camera", 0, 0, 10, new BABYLON.Vector3(0, 0, 0));
  camera.setPosition(new BABYLON.Vector3(0, 0, -20));
  // camera.attachControl(canvas, true);

  const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, -0.5, 10));
  light.diffuse = new BABYLON.Color3.FromHexString("#ff2975");
  light.specular = new BABYLON.Color3.FromHexString("#8c1eff");

  const mat1 = new BABYLON.StandardMaterial("mat1", scene);
  mat1.emissiveColor = new BABYLON.Color3.FromHexString(shapeColor);

  // Create some sound objects
  const breathIn = new BABYLON.Sound("breathIn", "../assets/audio/BreathIn.mp3", scene);
  const breathOut = new BABYLON.Sound("breathOut", "../assets/audio/BreathOut.mp3", scene);
  const breathWait = new BABYLON.Sound("breathWait", "../assets/audio/BreathWait.mp3", scene);

  // Create a mesh
  let shape1 = BABYLON.MeshBuilder.CreateIcoSphere("shape1", { radius: 1, flat: false, subdivisions: 16 }, scene);
  shape1.material = mat1;

  // Setup for animations
  let frameRate = 30;

  const keyFrames = [];
  let seg1 = set1;
  let seg2 = set1 + set2;
  let seg3 = set1 + set2 + set3;
  let seg4 = set1 + set2 + set3 + set4;
  var totalTime = seg1 + seg2 + seg3 + seg4;

  keyFrames.push({
    frame: 0,
    value: 0.5
  });

  keyFrames.push({
    frame: seg1 * frameRate,
    value: 0.5
  });

  keyFrames.push({
    frame: seg2 * frameRate,
    value: 2.5
  });

  keyFrames.push({
    frame: seg3 * frameRate,
    value: 2.5
  });

  keyFrames.push({
    frame: seg4 * frameRate,
    value: 0.5
  });

  const scaleX = new BABYLON.Animation(
    "xSlide",
    "scaling.x",
    frameRate,
    BABYLON.Animation.ANIMATIONTYPE_FLOAT,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
  );

  const playIn = new BABYLON.AnimationEvent(
    seg1 * frameRate,
    function () {
      breathIn.play();
    },
    false
  );
  const playWait = new BABYLON.AnimationEvent(
    (seg2 - 0.5) * frameRate,
    function () {
      breathWait.play();
    },
    false
  );
  const playOut = new BABYLON.AnimationEvent(
    seg3 * frameRate,
    function () {
      breathOut.play();
    },
    false
  );
  const playWait2 = new BABYLON.AnimationEvent(
    (seg4 - 0.5) * frameRate,
    function () {
      breathWait.play();
    },
    false
  );
  scaleX.addEvent(playIn);
  scaleX.addEvent(playWait);
  scaleX.addEvent(playOut);
  scaleX.addEvent(playWait2);

  scaleX.setKeys(keyFrames);
  const scaleY = new BABYLON.Animation(
    "xSlide",
    "scaling.y",
    frameRate,
    BABYLON.Animation.ANIMATIONTYPE_FLOAT,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
  );
  scaleY.setKeys(keyFrames);
  const scaleZ = new BABYLON.Animation(
    "xSlide",
    "scaling.z",
    frameRate,
    BABYLON.Animation.ANIMATIONTYPE_FLOAT,
    BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
  );
  scaleZ.setKeys(keyFrames);

  shape1.animations.push(scaleX);
  shape1.animations.push(scaleY);
  shape1.animations.push(scaleZ);

  var easingFunction = new BABYLON.QuadraticEase();

  // For each easing function, you can choose beetween EASEIN (default), EASEOUT, EASEINOUT
  easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);

  // Adding easing function to my animation
  scaleX.setEasingFunction(easingFunction);
  scaleY.setEasingFunction(easingFunction);
  scaleZ.setEasingFunction(easingFunction);

  console.log("total time:", totalTime);
  console.table(keyFrames);
  scene.beginAnimation(shape1, 0, totalTime * frameRate, true);

  return scene;
};

// Call the scene
const scene = createScene(); //Call the createScene function

// Register a render loop to repeatedly render the scene
engine.runRenderLoop(function () {
  scene.render();
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
  engine.resize();
});

1 2 3 4 5 6