Git Product home page Git Product logo

joml's Introduction

JOML – Java OpenGL Math Library

A Java math library for OpenGL rendering calculations | use it on: Desktop / Android / GWT

Build and Publish Maven Release Maven Snapshot

Design goals

The goal of JOML [ʤˈɒml̩] is to provide easy-to-use, feature-rich and efficient linear algebra operations, needed by any 3D application. At the same time, JOML tries to pose the lowest possible requirements to an execution environment by being compatible with Java 1.4 and not making use of JNI.

If you like to know more about JOML's design, see the corresponding Wiki page.

Vector arithmetic

All operations in JOML are designed to modify the object on which the operation is invoked. This helps in completely eliminating any object allocations, which the client could otherwise not control and which impact the GC performance resulting in small hickups. The client is responsible to allocate the needed working objects.

Vector3f v = new Vector3f(0.0f, 1.0f, 0.0f);
Vector3f a = new Vector3f(1.0f, 0.0f, 0.0f);
// v = v + a
v.add(a);
// a = a x v
a.cross(v);
// a = a/|a|
a.normalize();

Matrix API

Using JOML you can build matrices out of basic transformations, such as scale, translate and rotate, using a fluent interface style. All such operations directly modify the matrix instance on which they are invoked. The following example builds a transformation matrix which effectively first scales all axes by 0.5 and then translates x by 2.0:

Vector3f v = ...;
new Matrix4f().translate(2.0f, 0.0f, 0.0f)
              .scale(0.5f)
              .transformPosition(v);
// v is now transformed by the specified transformation

Common transformation idioms, such as rotating about a given axis using a specific rotation center, can be expressed in a simple way. The following example rotates the point (0, 4, 4) about the x-axis and uses (0, 3, 4) as the rotation center:

Vector3f center = new Vector3f(0.0f, 3.0f, 4.0f);
Vector3f pointToRotate = new Vector3f(0.0f, 4.0f, 4.0f);
new Matrix4f().translate(center)
              .rotate((float) Math.toRadians(90.0f), 1.0f, 0.0f, 0.0f)
              .translate(center.negate())
              .transformPosition(pointToRotate);

The vector pointToRotate will now represent (0, 3, 5).

Post-multiplication

All transformation operations in the matrix and quaternion classes act in the same way as OpenGL and GLU by post-multiplying the operation's result to the object on which they are invoked. This allows to chain multiple transformations in the same way as with OpenGL's legacy matrix stack operations, and allows to decompose the resulting effective matrix as a decomposition of multiple matrix multiplications. One such common decomposition are the projection and modelview matrices, written as: P * MV. The modelview matrix of course can be further decomposed into individual matrix multiplications, as far as this seems necessary.

When invoking transformation methods in JOML's matrix classes, a convenient way now is to think of Java's dot operator as a matrix multiplication. If multiple matrix operations are chained after one another, as shown in the above example, each individual operation/method creates its matrix which is then post-multiplied to the matrices built before.

In addition to the post-multiplying methods, there are still ways to set a matrix or quaternion to a given transformation regardless of what that matrix or quaternion was before:

Matrix4f m = new Matrix4f();
Vector3f point = new Vector3f(1.0f, 2.0f, 3.0f);
Vector3f offset = new Vector3f(1.0f, 0.0f, 0.0f);
...
m.translation(offset).transformPosition(point);

In the above example, the matrix m is being set to a translation, instead of applying the translation to it. These methods are useful when the same matrix is being used in a sequence of consecutive operations or repeatedly in a loop without having to set it to the identity each time.

Building a camera transformation

In the same way that you can concatenate multiple simple affine transformations, you can use the methods perspective(), frustum() and ortho() to specify a perspective or orthographic projection and lookAt() to create an orthonormal transformation that mimics a camera looking at a given point. Those methods resemble the ones known from GLU and act in the same way (i.e. they apply their transformations to an already existing transformation):

Matrix4f m = new Matrix4f()
     .perspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f)
     .lookAt(0.0f, 0.0f, 10.0f,
             0.0f, 0.0f, 0.0f,
             0.0f, 1.0f, 0.0f);
// the camera transformation is now in m

The above transformation can then be used as a "view-projection" matrix in a shader.

By convention, the methods perspective() and lookAt() will assume a right-handed coordinate system. This convention was established for OpenGL and first realized in the OpenGL Utility Library (GLU). JOML follows this convention.

In addition, JOML also supports a left-handed coordinate system, as is used by Direct3D's matrix library. To use a left-handed coordinate system, there are the methods perspectiveLH() and lookAtLH(), as well as others, whose names end with LH:

Matrix4f m = new Matrix4f()
     .perspectiveLH((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f)
     .lookAtLH(0.0f, 0.0f, -10.0f,
               0.0f, 0.0f, 0.0f,
               0.0f, 1.0f, 0.0f);

Computation result

Usually, instance methods operate on the object (matrix, vector, quaternion) on which they are invoked by writing the computation result back into that object. Most of the methods however also allow to specify another destination object to write the result into. This is useful if you do not want to overwrite the original object with the computation result. This can be useful for computing the view-projection matrix and its inverse in one go:

Matrix4f viewProj = new Matrix4f();
Matrix4f invViewProj = new Matrix4f();
viewProj.perspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f)
        .lookAt(0.0f, 1.0f, 3.0f,
                0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f)
        .invert(invViewProj);

The invViewProj matrix now contains the inverse of the viewProj matrix, but the latter is still intact.

Using with LWJGL

JOML can be used together with LWJGL to build a transformation matrix and set it as a uniform mat4 in a shader. For this, the Matrix4f class provides a method to transfer a matrix into a Java NIO FloatBuffer, which can then be used by LWJGL when calling into OpenGL:

