Why does dividing the faces of an Octahedron produce unequal faces?

I’m sure the answer is something peculiar to 3 dimensional geometry that I don’t understand but for the life of me, I can’t work out why this is the result my code is producing.

I start with an Octahedron produced by this code…

private void setTriangleTreeRoots() {        
    TriangleNode triangleNode0 = new TriangleNode();
    triangleNode0.setTypeNode(TypeNode.ROOT);
    triangleNode0.setAncestor(null);
    triangleNode0.setTriangle(new Triangle(
            new Vector3f(0f, sphere.getRadius(), 0f),
            new Vector3f(sphere.getRadius(), 0f, 0f),
            new Vector3f(0f, 0f, sphere.getRadius())));
    triangleNode0.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[0] = triangleNode0;        
    TriangleNode triangleNode1 = new TriangleNode();
    triangleNode1.setTypeNode(TypeNode.ROOT);
    triangleNode1.setAncestor(null);
    triangleNode1.setTriangle(new Triangle(
            new Vector3f(0f, sphere.getRadius(), 0f),
            new Vector3f(0f, 0f, sphere.getRadius()),
            new Vector3f(-sphere.getRadius(), 0f, 0f)));
    triangleNode1.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[1] = triangleNode1;        
    TriangleNode triangleNode2 = new TriangleNode();
    triangleNode2.setTypeNode(TypeNode.ROOT);
    triangleNode2.setAncestor(null);
    triangleNode2.setTriangle(new Triangle(
            new Vector3f(0f, sphere.getRadius(), 0f),
            new Vector3f(-sphere.getRadius(), 0f, 0f),
            new Vector3f(0f, 0f, -sphere.getRadius())));
    triangleNode2.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[2] = triangleNode2;        
    TriangleNode triangleNode3 = new TriangleNode();
    triangleNode3.setTypeNode(TypeNode.ROOT);
    triangleNode3.setAncestor(null);
    triangleNode3.setTriangle(new Triangle(
            new Vector3f(0f, sphere.getRadius(), 0f),
            new Vector3f(0f, 0f, -sphere.getRadius()),
            new Vector3f(sphere.getRadius(), 0f, 0f)));
    triangleNode3.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[3] = triangleNode3;        
    TriangleNode triangleNode4 = new TriangleNode();
    triangleNode4.setTypeNode(TypeNode.ROOT);
    triangleNode4.setAncestor(null);
    triangleNode4.setTriangle(new Triangle(
            new Vector3f(0f, -sphere.getRadius(), 0f),
            new Vector3f(0f, 0f, sphere.getRadius()),
            new Vector3f(sphere.getRadius(), 0f, 0f)));
    triangleNode4.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[4] = triangleNode4;        
    TriangleNode triangleNode5 = new TriangleNode();
    triangleNode5.setTypeNode(TypeNode.ROOT);
    triangleNode5.setAncestor(null);
    triangleNode5.setTriangle(new Triangle(
            new Vector3f(0f, -sphere.getRadius(), 0f),
            new Vector3f(sphere.getRadius(), 0f, 0f),
            new Vector3f(0f, 0f, -sphere.getRadius())));
    triangleNode5.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[5] = triangleNode5;        
    TriangleNode triangleNode6 = new TriangleNode();
    triangleNode6.setTypeNode(TypeNode.ROOT);
    triangleNode6.setAncestor(null);
    triangleNode6.setTriangle(new Triangle(
            new Vector3f(0f, -sphere.getRadius(), 0f),
            new Vector3f(0f, 0f, -sphere.getRadius()),
            new Vector3f(-sphere.getRadius(), 0f, 0f)));
    triangleNode6.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[6] = triangleNode6;        
    TriangleNode triangleNode7 = new TriangleNode();
    triangleNode7.setTypeNode(TypeNode.ROOT);
    triangleNode7.setAncestor(null);
    triangleNode7.setTriangle(new Triangle(
            new Vector3f(0f, -sphere.getRadius(), 0f),
            new Vector3f(-sphere.getRadius(), 0f, 0f),
            new Vector3f(0f, 0f, sphere.getRadius())));
    triangleNode7.setDescendents(new ArrayList<TriangleNode>());
    triangleTrees[7] = triangleNode7;    
}

