Suggestion: Add methods to facilitate Mesh class

I feel like the mesh class could be more convenient to work with if a simple utility method was added to it. I’ve created a class below that extends mesh just to show what I mean, but it makes life a lot simpler when dealing with mesh creation; arrays vs lists, Buffers, and all the little niggling things that are annoying - like creating a List<Integer> for triangles because you can’t use primitives in a collection, having to convert it to a int[] array, etc… All the little annoyances that can be taken care of with boilerplate code.

JmeMesh mesh = new JmeMesh();

// set the buffer for any type using collections or arrays, primitives or wrapped primitives.

List<Vector3f> vertsList....
Vector3f[] normArray....
List<Integer> triangles...

mesh.set(Type.Position, vertsList);
mesh.set(Type.Normal, normals);
mesh.set(Type.Index, triangles);

// and so on...

package com.jayfella.pixels.grid;

import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.util.BufferUtils;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Collection;

public class JmeMesh extends Mesh {

    public JmeMesh() {
        super();
    }

    public void set(VertexBuffer.Type type, Collection<?> inputValues) {
        set(type, inputValues.toArray());
    }

    public void set(VertexBuffer.Type type, Object[] inputValues) {
        set(type, null, inputValues);
    }

    public void set(VertexBuffer.Type type, Integer components, Object[] inputValues) {

        if (inputValues == null || inputValues.length == 0) {
            clearBuffer(type);
            return;
        }

        if (inputValues.getClass().getComponentType() == float.class || inputValues.getClass().getComponentType() == Float.class) {

            FloatBuffer fb = BufferUtils.createFloatBuffer(inputValues.length);

            for (Object inputValue : inputValues) {
                fb.put( (float) inputValue );
            }

            int c = (components == null ? 1 : components);
            setBuffer(type, c, fb);
        }

        if (inputValues.getClass().getComponentType() == int.class || inputValues.getClass().getComponentType() == Integer.class) {

            IntBuffer ib = BufferUtils.createIntBuffer(inputValues.length);

            for (Object inputValue : inputValues) {
                ib.put((int) inputValue);
            }

            int c = type == VertexBuffer.Type.Index
                    ? 3
                    : (components == null ? 3 : components);
            
            setBuffer(type, c, ib);
        }

        else if (inputValues.getClass().getComponentType() == Vector2f.class) {

            FloatBuffer fb = BufferUtils.createFloatBuffer(inputValues.length * 2);

            for (Object inputValue : inputValues) {
                fb.put(((Vector2f)inputValue).x);
                fb.put(((Vector2f)inputValue).y);
            }

            setBuffer(type, 2, fb);
        }

        else if (inputValues.getClass().getComponentType() == Vector3f.class) {

            FloatBuffer fb = BufferUtils.createFloatBuffer(inputValues.length * 3);

            for (Object inputValue : inputValues) {
                fb.put(((Vector3f)inputValue).x);
                fb.put(((Vector3f)inputValue).y);
                fb.put(((Vector3f)inputValue).z);
            }

            setBuffer(type, 3, fb);

        }

        else if (inputValues.getClass().getComponentType() == Vector4f.class) {

            FloatBuffer fb = BufferUtils.createFloatBuffer(inputValues.length * 4);

            for (Object inputValue : inputValues) {
                fb.put(((Vector4f)inputValue).x);
                fb.put(((Vector4f)inputValue).y);
                fb.put(((Vector4f)inputValue).z);
                fb.put(((Vector4f)inputValue).w);
            }

            setBuffer(type, 4, fb);
        }

        // if we update the positions of the vertices, we also need to update the bounds of the mesh.
        if (type == VertexBuffer.Type.Position) {
            updateBound();
        }

    }

}


1 Like

In what use-cases do you not already know the types of the things that you are setting?

I didn’t really understand a lot of what you mentioned in your justification.

I don’t understand why triangles means a list of integers… or even an array of integers.

I get the impression that some people make meshes in strange ways… like half way between object oriented and raw mesh and somehow making 9 copies of their data structures along the way.

I meant indices, or to be clear, the VertexBuffer.Type.Index buffer. For example if I’m creating a mesh from noise, I won’t know the amount of indices I’ll need. You can’t explicitly convert a list of Integer to an int array, so you have to create an int array and fill it with the values of the list. Which is tedious (not exactly a valid argument) but boilerplate. Repeatable.

I’m trying to understand how you can know what your indexes will be for your triangles without knowing how many triangles you will have.

An index buffer is only useful when triangles share vertexes… which already implies some kind of foreknowledge of the data structure.