try (MemoryStack stack = MemoryStack.stackPush()) {
  FloatBuffer fb = new Matrix4f()
    .perspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f)
    .lookAt(0.0f, 0.0f, 10.0f,
            0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f)
    .get(stack.mallocFloat(16));
  glUniformMatrix4fv(mat4Location, false, fb);
}

The above example first creates a transformation matrix and then uploads that matrix to a uniform variable of the active shader program using the LWJGL 3 method glUniformMatrix4fv.

Also please read Memory Management in LWJGL 3 on how to properly manage native memory that JOML will store the matrices to.

Instead of using the uniform methods, one or multiple matrices can also be uploaded to an OpenGL buffer object and then sourced from that buffer object from within a shader when used as an uniform buffer object or a shader storage buffer object. The following uploads a matrix to an OpenGL buffer object which can then be used as an uniform buffer object in a shader:

Matrix4f m = ...; // <- the matrix to upload
int ubo = ...; // <- name of a created and already initialized UBO
try (MemoryStack stack = MemoryStack.stackPush()) {
  glBindBuffer(GL_UNIFORM_BUFFER, ubo);
  glBufferSubData(GL_UNIFORM_BUFFER, 0, m.get(stack.mallocFloat(16)));
}

If you prefer not to use shaders but the fixed-function pipeline and want to use JOML to build the transformation matrices, you can do so. Instead of uploading the matrix as a shader uniform you can then use the OpenGL API call glLoadMatrixf() provided by LWJGL to set a JOML matrix as the current matrix in OpenGL's matrix stack:

Matrix4f m = new Matrix4f();
m.setPerspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f);
glMatrixMode(GL_PROJECTION);
try (MemoryStack stack = MemoryStack.stackPush()) {
  glLoadMatrixf(m.get(stack.mallocFloat(16)));
}
m.setLookAt(0.0f, 0.0f, 10.0f,
            0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f);
glMatrixMode(GL_MODELVIEW);
try (MemoryStack stack = MemoryStack.stackPush()) {
  glLoadMatrixf(m.get(stack.mallocFloat(16)));
}

Using with Vulkan and LWJGL 3

You can use VK10.vkMapMemory() provided by LWJGL to map a Vulkan memory object, which may be the backing store of a Uniform Buffer, into Java and use the returned Java NIO ByteBuffer to upload the matrix like you would with OpenGL by calling get() on the matrix:

Matrix4f m = ...;
VkDevice device = ...; // <- the vulkan device
long memory = ...; // <- handle to the vulkan device memory
try (MemoryStack stack = MemoryStack.stackPush()) {
  PointerBuffer pb = stack.mallocPointer(1);
  if (vkMapMemory(device, memory, 0, 16 << 2, 0, pb) == VK_SUCCESS) {
    m.get(MemoryUtil.memByteBuffer(pb.get(0), 16 << 2));
    vkUnmapMemory(device, memory);
  }
}

Since Vulkan uses a clip space z range between 0 <= z <= w you need to tell JOML about it when creating a projection matrix. For this, the projection methods on the Matrix4f class have an additional overload taking a boolean parameter to indicate whether Z should be within [0..1] like in Vulkan or [-1..+1] like in OpenGL. The existing method overload without that parameter will default to OpenGL behaviour.

Alternatively, you can use Vulkan's Push Constants to quickly upload a matrix in a shader push-constant when recording a command buffer. The following code updates a Matrix4f used as a push-constant in the vertex shader:

Matrix4f m = ...;
VkCommandBuffer cmdBuf = ...; // <- the VkCommandBuffer
long layout = ...; // <- handle to a Vulkan VkPipelineLayout
try (MemoryStack stack = MemoryStack.stackPush()) {
  vkCmdPushConstants(cmdBuf, layout, VK_SHADER_STAGE_VERTEX_BIT,
                     0, m.get(stack.mallocFloat(16)));
}

Also, care must be taken regarding the difference between Vulkan's viewport transformation on the one side and Direct3D's and OpenGL's different viewport transformation on the other side. Since Vulkan does not perform any inversion of the Y-axis from NDC to window coordinates, NDC space and clip space will have its +Y axis pointing downwards (with regard to the screen). In order to account for this, you need to use a premultiplied scaling transformation that inverts the Y-axis.

In essence, to create a projection transformation which will work with Vulkan, use the following code:

Matrix4f m = new Matrix4f();
m.scale(1.0f, -1.0f, 1.0f) // <- inversion of Y axis
 .perspective((float) Math.toRadians(45.0f),
               1.0f, 0.01f, 100.0f, true); // <- true indicates Z in [0..1]

Using with JOGL

JOML can be used together with JOGL to build a transformation matrix and set it as a uniform mat4 in a shader (for example as a replacement of com.jogamp.opengl.util.glsl.fixedfunc.FixedFuncUtil and com.jogamp.opengl.util.PMVMatrix to emulate the fixed pipeline). For this, the Matrix4f class provides a method to transfer a matrix into a Java NIO FloatBuffer, which can then be used by JOGL when calling into OpenGL:

FloatBuffer fb = Buffers.newDirectFloatBuffer(16);
new Matrix4f().perspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f)
              .lookAt(0.0f, 0.0f, 10.0f,
                      0.0f, 0.0f, 0.0f,
                      0.0f, 1.0f, 0.0f).get(fb);
gl.glUniformMatrix4fv(mat4Location, 1, false, fb);

The above example first creates a transformation matrix and then uploads that matrix to a uniform variable of the active shader program using the JOGL 2 method glUniformMatrix4fv.