and then I iterate through triangleTrees[] and apply a recursive method to sub-divide each face equally, limited by a predetermined ‘granularity’ value…

private void divideTriangleNode(int granularity, TriangleNode triangleNode) {        
    if(granularity == 0) {            
        triangleNode.setTypeNode(TypeNode.LEAF);            
        // IMPORTANT : add the vertices in reverse order
        vertices.add(triangleNode.getTriangle().get3());
        vertices.add(triangleNode.getTriangle().get2());
        vertices.add(triangleNode.getTriangle().get1());            
        addAreaToAreaList(calculateArea(triangleNode.getTriangle()));            
    } else {
        triangleNode.setTypeNode(TypeNode.BRANCH);
        Triangle[] triangles = new Triangle[4];
        Triangle triangle0 = new Triangle();
        triangle0.set1(triangleNode.getTriangle().get1());
        triangle0.set2(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get1(), triangleNode.getTriangle().get2()).normalize().mult(sphere.getRadius()));
        triangle0.set3(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get3(), triangleNode.getTriangle().get1()).normalize().mult(sphere.getRadius()));
        triangles[0] = triangle0;            
        Triangle triangle1 = new Triangle();
        triangle1.set1(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get1(), triangleNode.getTriangle().get2()).normalize().mult(sphere.getRadius()));
        triangle1.set2(triangleNode.getTriangle().get2());
        triangle1.set3(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get2(), triangleNode.getTriangle().get3()).normalize().mult(sphere.getRadius()));
        triangles[1] = triangle1;            
        Triangle triangle2 = new Triangle();
        triangle2.set1(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get3(), triangleNode.getTriangle().get1()).normalize().mult(sphere.getRadius()));
        triangle2.set2(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get2(), triangleNode.getTriangle().get3()).normalize().mult(sphere.getRadius()));
        triangle2.set3(triangleNode.getTriangle().get3());
        triangles[2] = triangle2;            
        Triangle triangle3 = new Triangle();
        triangle3.set1(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get1(), triangleNode.getTriangle().get2()).normalize().mult(sphere.getRadius()));
        triangle3.set2(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get2(), triangleNode.getTriangle().get3()).normalize().mult(sphere.getRadius()));
        triangle3.set3(FastMath.interpolateLinear(0.5f, triangleNode.getTriangle().get3(), triangleNode.getTriangle().get1()).normalize().mult(sphere.getRadius()));
        triangles[3] = triangle3;
        TriangleNode newTriangleNode;            
        for(Triangle triangle : triangles) {            
            newTriangleNode = new TriangleNode();
            newTriangleNode.setAncestor(triangleNode);
            newTriangleNode.setTriangle(triangle);
            newTriangleNode.setDescendents(new ArrayList<TriangleNode>());
            triangleNode.getDescendents().add(newTriangleNode);                
            divideTriangleNode(granularity-1,newTriangleNode);            
        }                
    }        
}

… the following function calculates the area of each triangle…

private double calculateArea(Triangle triangle) {
    double result;        
    float sideA = triangle.get1().distance(triangle.get2());
    float sideB = triangle.get2().distance(triangle.get3());
    float sideC = triangle.get3().distance(triangle.get1());
    float s = (sideA + sideB + sideC) / 2;
    double d = s * (s - sideA)*(s - sideB)*(s - sideC);
    result = Math.sqrt(d);        
    return result;
}

… and the following function calculates the minimum, maximum and average areas…

private void outputInformation() {
    Double smallest = (Double) Collections.min(areaList);
    Double largest = (Double) Collections.max(areaList);
    Double difference = largest - smallest;        
    System.out.println("Smallest area : "+smallest);
    System.out.println("largest area  : "+largest);
    System.out.println("Difference    : "+difference);        
}

Using a granularity of 0 produces the following…

Smallest area : 8660.254037844386
largest area : 8660.254037844386
Difference : 0.0