Because otherwise, my point would be to build a list of triangle objects and from that go straight to buffers. By pass creating the other lists, creating the other extra arrays, etc… It’s all unnecessary RAM.

1 Like

I’m just trying to digest your first statement. My belief is the opposite. Let me think on that for a few minutes…

My thinking:
If you are using indexes at all then triangles are sharing vertexes. Which means there is already some predictability to how many vertexes you will have. Else how will you know when triangle A shares vertexes with triangle B in the next ‘row’.

Either, you have a predetermined arrangement of vertexes or you are really dealing with pairs of triangles that only share the inner edge (like for a quad).

Unless you are using an index buffer when you don’t even need one.

I get what you’re saying now. Yeah. So it doesn’t make sense for indices. I was scratching my head wondering how I could know how many vertices I need, but you were taking about indices specifically.

And if you are not sharing vertexes, ie: no index buffer and you don’t know how many vertexes you have then it implies that you are skipping a data structure.

Instead of a list of triangles and going straight to buffers you are somehow managing your individual vertexes as sets of three in an array list.

…which you would then convert to an array of Vector3f… to convert to a buffer of floats. When you could have gone straight from triangle → buffers.

1 Like

The last reply to this topic was 10 months ago . But my solution (no matter if good or bad) fits exactly to this question.

I wanted to cut a mesh so that I get a part exactly above the water surface and a part exactly below the water - the last has to colored blue.

The following can happen to the triangles:

A college programmer would collapse because of the triple copy of the source code, but sometimes it is practical and more pleasant to simply write down the thoughts one after the other.

My calculator:


public class JaReWaterLevelCalculator extends JaReMeshCalculator {

	private final float level;

	public JaReWaterLevelCalculator(final float level) {
		this.level = level;
	}