If you prefer not to use shaders but the fixed-function pipeline and want to use JOML to build the transformation matrices, you can do so. Instead of uploading the matrix as a shader uniform you can then use the OpenGL API call glLoadMatrixf() provided by JOGL to set a JOML matrix as the current matrix in OpenGL's matrix stack:

FloatBuffer fb = Buffers.newDirectFloatBuffer(16);
Matrix4f m = new Matrix4f();
m.setPerspective((float) Math.toRadians(45.0f), 1.0f, 0.01f, 100.0f);
gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
gl.glLoadMatrixf(m.get(fb));
m.setLookAt(0.0f, 0.0f, 10.0f,
            0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f);
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
gl.glLoadMatrixf(m.get(fb));

Using with Java 2D

Java 2D holds the transformation applied to drawn primitives in an instance of the AffineTransform class, which can be manipulated directly or operated on via methods on the Graphics2D class, such as rotate() or translate(). Instead of the AffineTransformation, JOML's matrix classes can be used to build the transformations. When a matrix has been built in JOML, its represented transformation can be copied to an AffineTransform instance like so:

Matrix4f m = ...; // <- any affine 2D transformation built with JOML
AffineTransform t = ...; // <- any Java 2D AffineTransform (e.g. Graphics2D.getTransform())
t.setTransform(m.m00(), m.m01(), m.m10(), m.m11(), m.m30(), m.m31());

The above will copy any affine 2D transformation, represented by the Matrix4f, to the provided AffineTransform instance.

Since Java 2D cannot represent 3D transformations, using a Matrix4f in JOML is not necessary. For 2D transformations JOML provides the Matrix3x2f and Matrix3x2d classes. If those are used, copying the 2D transformation into a Java 2D AffineTransform instance works like this:

Matrix3x2f m = ...; // <- any 2D transformation built with JOML
AffineTransform t = ...; // <- any Java 2D AffineTransform
t.setTransform(m.m00, m.m01, m.m10, m.m11, m.m20, m.m21);

(Note: The AffineTransform class uses a different order for the row and column indices of a matrix element. Here, the row index comes first, and then the column index)

Using with JavaFX

JavaFX holds arbitrary affine transformations in an instance of the Affine class. Instead of operating on Affine objects, JOML's matrix classes can be used to build the transformations. When a matrix has been built in JOML, its represented transformation can be copied to an Affine instance like so:

Matrix4f m = ...; // <- any affine transformation built with JOML
Affine t = ...; // <- any JavaFX Affine instance
t.setToTransform(m.m00(), m.m10(), m.m20(), m.m30(),
                 m.m01(), m.m11(), m.m21(), m.m31(),
                 m.m02(), m.m12(), m.m22(), m.m32());

The above will copy any affine transformation, represented by the Matrix4f, to the provided Affine instance.

(Note: The Affine class uses a different order for the row and column indices of a matrix element. Here, the row index comes first, and then the column index)

Staying allocation-free

JOML is designed to be completely allocation-free for all methods. That means JOML will never allocate Java objects on the heap unless you as the client specifically requests to do so via the new keyword when creating a new matrix or vector or calling the toString() method on them.

JOML also does not allocate any unexpected internal helper/temporary/working objects itself, neither in instance nor static fields, thus giving you full control over object allocations.

Since you have to create a matrix or a vector at some point in order to make any computations with JOML on them, you are advised to do so once at the initialization of your program. Those objects will then be the working memory/objects for JOML. These working objects can then be reused in the hot path of your application without incurring any additional allocations. The following example shows a typical usecase with LWJGL:

FloatBuffer fb;
Matrix4f m;

void init() {
  fb = MemoryUtil.memAllocFloat(16);
  m = new Matrix4f();
  ...
}
void destroy() {
  MemoryUtil.memFree(fb);
}

void frame() {
  ...
  // compute view-projection matrix
  m.identity()
   .perspective((float) Math.toRadians(45.0f), (float)width/height, 0.01f, 100.0f)
   .lookAt(0.0f, 0.0f, 10.0f,
           0.0f, 0.0f, 0.0f,
           0.0f, 1.0f, 0.0f);
  // possibly apply more model transformations
  m.rotateY(angle);
  // get matrix into FloatBuffer and upload to OpenGL
  glUniformMatrix4fv(mat4Location, false, m.get(fb));
  ...
}

In the example above, a single Matrix4f is allocated during some initialization time when the init() method is called. Then each frame() we reinitialize the same matrix with the identity() and recompute the camera transformation based on some other parameters.

Read-only Views

In more complex applications with multiple API providers and consumers/clients, it is sometimes desirable to communicate the intent that a certain object returned by an API call or consumed by a call should not get modified by the caller or the callee, respectively. This can be helpful to encourage efficient memory usage by sharing objects and to discourage/disallow unintended object mutations.

For this, JOML provides read-only views on each class, realized via a Java interface implemented by each class. This interface only contains methods that will not mutate the object on which it is called.

private Vector4f sharedVector;
public Vector4fc getVector() {
  return sharedVector;
}
public void consume(Vector4fc v) {
  // ...
}

Using read-only views, it is possible to declare who is responsible of mutating an object and who is not allowed to.

Multithreading

Due to JOML not using any internal temporary objects during any computations, you can use JOML in a multithreaded application. You only need to make sure not to call a method modifying the same matrix or vector from two different threads. Other than that, there is no internal or external synchronization necessary.

Matrix stack

JOML also features an interface that resembles the matrix stack from legacy OpenGL. This allows you to use all of the legacy OpenGL matrix stack operations even in modern OpenGL applications, but without the otherwise necessary JNI calls into the graphics driver. Note that JOML does not interface in any way with the OpenGL API. It merely provides matrix and vector arithmetics.