… which is unsurprising as no division has taken place and all faces of an Octahedron have equal area.

Using a granularity of 1 produces the following…

Smallest area : 2897.3548281147755
largest area : 4330.128173622577
Difference : 1432.7733455078019

… and this is what I am finding to be confusing as I expected all of the resulting faces to be equal in side length, angles and area but they are clearly not!

Is this a peculiarity of 3 dimensional geometry or an error in my thinking and resultant code?

Note - the resulting sphere renders perfectly at any granularity (for practicality I limit this to a maximum of 6) although at these higher levels of granularity the resultant wireframe mesh has visibly different sized faces - localised to the ‘root’ faces of the original Octahedron.

Presumably, you started with equilateral triangles. Each successive generation gets more skewed (because conjoined equilateral triangles must eventually lay flat so cannot make a sphere) and so certain triangles start to generate children that are different area (and shape) than their siblings… and it gets worse with each successive generation.

Edit: actually even in one subdivision you should be able to see this happening. The original edges will be longer than any of the new edges. When you subdivide those triangles the areas will be different in the children. (Scratch that as I realized you are dividing into four triangles… still the first part of what I said holds. Equilateral triangles must lay flat so they must not be equilateral.)

Sorry for the slow reply - I had my son staying with me over the weekend - and thanks for taking the time to reply.

I’m not disagreeing with your explanation, I just don’t understand how the ‘skewing’ is happening - I can’t picture it in my head. First world problems eh :wink:

I’ve altered my code to output the side lengths and angle values of each triangle processed by the recursive granularity method. I think I have written the side length and angle value code correctly but would appreciate fresh eyes on it as at the moment the initial eight equilateral triangles (1, 6, 11, 16, 21, 26, 31, 36) are displaying as having three internal angles of 90 degrees each! That can’t be right surely?

private void outputTriangleInfo(Triangle triangle) {        
    StringBuilder sb = new StringBuilder();
    sb.append("Triangle : ").append(triangleCounter).append("\n");
    sb.append("Side A length : ").append(triangle.get1().distance(triangle.get2())).append("\n");
    sb.append("Side B length : ").append(triangle.get2().distance(triangle.get3())).append("\n");
    sb.append("Side C length : ").append(triangle.get3().distance(triangle.get1())).append("\n");
    sb.append("Angle AB : ").append(triangle.get1().normalize().angleBetween(triangle.get2().normalize()) * 180 / FastMath.PI ).append("\n");
    sb.append("Angle BC : ").append(triangle.get2().normalize().angleBetween(triangle.get3().normalize()) * 180 / FastMath.PI ).append("\n");
    sb.append("Angle CA : ").append(triangle.get3().normalize().angleBetween(triangle.get1().normalize()) * 180 / FastMath.PI ).append("\n");
    sb.append("Area : ").append(calculateArea(triangle)).append("\n");
    System.out.println(sb.toString());
    triangleCounter++;        
}

I think the results show the skewing you are talking about (see below) and if the code I am using to work out the side lengths and angles is correct, then I am even more puzzled as to how this skewing happens as the descendant triangles are the result of:

  1. Find the halfway point of each side of the ancestor triangle.
  2. For each side, project a line from the centre of the ‘sphere’ (0,0,0) through the halfway point on a side for the length of the specified radius.
  3. Use the three original points of the ancestor triangle plus the three newly generated points to construct four new triangles.

What is confusing me is that each of the initial triangles is an equilateral - equal side lengths and angles - so when halving the side lengths and displacing the halfway point in the direction and length of the radius the resulting four new equilateral triangles should (according to my thinking) share the same side lengths and angles, plus their areas should be equal.

Assuming the code is correct then the results below illustrate how very wrong I am - but I just don’t understand what’s happening.

Triangle : 1
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 2
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 3
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 4
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 5
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 6
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 7
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 8
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 9
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 10
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 11
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 12
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 13
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 14
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 15
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 16
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 17
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 18
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 19
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 20
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 21
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 22
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 23
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 24
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 25
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 26
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 27
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 28
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 29
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 30
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 31
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 32
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 33
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 34
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 35
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