	public void calculate(final short[] indexbuffer, final float[] texCoord, final float[] vertexbuffer,
			final float[] colorbuffer, final JaReWaterLevelConsumer consumer) {
		final ArrayList<Short> overIndex = new ArrayList<>(indexbuffer.length + 6);
		final ArrayList<Short> underIndex = new ArrayList<>(indexbuffer.length + 6);
		final ArrayList<Float> newTexCord = filledList(texCoord);
		final ArrayList<Float> newColorbuffer = filledList(colorbuffer);
		final ArrayList<Float> newVertex = filledList(vertexbuffer);
		for (int i = 0; i < indexbuffer.length; i = i + 3) {
			final int j = i + 1;
			final int k = j + 1;
			final short index_i = indexbuffer[i];
			final short index_j = indexbuffer[j];
			final short index_k = indexbuffer[k];
			final float ah = vertexbuffer[index_i * 3 + 1];
			final float bh = vertexbuffer[index_j * 3 + 1];
			final float ch = vertexbuffer[index_k * 3 + 1];
			if (ah + EPSILON >= level && bh + EPSILON >= level && ch + EPSILON >= level) {
				overIndex.add(index_i);
				overIndex.add(index_j);
				overIndex.add(index_k);
			} else if (ah - EPSILON <= level && bh - EPSILON <= level && ch - EPSILON <= level) {
				underIndex.add(index_i);
				underIndex.add(index_j);
				underIndex.add(index_k);
			} else if (ah + EPSILON >= level && ah - EPSILON <= level) {
				// splitt 2
				final float b_c = (level - bh) / (ch - bh);
				final float bch = level;
				final short index_bc = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_j * 3 + 0] * (1.0f - b_c) //
						+ vertexbuffer[index_k * 3 + 0] * b_c);
				newVertex.add(bch);
				newVertex.add(vertexbuffer[index_j * 3 + 2] * (1.0f - b_c) //
						+ vertexbuffer[index_k * 3 + 2] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 0] * (1.0f - b_c) //
						+ texCoord[index_k * TEX_COORD_COMP + 0] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 1] * (1.0f - b_c) //
						+ texCoord[index_k * TEX_COORD_COMP + 1] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_j * 4 + 0] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 0] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 1] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 1] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 2] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 2] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 3] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 3] * b_c);
				if (bh < level) {
					underIndex.add(index_i);
					underIndex.add(index_j);
					underIndex.add(index_bc);
				} else {
					overIndex.add(index_i);
					overIndex.add(index_j);
					overIndex.add(index_bc);
				}
				if (ch < level) {
					underIndex.add(index_k);
					underIndex.add(index_i);
					underIndex.add(index_bc);
				} else {
					overIndex.add(index_k);
					overIndex.add(index_i);
					overIndex.add(index_bc);
				}
			} else if (bh + EPSILON >= level && bh - EPSILON <= level) {
				// splitt 2
				final float c_a = (level - ch) / (ah - ch);
				final float cah = level;
				final short index_ca = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_k * 3 + 0] * (1.0f - c_a) //
						+ vertexbuffer[index_i * 3 + 0] * c_a);
				newVertex.add(cah);
				newVertex.add(vertexbuffer[index_k * 3 + 2] * (1.0f - c_a) //
						+ vertexbuffer[index_i * 3 + 2] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 0] * (1.0f - c_a) //
						+ texCoord[index_i * TEX_COORD_COMP + 0] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 1] * (1.0f - c_a) //
						+ texCoord[index_i * TEX_COORD_COMP + 1] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_k * 4 + 0] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 0] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 1] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 1] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 2] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 2] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 3] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 3] * c_a);
				if (ah < level) {
					underIndex.add(index_i);
					underIndex.add(index_j);
					underIndex.add(index_ca);
				} else {
					overIndex.add(index_i);
					overIndex.add(index_j);
					overIndex.add(index_ca);
				}
				if (ch < level) {
					underIndex.add(index_j);
					underIndex.add(index_k);
					underIndex.add(index_ca);
				} else {
					overIndex.add(index_j);
					overIndex.add(index_k);
					overIndex.add(index_ca);
				}
			} else if (ch + EPSILON >= level && ch - EPSILON <= level) {
				// splitt 2
				float a_b = (level - ah) / (bh - ah);
				float abh = level;
				if ((ah > level && bh < level) || (ah < level && bh > level)) {
					a_b = (level - ah) / (bh - ah);
					abh = level;
				}
				final short index_ab = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_i * 3 + 0] * (1.0f - a_b) //
						+ vertexbuffer[index_j * 3 + 0] * a_b);
				newVertex.add(abh);
				newVertex.add(vertexbuffer[index_i * 3 + 2] * (1.0f - a_b) //
						+ vertexbuffer[index_j * 3 + 2] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 0] * (1.0f - a_b) //
						+ texCoord[index_j * TEX_COORD_COMP + 0] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 1] * (1.0f - a_b) //
						+ texCoord[index_j * TEX_COORD_COMP + 1] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_i * 4 + 0] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 0] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 1] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 1] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 2] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 2] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 3] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 3] * a_b);
				if (ah < level) {
					underIndex.add(index_k);
					underIndex.add(index_i);
					underIndex.add(index_ab);
				} else {
					overIndex.add(index_k);
					overIndex.add(index_i);
					overIndex.add(index_ab);
				}
				if (bh < level) {
					underIndex.add(index_j);
					underIndex.add(index_k);
					underIndex.add(index_ab);
				} else {
					overIndex.add(index_j);
					overIndex.add(index_k);
					overIndex.add(index_ab);
				}
			} else {
				// splitt 4
				float a_b = 0.5f;
				float b_c = 0.5f;
				float c_a = 0.5f;
				float abh = (ah + bh) / 2;
				float bch = (bh + ch) / 2;
				float cah = (ch + ah) / 2;
				if ((ah > level && bh < level) || (ah < level && bh > level)) {
					a_b = (level - ah) / (bh - ah);
					abh = level;
				}
				if ((bh > level && ch < level) || (bh < level && ch > level)) {
					b_c = (level - bh) / (ch - bh);
					bch = level;
				}
				if ((ch > level && ah < level) || (ch < level && ah > level)) {
					c_a = (level - ch) / (ah - ch);
					cah = level;
				}
				final short index_ab = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_i * 3 + 0] * (1.0f - a_b) //
						+ vertexbuffer[index_j * 3 + 0] * a_b);
				newVertex.add(abh);
				newVertex.add(vertexbuffer[index_i * 3 + 2] * (1.0f - a_b) //
						+ vertexbuffer[index_j * 3 + 2] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 0] * (1.0f - a_b) //
						+ texCoord[index_j * TEX_COORD_COMP + 0] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 1] * (1.0f - a_b) //
						+ texCoord[index_j * TEX_COORD_COMP + 1] * a_b);
				newTexCord.add(texCoord[index_i * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_i * 4 + 0] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 0] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 1] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 1] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 2] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 2] * a_b);
				newColorbuffer.add(colorbuffer[index_i * 4 + 3] * (1.0f - a_b) //
						+ colorbuffer[index_j * 4 + 3] * a_b);

				final short index_bc = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_j * 3 + 0] * (1.0f - b_c) //
						+ vertexbuffer[index_k * 3 + 0] * b_c);
				newVertex.add(bch);
				newVertex.add(vertexbuffer[index_j * 3 + 2] * (1.0f - b_c) //
						+ vertexbuffer[index_k * 3 + 2] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 0] * (1.0f - b_c) //
						+ texCoord[index_k * TEX_COORD_COMP + 0] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 1] * (1.0f - b_c) //
						+ texCoord[index_k * TEX_COORD_COMP + 1] * b_c);
				newTexCord.add(texCoord[index_j * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_j * 4 + 0] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 0] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 1] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 1] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 2] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 2] * b_c);
				newColorbuffer.add(colorbuffer[index_j * 4 + 3] * (1.0f - b_c) //
						+ colorbuffer[index_k * 4 + 3] * b_c);

				final short index_ca = (short) (newTexCord.size() / TEX_COORD_COMP);
				newVertex.add(vertexbuffer[index_k * 3 + 0] * (1.0f - c_a) //
						+ vertexbuffer[index_i * 3 + 0] * c_a);
				newVertex.add(cah);
				newVertex.add(vertexbuffer[index_k * 3 + 2] * (1.0f - c_a) //
						+ vertexbuffer[index_i * 3 + 2] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 0] * (1.0f - c_a) //
						+ texCoord[index_i * TEX_COORD_COMP + 0] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 1] * (1.0f - c_a) //
						+ texCoord[index_i * TEX_COORD_COMP + 1] * c_a);
				newTexCord.add(texCoord[index_k * TEX_COORD_COMP + 2]);
				newColorbuffer.add(colorbuffer[index_k * 4 + 0] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 0] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 1] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 1] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 2] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 2] * c_a);
				newColorbuffer.add(colorbuffer[index_k * 4 + 3] * (1.0f - c_a) //
						+ colorbuffer[index_i * 4 + 3] * c_a);

				if (ah <= level && abh <= level && cah <= level) {
					underIndex.add(index_i);
					underIndex.add(index_ab);
					underIndex.add(index_ca);
				} else {
					overIndex.add(index_i);
					overIndex.add(index_ab);
					overIndex.add(index_ca);
				}
				if (bh <= level && bch <= level && abh <= level) {
					underIndex.add(index_j);
					underIndex.add(index_bc);
					underIndex.add(index_ab);
				} else {
					overIndex.add(index_j);
					overIndex.add(index_bc);
					overIndex.add(index_ab);
				}
				if (ch <= level && cah <= level && bch <= level) {
					underIndex.add(index_k);
					underIndex.add(index_ca);
					underIndex.add(index_bc);
				} else {
					overIndex.add(index_k);
					overIndex.add(index_ca);
					overIndex.add(index_bc);
				}
				if (abh <= level && bch <= level && cah <= level) {
					underIndex.add(index_ab);
					underIndex.add(index_bc);
					underIndex.add(index_ca);
				} else {
					overIndex.add(index_ab);
					overIndex.add(index_bc);
					overIndex.add(index_ca);
				}
			}
		}
		consumer.setIndexbufferUnder(toShortArray(underIndex));
		consumer.setIndexbufferOver(toShortArray(overIndex));
		consumer.setTexCoord(toFloatArray(newTexCord));
		consumer.setColorBuffer(toFloatArray(newColorbuffer));
		consumer.setVertexBuffer(toFloatArray(newVertex));
	}

	public float getLevel() {
		return level;
	}

}

with:


public abstract class JaReMeshCalculator {

	public static final float EPSILON = 0.001f;

	protected ArrayList<Float> filledList(final float[] array) {
		final int size = array.length;
		final ArrayList<Float> arrayList = new ArrayList<>(size + 9);
		for (int i = 0; i < size; i++) {
			arrayList.add(array[i]);
		}
		return arrayList;
	}

	protected short[] toShortArray(final ArrayList<Short> list) {
		final int size = list.size();
		final short[] ret = new short[size];
		for (int i = 0; i < size; i++) {
			ret[i] = list.get(i);
		}
		return ret;
	}

	protected float[] toFloatArray(final ArrayList<Float> list) {
		final int size = list.size();
		final float[] ret = new float[size];
		for (int i = 0; i < size; i++) {
			ret[i] = list.get(i);
		}
		return ret;
	}
}

with
TEX_COORD_COMP = 3;

Note: The Vertrex and TexCord are still duplicated in both meshes! Only the indices are divided.

I still have to revise that.

1 Like