Matrix4fStack s = new Matrix4fStack(2);
s.translate(2.0f, 0.0f, 0.0f);
s.pushMatrix();
{
  s.scale(0.5f, 0.5f, 0.5f);
  // do something with the Matrix4f of 's'
}
s.popMatrix();
s.rotate((float) Math.toRadians(45.0f), 0.0f, 0.0f, 1.0f);
// do something with the Matrix4f of 's'

Literature

This section contains a list of books/tutorials/articles featuring JOML.

joml's People

Contributors

0x000006 avatar bcdaughe avatar earthcomputer avatar fortressbuilder avatar heisluft avatar httpdigest avatar huhlig avatar ieperen3039 avatar immortius avatar jordan4ibanez avatar jt122406 avatar kblaszczyk avatar kerai avatar leacoleaco avatar mfg92 avatar mohamed-abdelhameed avatar mxi avatar nicbomb avatar physiix avatar pollend avatar ps-soundwave avatar ra4king avatar rgreenlees avatar squid233 avatar sriharshachilakapati avatar tpietzsch avatar tushgm avatar xenoamess avatar zeroerrors avatar zmrodriguez91 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

joml's Issues

Wrong quaternion conjugate method

The conjugate() methods in Quaternion and QuaternionD seem to give wrong results.

public static void conjugate(Quaternion a, Quaternion dest) {
    dest.x = a.x;
    dest.y = -a.y;
    dest.z = -a.z;
    dest.w = -a.w;
}

As far as I know, the conjugate of a quaternion is the quaternion with the inverse vector component and the same real component. In this case, x, y and z are the components of the vector component, and w is the real component.
Therefore, it should rather be like this:

public static void conjugate(Quaternion a, Quaternion dest) {
    dest.x = -a.x;
    dest.y = -a.y;
    dest.z = -a.z;
    dest.w = a.w;
}

The same also applies for Quaternion conjugate() and the same methods in QuaternionD.

Using JOML types as constants/immutability

I heard about JOML today, and as I'm always on the look out for a good math library have been looking over it.

I guess my one concern (and this was an issue with vecmath too) is that there is no safe way to have a constant of any of the JOML types. Because all the class variables are public they are always mutable. So something like:

public static final Vector3f UP = new Vector3f(0, 1, 0);

Is always at risk of unintentional (or malicious) modification. (public variables also prevent injecting byte code to intercept value changes as that requires the use of setters, although that is not an issue for me as such).

Possible approaches to address, in order of ascending complexity:

1. Completely separate immutable types

This could be done outside of JOML - having a separate class of each type that is immutable, and can produce the JOML class as needed.

Downside is object allocation.

2. Make class variables private, add accessor methods

something like

private float x, y, z;

public float x() {
  return x;
}

public void x(float value) {
  x = value;
}

This would allow a subclass to prevent changes by throwing exceptions on usage of method that would modify the class. The subclasses would still be usable in expected ways though such as v.add(UP).

Downside is possibility of accidental leakage of immutable types into mutable context, resulting in a crash down the track when an attempt is made to mutate them. At least that is an obvious issues as opposed to the accidental alteration of a constant which can be hard to debug.

3. Read-only base interface

A 'trick' which I came up with that was used by the TeraMath library:

First, you have an interface that provides the non-modifying methods of the type:

public interface Vector3fConst {
   float x();
   float y();
   float z();

   float length();
}

(this could also be an abstract class, with methods like length implemented in terms of the variable accessor methods)

then you have the mutable implementation

public final class Vector3f implements Vector3fConst {
   public float x, y, z;

   public Vector3f set(Vector3fConst other) {
      x = other.x();
      y = other.y();
      z = other.z();
      return this;
   }  
}

This provides a few benefits:

  • Method signatures clearly indicate whether they mutate a vector argument. If you send a Vector3f to the set method, you know it will not be modified.
  • It is possible to implement a true immutable vector to guarantee that the user won't just cast the value to the mutable implementation. This won't have a bunch of mutating methods that throw exceptions.
  • Also helps prevent accidental sharing of values between contexts. If you have the method
void setEntityPosition(Vector3f newPosition)

There is always the worry that it does

position = newPosition;

instead of the desired

position.set(newPosition);

With the signature

void setEntityPosition(Vector3fConst other);

you know even if it is shared it won't be modified at least.

Downside is the method calls required access the values, the extra complexity of the class model, and without the actual immutable type it is only a soft guarantee of immutability. Also it is sort of dragging C++ behaviors into Java, which may be confusing.

Anyway, just wanted to share these thoughts and ideas. I don't know whether these sorts of concerns are within the scope of JOML or not.

JAR files

Do you think we could have the .jar files back on the releases please? It would be great to just drop it into the project!

Using JOML with Gradle

Because JOML uses maven central and sonatype is easy to use gradle with it now so it might be a good idea to have the information on how to use it with gradle in the wiki or readme pages.

The whole thing looks like this when only using JOML as an extra dependency using the latest version of JOML

