# Gimbals, quaternions, and manual rotation

**URL:** <https://discourse.processing.org/t/gimbals-quaternions-and-manual-rotation/17065>\
**Category:** Coding Questions\
**Created:** [January 13, 2020, 10:14am UTC](https://discourse.processing.org/t/gimbals-quaternions-and-manual-rotation/17065 "2020-01-13T10:14:06Z")\
**Posts on this page:** 1\
**Showing post:** 21

<div class="post-metadata">

**Author:** ![behreajj](https://avatars.discourse-cdn.com/v4/letter/b/b5a626/32.png) [@behreajj](https://discourse.processing.org/u/behreajj)\
**Post date:** [January 17, 2020, 2:13pm UTC](https://discourse.processing.org/t/gimbals-quaternions-and-manual-rotation/17065/21 "2020-01-17T14:13:24Z")

</div>

The long answer:

If a `PShape` is a `GROUP` that contains other `PShape`s, then you need to either get its children first, or try to tessellate it. `PShape`’s methods can be quite stingy, so I’ve found it’s better to store important shape data separately: namely, the transform matrix and a collection of vertices.

It’s possible for `getVertex` to return redundant information. Processing’s UV sphere is a good illustration of this: the same vertex at the north and south poles of a sphere is repeated for every longitude. If you know about `collections` you can use a `Set` to hold only unique instances of a vertex coordinate.

Another consideration: the vertices set when you create a shape are in object space. For example, in a sphere with a radius of 0.5, the north pole is at (0.0, 0.5, 0.0) and the soulth pole is at (0.0, -0.5, 0.0) . They will _not_ necessarily change as you translate, rotate or scale the shape in `draw`. To change from object space to to world space, you multiply a vertex with the shape’s transform.

![0287](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/2X/c/cab3b6c4036109ca8b86c1f92ed5e65250486783.png)

```auto
import java.util.*;

// A shape's points can be retrieved with getVertex . However, a shape
// may return multiple copies of the same point -- for example -- the
// same two points at the north and south pole of a sphere. A set
// prevents duplicates.
Set<PVector> vertices = new HashSet<PVector>();

// A shape's transform can only be indirectly set with resetMatrix
// and applyMatrix
PMatrix3D shapeMat = new PMatrix3D();

// To show how the screen function works, the renderer is needed.
PGraphics3D rndr;

// To avoid the sqrt incurred by calculating the distance,
// square the radius.
float radius = 3.0;
float radsq = radius * radius;

void setup() {
  size(256, 256, P3D);
  ellipseMode(RADIUS);
  perspective();
  camera(
    0.0, 0.0, height * 0.86602,
    0.0, 0.0, 0.0,
    0.0, 1.0, 0.0);

  // The number of latitudes and longitudes in a sphere,
  // even one stored in a PShape, is determined by sphereDetail .
  sphereDetail(27, 9);
  PShape shape = createShape(SPHERE, 0.5);

  // Acquire vertices from the PShape.
  int len = shape.getVertexCount();
  for (int i = 0; i < len; ++i) {
    vertices.add(shape.getVertex(i));
  }

  // The set has fewer vertices than the shape.
  println(len, vertices.size());

  rndr = (PGraphics3D)getGraphics();
  shapeMat.scale(200.0);
}

void draw() {
  shapeMat.rotate(0.002, 0.7071, 0.7071, 0.0);
  background(#fff7d5);

  // The vertex as transformed by shapeMat.
  PVector trvert = new PVector();

  // The transformed vertex in screen space.
  PVector scvert = new PVector();

  // The mouse input.
  PVector mouse = new PVector(mouseX, mouseY);

  // The difference from subtracting the mouse from scvert.
  PVector diff = new PVector();

  // Loop over all the vertices.
  for (PVector vert : vertices) {

    // Two transformations of the points: from object
    // space to world space, then to screen space.
    shapeMat.mult(vert, trvert);
    screen(rndr, trvert, scvert);

    // Find the distance between the mouse and the point.
    // Euclidean distance squared is used, not distance, to
    // avoid the square-root.
    PVector.sub(scvert, mouse, diff);
    float distsq = PVector.dot(diff, diff);

    // If the point is close by, draw red.
    // If not, black.
    if (distsq < radsq) {
      stroke(#ff2828);
      strokeWeight(10.0);
    } else {
      strokeWeight(4.0);
      stroke(#202020);
    }

    point(trvert.x, trvert.y, trvert.z);
  }
}

```

It may help to do some research on how `screen` and `model` functions work. I’ve never had any luck with those two functions, but I think others have worked on the issue you’re exploring now. This forum thread is one place to start [P3D Formula : opposite of screenX and screenY is ...? - #6 by Chrisir](https://discourse.processing.org/t/p3d-formula-opposite-of-screenx-and-screeny-is/10121/6) . The source code for `screen` is opaque; just know that – just like changing vertices from object space to world space above – multiplying points and transforms is involved.

[![](https://img.youtube.com/vi/o1n02xKP138/maxresdefault.jpg "Math for Game Programmers: Understanding Homogeneous Coordinates") ](https://www.youtube.com/watch?v=o1n02xKP138)

```auto
static Vec4 promote(PVector src, float w, Vec4 trg) {

  // Promote a 3D vector to a 4D vector.
  return trg.set(src.x, src.y, src.z, w);
}

static Vec4 mul(PMatrix3D m, Vec4 src, Vec4 trg) {

  // [ m00, m01, m02, m03, [ x, [ dot(m0c, x),
  // m10, m11, m12, m13, x y, := dot(m1c, y),
  // m20, m21, m22, m23, z, dot(m2c, z),
  // m30, m31, m32, m33 ] w ] dot(m3c, w) ]
  //
  // where 'c' is a matrix column index.

  return trg.set(
    m.m00 * src.x + m.m01 * src.y + m.m02 * src.z + m.m03 * src.w,
    m.m10 * src.x + m.m11 * src.y + m.m12 * src.z + m.m13 * src.w,
    m.m20 * src.x + m.m21 * src.y + m.m22 * src.z + m.m23 * src.w,
    m.m30 * src.x + m.m31 * src.y + m.m32 * src.z + m.m33 * src.w);
}

static PVector demote(Vec4 source, PVector target) {

  // Demote a homogenous coordinate to a point by dividing
  // the x, y and z components by w.
  if (source.w == 0.0) {
    return target.set(0.0, 0.0, 0.0);
  }
  float wInv = 1.0 / source.w;
  return target.set(
    source.x * wInv,
    source.y * wInv,
    source.z * wInv);
}

static PVector screen(
  PGraphicsOpenGL rndr,
  PVector src,
  PVector trg) {

  Vec4 point = promote(src, 1.0, new Vec4());

  // Multiply by the modelview, then by the projection.
  Vec4 mvmul = mul(rndr.modelview, point, new Vec4());
  Vec4 projmul = mul(rndr.projection, mvmul, new Vec4());

  demote(projmul, trg);

  // Shift range from [-1.0, 1.0] to [0.0, 1.0] .
  trg.add(1.0, 1.0, 1.0).mult(0.5);  

  // Multiply by screen dimensions.
  trg.x *= rndr.width;
  trg.y *= rndr.height;

  // Flip y-axis because Processing.
  trg.y = rndr.height - trg.y;
  return trg;
}

static class Vec4 {
  float x, y, z, w;

  Vec4() { }

  Vec4(float x, float y, float z, float w) {
    set(x, y, z, w);
  }

  Vec4 set(float x, float y, float z, float w) {
    this.x = x; this.y = y; this.z = z; this.w = w;
    return this;
  }
}

```

Regards,

---

_[View the full topic](https://discourse.processing.org/t/gimbals-quaternions-and-manual-rotation/17065)._
