Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
f1d2d2224This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.makelangelo;
import com.jogamp.opengl.GL3;
import com.marginallyclever.convenience.helpers.OpenGLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.event.EventListenerList;
import javax.vecmath.Point3d;
import javax.vecmath.Vector2d;
import javax.vecmath.Vector3d;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* <p>{@link Mesh} contains the vertex data for a 3D model. It may also contain normal, color, and texture data.</p>
* <p>It uses <a href="https://www.khronos.org/opengl/wiki/Vertex_Specification">Vertex Array Objects and Vertex
* Buffer Objects</a> to optimize rendering large collections of triangles.</p>
*/
public class Mesh {
private static final Logger logger = LoggerFactory.getLogger(Mesh.class);
public static final int NUM_BUFFERS=5; // verts, normals, colors, textureCoordinates, index
public final transient List<Float> vertexArray = new ArrayList<>();
public final transient List<Float> normalArray = new ArrayList<>();
public final transient List<Float> colorArray = new ArrayList<>();
public final transient List<Float> textureArray = new ArrayList<>();
public final transient List<Integer> indexArray = new ArrayList<>();
private transient boolean hasNormals = false;
private transient boolean hasColors = false;
private transient boolean isTransparent = false;
private transient boolean hasTextures = false;
private transient boolean hasIndexes = false;
private transient boolean isDirty = false;
private transient boolean isLoaded = false;
private transient int[] VAO;
private transient int[] VBO;
public int renderStyle = GL3.GL_TRIANGLES;
private String fileName = "";
// bounding limits
private final EventListenerList listeners = new EventListenerList();
public Mesh() {
super();
}
public Mesh(int renderStyle) {
this();
this.renderStyle = renderStyle;
}
/**
* Remove all vertexes, normals, colors, texture coordinates, etc.
* on the next call to {@link Mesh#render(GL3)} the mesh will be rebuilt to nothing.
* See also {@link Mesh#unload(GL3)}
*/
public void clear() {
vertexArray.clear();
normalArray.clear();
colorArray.clear();
textureArray.clear();
indexArray.clear();
isDirty=true;
}
public void setSourceName(String filename) {
this.fileName = filename;
}
public String getSourceName() {
return fileName;
}
public boolean isLoaded() {
return isLoaded;
}
public void setLoaded(boolean loaded) {
isLoaded=loaded;
}
public boolean isTransparent() {
return isTransparent;
}
/**
* Destroy the optimized rendering buffers for the fixed function pipeline.
* This does not free the memory used by the mesh. See also {@link Mesh#clear()}
* @param gl the OpenGL context
*/
public void unload(GL3 gl) {
if(!isLoaded) return;
isLoaded=false;
destroyBuffers(gl);
}
private void createBuffers(GL3 gl) {
VAO = new int[1];
gl.glGenVertexArrays(1, VAO, 0);
OpenGLHelper.checkGLError(gl,logger);
VBO = new int[NUM_BUFFERS];
gl.glGenBuffers(NUM_BUFFERS, VBO, 0);
OpenGLHelper.checkGLError(gl,logger);
}
private void destroyBuffers(GL3 gl) {
if(VBO != null) {
gl.glDeleteBuffers(NUM_BUFFERS, VBO, 0);
VBO = null;
}
if(VAO != null) {
gl.glDeleteVertexArrays(1, VAO, 0);
VAO = null;
}
}
/**
* Regenerate the optimized rendering buffers for the fixed function pipeline.
* Also recalculate the bounding box.
* @param gl the OpenGL context
*/
private void updateBuffers(GL3 gl) {
long numVertexes = getNumVertices();
gl.glBindVertexArray(VAO[0]);
OpenGLHelper.checkGLError(gl,logger);
checkBufferSizes();
setupArray(gl,0,3,numVertexes,vertexArray);
if(hasNormals ) setupArray(gl,1,3,numVertexes,normalArray );
if(hasColors ) setupArray(gl,2,4,numVertexes,colorArray );
if(hasTextures) setupArray(gl,3,2,numVertexes,textureArray);
if(hasIndexes) {
IntBuffer data = IntBuffer.allocate(indexArray.size());
for (Integer integer : indexArray) data.put(integer);
data.rewind();
gl.glBindBuffer(GL3.GL_ELEMENT_ARRAY_BUFFER, VBO[4]);
OpenGLHelper.checkGLError(gl,logger);
gl.glBufferData(GL3.GL_ELEMENT_ARRAY_BUFFER, (long) indexArray.size() * Integer.BYTES, data, GL3.GL_STATIC_DRAW);
OpenGLHelper.checkGLError(gl,logger);
}
gl.glBindVertexArray(0);
OpenGLHelper.checkGLError(gl,logger);
}
private void checkBufferSizes() {
var va = vertexArray.size();
var na = normalArray.size();
var ca = colorArray.size();
var ta = textureArray.size();
if(na>0 && na!=va) {
throw new IllegalStateException("normalArray.size() != vertexArray.size()");
}
if(ca>0 && ca*3!=va*4) {
throw new IllegalStateException("colorArray.size() != vertexArray.size()");
}
if(ta>0 && ta*3!=va*2) {
throw new IllegalStateException("textureArray.size() != vertexArray.size()");
}
}
private void bindArray(GL3 gl, int attribIndex, int size) {
gl.glEnableVertexAttribArray(attribIndex);
OpenGLHelper.checkGLError(gl,logger);
gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, VBO[attribIndex]);
OpenGLHelper.checkGLError(gl,logger);
gl.glVertexAttribPointer(attribIndex,size,GL3.GL_FLOAT,false,0,0);
OpenGLHelper.checkGLError(gl,logger);
}
private void setupArray(GL3 gl, int attribIndex, int size, long numVertexes,List<Float> list) {
FloatBuffer data = FloatBuffer.allocate(list.size());
for( Float f : list ) data.put(f);
data.rewind();
bindArray(gl,attribIndex,size);
gl.glBufferData(GL3.GL_ARRAY_BUFFER, numVertexes*size*Float.BYTES, data, GL3.GL_STATIC_DRAW);
OpenGLHelper.checkGLError(gl,logger);
}
/**
* Render the entire mesh.
* @param gl the OpenGL context
*/
public void render(GL3 gl) {
if (hasIndexes) {
render(gl,0,indexArray.size());
} else {
render(gl,0,getNumVertices());
}
}
/**
* Render a portion of the mesh.
* @param gl the OpenGL context
* @param startIndex index of the first vertex to viewport
* @param count number of vertices to viewport
*/
public void render(GL3 gl,int startIndex,int count) {
if(!isLoaded) {
isLoaded=true;
isDirty=true;
}
if(isDirty) {
createBuffers(gl);
updateBuffers(gl);
isDirty=false;
}
gl.glBindVertexArray(VAO[0]);
OpenGLHelper.checkGLError(gl,logger);
if (hasIndexes) {
gl.glDrawElements(renderStyle, indexArray.size(), GL3.GL_UNSIGNED_INT, 0);
} else {
gl.glDrawArrays(renderStyle, startIndex, count);
}
OpenGLHelper.checkGLError(gl,logger);
gl.glBindVertexArray(0);
OpenGLHelper.checkGLError(gl,logger);
}
public void addNormal(float x,float y,float z) {
normalArray.add(x);
normalArray.add(y);
normalArray.add(z);
hasNormals=true;
}
public void addVertex(float x,float y,float z) {
vertexArray.add(x);
vertexArray.add(y);
vertexArray.add(z);
}
/**
* Add a color to the mesh.
* @param r red, 0-1
* @param g green, 0-1
* @param b blue, 0-1
* @param a alpha, 0-1
*/
public void addColor(float r,float g,float b,float a) {
colorArray.add(r);
colorArray.add(g);
colorArray.add(b);
colorArray.add(a);
if(a!=1) isTransparent=true;
hasColors=true;
}
/**
* Add a texture coordinate to the mesh.
* @param u 0-1
* @param v 0-1
*/
public void addTexCoord(float u,float v) {
textureArray.add(u);
textureArray.add(v);
hasTextures =true;
}
public void addIndex(int n) {
indexArray.add(n);
hasIndexes=true;
}
public int getNumTriangles() {
return vertexArray.size()/9;
}
public int getNumVertices() {
return (vertexArray==null) ? 0 : vertexArray.size()/3;
}
public Vector3d getVertex(int t) {
t*=3;
double x = vertexArray.get(t++);
double y = vertexArray.get(t++);
double z = vertexArray.get(t++);
return new Vector3d(x,y,z);
}
public Vector3d getNormal(int t) {
t*=3;
double x = normalArray.get(t++);
double y = normalArray.get(t++);
double z = normalArray.get(t++);
return new Vector3d(x,y,z);
}
public Vector2d getTexCoord(int t) {
t*=2;
double u = textureArray.get(t++);
double v = textureArray.get(t++);
return new Vector2d(u,v);
}
public boolean isDirty() {
return isDirty;
}
public void setDirty(boolean isDirty) {
this.isDirty = isDirty;
}
public boolean getHasNormals() {
return hasNormals;
}
public boolean getHasColors() {
return hasColors;
}
public boolean getHasTextures() {
return hasTextures;
}
public boolean getHasIndexes() {
return hasIndexes;
}
public void setRenderStyle(int style) {
renderStyle = style;
}
public int getRenderStyle() {
return renderStyle;
}
public void setVertex(int i, double x, double y, double z) {
i*=3;
vertexArray.set(i++, (float)x);
vertexArray.set(i++, (float)y);
vertexArray.set(i++, (float)z);
}
public void setTexCoord(int i, double u, double v) {
i*=2;
textureArray.set(i++, (float)u);
textureArray.set(i++, (float)v);
}
public void updateVertexBuffers(GL3 gl3) {
if(VBO==null) return;
setupArray(gl3,0,3,getNumVertices(),vertexArray);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listeners.remove(PropertyChangeListener.class,listener);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.add(PropertyChangeListener.class,listener);
}
public void fireMeshChanged() {
PropertyChangeEvent p = null;
for( var v : listeners.getListeners(PropertyChangeListener.class)) {
if(p==null) p = new PropertyChangeEvent(this,"mesh",null,this);
v.propertyChange(p);
}
}
/**
* <p>Assumes that all triangles are outward facing and part of a closed, convex, non-intersecting mesh.</p>
* <p>This is a very rough approximation. The most correct way to do this is to find the area of every triangle,
* then pick a random triangle weighted by area, then pick a random point on that triangle.</p>
* <p>The actual algorithm picks a random triangle, then a random point inside that triangle.</p>
* @return a random point on the surface of this mesh.
*/
public Point3d getRandomPointOnSurface() {
int triangle = (int)(Math.random()*getNumTriangles());
return getRandomPointOnTriangle(triangle);
}
public Point3d getRandomPointOnTriangle(int triangleIndex) {
Vector3d v0 = getVertex(triangleIndex*3);
Vector3d v1 = getVertex(triangleIndex*3+1);
Vector3d v2 = getVertex(triangleIndex*3+2);
double a = Math.random();
double b = Math.random();
if(a+b>1) {
a=1-a;
b=1-b;
}
// this is a weighted average of the three points.
double x = v0.x + a * (v1.x - v0.x) + b * (v2.x - v0.x);
double y = v0.y + a * (v1.y - v0.y) + b * (v2.y - v0.y);
double z = v0.z + a * (v1.z - v0.z) + b * (v2.z - v0.z);
return new Point3d(x,y,z);
}
public double getTriangleArea(int triangleIndex) {
Vector3d v0 = getVertex(triangleIndex*3);
Vector3d v1 = getVertex(triangleIndex*3+1);
Vector3d v2 = getVertex(triangleIndex*3+2);
var v20 = new Vector3d(v2);
v20.sub(v0);
var v10 = new Vector3d(v1);
v10.sub(v0);
var cross = new Vector3d();
cross.cross(v20,v10);
return cross.length()/2;
}
}