repositories {
    mavenCentral()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

project.ext.jomlVersion = "+" 

dependencies {
    compile "org.joml:joml:${jomlVersion}"
}

i probably left something out so ask or tell me if there is

Have all get*(...) methods return the object that is being set

For example Matrix4f.get(ByteBuffer) should return ByteBuffer, not Matrix4f.
I can implement this if a consensus is reached 👍

A good argument for this is that it compresses 3 lines of code into 1, as well as eliminate the need for a named local variable. From this:

ByteBuffer buffer = getBufferMagically()
myMatrix.get(buffer);
glUniformMatrix4fv(myMatrixUniform, false, buffer);

Into this:

glUniformMatrix4fv(myMatrixUniform, false, myMatrix.get(getBufferMagically()));

This way, what is happening here is easy to understand in 1 line as all I need to see is everything up to myMatrix, ignoring everything after and including the .get. Thus I can see that myMatrix is being set at the value of myMatrixUniform.

'set' variants of 'rotate' methods.

Hi! It seems 'rotate' functions dont have a 'set' variant like other methods (setPerspective, setLookAt, setTranslation, etc). They all multiply the existing matrix.

I'm mostly interested in a 'set' variant for 'rotateXYZ' mostly, but I see there are other variations.

GLM support

Hi,

I am really looking forward to use joml in all my projects. Most of them are ports of C/C++ codes, where glm is intensively used.

Would it be possible to integrate in joml (some of) the glm functions?

To have an idea what I am talking about you can see this or this

Move the foreign library utils to a separate package

I've been looking over your library and I love your work, brilliant.

But I copied the source to give it a try and I noticed how you handled the problem of the LWJGL, JAVAX, and GDX vector libraries... It's okay, but I propose to move these utilities to separate packages such as "GDXVectorUtil" and "LWJGLVectorUtil", this would allow you to have a companion library for converting these 'foreign vectors'.

Of course this is no big issue, but it does allow you to allocate this solution to a specific part of your library, and not have random mentions to non-existent libraries. It's really just a design suggestion, I had the idea earlier and figured someone would be interested, as I'm too lazy to code it myself.

Add normal() to Matrix4 and Matrix3

This method calculates a normal matrix based on the current value of this.
In general the normal matrix will be the transpose of the inverse of this if non-uniform scaling is applied and can just be the transpose if only uniform scaling is applied.

Better formatting without allocations

The JOML readme says

JOML also does not allocate any unexpected internal helper/temporary/working objects itself, neither in instance nor static fields, thus giving you full control over object allocations.

However, in some classes like Matrix4f, toString() creates a new DecimalFormatter, which is against this policy.

So, the solutions:

  1. Move the DecimalFormatter to a static field and allow the user to set it with static setFormatter(DecimalFormatter). However, it would still have to be initialized once so that toString() works even when the user doesn't explicitly set a formatter.
  2. Use String.format(String, Object...) instead to format the values.
    • Let the user set the precision needed with a static method, and/or add an overloaded toString(int significantDigits) method.

Solution 2 is probably the better.

Additionally, some classes like Vector3f don't use formatting at all. This should be done in all classes for consistency.

Provide a simple yet complete example with LWJGL 3

Based off the simple "Getting Started" demo on the LWJGL 3 website, create a fully working and complete example with projection and view transformation using JOML.

This should provide the following:

  • a simple getting started demo with JOML and LWJGL 3 to have a working program when starting with JOML
  • show how to stay allocation-free with JOML in a render loop

Use radians instead of degrees

In commit a17139f some methods were changed to return the angle in degrees instead of radians. However, I think it would be better to use radians instead of degrees with JOML. A lot of precision is lost when having to convert this angle back to radians if one needs to use the result with Math.sin() or similar functions. This is also an issue when "piping" different methods together. For example, by using Vector3f.angle() results as arguments to Matrix4f.perspective(), there is a conversion from radians to degrees in Vector3f.angle() and back to radians in Matrix4f.perspective().

I know the reason for using degrees is that it is "more user-friendly". However, I don't think it would be asking too much from users to explicitly call Math.toDegrees() and Math.toRadians() if they want to use degrees. JOML is a library for using with OpenGL, which means performance and complete control over the code have a greater priority than usability. Users who want an easy-to-use library should use a framework like JME or LibGDX.

tiny lookAt improvement

in https://github.com/JOML-CI/JOML/blob/master/src/org/joml/Matrix4f.java#L5065 , you can reuse the already calculated values, so

        float dirX, dirY, dirZ;
        dirX = centerX - eyeX;
        dirY = centerY - eyeY;
        dirZ = centerZ - eyeZ;
        // Normalize direction
        float invDirLength = 1.0f / (float) Math.sqrt(
                  (centerX - eyeX) * (centerX - eyeX)
                + (centerY - eyeY) * (centerY - eyeY)
                + (centerZ - eyeZ) * (centerZ - eyeZ));

can become

        float dirX, dirY, dirZ;
        dirX = centerX - eyeX;
        dirY = centerY - eyeY;
        dirZ = centerZ - eyeZ;
        // Normalize direction
        float invDirLength = 1.0f / (float) Math.sqrt( dirX * dirX + dirY * dirY + dirZ * dirZ );

Java 9 SIMD and GPU support

Hello , I have looked your source codes.
They are using old technique for Java.
You can add SIMD native java 9.
Java 9 has IntStream that uses simd

IntStream.iterate(17, i -> i % 2 == 1 ? 3 * i + 1 : i / 2)
.takeWhile(i -> i > 1)
.forEach(System.out::println);

1 public static void testForEach() {
2
3 // Vector addition
4
5 int[] a = new int[SIZE];
6 int[] b = new int[SIZE];
7 int[] c = new int[SIZE];
8
9 Arrays.fill(a, 10);
10 Arrays.fill(b, 20);
11
12 IntStream.range(0, a.length)
13 .parallel()
14 .forEach( id -> c[id] = a[id] + b[id]);
15
16 long end = System.nanoTime();
17
18 System.out.println(Arrays.toString(c));
19 }

http://snatverk.blogspot.com.tr/2014/05/java-streams-in-jdk8-examples.html

Investigate SSE JIT support for JOML

Investigate the possibility to use native code (SIMD instructions specifically) for a sequence of Matrix4f operations. This would probably allow matrix operations to be executed faster.

What is the motivation behind this?
Increase performance by making use of SIMD instructions for matrix operations.

Limitations with the JVM
Any native code, that a high-performance library such as JOML wants to use, will suffer tremendously from JNI's latency. An example is libGDX, which implements its matrix methods as native code and because of this is 2x slower than JOML.

Possible solution
The only way to get rid of the JNI overhead is to hide it. That means, a JNI function must do enough work faster than Java code could do it. Instead of providing JNI methods for each of the individual JOML matrix functions, we would need to batch/record/journal as many of the matrix operations as possible as they are invoked by client code on a Matrix4f instance.
A typical usecase of JOML could be: m.perspective(...).lookAt(...).mul(matrix).invert().get(fb).
Of course this fluent interface style of writing the operations is purely syntactical sugar, but it illustrates that for this enhancement to possibly succeed, there must be a static sequence of Matrix4f method calls in the client code that can be recorded as they happen for further batch-processing later on.
Journaling and lazily evaluating those operations works because:

  • a) all Matrix4f methods operate on this,
  • b) most of the Matrix4f methods actually do not allow their effects to be visible by client code, other than the client code actually reading the Matrix4f fields in-between those operations, and
  • c) the most likely usecase of JOML is that of a modern OpenGL application where the client does not want to transform Vector4f instances by a matrix but do some sequence of matrix transformations and finally upload the resulting matrix to OpenGL to be used by the fixed-function or the shader pipeline.

