Sign in to follow this  
timm

How to Generate a Flat Oval Shape in babylon.js

Recommended Posts

Hello All,

I have been using Babylon.js for over a year and have been able to get along quite well, thanks a lot in part the the valuable advice and playground examples I find on this forum.  I have run into one issue that I have searched for to no avail and I am looking for an elegant solution to.  The shape I would like to generate is a 3D flat oval.  I found an example of how to do this in three.js using a set of lines that are extruded, but I am not sure how to do this operation easily in Babylon.js.  The example below is a link to the three.js example and I have extracted the portion of the code that generates what they call a track shape, as a flat oval is shaped just like a 400m running track.  Here is a snippet of the code and a link to that example.  I have thought about generating this by building a left half circle, box, right half that are aligned left to right, but I wanted to see if there is a more elegant way to do this.  I did a playground for example for this, but it did not come out the way I would have liked it to.  The ends of the extruded object do not have the caps filled in.

My babylon.js attempt to do this:
https://playground.babylonjs.com/#3ZAKGF#4

three.js link to the example I am talking about (it is the emerlad/teal shape in the example):
https://threejs.org/examples/webgl_geometry_shapes.html

Code snippet in three.js that they use to generate the flat oval:

				// Track

				var trackShape = new THREE.Shape();

				trackShape.moveTo( 40, 40 );
				trackShape.lineTo( 40, 160 );
				trackShape.absarc( 60, 160, 20, Math.PI, 0, true );
				trackShape.lineTo( 80, 40 );
				trackShape.absarc( 60, 40, 20, 2 * Math.PI, Math.PI, true );

I am thinking that if I could do something like the following, that would work in Babylon.js.

    // Track in Babylon.JS
    var deltaTheta = 0.1;
    var flatLength = 10;
    var flatWidth = 4;
    var radius = flatWidth / 2;

    // create top line for the track
    var line1Points = [];
    line1Points = [
        new BABYLON.Vector3(+flatLength / 2, +flatWidth / 2, 0),
        new BABYLON.Vector3(-flatLength / 2, +flatWidth / 2, 0)
    ];

    // create bottom line for the track
    var line2Points = [];
    line2Points = [
        new BABYLON.Vector3(-flatLength / 2, -flatWidth / 2, 0),
        new BABYLON.Vector3(+flatLength / 2, -flatWidth / 2, 0)
    ];

    // create left side half-circle for the oval
    var leftHalfCirclePath = [];
    for(var theta = Math.PI / 2; theta < 3.0 / 2.0 * Math.PI; theta += deltaTheta ) {
        leftHalfCirclePath.push(new BABYLON.Vector3(radius * Math.cos(theta) + -flatLength / 2, radius * Math.sin(theta), 0));
    }
    var leftHalfCircle = new BABYLON.Curve3(leftHalfCirclePath);

    // create right side half-circle for the oval
    var rightHalfCirclePath = [];
    for(var theta = -Math.PI / 2; theta < Math.PI / 2; theta += deltaTheta ) {
        rightHalfCirclePath.push(new BABYLON.Vector3(radius * Math.cos(theta) + +flatLength / 2, radius * Math.sin(theta), 0));
    }
    var rightHalfCircle = new BABYLON.Curve3(rightHalfCirclePath);

    // join the paths together into myShape
    // join leftHalfCircle, line1, line2, rightHalfCircle
    // I have to figure this part out, how to get points from shape and join them
    var myShape = [];
    for (var i in line1Points) {
        myShape.push(line1Points[i]);
    }
    var points = leftHalfCircle.getPoints();
    for (var i in points) {
        myShape.push(points[i]);
    }
    for (var i in line2Points) {
        myShape.push(line2Points[i]);
    }
    var points = rightHalfCircle.getPoints();
    for (var i in points) {
        myShape.push(points[i]);
    }
    myShape.push(line1Points[0]); // Close the shape back to the starting point

    // Define the extrusion path for depth of 1 unit
    var myPath = [
        new BABYLON.Vector3(0, 0, 0),
        new BABYLON.Vector3(0, 0, 1)
    ];

    // creates an instance of a Custom Extruded Shape
    var extruded = BABYLON.MeshBuilder.ExtrudeShapeCustom("ext", {shape: myShape, path: myPath, sideOrientation: BABYLON.Mesh.DOUBLESIDE}, scene);

Is there an easier way to do this using an existing Babylon.js mesh (I have been unable to find one), or is there another much simpler way to do this that anyone is aware of?  Also, would I need to build the caps of the extruded object as 2D planes to put on the top and bottom, or can that be done in another way?

Thank you,

Tim

Share this post


Link to post
Share on other sites

I have a minor update.  I was able to get the caps working, didn't realize there was a cap option to extrude 😃  Whoever added that, thank you!

I cannot directly render edges on the mesh for the outer shape as it shows edges on all of the internal triangles.  You can uncomment the enableEdgesRendering() statement at the bottom of this first playground to see the effect.
https://playground.babylonjs.com/#3ZAKGF#7

