I have written some JS code to plot a 3D wireframe sphere into a HTML5 canvas.
I started from this post and improved it by using Qt3D vertices generation for a sphere mesh. The JS code does 2 passes on the vertices: the first to display rings, and the second to display slices. Normally OpenGL would connect all the vertices automatically with triangles.
I kept the slices/rings configurable but I have issues with the transformation code, for example when I rotate the sphere along the X axis.
So, starting from the basics. Here's a 1-pass, 4 rings, 4 slices, no transformation:
Seems all good. Now 2-passes, 10 rings, 10 slices, no transformation:
Still good, but if I rotate it 30° on the X Axis, the top and bottom vertices (Y position only apparently) get messed up.
I suspect there is something wrong in the rotation functions, or in the projection function.
Can someone please help me to figure out what's going on here ?
(Note that I don't want to use Three.js cause my goal is to port this in a QML application)
Here's the full code.
var sphere = new Sphere3D();
var rotation = new Point3D();
var distance = 1000;
var lastX = -1;
var lastY = -1;
function Point3D() {
  this.x = 0;
  this.y = 0;
  this.z = 0;
}
function Sphere3D(radius) {
  this.vertices = new Array();
  this.radius = (typeof(radius) == "undefined" || typeof(radius) != "number") ? 20.0 : radius;
  this.rings = 10;
  this.slices = 10;
  this.numberOfVertices = 0;
  var M_PI_2 = Math.PI / 2;
  var dTheta = (Math.PI * 2) / this.slices;
  var dPhi = Math.PI / this.rings;
  // Iterate over latitudes (rings)
  for (var lat = 0; lat < this.rings + 1; ++lat) {
    var phi = M_PI_2 - lat * dPhi;
    var cosPhi = Math.cos(phi);
    var sinPhi = Math.sin(phi);
    // Iterate over longitudes (slices)
    for (var lon = 0; lon < this.slices + 1; ++lon) {
      var theta = lon * dTheta;
      var cosTheta = Math.cos(theta);
      var sinTheta = Math.sin(theta);
      p = this.vertices[this.numberOfVertices] = new Point3D();
      p.x = this.radius * cosTheta * cosPhi;
      p.y = this.radius * sinPhi;
      p.z = this.radius * sinTheta * cosPhi;
      this.numberOfVertices++;
    }
  }
}
function rotateX(point, radians) {
  var y = point.y;
  point.y = (y * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
  point.z = (y * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateY(point, radians) {
  var x = point.x;
  point.x = (x * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
  point.z = (x * Math.sin(radians)) + (point.z * Math.cos(radians));
}
function rotateZ(point, radians) {
  var x = point.x;
  point.x = (x * Math.cos(radians)) + (point.y * Math.sin(radians) * -1.0);
  point.y = (x * Math.sin(radians)) + (point.y * Math.cos(radians));
}
function projection(xy, z, xyOffset, zOffset, distance) {
  return ((distance * xy) / (z - zOffset)) + xyOffset;
}
function strokeSegment(index, ctx, width, height) {
  var x, y;
  var p = sphere.vertices[index];
  rotateX(p, rotation.x);
  rotateY(p, rotation.y);
  rotateZ(p, rotation.z);
  x = projection(p.x, p.z, width / 2.0, 100.0, distance);
  y = projection(p.y, p.z, height / 2.0, 100.0, distance);
  if (lastX == -1 && lastY == -1) {
    lastX = x;
    lastY = y;
    return;
  }
  if (x >= 0 && x < width && y >= 0 && y < height) {
    if (p.z < 0) {
      ctx.strokeStyle = "gray";
    } else {
      ctx.strokeStyle = "white";
    }
    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.stroke();
    ctx.closePath();
    lastX = x;
    lastY = y;
  }
}
function render() {
  var canvas = document.getElementById("sphere3d");
  var width = canvas.getAttribute("width");
  var height = canvas.getAttribute("height");
  var ctx = canvas.getContext('2d');
  var p = new Point3D();
  ctx.fillStyle = "black";
  ctx.clearRect(0, 0, width, height);
  ctx.fillRect(0, 0, width, height);
  // draw each vertex to get the first sphere skeleton
  for (i = 0; i < sphere.numberOfVertices; i++) {
    strokeSegment(i, ctx, width, height);
  }
  // now walk through rings to draw the slices
  for (i = 0; i < sphere.slices + 1; i++) {
    for (var j = 0; j < sphere.rings + 1; j++) {
      strokeSegment(i + (j * (sphere.slices + 1)), ctx, width, height);
    }
  }
}
function init() {
  rotation.x = Math.PI / 6;
  render();
}canvas {
  background: black;
  display: block;
}<body onLoad="init();">
  <canvas id="sphere3d" width="500" height="500">
    Your browser does not support HTML5 canvas.
  </canvas>
</body>


 
     
    