The methods that do allow to inspect the state of an operation, such as Matrix4f.get(FloatBuffer) would need to be declared as terminal operations.
There, the batching/journaling of matrix operations on a particular Matrix4f instance would need to end.

What happens after journaling is ended?
After client code executed a sequence of matrix operations on a particular Matrix4f object for the first time and then invoked a terminal operation, that Matrix4f instance needs to change its state from not terminated to terminated.
Additionally the recorded operations in the journal are now being read and translated to native code, using some templated SSE code for each operation.
After that termination happened, subsequent invocations of the same sequence of methods on that Matrix4f instance will not journal those operations anymore but instead use the JIT'ted native function to evaluate the whole sequence with one native function call at once.

How do we access actual arguments?
The actual arguments to the sequenced matrix operations can be buffered into a direct ByteBuffer held by the Matrix4f instance. Once the terminal operation is executed, this ByteBuffer is being used by the JIT'ted code to read off all actual arguments.
The JIT'ted native code would need to know the offsets of course for that to happen, which are written during the code generation when the terminal operation is executed the first time.

How do we store the matrix elements?
Accessing the 16 float fields would be too slow. We should store those fields directly in off-heap memory accessible as float* by the native code. The native code would therefore need to have a float[16] array for each terminated Matrix4f instance to still allow multithreading.
The cost is that client code would therefore not be able to inspect the state of a Matrix4f anymore by accessing the individual float fields.

Problems with this approach
Since we introduce state, impose usage restrictions and also generate native code using operating system functionality, which is not lightweight, there is a high potential for misuse of JOML, which will likely lead to high memory usage and poor overall performance. This can be because client code allocates new Matrix4f objects for each frame or because there are multiple terminal operations executed on the same Matrix4f object.

Restrictions

  • how to resolve calling multiple terminated sequences on the same matrix?
  • how to ensure that always the same sequence of operations is called on a given matrix? (i.e. client code with branches calling different matrix methods on different conditions is problematic)

Corner points:

Bug in maxComponent on Vector3f /3d

The maxComponent method of the Vector class has an implementation error.

maxComponent on a vector like Vector(3,1,2) will return 2.

if (xy && yz) { // false, because 1 < 2
return 0;
} else if (yz) { // false, because 1 < 2
return 1;
}
return 2;

BTW: A minComponent would be great too.

GWT Support?

Right now, I'm thinking of having GWT support in JOML, I'm working on a WebGL wrapper (WebGL4J) for GWT and would like to have some support on integrating JOML with it. I think that is possible by adding a source JAR export, and a simple joml.gwt.xml file that defines the GWT module (Not used in non GWT apps).

Do you think we can add a new variation of this library?

Rename AngleAxis4f back to AxisAngle4f

We named it from AxisAngle4f to AngleAxis4f originally, since the angle comes first in usual OpenGL and math methods. But "AxisAngle" is the more common term here.

Add JOGL migration guide

Add an article in the Wiki explaining how to convert from JOGL's vecmath math classes to JOML.
This will help people switching from JOGL's to LWJGL 3 math classes to using JOML.

What about a generalized set/get row/column in the matrix classes?

Generalized getter and setter methods for matrix classes, for example

Vector4f v4 = ...;
Vector3f v3 = ...;

Matrix4f m4 = ...;
Matrix4f m3 = ...;

float x=1, y=2, z=3, w=4, s=1;
int index = 0;

m3.setRow(index, v4); // is the equivalent of m3.setRow(index, v4.x, v4.y, v4.z)
m3.setRow(index, v3); // is the equivalent of m3.setRow(index, v3.x, v3.y, v3.z)
m3.setRow(index, s); // is the equivalent of m3.setRow(index, s, s, s)
m3.setRow(index, x, y, z);
m3.setColumn(index, v4); // is the equivalent of m3.setColumn(index, v4.x, v4.y, v4.z)
m3.setColumn(index, v3); // is the equivalent of m3.setColumn(index, v3.x, v3.y, v3.z)
m3.setColumn(index, s); // is the equivalent of m3.setColumn(index, s, s, s)
m3.setColumn(index, x, y, z);