Triangle : 36
Side A length : 141.42136
Side B length : 141.42136
Side C length : 141.42136
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0
Area : 8660.254037844386

Triangle : 37
Side A length : 76.53669
Side B length : 100.00001
Side C length : 76.53669
Angle AB : 45.0
Angle BC : 60.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 38
Side A length : 76.53669
Side B length : 76.53669
Side C length : 100.00001
Angle AB : 45.0
Angle BC : 45.0
Angle CA : 60.0
Area : 2897.3548281147755

Triangle : 39
Side A length : 100.00001
Side B length : 76.53669
Side C length : 76.53669
Angle AB : 60.0
Angle BC : 45.0
Angle CA : 45.0
Area : 2897.3548281147755

Triangle : 40
Side A length : 100.00001
Side B length : 100.00001
Side C length : 100.00001
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 4330.128173622577

The bolded part is the issue… the projection produces edges of different length… it has to.

Imagine equilateral triangles laid… laid flat they can only form a flat grid (of hexagons if it helps). Yeah, so let’s take that hexagon as an example. If you want to make the hexagon fit a sphere then you have to push its center point out. As soon as you do that you’ve made the inner edges longer than the circumference edges.

In your case…

http://i.imgur.com/CFZGUC6.png

The green edges should end up being slightly longer than the blue ones. Those center points moved out from the center of the triangle when they were pushed out by the sphere.

I think I understand now.

If the initial Octahedron were to be flattened out onto one plane - say the xy plane - then each of the triangular faces would be equilateral as they would have equal sides and equal internal angles of 60 degrees.

Now the part that blows my mind is that if I then move the 0,0 point of the xy plane into the centre of one of the equilateral faces and then add a z axis and move the 0,0,0 point along say the negative z axis to a point where three lines (originating at this new point) connect to each corner of the equilateral face and have equal length, then I think this forms a three dimensional pyramid of equilateral triangles but each of the faces of this pyramid now have internal angles of 90 degrees each!

Moving from two dimensions into three dimensions and moving the origin has changed the angles in some weird way that I can sort of visualize now.

I think that part of what was confusing me was that every reference to a face of an Octahedron stated that it was an equilateral triangle having equal sides and internal angles of 60 degrees but that now seems to me to be a misleading statement. It’s true if each face is considered as being on its own two dimensional plane but as an Octahedron is a three dimensional shape in three dimensional space, applying two dimensional definitions is… well… misleading.

Thanks for your help @pspeed

It is. An octahedron is one of the few primitives that can be created with equilateral triangles. A tetrahedron being another. (If you see otherwise then you are looking at the wrong thing or your math is wrong.)

Imaging your hexagon again… 6 equilateral triangles. The only way to make it not flat and to still have equilateral triangles is to remove a triangle and fold to remove the empty space.

If you do that once, you have five triangles in a ring with a high point in the middle. I can’t remember for sure if this repeats over a sphere but I’m pretty sure it does (because I think you can make a sphere with a specific amount of pentagons, too).

If you remove another triangle from your hexagon now you have four equilateral triangles in a pyramid. This is half of an octahedron.

If you remove another triangle from your hexagon now you have three equilateral triangels in a 3-sides pyramid… add a bottom and its a tetrahedron. (4-sided die… roll for Mage’s max HP)

If you have to slide points to make a sphere then some edge lengthens more than some other… it can’t be otherwise unless you are making one of the convex primitives described.

Sorry to carry this on here but when I try to get help understanding this on maths forums, they say they don’t program using JME3 and so can’t comment on the code.

I decided to draw an Isoscelese right angled triangle, using points Vector3f(0,0,0), Vector3f(0,1,0) and Vector3f(1,0,0) and when I checked the side lengths and angles I got the following:

Triangle : 1
Side A length : 1.4142135
Side B length : 1.0
Side C length : 1.0
Angle AB : 90.0
Angle BC : 90.0
Angle CA : 90.0

Now the side lengths are correct but three internal 90 degree angles! Could you please have a look at the code I am using to calculate the internal angles of a triangle? It’s in my original post above. I’m expecting the following:

Angle AB : 45.0
Angle BC : 90.0
Angle CA : 45.0

i.e. the internal angles of an Isoscelese right-angled triangle drawn on a two dimensional plane.

I’m trying to understand this three dimensional geometry without pestering people on here but until I can be confident that the code I am using to calculate angles is correct, I’m sort of working in the dark.

It looks to me like you are calculating the angle between normalized positions instead of the angle between edges.

Hint: v1 is a vertex… v2.subtract(v1) is an edge.

Following your hint, I played around with the output code…

private void outputTriangleInfo(Triangle triangle) {        
    StringBuilder sb = new StringBuilder();
    sb.append("Triangle : ").append(triangleCounter).append("\n");
    sb.append("Side A length : ").append(triangle.get1().distance(triangle.get2())).append("\n");
    sb.append("Side B length : ").append(triangle.get2().distance(triangle.get3())).append("\n");
    sb.append("Side C length : ").append(triangle.get3().distance(triangle.get1())).append("\n");        

    Vector3f edgeAB = triangle.get1().subtract(triangle.get2());
    Vector3f edgeBC = triangle.get2().subtract(triangle.get3());
    Vector3f edgeCA = triangle.get3().subtract(triangle.get1());

    float angleAB = edgeAB.normalize().angleBetween(edgeBC.normalize());
    float angleBC = edgeBC.normalize().angleBetween(edgeCA.normalize());
    float angleCA = edgeCA.normalize().angleBetween(edgeAB.normalize());        

    sb.append("Angle AB : ").append(angleAB * 180 / FastMath.PI ).append("\n");
    sb.append("Angle BC : ").append(angleBC * 180 / FastMath.PI ).append("\n");
    sb.append("Angle CA : ").append(angleCA * 180 / FastMath.PI ).append("\n");        
    sb.append("Area : ").append(calculateArea(triangle)).append("\n");

    System.out.println(sb.toString());
    triangleCounter++;        
}

and applying it to the right angled Isoscelese triangle, I got…

Angle AB : 135.0
Angle BC : 90.0
Angle CA : 135.0

The 135 degrees threw me until I realised that 180 minus 45 is 135 so, I changed subtract to add and got…

Angle AB : 45.0
Angle BC : 90.0
Angle CA : 45.0

Which was great… until I ran the original Octahedron with granularity of 1 and got this…

Triangle : 1
Side A length : 1.4142135
Side B length : 1.4142135
Side C length : 1.4142135
Angle AB : 60.0
Angle BC : 60.0
Angle CA : 60.0
Area : 0.8660252317206331

Triangle : 2
Side A length : 0.76536685
Side B length : 0.99999994
Side C length : 0.76536685
Angle AB : 24.415161
Angle BC : 24.415161
Angle CA : 31.399704
Area : 0.28973537177088404

Triangle : 3
Side A length : 0.76536685
Side B length : 0.76536685
Side C length : 0.99999994
Angle AB : 31.399704
Angle BC : 24.415161
Angle CA : 24.415161
Area : 0.28973537177088404

Triangle : 4
Side A length : 0.99999994
Side B length : 0.76536685
Side C length : 0.76536685
Angle AB : 24.415161
Angle BC : 31.399704
Angle CA : 24.415161
Area : 0.28973537177088404

Triangle : 5
Side A length : 0.99999994
Side B length : 0.99999994
Side C length : 0.99999994
Angle AB : 33.557316
Angle BC : 33.557316
Angle CA : 33.557316
Area : 0.43301263306669846

Triangle 1 is the original face of the Octahedron and this has angles of 60 degrees, whereas the previous output code produced 90 degree angles! After reading up on non Euclidian geometry and Platonic shapes, I thought the angles had to be 90 degrees, not 60? This has confused me.

Triangles 2, 3 and 4 are the generated ‘north’, ‘south east’ and ‘south west’ faces - their internal angles don’t add up to 180! The general pattern of the side lengths and angles seems to be implying that these are right angled Isoscelese triangles in that they have two equal sides and one longer side, plus two equal angles plus one larger angle but (if they are right angled Isoscelese triangles) I would have expected the output code to describe the angles as two 45’s and a 90.

