diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java
index 4e3330cae..0563a6cd9 100644
--- a/engine/src/core/com/jme3/scene/Mesh.java
+++ b/engine/src/core/com/jme3/scene/Mesh.java
@@ -46,6 +46,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
+import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f;
import com.jme3.math.Triangle;
import com.jme3.math.Vector2f;
@@ -65,22 +66,85 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
+/**
+ * Mesh
is used to store rendering data.
+ *
+ * All visible elements in a scene are represented by meshes.
+ * Meshes may contain three types of geometric primitives:
+ *
+ * - Points
+ * - Lines
+ * - Triangles
+ *
+ *
+ * @author Kirill Vainer
+ */
public class Mesh implements Savable, Cloneable {
- // TODO: Document this enum
+ /**
+ * The mode of the Mesh specifies both the type of primitive represented
+ * by the mesh and how the data should be interpreted.
+ */
public enum Mode {
+ /**
+ * A primitive is a single point in space. The size of the points
+ * can be specified with {@link Mesh#setPointSize(float) }.
+ */
Points,
+
+ /**
+ * A primitive is a line segment. Every two vertices specify
+ * a single line. {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
Lines,
- LineLoop,
+
+ /**
+ * A primitive is a line segment. The first two vertices specify
+ * a single line, while subsequent vertices are combined with the
+ * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can
+ * be used to set the width of the lines.
+ */
LineStrip,
+
+ /**
+ * Identical to {@link #LineStrip} except that at the end
+ * the last vertex is connected with the first to form a line.
+ * {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
+ LineLoop,
+
+ /**
+ * A primitive is a triangle. Each 3 vertices specify a single
+ * triangle.
+ */
Triangles,
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, while subsequent vertices are combined with
+ * the previous two to form a triangle.
+ */
TriangleStrip,
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, each 2 subsequent vertices are combined
+ * with the very first vertex to make a triangle.
+ */
TriangleFan,
- Hybrid
+
+ /**
+ * A combination of various triangle modes. It is best to avoid
+ * using this mode as it may not be supported by all renderers.
+ * The {@link Mesh#setModeStart(int[]) mode start points} and
+ * {@link Mesh#setElementLengths(int[]) element lengths} must
+ * be specified for this mode.
+ */
+ Hybrid;
}
-// private static final int BUFFERS_SIZE = VertexBuffer.Type.BoneIndex.ordinal() + 1;
-
/**
* The bounding volume that contains the mesh entirely.
* By default a BoundingBox (AABB).
@@ -89,6 +153,7 @@ public class Mesh implements Savable, Cloneable {
private CollisionData collisionTree = null;
+ private ArrayList buffersList = new ArrayList(5);
private IntMap buffers = new IntMap();
private VertexBuffer[] lodLevels;
private float pointSize = 1;
@@ -105,27 +170,47 @@ public class Mesh implements Savable, Cloneable {
private Mode mode = Mode.Triangles;
+ /**
+ * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
+ */
public Mesh(){
}
+ /**
+ * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
+ * buffers} are shared between this and the clone mesh, the rest
+ * of the data is cloned.
+ *
+ * @return A shallow clone of the mesh
+ */
@Override
- public Mesh clone(){
- try{
+ public Mesh clone() {
+ try {
Mesh clone = (Mesh) super.clone();
clone.meshBound = meshBound.clone();
clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.buffers = buffers.clone();
+ clone.buffersList = new ArrayList(buffersList);
clone.vertexArrayID = -1;
- if (elementLengths != null)
+ if (elementLengths != null) {
clone.elementLengths = elementLengths.clone();
- if (modeStart != null)
+ }
+ if (modeStart != null) {
clone.modeStart = modeStart.clone();
+ }
return clone;
- }catch (CloneNotSupportedException ex){
+ } catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
+ /**
+ * Creates a deep clone of this mesh.
+ * The {@link VertexBuffer vertex buffers} and the data inside them
+ * is cloned.
+ *
+ * @return a deep clone of this mesh.
+ */
public Mesh deepClone(){
try{
Mesh clone = (Mesh) super.clone();
@@ -136,13 +221,21 @@ public class Mesh implements Savable, Cloneable {
clone.collisionTree = null; // it will get re-generated in any case
clone.buffers = new IntMap();
+ clone.buffersList = new ArrayList();
for (Entry ent : buffers){
- clone.buffers.put(ent.getKey(), ent.getValue().clone());
+ VertexBuffer bufClone = ent.getValue().clone();
+ clone.buffers.put(ent.getKey(), bufClone);
+ clone.buffersList.add(bufClone);
}
+
clone.vertexArrayID = -1;
clone.vertCount = -1;
clone.elementCount = -1;
- clone.maxNumWeights = -1;
+
+ // although this could change
+ // if the bone weight/index buffers are modified
+ clone.maxNumWeights = maxNumWeights;
+
clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
clone.modeStart = modeStart != null ? modeStart.clone() : null;
return clone;
@@ -151,6 +244,15 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Clone the mesh for animation use.
+ * This creates a shallow clone of the mesh, sharing most
+ * of the {@link VertexBuffer vertex buffer} data, however the
+ * {@link Type#BindPosePosition} and {@link Type#BindPoseNormal} buffers
+ * are deeply cloned.
+ *
+ * @return A clone of the mesh for animation use.
+ */
public Mesh cloneForAnim(){
Mesh clone = clone();
if (getBuffer(Type.BindPosePosition) != null){
@@ -170,8 +272,17 @@ public class Mesh implements Savable, Cloneable {
return clone;
}
- public void generateBindPose(boolean swAnim){
- if (swAnim){
+ /**
+ * Generates the {@link Type#BindPosePosition} and {@link Type#BindPoseNormal}
+ * buffers for this mesh by duplicating them based on the position and normal
+ * buffers already set on the mesh.
+ * This method does nothing if the mesh has no bone weight or index
+ * buffers.
+ *
+ * @param forSoftwareAnim Should be true if the bind pose is to be generated.
+ */
+ public void generateBindPose(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
VertexBuffer pos = getBuffer(Type.Position);
if (pos == null || getBuffer(Type.BoneIndex) == null) {
// ignore, this mesh doesn't have positional data
@@ -200,13 +311,17 @@ public class Mesh implements Savable, Cloneable {
setBuffer(bindNorm);
norm.setUsage(Usage.Stream);
}
-
- norm.setUsage(Usage.Stream);
}
}
- public void prepareForAnim(boolean swAnim){
- if (swAnim){
+ /**
+ * Prepares the mesh for software skinning by converting the bone index
+ * and weight buffers to heap buffers.
+ *
+ * @param forSoftwareAnim Should be true to enable the conversion.
+ */
+ public void prepareForAnim(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
// convert indices
VertexBuffer indices = getBuffer(Type.BoneIndex);
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
@@ -225,6 +340,11 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Set the LOD (level of detail) index buffers on this mesh.
+ *
+ * @param lodLevels The LOD levels to set
+ */
public void setLodLevels(VertexBuffer[] lodLevels){
this.lodLevels = lodLevels;
}
@@ -237,62 +357,152 @@ public class Mesh implements Savable, Cloneable {
return lodLevels != null ? lodLevels.length : 0;
}
+ /**
+ * Returns the lod level at the given index.
+ *
+ * @param lod The lod level index, this does not include
+ * the main index buffer.
+ * @return The LOD index buffer at the index
+ *
+ * @throws IndexOutOfBoundsException If the index is outside of the
+ * range [0, {@link #getNumLodLevels()}].
+ *
+ * @see #setLodLevels(com.jme3.scene.VertexBuffer[])
+ */
public VertexBuffer getLodLevel(int lod){
return lodLevels[lod];
}
+ /**
+ * Get the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return element lengths
+ */
public int[] getElementLengths() {
return elementLengths;
}
+ /**
+ * Set the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @param elementLengths The element lengths to set
+ */
public void setElementLengths(int[] elementLengths) {
this.elementLengths = elementLengths;
}
+ /**
+ * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
public int[] getModeStart() {
return modeStart;
}
+ /**
+ * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
public void setModeStart(int[] modeStart) {
this.modeStart = modeStart;
}
+ /**
+ * Returns the mesh mode
+ *
+ * @return the mesh mode
+ *
+ * @see #setMode(com.jme3.scene.Mesh.Mode)
+ */
public Mode getMode() {
return mode;
}
+ /**
+ * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
+ *
+ * @param mode The new mode to set
+ *
+ * @see Mode
+ */
public void setMode(Mode mode) {
this.mode = mode;
updateCounts();
}
+ /**
+ * Returns the maximum number of weights per vertex on this mesh.
+ *
+ * @return maximum number of weights per vertex
+ *
+ * @see #setMaxNumWeights(int)
+ */
public int getMaxNumWeights() {
return maxNumWeights;
}
+ /**
+ * Set the maximum number of weights per vertex on this mesh.
+ * Only relevant if this mesh has bone index/weight buffers.
+ * This value should be between 0 and 4.
+ *
+ * @param maxNumWeights
+ */
public void setMaxNumWeights(int maxNumWeights) {
this.maxNumWeights = maxNumWeights;
}
+ /**
+ * Returns the size of points for point meshes
+ *
+ * @return the size of points
+ *
+ * @see #setPointSize(float)
+ */
public float getPointSize() {
return pointSize;
}
+ /**
+ * Set the size of points for meshes of mode {@link Mode#Points}.
+ * The point size is specified as on-screen pixels, the default
+ * value is 1.0. The point size
+ * does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
+ * render state is enabled, in that case, the vertex shader must specify the
+ * point size by writing to gl_PointSize
.
+ *
+ * @param pointSize The size of points
+ */
public void setPointSize(float pointSize) {
this.pointSize = pointSize;
}
+ /**
+ * Returns the line width for line meshes.
+ *
+ * @return the line width
+ */
public float getLineWidth() {
return lineWidth;
}
+ /**
+ * Specify the line width for meshes of the line modes, such
+ * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
+ * the default value is 1.0.
+ *
+ * @param lineWidth The line width
+ */
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
/**
- * Locks the mesh so it cannot be modified anymore, thus
- * optimizing its data.
+ * Indicates to the GPU that this mesh will not be modified (a hint).
+ * Sets the usage mode to {@link Usage#Static}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
*/
public void setStatic() {
for (Entry entry : buffers){
@@ -301,8 +511,9 @@ public class Mesh implements Savable, Cloneable {
}
/**
- * Unlocks the mesh so it can be modified, this
- * will un-optimize the data!
+ * Indicates to the GPU that this mesh will be modified occasionally (a hint).
+ * Sets the usage mode to {@link Usage#Dynamic}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
*/
public void setDynamic() {
for (Entry entry : buffers){
@@ -310,12 +521,22 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Indicates to the GPU that this mesh will be modified every frame (a hint).
+ * Sets the usage mode to {@link Usage#Stream}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
public void setStreamed(){
for (Entry entry : buffers){
entry.getValue().setUsage(Usage.Stream);
}
}
+ /**
+ * Interleaves the data in this mesh. This operation cannot be reversed.
+ * Some GPUs may prefer the data in this format, however it is a good idea
+ * to avoid using this method as it disables some engine features.
+ */
public void setInterleaved(){
ArrayList vbs = new ArrayList();
for (Entry entry : buffers){
@@ -339,8 +560,10 @@ public class Mesh implements Savable, Cloneable {
VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
+
// adding buffer directly so that no update counts is forced
buffers.put(Type.InterleavedData.ordinal(), allData);
+ buffersList.add(allData);
for (int vert = 0; vert < getVertexCount(); vert++){
for (int i = 0; i < vbs.size(); i++){
@@ -415,6 +638,16 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Update the {@link #getVertexCount() vertex} and
+ * {@link #getTriangleCount() triangle} counts for this mesh
+ * based on the current data. This method should be called
+ * after the {@link Buffer#capacity() capacities} of the mesh's
+ * {@link VertexBuffer vertex buffers} has been altered.
+ *
+ * @throws IllegalStateException If this mesh is in
+ * {@link #setInterleaved() interleaved} format.
+ */
public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null)
throw new IllegalStateException("Should update counts before interleave");
@@ -431,6 +664,12 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Returns the triangle count for the given LOD level.
+ *
+ * @param lod The lod level to look up
+ * @return The triangle count for that LOD level
+ */
public int getTriangleCount(int lod){
if (lodLevels != null){
if (lod < 0)
@@ -447,10 +686,17 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Returns how many triangles are on this Mesh.
+ * This value is only updated when {@link #updateCounts() } is called.
+ *
+ * @return how many triangles are on this Mesh.
+ */
public int getTriangleCount(){
return elementCount;
}
+
public int getVertexCount(){
return vertCount;
}
@@ -540,6 +786,7 @@ public class Mesh implements Savable, Cloneable {
vb.setupData(Usage.Dynamic, components, Format.Float, buf);
// buffers.put(type, vb);
buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
}else{
vb.setupData(Usage.Dynamic, components, Format.Float, buf);
}
@@ -556,6 +803,7 @@ public class Mesh implements Savable, Cloneable {
vb = new VertexBuffer(type);
vb.setupData(Usage.Dynamic, components, Format.UnsignedInt, buf);
buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
updateCounts();
}
}
@@ -570,6 +818,7 @@ public class Mesh implements Savable, Cloneable {
vb = new VertexBuffer(type);
vb.setupData(Usage.Dynamic, components, Format.UnsignedShort, buf);
buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
updateCounts();
}
}
@@ -584,6 +833,7 @@ public class Mesh implements Savable, Cloneable {
vb = new VertexBuffer(type);
vb.setupData(Usage.Dynamic, components, Format.UnsignedByte, buf);
buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
updateCounts();
}
}
@@ -593,12 +843,16 @@ public class Mesh implements Savable, Cloneable {
throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
buffers.put(vb.getBufferType().ordinal(), vb);
+ buffersList.add(vb);
updateCounts();
}
public void clearBuffer(VertexBuffer.Type type){
- buffers.remove(type.ordinal());
- updateCounts();
+ VertexBuffer vb = buffers.remove(type.ordinal());
+ if (vb != null){
+ buffersList.remove(vb);
+ updateCounts();
+ }
}
public void setBuffer(Type type, int components, short[] buf){
@@ -642,6 +896,13 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Scales the texture coordinate buffer on this mesh by the given
+ * scale factor.
+ *
+ *
+ * @param scaleFactor
+ */
public void scaleTextureCoordinates(Vector2f scaleFactor){
VertexBuffer tc = getBuffer(Type.TexCoord);
if (tc == null)
@@ -667,6 +928,11 @@ public class Mesh implements Savable, Cloneable {
tc.updateData(fb);
}
+ /**
+ * Updates the bounding volume of this mesh.
+ * The method does nothing if the mesh has no {@link Type#Position} buffer.
+ * It is expected that the position buffer is a float buffer with 3 components.
+ */
public void updateBound(){
VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position);
if (meshBound != null && posBuf != null){
@@ -674,17 +940,42 @@ public class Mesh implements Savable, Cloneable {
}
}
+ /**
+ * Returns the {@link BoundingVolume} of this Mesh.
+ * By default the bounding volume is a {@link BoundingBox}.
+ *
+ * @return the bounding volume of this mesh
+ */
public BoundingVolume getBound() {
return meshBound;
}
+ /**
+ * Sets the {@link BoundingVolume} for this Mesh.
+ * The bounding volume is recomputed by calling {@link #updateBound() }.
+ *
+ * @param modelBound The model bound to set
+ */
public void setBound(BoundingVolume modelBound) {
meshBound = modelBound;
}
+ /**
+ * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
+ * The integer key for the map is the {@link Enum#ordinal() ordinal}
+ * of the vertex buffer's {@link Type}.
+ * Note that the returned map is a reference to the map used internally,
+ * modifying it will cause undefined results.
+ *
+ * @return map of vertex buffers on this mesh.
+ */
public IntMap getBuffers(){
return buffers;
}
+
+ public ArrayList getBufferList(){
+ return buffersList;
+ }
public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this);
@@ -726,6 +1017,10 @@ public class Mesh implements Savable, Cloneable {
// in.readStringSavableMap("buffers", null);
buffers = (IntMap) in.readIntSavableMap("buffers", null);
+ for (Entry entry : buffers){
+ buffersList.add(entry.getValue());
+ }
+
Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
if (lodLevelsSavable != null) {
lodLevels = new VertexBuffer[lodLevelsSavable.length];
diff --git a/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java
index aa4ab978a..424f53a6a 100644
--- a/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java
+++ b/engine/src/lwjgl-ogl/com/jme3/renderer/lwjgl/LwjglRenderer.java
@@ -75,6 +75,7 @@ import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
+import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
@@ -478,6 +479,14 @@ public class LwjglRenderer implements Renderer {
public void setBackgroundColor(ColorRGBA color) {
glClearColor(color.r, color.g, color.b, color.a);
}
+
+ public void setAlphaToCoverage(boolean value) {
+ if (value) {
+ glEnable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
+ } else {
+ glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
+ }
+ }
public void applyRenderState(RenderState state) {
if (state.isWireframe() && !context.wireframe) {
@@ -2260,8 +2269,10 @@ public class LwjglRenderer implements Renderer {
}
private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
- if (mesh.getId() == -1) {
+ if (mesh.getId() == -1){
updateVertexArray(mesh);
+ }else{
+ // TODO: Check if it was updated
}
if (context.boundVertexArray != mesh.getId()) {
@@ -2269,18 +2280,17 @@ public class LwjglRenderer implements Renderer {
context.boundVertexArray = mesh.getId();
}
- IntMap buffers = mesh.getBuffers();
+// IntMap buffers = mesh.getBuffers();
VertexBuffer indices = null;
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
- indices = buffers.get(Type.Index.ordinal());
+ indices = mesh.getBuffer(Type.Index);
}
if (indices != null) {
drawTriangleList(indices, mesh, count);
} else {
-// throw new UnsupportedOperationException("Cannot render without index buffer");
- glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount());
+ drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
}
clearVertexAttribs();
clearTextureUnits();
@@ -2294,15 +2304,19 @@ public class LwjglRenderer implements Renderer {
updateBufferData(interleavedData);
}
- IntMap buffers = mesh.getBuffers();
+ //IntMap buffers = mesh.getBuffers();
+ ArrayList buffersList = mesh.getBufferList();
+
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
- indices = buffers.get(Type.Index.ordinal());
+ indices = mesh.getBuffer(Type.Index);
}
- for (Entry entry : buffers) {
- VertexBuffer vb = entry.getValue();
-
+ //for (Entry entry : buffers) {
+ // VertexBuffer vb = entry.getValue();
+ for (int i = 0; i < buffersList.size(); i++){
+ VertexBuffer vb = buffersList.get(i);
+
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|| vb.getBufferType() == Type.Index) {
@@ -2317,11 +2331,11 @@ public class LwjglRenderer implements Renderer {
setVertexAttrib(vb, interleavedData);
}
}
+
if (indices != null) {
drawTriangleList(indices, mesh, count);
} else {
-// throw new UnsupportedOperationException("Cannot render without index buffer");
- glDrawArrays(convertElementMode(mesh.getMode()), 0, mesh.getVertexCount());
+ drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
}
clearVertexAttribs();
clearTextureUnits();
@@ -2344,15 +2358,7 @@ public class LwjglRenderer implements Renderer {
// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
// renderMeshVertexArray(mesh, lod, count);
// }else{
- renderMeshDefault(mesh, lod, count);
+ renderMeshDefault(mesh, lod, count);
// }
}
-
- public void setAlphaToCoverage(boolean value) {
- if (value) {
- glEnable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
- } else {
- glDisable(ARBMultisample.GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);
- }
- }
}