From 5c84f06d8969c340007d47f3abf1fc8ea4231c69 Mon Sep 17 00:00:00 2001 From: wyq Date: Tue, 7 Mar 2023 17:00:21 +0800 Subject: [PATCH] add ModelRender class --- .../chart/graphic/GraphicFactory.java | 43 +++ .../org/meteoinfo/chart/graphic/Model.java | 51 ++-- .../chart/graphic/TriMeshGraphic.java | 18 +- .../chart/graphic/cylinder/Cylinder.java | 16 +- .../java/org/meteoinfo/chart/jogl/GLPlot.java | 29 ++- .../chart/render/jogl/ModelRender.java | 245 ++++++++++++++++++ 6 files changed, 363 insertions(+), 39 deletions(-) create mode 100644 meteoinfo-chart/src/main/java/org/meteoinfo/chart/render/jogl/ModelRender.java diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java index c343af16..df5d10cc 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/GraphicFactory.java @@ -8137,6 +8137,49 @@ public class GraphicFactory { return meshGraphic; } + /** + * Create model graphic + * + * @param faceIndices Vertex indices array + * @param x X coordinates array + * @param y Y coordinates array + * @param z Z coordinates array + * @param ls Legend scheme + * @return + */ + public static Model model(Array faceIndices, Array x, Array y, + Array z, LegendScheme ls) { + Model model = new Model(); + model.setTriangles(faceIndices, x, y, z); + model.setLegendScheme(ls); + + return model; + } + + /** + * Create model graphic + * + * @param faceIndices Vertex indices array + * @param x X coordinates array + * @param y Y coordinates array + * @param z Z coordinates array + * @param normal Normal array + * @param ls Legend scheme + * @return + */ + public static Model model(Array faceIndices, Array x, Array y, + Array z, Array normal, LegendScheme ls) { + if (normal == null) { + return model(faceIndices, x, y, z, ls); + } + + Model model = new Model(); + model.setTriangles(faceIndices, x, y, z, normal); + model.setLegendScheme(ls); + + return model; + } + /** * Create volume graphics * diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Model.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Model.java index b8f416cd..67e3ab20 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Model.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/Model.java @@ -1,7 +1,13 @@ package org.meteoinfo.chart.graphic; -public class Model { - protected TriMeshGraphic triMeshGraphic; +import org.joml.Vector3f; + +import java.util.List; + +public class Model extends TriMeshGraphic { + + protected Vector3f angle = new Vector3f(); + protected float scale = 1; /** * Constructor @@ -11,28 +17,43 @@ public class Model { } /** - * Constructor - * - * @param triMeshGraphic Triangle mesh graphic + * Get angle + * @return The angle */ - public Model(TriMeshGraphic triMeshGraphic) { - this.triMeshGraphic = triMeshGraphic; + public Vector3f getAngle() { + return angle; } /** - * Get triangle mesh graphic - * @return Triangle mesh graphic + * Set angle + * @param value Then angle */ - public TriMeshGraphic getTriMeshGraphic() { - return this.triMeshGraphic; + public void setAngle(Vector3f value) { + angle = value; } /** - * Set triangle mesh graphic - * @param value Triangle mesh graphic + * Set angle + * @param value The angle */ - public void setTriMeshGraphic(TriMeshGraphic value) { - this.triMeshGraphic = value; + public void setAngle(List value) { + angle = new Vector3f(value.get(0), value.get(1), value.get(2)); + } + + /** + * Get scale + * @return The scale + */ + public float getScale() { + return scale; + } + + /** + * Set value + * @param value The value + */ + public void setScale(float value) { + scale = value; } /** diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java index ee1b1f55..0dab277f 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/TriMeshGraphic.java @@ -20,16 +20,16 @@ import java.util.logging.Logger; public class TriMeshGraphic extends GraphicCollection3D { private Logger logger = Logger.getLogger("TriMeshGraphic"); - private float[] vertexPosition; - private float[] vertexValue; - private float[] vertexColor; - private float[] vertexNormal; - private int[] vertexIndices; + protected float[] vertexPosition; + protected float[] vertexValue; + protected float[] vertexColor; + protected float[] vertexNormal; + protected int[] vertexIndices; //private LinkedHashMap> triangleMap; - private boolean faceInterp; - private boolean edgeInterp; - private boolean mesh; - private boolean normalLoaded = false; + protected boolean faceInterp; + protected boolean edgeInterp; + protected boolean mesh; + protected boolean normalLoaded = false; /** * Constructor diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/cylinder/Cylinder.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/cylinder/Cylinder.java index 2dd79e07..23860781 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/cylinder/Cylinder.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/graphic/cylinder/Cylinder.java @@ -9,7 +9,7 @@ import org.meteoinfo.chart.graphic.TriMeshGraphic; import java.util.ArrayList; import java.util.List; -public class Cylinder extends Model { +public class Cylinder { private float baseRadius; private float topRadius; private float height; @@ -64,9 +64,9 @@ public class Cylinder extends Model { } } - @Override - protected void buildTriMeshGraphic() { - this.triMeshGraphic = new TriMeshGraphic(); + + protected TriMeshGraphic buildTriMeshGraphic() { + TriMeshGraphic triMeshGraphic = new TriMeshGraphic(); int n = this.vertices.size(); float[] vertexPosition = new float[n * 3]; float[] vertexNormal = new float[n * 3]; @@ -82,9 +82,11 @@ public class Cylinder extends Model { vertexNormal[j + 2] = v.z; } int[] vertexIndices = this.indices.stream().mapToInt(Integer::intValue).toArray(); - this.triMeshGraphic.setVertexPosition(vertexPosition); - this.triMeshGraphic.setVertexNormal(vertexNormal); - this.triMeshGraphic.setVertexIndices(vertexIndices); + triMeshGraphic.setVertexPosition(vertexPosition); + triMeshGraphic.setVertexNormal(vertexNormal); + triMeshGraphic.setVertexIndices(vertexIndices); + + return triMeshGraphic; } /** diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java index 7c88e449..d722dca2 100644 --- a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/jogl/GLPlot.java @@ -2518,15 +2518,28 @@ public class GLPlot extends Plot { pointRender.updateMatrix(); pointRender.draw(); } else if (graphic instanceof TriMeshGraphic) { - if (!this.renderMap.containsKey(graphic)) { - renderMap.put(graphic, new TriMeshRender(gl, (TriMeshGraphic) graphic)); + if (graphic instanceof Model) { + if (!this.renderMap.containsKey(graphic)) { + renderMap.put(graphic, new ModelRender(gl, (Model) graphic)); + } + ModelRender modelRender = (ModelRender) renderMap.get(graphic); + modelRender.setTransform(this.transform, this.alwaysUpdateBuffers); + modelRender.setOrthographic(this.orthographic); + modelRender.setLighting(this.lighting); + modelRender.updateMatrix(); + modelRender.setRotateModelView(this.modelViewMatrixR); + modelRender.draw(); + } else { + if (!this.renderMap.containsKey(graphic)) { + renderMap.put(graphic, new TriMeshRender(gl, (TriMeshGraphic) graphic)); + } + TriMeshRender triMeshRender = (TriMeshRender) renderMap.get(graphic); + triMeshRender.setTransform(this.transform, this.alwaysUpdateBuffers); + triMeshRender.setOrthographic(this.orthographic); + triMeshRender.setLighting(this.lighting); + triMeshRender.updateMatrix(); + triMeshRender.draw(); } - TriMeshRender triMeshRender = (TriMeshRender) renderMap.get(graphic); - triMeshRender.setTransform(this.transform, this.alwaysUpdateBuffers); - triMeshRender.setOrthographic(this.orthographic); - triMeshRender.setLighting(this.lighting); - triMeshRender.updateMatrix(); - triMeshRender.draw(); } else if (graphic instanceof VolumeGraphic) { try { if (this.clipPlane) diff --git a/meteoinfo-chart/src/main/java/org/meteoinfo/chart/render/jogl/ModelRender.java b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/render/jogl/ModelRender.java new file mode 100644 index 00000000..d1c3436d --- /dev/null +++ b/meteoinfo-chart/src/main/java/org/meteoinfo/chart/render/jogl/ModelRender.java @@ -0,0 +1,245 @@ +package org.meteoinfo.chart.render.jogl; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2; +import com.jogamp.opengl.util.GLBuffers; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.meteoinfo.chart.graphic.Model; +import org.meteoinfo.chart.graphic.TriMeshGraphic; +import org.meteoinfo.chart.jogl.Program; +import org.meteoinfo.chart.jogl.Transform; +import org.meteoinfo.chart.jogl.Utils; +import org.meteoinfo.geometry.legend.PolygonBreak; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public class ModelRender extends JOGLGraphicRender { + + private Model model; + private IntBuffer vbo; + //private IntBuffer vboNormal; + private Program program; + private float[] vertexPosition; + private int sizePosition; + private int sizeNormal; + private int sizeColor; + + /** + * Constructor + * + * @param gl The JOGL GL2 object + */ + public ModelRender(GL2 gl) { + super(gl); + + useShader = false; + if (useShader) { + try { + this.compileShaders(); + } catch (Exception e) { + e.printStackTrace(); + } + } + initVertexBuffer(); + } + + /** + * Constructor + * + * @param gl The opengl pipeline + * @param model The model + */ + public ModelRender(GL2 gl, Model model) { + this(gl); + + this.model = model; + this.setBufferData(); + } + + private void initVertexBuffer() { + vbo = GLBuffers.newDirectIntBuffer(2); + } + + private void setBufferData() { + if (vertexPosition == null) { + vertexPosition = model.getVertexPosition(); + } + FloatBuffer vertexBuffer = GLBuffers.newDirectFloatBuffer(vertexPosition); + if (model.getVertexNormal() == null) { + model.calculateNormalVectors(vertexPosition); + } + FloatBuffer normalBuffer = GLBuffers.newDirectFloatBuffer(model.getVertexNormal()); + FloatBuffer colorBuffer = GLBuffers.newDirectFloatBuffer(model.getVertexColor()); + sizePosition = vertexBuffer.capacity() * Float.BYTES; + sizeNormal = normalBuffer.capacity() * Float.BYTES; + sizeColor = colorBuffer.capacity() * Float.BYTES; + + gl.glGenBuffers(2, vbo); + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo.get(0)); + gl.glBufferData(GL.GL_ARRAY_BUFFER, sizePosition + sizeNormal + sizeColor, null, GL.GL_STATIC_DRAW); + gl.glBufferSubData(GL.GL_ARRAY_BUFFER, 0, sizePosition, vertexBuffer); + gl.glBufferSubData(GL.GL_ARRAY_BUFFER, sizePosition, sizeNormal, normalBuffer); + gl.glBufferSubData(GL.GL_ARRAY_BUFFER, sizePosition + sizeNormal, sizeColor, colorBuffer); + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); + + IntBuffer indexBuffer = GLBuffers.newDirectIntBuffer(model.getVertexIndices()); + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, vbo.get(1)); + gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.capacity() * Integer.BYTES, indexBuffer, GL.GL_STATIC_DRAW); + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0); + } + + @Override + public void setTransform(Transform transform, boolean alwaysUpdateBuffers) { + boolean updateBuffer = true; + if (!alwaysUpdateBuffers && this.transform != null && this.transform.equals(transform)) + updateBuffer = false; + + super.setTransform((Transform) transform.clone()); + + if (alwaysUpdateBuffers) { + setBufferData(); + } + } + + void compileShaders() throws Exception { + String vertexShaderCode = Utils.loadResource("/shaders/mesh/vertex.vert"); + String fragmentShaderCode = Utils.loadResource("/shaders/mesh/mesh.frag"); + program = new Program("mesh", vertexShaderCode, fragmentShaderCode); + } + + /** + * Update shaders + */ + public void updateShaders() { + if (program == null) { + try { + compileShaders(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + void setUniforms() { + program.allocateUniform(gl, "matrixModelViewProjection", (gl2, loc) -> { + gl2.glUniformMatrix4fv(loc, 1, false, this.viewProjMatrix.get(Buffers.newDirectFloatBuffer(16))); + }); + program.allocateUniform(gl, "matrixModelView", (gl2, loc) -> { + gl2.glUniformMatrix4fv(loc, 1, false, toMatrix(this.mvmatrix).get(Buffers.newDirectFloatBuffer(16))); + }); + Matrix4f matrixNormal = toMatrix(this.mvmatrix); + matrixNormal.setColumn(3, new Vector4f(0,0,0,1)); + program.allocateUniform(gl, "matrixNormal", (gl2, loc) -> { + gl2.glUniformMatrix4fv(loc, 1, false, matrixNormal.get(Buffers.newDirectFloatBuffer(16))); + }); + float[] rgba = model.getColor().getRGBComponents(null); + program.allocateUniform(gl, "color", (gl2, loc) -> { + gl2.glUniform4fv(loc, 1, rgba, 0); + }); + program.allocateUniform(gl, "lightPosition", (gl2, loc) -> { + gl2.glUniform4fv(loc, 1, lighting.getPosition(), 0); + }); + program.allocateUniform(gl, "lightAmbient", (gl2, loc) -> { + gl2.glUniform4fv(loc, 1, lighting.getAmbient(), 0); + }); + program.allocateUniform(gl, "lightDiffuse", (gl2, loc) -> { + gl2.glUniform4fv(loc, 1, lighting.getDiffuse(), 0); + }); + program.allocateUniform(gl, "lightSpecular", (gl2, loc) -> { + gl2.glUniform4fv(loc, 1, lighting.getSpecular(), 0); + }); + + program.setUniforms(gl); + } + + @Override + public void draw() { + gl.glPushMatrix(); + FloatBuffer fb = Buffers.newDirectFloatBuffer(16); + Matrix4f modelView = new Matrix4f(this.modelViewMatrixR); + modelView.scale(this.model.getScale()); + modelView.rotateXYZ(model.getAngle()); + gl.glLoadMatrixf(modelView.get(fb)); + + if (useShader) { + program.use(gl); + setUniforms(); + + int attribVertexPosition = gl.glGetAttribLocation(program.getProgramId(), "vertexPosition"); + int attribVertexNormal = gl.glGetAttribLocation(program.getProgramId(), "vertexNormal"); + + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo.get(0)); + + gl.glEnableVertexAttribArray(attribVertexPosition); + gl.glEnableVertexAttribArray(attribVertexNormal); + + gl.glVertexAttribPointer(attribVertexPosition, 3, GL.GL_FLOAT, false, 0, 0); + gl.glVertexAttribPointer(attribVertexNormal, 3, GL.GL_FLOAT, false, 0, vertexPosition.length * Float.BYTES); + + gl.glDrawArrays(GL.GL_TRIANGLES, 0, model.getVertexNumber()); + + gl.glDisableVertexAttribArray(attribVertexPosition); + gl.glDisableVertexAttribArray(attribVertexNormal); + + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); + + gl.glUseProgram(0); + } else { + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo.get(0)); + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, vbo.get(1)); + + // enable vertex arrays + gl.glEnableClientState(GL2.GL_NORMAL_ARRAY); + gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); + gl.glNormalPointer(GL.GL_FLOAT, 0, sizePosition); + gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0); + gl.glEnableClientState(GL2.GL_COLOR_ARRAY); + gl.glColorPointer(4, GL.GL_FLOAT, 0, sizePosition + sizeNormal); + + PolygonBreak pb = (PolygonBreak) model.getLegendScheme().getLegendBreak(0); + if (pb.isDrawFill()) { + gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL); + gl.glPolygonOffset(1.0f, 1.0f); + if (model.isFaceInterp()) { + gl.glDrawElements(GL2.GL_TRIANGLES, model.getVertexIndices().length, GL.GL_UNSIGNED_INT, 0); + } else { + gl.glShadeModel(GL2.GL_FLAT); + gl.glDrawElements(GL2.GL_TRIANGLES, model.getVertexIndices().length, GL.GL_UNSIGNED_INT, 0); + gl.glShadeModel(GL2.GL_SMOOTH); + } + } + if (pb.isDrawOutline()) { + boolean lightEnabled = this.lighting.isEnable(); + if (lightEnabled) { + this.lighting.stop(gl); + } + gl.glLineWidth(pb.getOutlineSize() * this.dpiScale); + if (!model.isMesh()) { + gl.glDisableClientState(GL2.GL_COLOR_ARRAY); + float[] rgba = pb.getOutlineColor().getRGBComponents(null); + gl.glColor4fv(rgba, 0); + } + gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_LINE); + //gl.glDrawArrays(GL.GL_TRIANGLES, 0, meshGraphic.getVertexNumber()); + gl.glDrawElements(GL.GL_TRIANGLES, model.getVertexIndices().length, GL.GL_UNSIGNED_INT, 0); + gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_FILL); + if (lightEnabled) { + this.lighting.start(gl); + } + } + + gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); + gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); + gl.glDisableClientState(GL2.GL_COLOR_ARRAY); + + gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); + gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0); + } + + gl.glPopMatrix(); + } +}