m4.setRow(index, v4); // is the equivalent of m3.setRow(index, v4.x, v4.y, v4.z, v4.w)
m4.setRow(index, v3); // this could be equivalent to call m4.setRow(index, v3.x, v3.y, v3.z, 1)
m4.setRow(index, s); // is the equivalent of m4.setRow(index, s, s, s, s)
m4.setRow(index, x, y, z, w);
m4.setColumn(index, v4); // is the equivalent of m3.setColumn(index, v4.x, v4.y, v4.z, v4.w)
m4.setColumn(index, v3); // this could be equivalent to call m4.setColumn(index, v3.x, v3.y, v3.z, 1)
m4.setColumn(index, s); // is the equivalent of m4.setColumn(index, s, s, s)
m4.setColumn(index, x, y, z);

the implementation could look like this

public void setRow(int row, float x, float y, float z) {
    switch (row) {
    case 0:
        m00 = x;
        m10 = y;
        m20 = z;
        break;
    case 1:
        m01 = x;
        m11 = y;
        m21 = z;
        break;
    case 2:
        m02 = x;
        m12 = y;
        m22 = z;
        break;
    default:
        throw new IndexOutOfBoundsException();
    }
}

and this

public void setColumn(int row, float x, float y, float z) {
    switch (row) {
    case 0:
        m00 = x;
        m01 = y;
        m02 = z;
        break;
    case 1:
        m10 = x;
        m11 = y;
        m11 = z;
        break;
    case 2:
        m20 = x;
        m21 = y;
        m22 = z;
        break;
    default:
        throw new IndexOutOfBoundsException();
    }
}

I understand that Matrix4f have getColumn and getRow at this time, but they accept Vector4f only, adding support for Vector3f could be useful if we discard the w element (sometimes we only need the rotation axes for example).

"safe" normalize variant for vectors.

Hi! I have no immediate need for this but it might come in handy for some people:

public final Vector3f safeNormalize() // Or 'normalizeSafe'
{
  final float sqrdLen = lengthSquared();
  // If length is zero, normalize would produce NaNs.
  // If it is 1.0, then its not necessary to normalize.
  if ( sqrdLen != 0.0f && sqrdLen != 1.0f )
  {
    final float invLen = 1.0f / Math.sqrt(sqrdLen);
    this.x *= invLen;
    this.y *= invLen;
    this.z *= invLen;
  }
  return this;
}

Speeding up the calculations with lookup tables (+ other simplifications)

Hey,

