Ringsegment function leads to strange behaviour

I’m new to three.js and I wanted to create a Shape (special ringsegment with some space) manually in a function. see code below. My attempt is the following:

  1. my function takes 4 arguments (inner radius, outer radius, the segment angle, the space)
  2. i create a shape (circle segment)
  3. i create a hole-path, that will be removed.

My function worked well when I set the angle to 25°. But when I raise the angle up to 30° -> the Hole is not removed anymore? The size of the space has the same effect… if i set the space too small , the hole is not removed as well. Has someone an idea, why this happens? Or another approach to get to my goal? ringsegment

    Toolbox.prototype.createCircleElement = function(inner, outer, angle, space) {
	var radians = THREE.Math.degToRad(angle); //angle in radians
	var spaceAngleO = Math.asin( space / outer ); //difference of
	var spaceAngleI = Math.asin( space / inner );
	var xCenter = space / Math.tan( radians / 2 );
	var radiusDiff = Math.sqrt(Math.pow(space, 2) + Math.pow(xCenter, 2));
	var yspaceO = 0.5*Math.sqrt( Math.pow(2*outer*Math.sin(spaceAngleO), 2) - Math.pow(space, 2) ); 
	var yspaceI = 0.5*Math.sqrt( Math.pow(2*inner*Math.sin(spaceAngleI), 2) - Math.pow(space, 2) );

	//fill shape
	var buttonShape = new THREE.Shape();
    buttonShape.moveTo(xCenter, space);
    buttonShape.lineTo(outer - yspaceO, space);
    buttonShape.arc(-outer + xCenter + yspaceO, 0, outer - radiusDiff, 0, radians, false);
    buttonShape.lineTo(xCenter, space);

    //inner ring to remove
    var buttonHolePath = new THREE.Path();
    buttonHolePath.moveTo(xCenter, space);
    buttonHolePath.lineTo(inner - yspaceI, space);
    buttonHolePath.arc(-inner + xCenter + yspaceI, 0, inner - radiusDiff, 0, radians, false);
    buttonHolePath.lineTo(xCenter, space);


    buttonShape.holes.push(buttonHolePath);      

    var geometry = new THREE.ShapeGeometry(buttonShape);
    var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    var mesh = new THREE.Mesh(geometry, material);
    return mesh;

Here are three examples (two of them fail):

    var butn = vrToolbox.createCircleElement(1.5, 4, 45, 0.14); //did not work... space too small
    var butn2 = vrToolbox.createCircleElement(1.5, 4, 45, 0.15); //ok
    var butn3 = vrToolbox.createCircleElement(1.5, 4, 30, 0.15); //did not work angle to large

Why not just using RingBufferGeometry?

In any event, I suggest you have a look at the code of the class and maybe use it for your own implementation. The analytical approach of RingBufferGeometry is more efficient than using Shapes since they need to be triangulated first before rendering.

Thank you! I will try to create the mesh with a BufferGeometry analytical. But I still want to find out, why the Shape-attempt did not work in some particular cases. Can this emerge from math-rounding-errors?

I don’t think so. There might be a problem in the way you calculate the parameter for the method calls of Shape. Have you written this code based on a reference or documentation you can share?

Ok, It’s not based on a particular reference… I calculated the paramters using trigonometric functions.
I found it just strange, that in some cases the holeShape isn’t removed. Did somebody know under which conditions a hole won’t be removed? Can that occur, when some points of the hole lay outside of the Shape?
@Mugen87: Thanks for your advice to take the analytical way, this is very good to learn some additional Stuff:)

Hi!
Just out of curiousity, why do you want to use a hole instead of creating a shape of segment with two arcs?

My first attempt was exactly as you say. But I had no clue, how to create a path.arc that was koncave. It filled always the hole circle-segment.

Just an option:

var angle = 45;

var shape = new THREE.Shape();
shape.absarc(-5, 0, 10, 0, THREE.Math.degToRad(angle), false); // CCW - outer
shape.absarc(-5, 0, 5, THREE.Math.degToRad(angle), 0, true); // CW - inner

var shapeGeom = new THREE.ShapeGeometry(shape);
var shapeMat = new THREE.MeshBasicMaterial({color: 0x00ff00});
var shapeMesh = new THREE.Mesh(shapeGeom, shapeMat);
scene.add(shapeMesh);

But, if got your scheme correctly, you’re looking for something like this:

var angle = 45;
var Ro = 10;
var Ri = 5;
var center = new THREE.Vector2(0, 0);
var shift = 0.5; // "space" on your scheme, though, it would be better to call it "offset" here instead of "shift"