Similarly, Triangle 5 (the centre generated triangle) has angles that don’t add up to 180 but seems to be implying an equalateral triangle as all three sides are of equal length and all three angles are equal.

When I was at school many years ago and stuff like this started hurting my head, I left and joined a rock band. I can’t cop out so easily now at 50 years of age, so despite the aching brain I intend to persevere. I’m sure you are overjoyed :wink:

Triangles angles will ALWAYS add up to 180… so if they don’t then you did something wrong.

It doesn’t matter which way you rotate or stretch them. All three points always exist in a single plane and the angles always add to 180. Always.

I’m not disagreeing with you at all :smile:

I mentioned the non-euclidian equilateral triangle on a sphere (with three internal angles of 90 degrees adding up to 270) only because I stumbled across this while reading online in an effort to understand where my thinking is wrong and because my incorrect output code was giving me triangles with three internal 90 degree angles whilst I tried to construct a sphere out of many equilateral triangles in three dimensions.

I fully accept that attempting to create such a sphere results in the skewing you mentioned. I’m not trying to disprove anything. In my ignorance of the subject initially, I did think that this would work but it clearly doesn’t and that’s fine - in the process I have come to learn about Platonic solids and their properties.

I am very confused by the fact that the output code produces very different results when I apply it to a right angled Isoscelese triangle constructed on an xy plane and the three right angled Isoscelese triangles that result from the sub-division process.

Am I right in saying that JME3 calculates geometry in Euclidian space - that any triangle in JME3, no matter what its orientation, is handled on its own plane as described by its three Vector3f points? That was and still is the assumption I am working under.

Vector3f edgeAB = triangle.get1().add(triangle.get2());
Vector3f edgeBC = triangle.get2().add(triangle.get3());
Vector3f edgeCA = triangle.get3().add(triangle.get1());
float angleAB = edgeAB.normalize().angleBetween(edgeBC.normalize());
float angleBC = edgeBC.normalize().angleBetween(edgeCA.normalize());
float angleCA = edgeCA.normalize().angleBetween(edgeAB.normalize()); 

In the code above, have I understood and applied the hint you gave me for correctly calculating the angle between two edges of a triangle on the plane described by its three Vector3f points?

As a matter of fact, I’m highly into this topic (fooled around with icosahedron spheres in 2005 and again several times later). Also, I’m a pen-and-paper roleplayer and a computer scientist with background in computer graphics.

Yes, it’s 60-60-60 triangles (“equiliteral triangles”). If you ever saw a d8 (octahedron) or just imagine that all edges have same length - you will immediately understand.

  • it’s the same with all these Platonic bodies that use triangles (d4, d8, d20 - all use triangles). The other two (d6 and d12) use square and pentagon.

The skewing is something that I recognized very often. Pspeed pointed you to some way to understand it - you can’t use equiliteral triangles to cover a sphere - it will never happen.

I once made this observation: divide the area of the sphere by the number of subdividing triangles (i.e. first subdivision: 84 triangles, second subdivision: 84*4, etc.) and then calculate the edge length that an equiliteral triange must have - measure the edges you find and calculate the angular deviation by looking at the center of the sphere). You will quickly see that this deviation is quite huge even when using very many subdivisions.

I thought about that very often and also did some research on “radomes” (radar domes).
In the end it is very easy: There are only the 5 Platonic bodies - no other 3d shape will have same edge length for all 2d shapes on the surface. There are even mathematic-correct proves for this. Of course, there are some other shapes if you allow more than one shape (e.g. the telstar footbal - it’s a truncated icosahedron - or one of the Archimedian bodies) Adidas Telstar - Wikipedia

To be honest - I did not fully understand the possibilities of radomes (geodesic domes). Maybe there is a way to make a sphere have extremely small deviation from a perfect sphere - most techniques shift the vertex positions to “relax” the geodesic dome triangles.

@Ogli Hello :smile:

Yes it is a fascinating subject - I had never really considered how spheres and domes were constructed in real life. Although it hurts my tiny mind, it has prompted some interesting reading.

It’s funny that you and @pspeed both mentioned D&D die as examples for me to consider because I remember them from years ago. Some friends used to play D&D and I tried it a couple of times but I was more into Traveller. What prompted all of my foolish messing about with spheres was that I thought it might make an interesting project to code up the Traveller rules and when I was working on the star systems I had the (entirely futile it turns out) brainwave that by making a sphere out of equilateral triangles, I could have a ‘never ending’ world map, rather than using the Torus approach.

Well, these icospheres and octospheres are “beautiful” (especially the d8 and d20 can be aligned in such a way that they have a real north pole and south pole - and the d8 also has an ‘equator’). If you take a d20 instead of d8 you have more original triangles and thus the skewing should be reduced a little bit. Further, applying the “relaxing” might reduce the stretch (i.e. see the edges of the small triangles as springs having “spring forces” and do some iterations that shift the vertices a little bit on the surface - kinda like Blender does it when reducing UV stretch on unwrapped UV maps). Did you know that icosphere has been one of the Blender primitives since version (?) 2 or so? It’s one of the two built-in ways to make spheres in Blender.

I like to make little planets in such a way that you can see lines of lattitude and longitude - and thus quite easily align the patches to the geographic position. But I’m currently not yet making the game of my dreams (which would include planets). I’m making base technology now (perfect text library - for international chat).

Do you have a website for your space game? :chimpanzee_smile:

@Ogli I don’t have a website for it. I didn’t ever consider producing a game for people to play. It was more a case of using the rules as a ‘design specification’ and then finding best practise solutions to meet the specifications. My previous professional programming experience has been in website and web application development, so everything I have done has been based on satisfying someone else’s plan or spec. Aside from game programming being essentially a new area for me, having to create my own specification is also a new challenge - hence the idea of using Traveller rules. I was never very comfortable with that route though.

Todays thread and the mentioning of D&D die has made me consider something new (for me) which is what has always fascinated me is simplified simulated worlds. Given that I’m not interested in trying to recreate a highly realistic world, it dawned on me that (as you mentioned) a D20 shaped world could be very interesting to bring to life.

That’s a cool motivation. :chimpanzee_cool: I like working on my stuff too (and hope to earn money with it). Not having a boss is what every true artist is looking for. Well, maybe we will work together some day. But usually the jME community consists of several people following their dreams in their spare time (so cowork is difficult most of the time). However - I wish you luck and look forward to read more posts like this one. Enjoying it. Enjoying to see other people wanting to make similar things like me. :chimpanzee_smile:

For each vertex, you have to grab the sides that use that vertex as their origin. You can’t reuse edges or you get weird results relative to 90 degrees. If you always make sure to do the right edge directions then you don’t have to do any funny math.

ie: angle at a = angle between AB and AC where AB is b.subtract(a) and AC is c.subtract(a).

@pspeed Thank you for clarifying this for me. Now I have output code giving accurate angles and side lengths, I have been running the sphere builder and looking at the numbers. Now I can see the numbers, I can see skewing you highlighted and why it’s happening. Many thanks for your replies - I have learned so much from this ‘failure’ and from your comments plus all the background reading I have done on the subject.

@Ogli Glad you enjoyed the post. I’m one of those ‘several people following their dreams in their spare time’ and although working on a co-operative project would be rewarding in the future, I don’t think I have the skills or knowledge yet to contribute much. Do you have links to any of your ‘little worlds’ that you could share as I’d be interested to see what you have done?

Hm… here is a very old thing from 2005 / 2006: Sternenschwarm-Testseite
The website is very old and web 1.0 - as I don’t have time for that and am not much into websites. I plan to use Drupal or Typo3 for the next version (I soon will have a company and will make all new). The info found on my website is very old too (many things changed much during the last 10 / 15 years).

Many other projects slumber somewhere on my old hard drives (some things from 2005 and the years after). Many projects are quasi-lost (I made them with Qt and OpenGL and C++).