have you ever thought about speeding up the calculation functions such as sine or atan2 using lookup tables or some cool hacks/simplifications? Calculations may be much faster at the expense of accuracy (which is practically invisible) AND you can avoid frequent casting to float (because the java-math that uses doubles everywhere :( ).

These techniques work well in libraries like libGDX (MathUtils class).

Rotation() result divergence

Matrix4f.rotation(1f, 10f, 0f, 0f) returns m00 = 46.51007. glm returns instead m00 == 1

probabily because a normalization is missing

Add a "Coming from LWJGL 2" article

This article should be a migration guide for LWJGL 2 util "vector package" users to JOML.
The article should explain the differences and commonalities of how LWJGL 2 compared to JOML handles vecmath and how to convert LWJGL 2 vecmath code to use JOML.

Shorter naming scheme?

Hi! First and foremost, its great work what y'all are doing here 😄

To the point, have you considered shorter naming scheme for all types?

Things like

  • Vec3d instead of Vector3d
  • Mat4f instead of Matrix4f
  • Quatf instead of Quaternionf

And so on... What do you think? Its a bit closer to what you see in GLSL 😄

Fix bug in lookAt and lookAlong

There is a very severe bug in all lookAt and lookAlong (including their set* variants) methods in Matrix4 and Matrix3, which causes the x-column of the resulting matrix to not be a unit vector, and thus the whole 3x3 matrix to not be an orthonormal basis, which it MUST be.

Geometry primitives

Hi!

I spent a bunch of time googling around for geometry libs for Java. You know, things with intersection and containment tests between a bunch of geometric primitives (squares, triangles, circles, etc).

Most of them suffer of excessive object orientation (ie, Triangle class of three Point objects, that kind of thing), use all doubles for their computations (excessive for games), non permissive licences (GPL and company), and so on.

These things are the basis of spatial structures, and are also kinda hard to implement under the OOP umbrella since it is a combinatory explosion of optimized methods between specific types (ie, circle contains square isn't the same test as square contains circle, intersection tests are specialized between kinds of shapes, etc).

Ideally there should be a couple of primitives supported:

2D

  • Triangle
  • Rectangle
  • Circle

3D

  • Frustum
  • Axis aligned box
  • Sphere

With operations like shape.contains(otherShape) and shape.intersects(otherShape).

With these all normal spatial structures should be possible I believe. Octree of axis aligned box, sphere tree, frustum culling, etc.

I can contribute a bunch of the 2D implementations, but honestly math isn't my forté.

Incorrect formula in Quaternionf.get(Matrix4)

The formula for Quaternionf.get(Matrix4) is incorrect, you are actually generating a RHS matrix (the one that DirectX uses). You have to transpose it to get the correct matrix for use with OpenGL. This is what you are using.

dest.m00 = 1.0f - q11 - q22;
dest.m01 = q01 + q23;
dest.m02 = q02 - q13;
dest.m03 = 0.0f;
dest.m10 = q01 - q23;
dest.m11 = 1.0f - q22 - q00;
dest.m12 = q12 + q03;
dest.m13 = 0.0f;
dest.m20 = q02 + q13;
dest.m21 = q12 - q03;
dest.m22 = 1.0f - q11 - q00;
dest.m30 = 0.0f;
dest.m31 = 0.0f;
dest.m32 = 0.0f;
dest.m33 = 1.0f;

The correction is that you have to swap the operators - and + between operands for m01 m02 m10 m12 m20 m21 according to the LHS matrix formula found here: https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion

I will probably make a PR soon.

Theese features could be useful

I am changing my application code from using my custom library to use JOML instead since they both have very similar design desitions and I think theese simple functions could be useful:

Adding a scaled Vector3f to another Vector3f
Adding a scaled Vector4f to another Vector4f

Here is an example of their usage in my game —force, linearAcceleration, velocity, position, t and inverseMass are a floats

// linear integration
linearAcceleration.add(force, inverseMass);
velocity.add(linearAcceleration, t);
position.add(velocity, t);

That is equivalent to

// linear integration
linearAcceleration.x += force.x * inverseMass;
linearAcceleration.y += force.y * inverseMass;
linearAcceleration.z += force.z * inverseMass;
velocity.x += linearAcceleration.x * t;
velocity.y += linearAcceleration.y * t;
velocity.z += linearAcceleration.z * t;
position.x += velocity.x * t;
position.y += velocity.y * t;
position.z += velocity.z * t;

Add fma to Quaternion classes

Given that a Quaternion is used to represent orientations (...) and a Vector3 is used to represent rotations, can we have a method to add scaled rotations to a quaternion? —like the fma of vectors, by the way, what fma stands for, what does it mean?

// current way to add a rotation to a orientation
private static final void addRotationToOrientation(final Quaternionf orientation, final Vector3f rotation, final float dt) {
    Quaternionf q = new Quaternionf(rotation.x * dt, rotation.y * dt, rotation.z * dt, 0);
    q.mul(orientation);
    orientation.w += q.w * 0.5f;
    orientation.x += q.x * 0.5f;
    orientation.y += q.y * 0.5f;
    orientation.z += q.z * 0.5f;
}

// desired way
orientation.fma(dt, rotation);

Rename Quaternion classes

To go in line with the rest of JOML's class namings, do the following:

  • rename Quaternion to Quaternionf
  • rename QuaternionD to Quaterniond

Matrix4f.perspective() not working

The Matrix4f.setPerspective() and Matrix4f.perspective() methods don't work.

Expected output (with fov = 70, aspectRatio = 1, zNear = 0.1, zFar = 100):

7.849E-1  0.000E+0  0.000E+0  0.000E+0
0.000E+0  1.428E+0  0.000E+0  0.000E+0
0.000E+0  0.000E+0  1.000E+0 -2.000E-3
0.000E+0  0.000E+0  1.000E+0  0.000E+0

Real output:

7.849E-1  0.000E+0  0.000E+0 -0.000E+0
0.000E+0  1.428E+0  0.000E+0 -0.000E+0
0.000E+0  0.000E+0 -1.000E+0 -2.000E-3
0.000E+0  0.000E+0 -1.000E+0 -0.000E+0

(2, 2), (2, 3), (3, 0), (3, 1) and (3, 3) seem to be inverted (negative instead of positive).

I tried to multiply several vectors with the above perspective projection matrix but it gives me vectors with negative z components, which are out of view range.

Error in setEulerAnglesXYZ from Quaternionf

Currently is rotating around the wrong axis and causing a reflection, the error is at the end of the method

x = cx * cycz + sx * sysz;
y = sx * cycz - cx * sysz;
z = cx * sycz + sx * cysz;
w = cx * cysz - sx * sycz;

should be

w = cx * cycz + sx * sysz;
x = sx * cycz - cx * sysz;
y = cx * sycz + sx * cysz;
z = cx * cysz - sx * sycz;

Maybe it the same error exists in Quaterniond ot in setEulerAngles ZYX, but I didn´t use those.

Add interfaces to all public api

would it be possible to add to all data classes an interface?
eg interface for Vector3f ect.

a) own extended implementations (eg one combining the interface of javax.vecmath, jme3.math and joml without having to copy stuff all over between different used librarys)
b) make migration way easyer, as the public interface could stay the same as before, just the internals are changed to joml.
c) allow clean use in persistence contexts, as java.util proxys are possible without bytecode modifications

Add library into Maven.

It would be awesome to add this library into maven, so people can easily link using Grandle or Maven!.

Add XNA migration guide

This is for people coming from Direct3D using the math in XNA, and now want to use Java and JOML.

Add Matrix.mul4x3

This method will assume that the last row of the right operand is (0, 0, 0, 1).

There is a bug

Math.cos(Math.toRadians(90)) != 0 ;
I can't write in English.Also can't github...o(╯□╰)o

Quaternion toString() methods give wrong results

The toString() methods of Quaternion and QuaternionD include no space between the components when the following component is negative.

(-1.200E+0 2.100E+0-9.700E+0-3.700E-1 )

Here, between y = 2.100E+0 and z = -9.700E+0 there is no space, as well as between z and w = -3.700E-1. There is also no space before x = -1.200E+0.

Change Non-Static methods to Static

There are a number of methods inside JOML that could be changed into static methods without any real change in functionality other than needing to reference a class instead of an object to get to the method. For example

 public Vector3d sub(Vector3d v, Vector3d dest) {
        dest.x = x - v.x;
        dest.y = y - v.y;
        dest.z = z - v.z;
        return dest;
    }

could very easily be

 public static Vector3d sub(Vector3d v, Vector3d dest) {
        dest.x = x - v.x;
        dest.y = y - v.y;
        dest.z = z - v.z;
        return dest;
    }

with no loss of functionality and should reduce the overall size of the object by changing it from one copy of the method per instance to one total.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.