var Xo = getX(Ro, shift); // x-value for a point on the outer radius
var Po1 = new THREE.Vector2( Xo,  shift);  // find the first point on the outer radius
var Po2 = new THREE.Vector2( Xo, -shift).rotateAround(center, THREE.Math.degToRad(angle)); // find the second point on the outer radius
var Ao1 = Po1.angle(); // find the angle for the first point
var Ao2 = Po2.angle(); // find the angle for the second point

// the same actions for the point on the inner radius
var Xi = getX(Ri, shift);
var Pi1 = new THREE.Vector2( Xi,  shift);
var Pi2 = new THREE.Vector2( Xi, -shift).rotateAround(center, THREE.Math.degToRad(angle));
var Ai1 = Pi1.angle();
var Ai2 = Pi2.angle();

// gather all the things we did before to create a shape
var shape2 = new THREE.Shape();
shape2.absarc(center.x, center.y, Ro, Ao1, Ao2, false); // CCW - outer
shape2.absarc(center.x, center.y, Ri, Ai2, Ai1, true); // CW - inner

var shapeGeom2 = new THREE.ShapeGeometry(shape2);
var shapeMat2 = new THREE.MeshBasicMaterial({color: 0xffff00});
var shapeMesh2 = new THREE.Mesh(shapeGeom2, shapeMat2);
scene.add(shapeMesh2);

// helper function
function getX(radius, shift){
  return Math.sqrt(radius * radius - shift * shift);
}

2 Likes

Wow, this solution works perfect and does exactly what I want it to do:) Thank you @prisoner849!!!
I will add some checks to assure, that the shift doesn’t exceed the “width” of the circlesegment.

1 Like

You’re welcome :beers: :wink:

I created an analytical method to accomplish the spacedRingSegment. It’s based on the RingBufferGeometry.js (ringBufferGeometry.js). My approach calculates the startAngle, endAngle, Anglestep (thetaStartRel, thetaEndRel, segmentStep) anew for each phiSegment. I leaved out the possibility to set a startAngle, simply because I did not need it fo my objective. Improvements are always welcome:)

   spacedRingSegmentBufferGeometry = function(innerRadius, outerRadius, thetaSegments, phiSegments, thetaLength, space) {

    if((Math.sin((thetaLength) / 2) * innerRadius) < space) {
        var err = "Cannot create spacedRingSegment due to geometrical inconsitency: inner Radius or angle too small OR Space too large";
        throw new Error(err);
    }

    var thetaSegments = thetaSegments;
    var phiSegments = phiSegments;
    var thetaLength = thetaLength;
    var innerRadius = innerRadius;
    var outerRadius = outerRadius;
    var space = space;

    this.type = 'RingBufferGeometry';

    this.parameters = {
        innerRadius: innerRadius,
        outerRadius: outerRadius,
        thetaSegments: thetaSegments,
        phiSegments: phiSegments,
        thetaLength: thetaLength,
        space: space
    };

    innerRadius = innerRadius || 0.5;
    outerRadius = outerRadius || 1;

    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;

    thetaSegments = thetaSegments !== undefined ? Math.max(3, thetaSegments) : 8;
    phiSegments = phiSegments !== undefined ? Math.max(1, phiSegments) : 1;

    space = space || 0;

    // buffers
    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // some helper variables
    var segment;
    var radius = innerRadius;
    var radiusStep = ((outerRadius - innerRadius) / phiSegments);
    var segmentStep; 
    var thetaStartRel;
    var thetaEndRel;
    var vertex = new THREE.Vector3();
    var uv = new THREE.Vector2();
    var j, i;

    // generate vertices, normals and uvs
    for (j = 0; j <= phiSegments; j++) {

        thetaStartRel = Math.atan( space / (radius));
        thetaEndRel = thetaLength - thetaStartRel;

        segmentStep = (thetaEndRel - thetaStartRel) / thetaSegments;
        segment = thetaStartRel;

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

            vertex.x = Math.cos(segment) * radius;
            vertex.y = Math.sin(segment) * radius;

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

            // normal
            normals.push(0, 0, 1);

            // uv
            uv.x = (vertex.x / outerRadius + 1) / 2;
            uv.y = (vertex.y / outerRadius + 1) / 2;

            uvs.push(uv.x, uv.y);

            segment += segmentStep;
        }

        // increase the radius for next row of vertices
        radius += radiusStep;

    }

    // indices

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

        var thetaSegmentLevel = j * (thetaSegments + 1);

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

            segment = i + thetaSegmentLevel;

            var a = segment;
            var b = segment + thetaSegments + 1;
            var c = segment + thetaSegments + 2;
            var d = segment + 1;

            // faces

            indices.push(a, b, d);
            indices.push(b, c, d);

        }

    }

    // build geometry
    this.setIndex(indices);
    this.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    this.addAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
    this.addAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
}

//inheritance from Buffergeometry
spacedRingSegmentBufferGeometry.prototype = new THREE.BufferGeometry();

Would be great to have a live code example :slight_smile: