Three.js: Request about CircleGridHelper and RadiatingGridHelper

Created on 17 Nov 2016  路  35Comments  路  Source: mrdoob/three.js

(* This section is for bug reports and feature requests only. This is NOT a help site. Do not ask help questions here. If you need help, please use stackoverflow. *)

Description of the problem

Accordingly to #5572 and #10145.

A CircleGridHelper could be really nice to see, just a grid where cell after a specific radius will don't exist.

And a RadiatingGridHelper with line segments radiating out from the center, and circle shapes breaking those segments up more, could allow to play with cylindrical anamorphosis !

If some people are interested by that, please let me know !

Three.js version
  • [x] Dev
  • [ ] r82
  • [ ] ...
Browser
  • [x] All of them
  • [ ] Chrome
  • [ ] Firefox
  • [ ] Internet Explorer
OS
  • [x] All of them
  • [ ] Windows
  • [ ] Linux
  • [ ] Android
  • [ ] IOS
Hardware Requirements (graphics card, VR Device, ...)

All 35 comments

FYI: after having replied to your initial comment that you referenced, I wondered how difficult it would be to create one, and started toying with a CircleGridHelper class (akin in style to your RadiatingGridHelper description, however). If I can complete it I'll submit for inclusion obviously.

Your description of CircleGridHelper (rectangular grid with a circular cutoff) is also intriguing and I can imagine how to accomplish that also, but separately. Might be doable also.

Edit: jsfiddle for fun...
https://jsfiddle.net/87wg5z27/65/

Nice ! Where are you for now ? Do you would some helpe ?

What you see there is how I left it, more or less. I had success in adding Ellipses to the scene in proper placing but failed to get the geometry's vertices and colors added to array for building the BufferGeometry. I was going to have another go at it later this week, but you're welcome to try to.

Note that I've not tried to build any of this into a dev build of three.js yet (or built the source ever at all) so it remains to be seen if I can create something that integrates well, haha.

I have just edited your fiddle to see what look like the expected result !

You should take a look to RingGeometry !

Do you have a link to the new fiddle? It still shows the old one there.

Sorry, i'm confuse !

Here the link : https://jsfiddle.net/TristanValcke/s9p2ogua/2/

This approach is completely incorrect but the showing result look as expected ^^

Oh yeah. That's (somewhat) the visual I got with using ellipses directly. I think the Ellipse is a good place to start because it's a Path/Curve/Line of vertices that I should be able to treat the same as the radials are to draw the whole thing as a unit. Visually it should match the other helpers nicely also.

Whats the intended use of this helper?

As a visual aid for determining distances from a centerpoint, and radial lines to visualize angles from that point also. Essentially, an easy way to throw in a Polar Grid: https://en.m.wikipedia.org/wiki/Polar_coordinate_system

It's like GridHelper but using radial display. It's could be helpfull for cylindrical anamorphosis too, or like said Hectate for polar coordinates !

Whats the intended use of this helper?

It's specially popular this days with VR stuff.

Alright, I've got it working in the fiddle and I'm looking for some feedback on this before going further into actually implementing it as a new THREE class and submitting to dev. You can look at the current results https://jsfiddle.net/87wg5z27/78/ Note that line 37 circleGridHelper(); holds the settings for the helper, tweak it to view changes in the preview.

Feedback desired

  • Name: I recommend the class be called PolarGridHelper instead of Circle* or Radial*
  • Colors: Right now there are two colors, but it could easily use 3 or even 4 colors (to separate radials and circles), however simplicity is a benefit of using only two colors.
  • Circular "resolution": lines 89/90 use 100 points for each circle. This produces (at current scales) nicely smooth circles. Simply changing that 100 to radials in both instances devolves the circles down to single lines spanning between each radial, and reducing the number of vertices used significantly - at the cost of visual appearance. It does not seem like the Lines created here are costly, but it's worth considering if we want a parameter to set the smoothness of the circles - as well as what a sensible default would be (I noted that even 50 seems smooth enough still).

Edit: It's also possible to programmatically reduce the number of vertices in each circle as they are closer to the pole (and thus, smaller), or have them be a factor of the radial and circle counts.

Just moving here the code from the jsfiddle:

function circleGridHelper(radius, radials, circles, color1, color2) {

  var radials = radials || 1;
  var circles = circles || 1;
  color1 = new THREE.Color(color1 !== undefined ? color1 : 0x444444);
  color2 = new THREE.Color(color2 !== undefined ? color2 : 0x888888);

  var step = radius / circles;
  var degrees = 360 / radials;
  var vertices = [],
    colors = [];
    //create the radials
  for (var i = 0, j = 0; i <= radials; i++) {
    var rAngle = THREE.Math.degToRad(degrees * i);
    //console.log(rAngle);
    var vec3 = new THREE.Vector3(radius, 0, 0);
    vec3.applyAxisAngle(new THREE.Vector3(0, 0, 1), rAngle);
    vertices.push(0, 0, 0, vec3.x, 0, vec3.y);
    var color = i & 1 ? color1 : color2;
    color.toArray(colors, j);
    j += 3;
    color.toArray(colors, j);
    j += 3;
  }
  //create the circles
  var offset = colors.length;
  for( var i = 0; i <= circles; i++) {
    var curve = new THREE.EllipseCurve(
      0, 0, // ax, aY
      (radius - (step * i)), (radius - (step * i)), // xRadius, yRadius
      0, 2 * Math.PI, // aStartAngle, aEndAngle
      false, // aClockwise
      0 // aRotation 
    );

    var path = new THREE.Path(curve.getSpacedPoints(100));
    var geometry = path.createSpacedPointsGeometry(100);
    geometry.rotateX(THREE.Math.degToRad(90));
    var color = i & 1 ? color1 : color2;
    offset = colors.length;
    for ( var ii = 0, j = 0; ii < geometry.vertices.length-1; ii++ ) {
      vertices.push(
        geometry.vertices[ii].x,
        geometry.vertices[ii].y,
        geometry.vertices[ii].z,
        geometry.vertices[ii+1].x,
        geometry.vertices[ii+1].y,
        geometry.vertices[ii+1].z);
      //var color = new THREE.Color(0xff0000);
      color.toArray(colors, j + offset);
      j += 3;
      color.toArray(colors, j + offset);
      j += 3;
    }
    //close the circle by using the last/first vertex pair too
    vertices.push(
      geometry.vertices[geometry.vertices.length-1].x,
      geometry.vertices[geometry.vertices.length-1].y,
      geometry.vertices[geometry.vertices.length-1].z,
      geometry.vertices[0].x,
      geometry.vertices[0].y,
      geometry.vertices[0].z
    );
      color.toArray(colors, j + offset);
      j += 3;
      color.toArray(colors, j + offset);
      j += 3;
    }
  var geometry = new THREE.BufferGeometry();
  geometry.addAttribute('position', new THREE.Float32Attribute(vertices, 3));
  geometry.addAttribute('color', new THREE.Float32Attribute(colors, 3));

  var material = new THREE.LineBasicMaterial({
    vertexColors: THREE.VertexColors
  });
  var line = new THREE.LineSegments(geometry, material);
  scene.add(line);

}

I like the API, but I think the code could be much simpler just using Math.sin() and Math.cos().

Maybe @Mugen87 can give it a go?

Yeah ! Really nice ! I'm impatient to use it !

PolarGridHelper seems to be a good name for it. +1

Did you plan to make doc and example with the class, if not i could help you.

Agreed, the rotation functions I used were simple hacks to get it going and overcome my general inexperience with three.js and 3D in general. For example, converting the EllipseCurve > Path > Geometry was just so I could rotate the vertices. After thinking about it, simply mapping the vertices' Y value to the Z instead would accomplish that also.

Someone more brave could place the circle's vertices' directly instead of using EllipseCurve at all, in fact.

I'd enjoy seeing alternative approaches; it's part of how I learn too :)

@Hectate could you create a branch under dev call "PolarGridHelper" and push to your repo this code, i would participate to it. If you're agreed with that !

I would but my code didn't merge well with three.js when I built it. Something about the circles isn't working (the radials show up but not circles) after I converted it. Ran out of time to bug-test.

You can of course use this now by simply dropping that function into your project and calling it. I would probably modify it to return the new object instead of adding it to the scene, so you can assign it to a variable and continue to manipulate it.

Here is an updated version. The implementation uses only Math.sin() and Math.cos(). I've also adapted our codestyle.

Demo

function CircleGridHelper( radius, radials, circles, divisions, color1, color2 ) {

  radius = radius || 10;
  radials = radials || 16;
  circles = circles || 8;
  divisions = divisions || 50;
  color1 = new THREE.Color( color1 !== undefined ? color1 : 0x444444 );
  color2 = new THREE.Color( color2 !== undefined ? color2 : 0x888888 );

  var vertices = [];
  var colors = [];

  var vertex = new THREE.Vector3();
  var v, i, j, r;

  // create the radials

  for ( i = 0; i <= radials; i++ ) {

    v = ( i / radials ) * ( Math.PI * 2 );

    vertex.x = Math.sin( v ) * radius;
    vertex.y = 0;
    vertex.z = Math.cos( v ) * radius;

    vertices.push( 0, 0, 0 );
    vertices.push( vertex.x, vertex.y, vertex.z );

    colors.push( color1.r, color1.g, color1.b );
    colors.push( color1.r, color1.g, color1.b );

  }

  // create the circles

   for ( i = 0; i <= circles; i++ ) {

   r = radius - ( radius / circles * i )

      for ( j = 0; j < divisions; j ++ ) {

         // first vertex

         v = ( j / divisions ) * ( Math.PI * 2 );

         vertex.x = Math.sin( v ) * r;
         vertex.y = 0;
         vertex.z = Math.cos( v ) * r;

         vertices.push( vertex.x, vertex.y, vertex.z );
         colors.push( color2.r, color2.g, color2.b );

         // second vertex

         v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 );

         vertex.x = Math.sin( v ) * r;
         vertex.y = 0;
         vertex.z = Math.cos( v ) * r;

         vertices.push( vertex.x, vertex.y, vertex.z );
         colors.push( color2.r, color2.g, color2.b );

       }

   }

   var geometry = new THREE.BufferGeometry();
   geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
   geometry.addAttribute( 'color', new THREE.Float32Attribute( colors, 3 ) );

   var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );

   THREE.LineSegments.call( this, geometry, material );

}

CircleGridHelper.prototype = Object.create( THREE.LineSegments.prototype );
CircleGridHelper.prototype.constructor = CircleGridHelper;

Ok ok i added your repo as remote of mine, i will fork as soon as you create the branch.

@Mugen87 So... CircleGridHelper or PolarGridHelper ? And please, don't forget to change the default color before the prod ^^ It's hard for eyes !

Default colors are still 0x444444 and 0x888888 :wink:. I think i prefer PolarGridHelper.

Pretty awesome; my only thought is that you're forcing color1 to all radials and color2 to all circles. In my approach I just used the even/odd status of the loop's index to alternate between colors for both. Not sure if anyone else has a vote but I _personally_ prefer the look when they're flipped back and forth.

@Hectate You can change this behavior if you want. I like both approaches 馃憤

I had it in my mind that this would be helpful for non-grid stuff also.
capture
I like it!

@Mugen87 Here's the modification to alternate colors:
Demo

function CircleGridHelper( radius, radials, circles, divisions, color1, color2 ) {

    radius = radius || 10;
  radials = radials || 16;
  circles = circles || 8;
  divisions = divisions || 50;
  color1 = new THREE.Color( color1 !== undefined ? color1 : 0x444444 );
  color2 = new THREE.Color( color2 !== undefined ? color2 : 0x888888 );

  var vertices = [];
  var colors = [];

  var vertex = new THREE.Vector3();
  var v, i, j, r;

    // create the radials

  for ( i = 0; i <= radials; i++ ) {

        v = ( i / radials ) * ( Math.PI * 2 );

    vertex.x = Math.sin( v ) * radius;
    vertex.y = 0;
    vertex.z = Math.cos( v ) * radius;

    vertices.push( 0, 0, 0 );
    vertices.push( vertex.x, vertex.y, vertex.z );

    var color = i & 1 ? color1 : color2;

    colors.push( color.r, color.g, color.b );
    colors.push( color.r, color.g, color.b );

  }

  // create the circles

  for ( i = 0; i <= circles; i++ ) {

    var color = i & 1 ? color1 : color2;

    r = radius - ( radius / circles * i )

    for ( j = 0; j < divisions; j ++ ) {

        // first vertex

        v = ( j / divisions ) * ( Math.PI * 2 );

        vertex.x = Math.sin( v ) * r;
        vertex.y = 0;
        vertex.z = Math.cos( v ) * r;

        vertices.push( vertex.x, vertex.y, vertex.z );
      colors.push( color.r, color.g, color.b );

      // second vertex

      v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 );

        vertex.x = Math.sin( v ) * r;
        vertex.y = 0;
        vertex.z = Math.cos( v ) * r;

        vertices.push( vertex.x, vertex.y, vertex.z );
        colors.push( color.r, color.g, color.b );

    }

    }

    var geometry = new THREE.BufferGeometry();
    geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
    geometry.addAttribute( 'color', new THREE.Float32Attribute( colors, 3 ) );

    var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );

  THREE.LineSegments.call( this, geometry, material );

}