I created a second playground, where I built the curves for the caps at 1 dimensional lines then enabled Edge rendering on those lines.  It seems to work but does have some artifact issues on edge thickness.
https://playground.babylonjs.com/#G1MBAL#2

I would still be curious to know if there is a more elegant, shorter way to do this with existing Babylon.js methods, if not then this is a good playground to get started with if you want a similar flat oval object.

Share this post


Link to post
Share on other sites

Hi @timm and welcome to the forum from me. Well done with your solution. Here is an alternative using extrudePolygon but probably no better than yours as it has a couple of disadvantages

1. Edge rendering round circles shows up

2. You can see in the docs that for non-playground use extrudePolygon requires Earcut to be installed.

Also note that the path for the polygon needs to be in XZ plane

Share this post


Link to post
Share on other sites

Thank you @JohnK for putting that example together.  I took the Polygon that you did and added it as the an overlay to draw text to the front side of the object as an embedded innerTrack of smaller size.  It is starting to come out really nice.  Two issues I am running into that I could use some advice on from the community:

  1. Autosizing the text to fit the object is somewhat trial and error in that I have had to play with two sets of variables, one is the pixel size to use for the text and the other is the height/width of the dynamic text texture.  It would be nice if there was a cleaner way to do this so that it would scale to different sizes of the object without hardcoding.
  2. This one is quite strange.  When I added the innerTrack (2D polygon) in front of the outerTrack (3D extruded shape) I ran into two issues.
    1. One is that I need to move the 2D polygon just slightly in front of the 3D track or the two surfaces alias into each other.  So I did a tiny offset of -0.001 to prevent them from being exactly in the same plane.  Is there are better way to fix this.  In HTML/CSS there is a z-index property I use for  this.  Is there a better way to achieve this in Babylon.js than what I have done with the small offset?
    2. This one is really strange.  For some reason when I add the 2D shape in front of the 3D shape, the edges from the BACK of 3D shape are visible, even though they shouldn't be (I think?).  However if you get rid of the 2D innerTrack then the back endges don't render through the 3D object.
You can see it in this playground example I put together.  If you disable the 2D innerTrack the edges will not render through the 3D outerTrack.
https://playground.babylonjs.com/#G1MBAL#10

Share this post


Link to post
Share on other sites

Just to clarify, the edges are from a 1D shape that are located at the front and back of the 3D shape.  These 1D shapes are there only to enableEdges() as enabling Edges on the extruded shape did not provide the desired effect (there were too many edges for all of the smaller arcs of the circle).  The 3D object that is extruded has a high alpha (0.9) but the 1D object with the edge does not come through that object strongly since the alpha is so high.  However, when putting a text 2D object in front of the 3D object, the edges come through very strongly, it is as if adding the 2D shape in front of the 2D shape causes the alpha of that 3D shape to be ignored.  If you set the alpha of the 3D shape to 1.0, this does not happen, but set it to a high number just under 1.0, such as 0.99999 and the edge comes through just as if it was 0.0000.

I could probably put together a simpler Playground to show this effect if that would help, but I am guessing somebody who is more experienced with Babylon.js materials than I knows why this occurs.

Share this post


Link to post
Share on other sites

@timm Reference your questions above

1. Do not have an immediate answer but have an idea I will try later

2.1 In some sense by using a tiny offset you are doing in 3D the HTML/CSS equivalent of setting a different z-index, so yes this is the way to do it.

2.2 It is because you are setting the opacityTexture of your  textMaterial based on your textTexture which is a black and white image making the white areas transparent, just remove line 148 https://playground.babylonjs.com/#G1MBAL#19

Share this post


Link to post
Share on other sites

@timm as promised I did some work on  question 1 https://playground.babylonjs.com/#G1MBAL#20

Lines 140 to 148 use a temporary dynamictexture to calculate a ratio to use with the width of the dynamictexture and the oval  in order to find a suitable font size. Probably you could use just the textTexture dynamictexture once to find ratio and then drawText on it.

EDIT yes you can https://playground.babylonjs.com/#G1MBAL#21

Share this post


Link to post
Share on other sites

@JohnK.  Thanks so much for posting that solution for the text.  That is brilliant!  I have moved this to solved.

I understand that setting the opacityTexture fixes the problem, the issue I am having is I don't understand why.  I put together another rendering that shows the behavior.

The top oval had no text overlay, the middle oval has the text overlay with opacityTexture set, and the bottom oval has the text overlay without opacityTexture set.

https://playground.babylonjs.com/#G1MBAL#22

What I don't understand is why adding a text overlay with an opacityTexture makes the edge BRIGHTER and come through the rest of the 3D oval versus the case with no text overlay.  Also, why wouldn't the rest of the 3D oval render through the opacity texture versus only the 2D edge.   It seems like the opacityTexture setting completely ignores the 3D shape behind it (i.e. the main outer oval) but not the 2D shape behind it (i.e. the rear outer edge).  I would have expected opacityTexture to allow the combination of 2D and 3D shapes behind it to render through which would mean that rear edge would not be so bright and the other 3D caps would show up in the text texture as well.  I could put together a simpler example to show this behavior if this question doesn't make any sense.

Thanks for the help on this and with the dynamic text resizing!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.