Jesus Sandro
Jesus Sandro

Reputation: 13

Having trouble generating a voxel sphere

I'm trying to generate the coordinates for a sphere with a given radius, but am only managing to produce a cylinder and I'm not really figuring out why. Here's my current code:

function makeSphere(radius){
          var sphere3D = {};

          var radiusX = radius + 0.5;
          var radiusY = radius + 0.5;
          var radiusZ = radius + 0.5;

          var invRadiusX = 1 / radiusX;
          var invRadiusY = 1 / radiusY;
          var invRadiusZ = 1 / radiusZ;

          var ceilRadiusX = Math.ceil(radiusX);
          var ceilRadiusY = Math.ceil(radiusY);
          var ceilRadiusZ = Math.ceil(radiusZ);

          var nextXn = 0;
          forX: for (var x = 0; x <= ceilRadiusX; ++x) {
            var xn = nextXn;
            nextXn = (x + 1) * invRadiusX;
            var nextYn = 0;
            forY: for (var y = 0; y <= ceilRadiusY; ++y) {
              var yn = nextYn;
              nextYn = (y + 1) * invRadiusY;
              var nextZn = 0;
              forZ: for (var z = 0; z <= ceilRadiusZ; ++z) {
                var zn = nextZn;
                nextZn = (z + 1) * invRadiusZ;

                var distanceSq = lengthSq(xn, yn, zn);
                if (distanceSq > 1) {
                    if (z == 0) {
                        if (y == 0) {
                            break forX;
                        }
                        break forY;
                    }
                    break forZ;
                }


                if (lengthSq(nextXn, yn, zn) <= 1 && lengthSq(xn, nextYn, zn) <= 1 && lengthSq(xn, yn, nextZn) <= 1) {
                    continue;
                }


                  sphere3D[[x,y,z]] = true;
                sphere3D[[-x,y,z]] = true;
                sphere3D[[x,-y,z]] = true;
                sphere3D[[x,y,-z]] = true;
                sphere3D[[-x,-y,z]] = true;
                sphere3D[[x,-y,-z]] = true;
                sphere3D[[-x,y,-z]] = true;
                sphere3D[[-x,-y,-z]] = true;

              }
            }
          }
        }


        function lengthSq(x, y, z) {
            return (x * x) + (y * y) + (z * z);
        }

        function lengthSq(x, z) {
            return (x * x) + (z * z);
        }

Which gives the following output.

Any ideas on where I'm messing up? Thanks in advance for your attention.

Upvotes: 1

Views: 157

Answers (2)

Patrick Roberts
Patrick Roberts

Reputation: 51916

Here's an approach that might be easier to follow. You'll want to break your code up into four parts:

  • Generating a set of points p within a particular n-dimensional domain
  • Filtering the set of points to those q that are within 1 unit of a spherical surface defined by a radius and n-dimensional origin
  • Reflecting the set of points across each of the Cartesian axes intersecting at the origin to create the reflected set of points r
  • Adding the set of points r to an object nSphere

Below is a set of functions that address each of these concerns to create an n-sphere.

// 0-sphere of radius 5 centered at [6]
console.log(makeNSphere(5, 6)); // { r: [6 - 5], [6 + 5] }
// 2-sphere of radius 2 centered at [0, 0, 0]
console.log(makeNSphere(2, 0, 0, 0));

function makeNSphere (radius, ...origin) {
  function onSurface (p) {
    const d = distance(
      p.map(
        (x, i) => x - origin[i]
      )
    );

    return Math.abs(d - radius) < 1;
  }

  const nSphere = {};
  const ps = range(
    ...origin.map(
      x => [x, x + radius + 1]
    )
  );
  const reflection = reflect(...origin);

  for (const q of where(ps, onSurface)) {
    for (const r of reflection(...q)) {
      nSphere[r] = true;
    }
  }

  return nSphere;
}

function distance (p) {
  let sum = 0;

  for (const x of p) {
    sum += x * x;
  }

  return Math.sqrt(sum);
}

function* range (constraints = [], ...rest) {
  const base = rest.length === 0;
  let begin = 0;
  let end = Infinity;
  let increment = 1;

  switch (constraints.length) {
    case 0: break;
    case 1: [end] = constraints; break;
    case 2: [begin, end] = constraints; break;
    default: [begin, end, increment] = constraints; break;
  }

  for (let i = begin; i < end; i += increment) {
    if (base) {
      yield [i];
      continue;
    }

    for (const a of range(...rest)) {
      yield [i, ...a];
    }
  }
}

function* where (ps, predicateFn) {
  for (const p of ps) {
    if (predicateFn(p)) {
      yield p;
    }
  }
}

function reflect (...axes) {
  return function* recurse (x, ...rest) {
    if (rest.length === 0) {
      yield* base(x);
      return;
    }

    for (const xs of recurse(...rest)) {
      yield* base(x, ...xs);
    }
  }

  function* base (x, ...rest) {
    yield [x, ...rest];

    const axis = axes[axes.length - rest.length - 1];
    const y = axis - (x - axis);

    if (x !== y) {
      yield [y, ...rest];
    }
  }
}

Upvotes: 1

John
John

Reputation: 3785

Not sure if this solves you problem but you can't have 2 functions having the same name. In your case, the second lengthSq() will supersede the first one even if the parameters are different.

There is no native function overloading in Javascript. However you can try these suggestions if it important to stick with same function name that handle multiple parameters Function overloading in Javascript - Best practices

The alternative is to rename it as as lengthSqXZ(x, z) if you are using it elsewhere outside the code you have provided.

Upvotes: 0

Related Questions