CircleGridHelper.prototype = Object.create( THREE.LineSegments.prototype );
CircleGridHelper.prototype.constructor = CircleGridHelper;

LGTM! I've only changed a little thing so we don't declare color twice.

see https://jsfiddle.net/bb5249pz/14/

I had it in my mind that this would be helpful for non-grid stuff also.

It would be good if keep the helpers as simple as possible. Let's see what @mrdoob says...

Oh yeah, good catch.

I agree that the helpers should be simple. What I meant was merely that by simply using a single radial and circle (as in that image) it could be useful for something else. In that example I was envisioning displaying a model's direction (and perhaps, their collision space) as in a game engine, since an animation might make it appear that it's "pointing" elsewhere. That would be up to the end-user to utilize it in that fashion, of course.

I think i prefer PolarGridHelper.

Agreed.

One possible change is having the grid helpers occupy the X-Y plane, as does PlaneGeometry. That way, lookAt() will work correctly. On the other hand, users can always apply geometry.rotateX() after instantiation and achieve the same effect. Personally, I would have preferred the former.

@WestLangley Unfortunately GridHelper was done using the X-Z plane already...

@WestLangley Unfortunately GridHelper was done using the X-Z plane already...

Yes, I know. I would have preferred otherwise, but as I pointed out, it's no big deal.

We don't even need vertex 馃槉

function CircleGridHelper( radius, radials, circles, divisions, color1, color2 ) {

    radius = radius || 10;
    radials = radials || 16;
    circles = circles || 8;
    divisions = divisions || 50;
    color1 = new THREE.Color( color1 !== undefined ? color1 : 0x444444 );
    color2 = new THREE.Color( color2 !== undefined ? color2 : 0x888888 );

    var vertices = [];
    var colors = [];

    var x, z;
    var v, i, j, r, color;

    // create the radials

    for ( i = 0; i <= radials; i++ ) {

        v = ( i / radials ) * ( Math.PI * 2 );

        x = Math.sin( v ) * radius;
        z = Math.cos( v ) * radius;

        vertices.push( 0, 0, 0 );
        vertices.push( x, 0, z );

        color = ( i & 1 ) ? color1 : color2;

        colors.push( color.r, color.g, color.b );
        colors.push( color.r, color.g, color.b );

    }

    // create the circles

    for ( i = 0; i <= circles; i++ ) {

        color = ( i & 1 ) ? color1 : color2;

        r = radius - ( radius / circles * i )

        for ( j = 0; j < divisions; j ++ ) {

            // first vertex

            v = ( j / divisions ) * ( Math.PI * 2 );

            x = Math.sin( v ) * r;
            z = Math.cos( v ) * r;

            vertices.push( x, 0, z );
            colors.push( color.r, color.g, color.b );

            // second vertex

            v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 );

            x = Math.sin( v ) * r;
            z = Math.cos( v ) * r;

            vertices.push( x, 0, z );
            colors.push( color.r, color.g, color.b );

        }

    }

    var geometry = new THREE.BufferGeometry();
    geometry.addAttribute( 'position', new THREE.Float32Attribute( vertices, 3 ) );
    geometry.addAttribute( 'color', new THREE.Float32Attribute( colors, 3 ) );

    var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );

    THREE.LineSegments.call( this, geometry, material );

}

CircleGridHelper.prototype = Object.create( THREE.LineSegments.prototype );
CircleGridHelper.prototype.constructor = CircleGridHelper;

@Hectate Would you like to do the PR for PolarGridHelper?

Sure, if you like. Will do in a few.

Ok PolarGridHelper seems to be ok for the pull request.
I close !

I liked the grid algorithm, but can u create it as a circular plane!

In the interests of not cluttering this old thread, I recommend you start a new one with more information on what you're trying to request.

Was this page helpful?
0 / 5 - 0 ratings