/* * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.prism.impl; import com.sun.javafx.geom.Quat4f; import com.sun.javafx.geom.Vec2f; import com.sun.javafx.geom.Vec3f; import com.sun.prism.Mesh; import java.util.Arrays; import java.util.HashMap; import javafx.scene.shape.VertexFormat; import sun.util.logging.PlatformLogger; /** * TODO: 3D - Need documentation */ public abstract class BaseMesh extends BaseGraphicsResource implements Mesh { private int nVerts; private int nTVerts; private int nFaces; private float[] pos; private float[] uv; private int[] faces; private int[] smoothing; private boolean allSameSmoothing; private boolean allHardEdges; protected static final int POINT_SIZE = 3; protected static final int NORMAL_SIZE = 3; protected static final int TEXCOORD_SIZE = 2; protected static final int POINT_SIZE_VB = 3; protected static final int TEXCOORD_SIZE_VB = 2; protected static final int NORMAL_SIZE_VB = 4; //point (3 floats), texcoord (2 floats) and normal (as in 4 floats) protected static final int VERTEX_SIZE_VB = 9; // Data members container for a single face // Vec3i pVerts; // Vec3i tVerts; // int smGroup; public static enum FaceMembers { POINT0, TEXCOORD0, POINT1, TEXCOORD1, POINT2, TEXCOORD2, SMOOTHING_GROUP }; public static final int FACE_MEMBERS_SIZE = 7; protected BaseMesh(Disposer.Record disposerRecord) { super(disposerRecord); } public abstract boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, int[] indexBufferInt, int indexBufferLength); public abstract boolean buildNativeGeometry(float[] vertexBuffer, int vertexBufferLength, short[] indexBufferShort, int indexBufferLength); private boolean updateSkipMeshNormalGeometry(int[] posFromAndLengthIndices, int[] uvFromAndLengthIndices) { // Find out the list of modified tex coords. int startTexCoord = uvFromAndLengthIndices[0] / TEXCOORD_SIZE; int numTexCoords = (uvFromAndLengthIndices[1] / TEXCOORD_SIZE); if ((uvFromAndLengthIndices[1] % TEXCOORD_SIZE) > 0) { numTexCoords++; } if (numTexCoords > 0) { for (int i = 0; i < numTexCoords; i++) { int texCoordOffset = (startTexCoord + i) * TEXCOORD_SIZE; MeshGeomComp2VB mt2vb = (MeshGeomComp2VB) texCoord2vbMap.get(texCoordOffset); assert mt2vb != null; // mt2vb shouldn't be null. We can't have a texCoord referred by // the faces array that isn't in the vertexBuffer. if (mt2vb != null) { int[] locs = mt2vb.getLocs(); int validLocs = mt2vb.getValidLocs(); if (locs != null) { for (int j = 0; j < validLocs; j++) { int vbIndex = (locs[j] * VERTEX_SIZE_VB) + POINT_SIZE_VB; vertexBuffer[vbIndex] = uv[texCoordOffset]; vertexBuffer[vbIndex + 1] = uv[texCoordOffset + 1]; } } else { int loc = mt2vb.getLoc(); int vbIndex = (loc * VERTEX_SIZE_VB) + POINT_SIZE_VB; vertexBuffer[vbIndex] = uv[texCoordOffset]; vertexBuffer[vbIndex + 1] = uv[texCoordOffset + 1]; } } } } // Find out the list of modified points int startPoint = posFromAndLengthIndices[0] / POINT_SIZE; int numPoints = (posFromAndLengthIndices[1] / POINT_SIZE); if ((posFromAndLengthIndices[1] % POINT_SIZE) > 0) { numPoints++; } if (numPoints > 0) { for (int i = 0; i < numPoints; i++) { int pointOffset = (startPoint + i) * POINT_SIZE; MeshGeomComp2VB mp2vb = (MeshGeomComp2VB) point2vbMap.get(pointOffset); assert mp2vb != null; // mp2vb shouldn't be null. We can't have a point referred by // the faces array that isn't in the vertexBuffer. if (mp2vb != null) { int[] locs = mp2vb.getLocs(); int validLocs = mp2vb.getValidLocs(); if (locs != null) { for (int j = 0; j < validLocs; j++) { int vbIndex = locs[j] * VERTEX_SIZE_VB; vertexBuffer[vbIndex] = pos[pointOffset]; vertexBuffer[vbIndex + 1] = pos[pointOffset + 1]; vertexBuffer[vbIndex + 2] = pos[pointOffset + 2]; } } else { int loc = mp2vb.getLoc(); int vbIndex = loc * VERTEX_SIZE_VB; vertexBuffer[vbIndex] = pos[pointOffset]; vertexBuffer[vbIndex + 1] = pos[pointOffset + 1]; vertexBuffer[vbIndex + 2] = pos[pointOffset + 2]; } } } } if (indexBuffer != null) { return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBuffer, nFaces * 3); } else { return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, nFaces * 3); } } private boolean[] dirtyVertices; private float[] cachedNormals; private float[] cachedTangents; private float[] cachedBitangents; private float[] vertexBuffer; private int[] indexBuffer; private short[] indexBufferShort; private int indexBufferSize; private int numberOfVertices; private HashMap point2vbMap; private HashMap normal2vbMap; private HashMap texCoord2vbMap; private boolean buildSkipMeshNormalGeometry() { HashMap face2vbMap = new HashMap(); if (point2vbMap == null) { point2vbMap = new HashMap(); } else { point2vbMap.clear(); } if (texCoord2vbMap == null) { texCoord2vbMap = new HashMap(); } else { texCoord2vbMap.clear(); } Integer mf2vb; BaseMesh.MeshGeomComp2VB mp2vb; BaseMesh.MeshGeomComp2VB mt2vb; vertexBuffer = new float[nVerts * VERTEX_SIZE_VB]; indexBuffer = new int[nFaces * 3]; int ibCount = 0; int vbCount = 0; for (int faceCount = 0; faceCount < nFaces; faceCount++) { int faceIndex = faceCount * 6; for (int i = 0; i < 3; i++) { int vertexIndex = i * 2; int pointIndex = faceIndex + vertexIndex; int texCoordIndex = pointIndex + 1; long key = (long) ((long) (faces[pointIndex]) << 32 | faces[texCoordIndex]); mf2vb = (Integer) face2vbMap.get(key); if (mf2vb == null) { mf2vb = vbCount / VERTEX_SIZE_VB; face2vbMap.put(key, mf2vb); if (vertexBuffer.length <= vbCount) { float[] temp = new float[vbCount + 10 * VERTEX_SIZE_VB]; // Let's increment by 10 System.arraycopy(vertexBuffer, 0, temp, 0, vertexBuffer.length); vertexBuffer = temp; } int pointOffset = faces[pointIndex] * POINT_SIZE; int texCoordOffset = faces[texCoordIndex] * TEXCOORD_SIZE; vertexBuffer[vbCount] = pos[pointOffset]; vertexBuffer[vbCount + 1] = pos[pointOffset + 1]; vertexBuffer[vbCount + 2] = pos[pointOffset + 2]; vertexBuffer[vbCount + 3] = uv[texCoordOffset]; vertexBuffer[vbCount + 4] = uv[texCoordOffset + 1]; vertexBuffer[vbCount + 5] = 0; vertexBuffer[vbCount + 6] = 0; vertexBuffer[vbCount + 7] = 0; vertexBuffer[vbCount + 8] = 0; vbCount += VERTEX_SIZE_VB; mp2vb = point2vbMap.get(pointOffset); if (mp2vb == null) { // create mp2vb = new MeshGeomComp2VB(pointOffset, mf2vb); point2vbMap.put(pointOffset, mp2vb); } else { // addLoc mp2vb.addLoc(mf2vb); } mt2vb = texCoord2vbMap.get(texCoordOffset); if (mt2vb == null) { // create mt2vb = new MeshGeomComp2VB(texCoordOffset, mf2vb); texCoord2vbMap.put(texCoordOffset, mt2vb); } else { // addLoc mt2vb.addLoc(mf2vb); } } // Construct IndexBuffer indexBuffer[ibCount++] = mf2vb; } } numberOfVertices = vbCount / VERTEX_SIZE_VB; if (numberOfVertices > 0x10000) { // > 64K return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBuffer, nFaces * 3); } else { if (indexBufferShort == null || indexBufferShort.length < nFaces * 3) { indexBufferShort = new short[nFaces * 3]; } int ii = 0; for (int i = 0; i < nFaces; i++) { indexBufferShort[ii] = (short) indexBuffer[ii++]; indexBufferShort[ii] = (short) indexBuffer[ii++]; indexBufferShort[ii] = (short) indexBuffer[ii++]; } indexBuffer = null; // free return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, nFaces * 3); } } private void convertNormalsToQuats(MeshTempState instance, int numberOfVertices, float[] normals, float[] tangents, float[] bitangents, float[] vertexBuffer, boolean[] dirtys) { Vec3f normal = instance.vec3f1; Vec3f tangent = instance.vec3f2; Vec3f bitangent = instance.vec3f3; for (int i = 0, vbIndex = 0; i < numberOfVertices; i++, vbIndex += VERTEX_SIZE_VB) { // Note: If dirtys isn't null, dirtys.length == numberOfVertices is true if (dirtys == null || dirtys[i]) { int index = i * NORMAL_SIZE; normal.x = normals[index]; normal.y = normals[index + 1]; normal.z = normals[index + 2]; normal.normalize(); // tangent and bitangent have been normalized. tangent.x = tangents[index]; tangent.y = tangents[index + 1]; tangent.z = tangents[index + 2]; bitangent.x = bitangents[index]; bitangent.y = bitangents[index + 1]; bitangent.z = bitangents[index + 2]; instance.triNormals[0].set(normal); instance.triNormals[1].set(tangent); instance.triNormals[2].set(bitangent); MeshUtil.fixTSpace(instance.triNormals); buildVSQuat(instance.triNormals, instance.quat); vertexBuffer[vbIndex + 5] = instance.quat.x; vertexBuffer[vbIndex + 6] = instance.quat.y; vertexBuffer[vbIndex + 7] = instance.quat.z; vertexBuffer[vbIndex + 8] = instance.quat.w; } } } // Build PointNormalTexCoordGeometry private boolean doBuildPNTGeometry(float[] points, float[] normals, float[] texCoords, int[] faces) { if (point2vbMap == null) { point2vbMap = new HashMap(); } else { point2vbMap.clear(); } if (normal2vbMap == null) { normal2vbMap = new HashMap(); } else { normal2vbMap.clear(); } if (texCoord2vbMap == null) { texCoord2vbMap = new HashMap(); } else { texCoord2vbMap.clear(); } int vertexIndexSize = VertexFormat.POINT_NORMAL_TEXCOORD.getVertexIndexSize(); int faceIndexSize = vertexIndexSize * 3; int pointIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getPointIndexOffset(); int normalIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getNormalIndexOffset(); int texCoordIndexOffset = VertexFormat.POINT_NORMAL_TEXCOORD.getTexCoordIndexOffset(); int numPoints = points.length / POINT_SIZE; int numNormals = normals.length / NORMAL_SIZE; int numTexCoords = texCoords.length / TEXCOORD_SIZE; int numFaces = faces.length / faceIndexSize; assert numPoints > 0 && numNormals > 0 && numTexCoords > 0 && numFaces > 0; Integer mf2vb; BaseMesh.MeshGeomComp2VB mp2vb; BaseMesh.MeshGeomComp2VB mn2vb; BaseMesh.MeshGeomComp2VB mt2vb; // Allocate an initial size, may grow as we process the faces array. cachedNormals = new float[numPoints * NORMAL_SIZE]; cachedTangents = new float[numPoints * NORMAL_SIZE]; cachedBitangents = new float[numPoints * NORMAL_SIZE]; vertexBuffer = new float[numPoints * VERTEX_SIZE_VB]; indexBuffer = new int[numFaces * 3]; int ibCount = 0; int vbCount = 0; MeshTempState instance = MeshTempState.getInstance(); for (int i = 0; i < 3; i++) { if (instance.triPoints[i] == null) { instance.triPoints[i] = new Vec3f(); } if (instance.triTexCoords[i] == null) { instance.triTexCoords[i] = new Vec2f(); } } for (int faceCount = 0; faceCount < numFaces; faceCount++) { int faceIndex = faceCount * faceIndexSize; for (int i = 0; i < 3; i++) { int vertexIndex = faceIndex + (i * vertexIndexSize); int pointIndex = vertexIndex + pointIndexOffset; int normalIndex = vertexIndex + normalIndexOffset; int texCoordIndex = vertexIndex + texCoordIndexOffset; mf2vb = vbCount / VERTEX_SIZE_VB; if (vertexBuffer.length <= vbCount) { final int incrementedSize = vbCount + 20; // Let's increment by 20 float[] temp = new float[incrementedSize * VERTEX_SIZE_VB]; System.arraycopy(vertexBuffer, 0, temp, 0, vertexBuffer.length); vertexBuffer = temp; // Enlarge cachedNormals, cachedTangents and cachedBitangents too temp = new float[incrementedSize * 3]; System.arraycopy(cachedNormals, 0, temp, 0, cachedNormals.length); cachedNormals = temp; temp = new float[incrementedSize * 3]; System.arraycopy(cachedTangents, 0, temp, 0, cachedTangents.length); cachedTangents = temp; temp = new float[incrementedSize * 3]; System.arraycopy(cachedBitangents, 0, temp, 0, cachedBitangents.length); cachedBitangents = temp; } int pointOffset = faces[pointIndex] * POINT_SIZE; int normalOffset = faces[normalIndex] * NORMAL_SIZE; int texCoordOffset = faces[texCoordIndex] * TEXCOORD_SIZE; // Save the vertex of triangle instance.triPointIndex[i] = pointOffset; instance.triTexCoordIndex[i] = texCoordOffset; instance.triVerts[i] = vbCount / VERTEX_SIZE_VB; vertexBuffer[vbCount] = points[pointOffset]; vertexBuffer[vbCount + 1] = points[pointOffset + 1]; vertexBuffer[vbCount + 2] = points[pointOffset + 2]; vertexBuffer[vbCount + 3] = texCoords[texCoordOffset]; vertexBuffer[vbCount + 4] = texCoords[texCoordOffset + 1]; // Store the normal in the cachedNormals array int index = instance.triVerts[i] * NORMAL_SIZE; cachedNormals[index] = normals[normalOffset]; cachedNormals[index + 1] = normals[normalOffset + 1]; cachedNormals[index + 2] = normals[normalOffset + 2]; vbCount += VERTEX_SIZE_VB; mp2vb = point2vbMap.get(pointOffset); if (mp2vb == null) { // create mp2vb = new MeshGeomComp2VB(pointOffset, mf2vb); point2vbMap.put(pointOffset, mp2vb); } else { // addLoc mp2vb.addLoc(mf2vb); } mn2vb = normal2vbMap.get(normalOffset); if (mn2vb == null) { // create mn2vb = new MeshGeomComp2VB(normalOffset, mf2vb); normal2vbMap.put(normalOffset, mn2vb); } else { // addLoc mn2vb.addLoc(mf2vb); } mt2vb = texCoord2vbMap.get(texCoordOffset); if (mt2vb == null) { // create mt2vb = new MeshGeomComp2VB(texCoordOffset, mf2vb); texCoord2vbMap.put(texCoordOffset, mt2vb); } else { // addLoc mt2vb.addLoc(mf2vb); } // Construct IndexBuffer indexBuffer[ibCount++] = mf2vb; } // This is the best time to compute the tangent and bitangent for each // of the vertex. Go thro. the 3 vertices of a triangle for (int i = 0; i < 3; i++) { instance.triPoints[i].x = points[instance.triPointIndex[i]]; instance.triPoints[i].y = points[instance.triPointIndex[i] + 1]; instance.triPoints[i].z = points[instance.triPointIndex[i] + 2]; instance.triTexCoords[i].x = texCoords[instance.triTexCoordIndex[i]]; instance.triTexCoords[i].y = texCoords[instance.triTexCoordIndex[i] + 1]; } MeshUtil.computeTBNNormalized(instance.triPoints[0], instance.triPoints[1], instance.triPoints[2], instance.triTexCoords[0], instance.triTexCoords[1], instance.triTexCoords[2], instance.triNormals); for (int i = 0; i < 3; i++) { int index = instance.triVerts[i] * NORMAL_SIZE; cachedTangents[index] = instance.triNormals[1].x; cachedTangents[index + 1] = instance.triNormals[1].y; cachedTangents[index + 2] = instance.triNormals[1].z; cachedBitangents[index] = instance.triNormals[2].x; cachedBitangents[index + 1] = instance.triNormals[2].y; cachedBitangents[index + 2] = instance.triNormals[2].z; } } numberOfVertices = vbCount / VERTEX_SIZE_VB; convertNormalsToQuats(instance, numberOfVertices, cachedNormals, cachedTangents, cachedBitangents, vertexBuffer, null); indexBufferSize = numFaces * 3; if (numberOfVertices > 0x10000) { // > 64K return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBuffer, indexBufferSize); } else { if (indexBufferShort == null || indexBufferShort.length < indexBufferSize) { indexBufferShort = new short[indexBufferSize]; } int ii = 0; for (int i = 0; i < numFaces; i++) { indexBufferShort[ii] = (short) indexBuffer[ii++]; indexBufferShort[ii] = (short) indexBuffer[ii++]; indexBufferShort[ii] = (short) indexBuffer[ii++]; } indexBuffer = null; // free return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, indexBufferSize); } } // Update PointNormalTexCoordGeometry private boolean updatePNTGeometry(float[] points, int[] pointsFromAndLengthIndices, float[] normals, int[] normalsFromAndLengthIndices, float[] texCoords, int[] texCoordsFromAndLengthIndices) { if (dirtyVertices == null) { // Create a dirty array of size equal to number of vertices in vertexBuffer. dirtyVertices = new boolean[numberOfVertices]; } // Clear dirty array before use. Arrays.fill(dirtyVertices, false); // Find out the list of modified points int startPoint = pointsFromAndLengthIndices[0] / POINT_SIZE; int numPoints = (pointsFromAndLengthIndices[1] / POINT_SIZE); if ((pointsFromAndLengthIndices[1] % POINT_SIZE) > 0) { numPoints++; } if (numPoints > 0) { for (int i = 0; i < numPoints; i++) { int pointOffset = (startPoint + i) * POINT_SIZE; MeshGeomComp2VB mp2vb = (MeshGeomComp2VB) point2vbMap.get(pointOffset); assert mp2vb != null; // mp2vb shouldn't be null. We can't have a point referred by // the faces array that isn't in the vertexBuffer. if (mp2vb != null) { int[] locs = mp2vb.getLocs(); int validLocs = mp2vb.getValidLocs(); if (locs != null) { for (int j = 0; j < validLocs; j++) { int vbIndex = locs[j] * VERTEX_SIZE_VB; vertexBuffer[vbIndex] = points[pointOffset]; vertexBuffer[vbIndex + 1] = points[pointOffset + 1]; vertexBuffer[vbIndex + 2] = points[pointOffset + 2]; dirtyVertices[locs[j]] = true; } } else { int loc = mp2vb.getLoc(); int vbIndex = loc * VERTEX_SIZE_VB; vertexBuffer[vbIndex] = points[pointOffset]; vertexBuffer[vbIndex + 1] = points[pointOffset + 1]; vertexBuffer[vbIndex + 2] = points[pointOffset + 2]; dirtyVertices[loc] = true; } } } } // Find out the list of modified tex coords. int startTexCoord = texCoordsFromAndLengthIndices[0] / TEXCOORD_SIZE; int numTexCoords = (texCoordsFromAndLengthIndices[1] / TEXCOORD_SIZE); if ((texCoordsFromAndLengthIndices[1] % TEXCOORD_SIZE) > 0) { numTexCoords++; } if (numTexCoords > 0) { for (int i = 0; i < numTexCoords; i++) { int texCoordOffset = (startTexCoord + i) * TEXCOORD_SIZE; MeshGeomComp2VB mt2vb = (MeshGeomComp2VB) texCoord2vbMap.get(texCoordOffset); assert mt2vb != null; // mt2vb shouldn't be null. We can't have a texCoord referred by // the faces array that isn't in the vertexBuffer. if (mt2vb != null) { int[] locs = mt2vb.getLocs(); int validLocs = mt2vb.getValidLocs(); if (locs != null) { for (int j = 0; j < validLocs; j++) { int vbIndex = (locs[j] * VERTEX_SIZE_VB) + POINT_SIZE_VB; vertexBuffer[vbIndex] = texCoords[texCoordOffset]; vertexBuffer[vbIndex + 1] = texCoords[texCoordOffset + 1]; dirtyVertices[locs[j]] = true; } } else { int loc = mt2vb.getLoc(); int vbIndex = (loc * VERTEX_SIZE_VB) + POINT_SIZE_VB; vertexBuffer[vbIndex] = texCoords[texCoordOffset]; vertexBuffer[vbIndex + 1] = texCoords[texCoordOffset + 1]; dirtyVertices[loc] = true; } } } } // Find out the list of modified normals int startNormal = normalsFromAndLengthIndices[0] / NORMAL_SIZE; int numNormals = (normalsFromAndLengthIndices[1] / NORMAL_SIZE); if ((normalsFromAndLengthIndices[1] % NORMAL_SIZE) > 0) { numNormals++; } if (numNormals > 0) { MeshTempState instance = MeshTempState.getInstance(); for (int i = 0; i < numNormals; i++) { int normalOffset = (startNormal + i) * NORMAL_SIZE; MeshGeomComp2VB mn2vb = (MeshGeomComp2VB) normal2vbMap.get(normalOffset); assert mn2vb != null; // mn2vb shouldn't be null. We can't have a normal referred by // the faces array that isn't in the vertexBuffer. if (mn2vb != null) { int[] locs = mn2vb.getLocs(); int validLocs = mn2vb.getValidLocs(); if (locs != null) { for (int j = 0; j < validLocs; j++) { int index = locs[j] * NORMAL_SIZE; cachedNormals[index] = normals[normalOffset]; cachedNormals[index + 1] = normals[normalOffset + 1]; cachedNormals[index + 2] = normals[normalOffset + 2]; dirtyVertices[locs[j]] = true; } } else { int loc = mn2vb.getLoc(); int index = loc * NORMAL_SIZE; cachedNormals[index] = normals[normalOffset]; cachedNormals[index + 1] = normals[normalOffset + 1]; cachedNormals[index + 2] = normals[normalOffset + 2]; dirtyVertices[loc] = true; } } } } // Prepare process all dirty vertices MeshTempState instance = MeshTempState.getInstance(); for (int i = 0; i < 3; i++) { if (instance.triPoints[i] == null) { instance.triPoints[i] = new Vec3f(); } if (instance.triTexCoords[i] == null) { instance.triTexCoords[i] = new Vec2f(); } } // Every 3 vertices form a triangle for (int j = 0; j < numberOfVertices; j += 3) { // Only process the triangle that has one of more dirty vertices if (dirtyVertices[j] || dirtyVertices[j+1] || dirtyVertices[j+2]) { int vbIndex = j * VERTEX_SIZE_VB; // Go thro. the 3 vertices of a triangle for (int i = 0; i < 3; i++) { instance.triPoints[i].x = vertexBuffer[vbIndex]; instance.triPoints[i].y = vertexBuffer[vbIndex + 1]; instance.triPoints[i].z = vertexBuffer[vbIndex + 2]; instance.triTexCoords[i].x = vertexBuffer[vbIndex + POINT_SIZE_VB]; instance.triTexCoords[i].y = vertexBuffer[vbIndex + POINT_SIZE_VB + 1]; vbIndex += VERTEX_SIZE_VB; } MeshUtil.computeTBNNormalized(instance.triPoints[0], instance.triPoints[1], instance.triPoints[2], instance.triTexCoords[0], instance.triTexCoords[1], instance.triTexCoords[2], instance.triNormals); int index = j * NORMAL_SIZE; for (int i = 0; i < 3; i++) { cachedTangents[index] = instance.triNormals[1].x; cachedTangents[index + 1] = instance.triNormals[1].y; cachedTangents[index + 2] = instance.triNormals[1].z; cachedBitangents[index] = instance.triNormals[2].x; cachedBitangents[index + 1] = instance.triNormals[2].y; cachedBitangents[index + 2] = instance.triNormals[2].z; index += NORMAL_SIZE; } } } convertNormalsToQuats(instance, numberOfVertices, cachedNormals, cachedTangents, cachedBitangents, vertexBuffer, dirtyVertices); if (indexBuffer != null) { return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBuffer, indexBufferSize); } else { return buildNativeGeometry(vertexBuffer, numberOfVertices * VERTEX_SIZE_VB, indexBufferShort, indexBufferSize); } } @Override public boolean buildGeometry(boolean userDefinedNormals, float[] points, int[] pointsFromAndLengthIndices, float[] normals, int[] normalsFromAndLengthIndices, float[] texCoords, int[] texCoordsFromAndLengthIndices, int[] faces, int[] facesFromAndLengthIndices, int[] faceSmoothingGroups, int[] faceSmoothingGroupsFromAndLengthIndices) { if (userDefinedNormals) { return buildPNTGeometry(points, pointsFromAndLengthIndices, normals, normalsFromAndLengthIndices, texCoords, texCoordsFromAndLengthIndices, faces, facesFromAndLengthIndices); } else { return buildPTGeometry(points, pointsFromAndLengthIndices, texCoords, texCoordsFromAndLengthIndices, faces, facesFromAndLengthIndices, faceSmoothingGroups, faceSmoothingGroupsFromAndLengthIndices); } } // Build PointNormalTexCoordGeometry private boolean buildPNTGeometry( float[] points, int[] pointsFromAndLengthIndices, float[] normals, int[] normalsFromAndLengthIndices, float[] texCoords, int[] texCoordsFromAndLengthIndices, int[] faces, int[] facesFromAndLengthIndices) { boolean updatePoints = pointsFromAndLengthIndices[1] > 0; boolean updateNormals = normalsFromAndLengthIndices[1] > 0; boolean updateTexCoords = texCoordsFromAndLengthIndices[1] > 0; boolean updateFaces = facesFromAndLengthIndices[1] > 0; // First time creation boolean buildGeom = !(updatePoints || updateNormals || updateTexCoords || updateFaces); // We will need to rebuild geom buffers if there is a change to faces if (updateFaces) { buildGeom = true; } if ((!buildGeom) && (vertexBuffer != null) && ((indexBuffer != null) || (indexBufferShort != null))) { return updatePNTGeometry(points, pointsFromAndLengthIndices, normals, normalsFromAndLengthIndices, texCoords, texCoordsFromAndLengthIndices); } return doBuildPNTGeometry(points, normals, texCoords, faces); } // Build PointTexCoordGeometry private boolean buildPTGeometry(float[] pos, int[] posFromAndLengthIndices, float[] uv, int[] uvFromAndLengthIndices, int[] faces, int[] facesFromAndLengthIndices, int[] smoothing, int[] smoothingFromAndLengthIndices) { nVerts = pos.length / 3; nTVerts = uv.length / 2; nFaces = faces.length / (VertexFormat.POINT_TEXCOORD.getVertexIndexSize() * 3); assert nVerts > 0 && nFaces > 0 && nTVerts > 0; this.pos = pos; this.uv = uv; this.faces = faces; this.smoothing = smoothing.length == nFaces ? smoothing : null; if (PrismSettings.skipMeshNormalComputation) { boolean updatePoints = posFromAndLengthIndices[1] > 0; boolean updateTexCoords = uvFromAndLengthIndices[1] > 0; boolean updateFaces = facesFromAndLengthIndices[1] > 0; boolean updateSmoothing = smoothingFromAndLengthIndices[1] > 0; // First time creation boolean buildGeom = !(updatePoints || updateTexCoords || updateFaces || updateSmoothing); // We will need to rebuild if there is a change to faces or smoothing if (updateFaces || updateSmoothing) { buildGeom = true; } if ((!buildGeom) && (vertexBuffer != null) && ((indexBuffer != null) || (indexBufferShort != null))) { return updateSkipMeshNormalGeometry(posFromAndLengthIndices, uvFromAndLengthIndices); } return buildSkipMeshNormalGeometry(); } MeshTempState instance = MeshTempState.getInstance(); // big pool for all possible vertices if (instance.pool == null || instance.pool.length < nFaces * 3) { instance.pool = new MeshVertex[nFaces * 3]; } if (instance.indexBuffer == null || instance.indexBuffer.length < nFaces * 3) { instance.indexBuffer = new int[nFaces * 3]; } if (instance.pVertex == null || instance.pVertex.length < nVerts) { instance.pVertex = new MeshVertex[nVerts]; } else { Arrays.fill(instance.pVertex, 0, instance.pVertex.length, null); } // check if all hard edges or all smooth checkSmoothingGroup(); // compute [N, T, B] for each face computeTBNormal(instance.pool, instance.pVertex, instance.indexBuffer); // process sm and weld points int nNewVerts = MeshVertex.processVertices(instance.pVertex, nVerts, allHardEdges, allSameSmoothing); if (instance.vertexBuffer == null || instance.vertexBuffer.length < nNewVerts * VERTEX_SIZE_VB) { instance.vertexBuffer = new float[nNewVerts * VERTEX_SIZE_VB]; } buildVertexBuffer(instance.pVertex, instance.vertexBuffer); if (nNewVerts > 0x10000) { buildIndexBuffer(instance.pool, instance.indexBuffer, null); return buildNativeGeometry(instance.vertexBuffer, nNewVerts * VERTEX_SIZE_VB, instance.indexBuffer, nFaces * 3); } else { if (instance.indexBufferShort == null || instance.indexBufferShort.length < nFaces * 3) { instance.indexBufferShort = new short[nFaces * 3]; } buildIndexBuffer(instance.pool, instance.indexBuffer, instance.indexBufferShort); return buildNativeGeometry(instance.vertexBuffer, nNewVerts * VERTEX_SIZE_VB, instance.indexBufferShort, nFaces * 3); } } private void computeTBNormal(MeshVertex[] pool, MeshVertex[] pVertex, int[] indexBuffer) { MeshTempState instance = MeshTempState.getInstance(); // tmp variables int[] smFace = instance.smFace; int[] triVerts = instance.triVerts; Vec3f[] triPoints = instance.triPoints; Vec2f[] triTexCoords = instance.triTexCoords; Vec3f[] triNormals = instance.triNormals; final String logname = BaseMesh.class.getName(); for (int f = 0, nDeadFaces = 0, poolIndex = 0; f < nFaces; f++) { int index = f * 3; smFace = getFace(f, smFace); // copy from mesh to tmp smFace // Get tex. point. index triVerts[0] = smFace[BaseMesh.FaceMembers.POINT0.ordinal()]; triVerts[1] = smFace[BaseMesh.FaceMembers.POINT1.ordinal()]; triVerts[2] = smFace[BaseMesh.FaceMembers.POINT2.ordinal()]; if (MeshUtil.isDeadFace(triVerts) && PlatformLogger.getLogger(logname).isLoggable(PlatformLogger.Level.FINE)) { // Log degenerated triangle nDeadFaces++; PlatformLogger.getLogger(logname).fine("Dead face [" + triVerts[0] + ", " + triVerts[1] + ", " + triVerts[2] + "] @ face group " + f + "; nEmptyFaces = " + nDeadFaces); } for (int i = 0; i < 3; i++) { triPoints[i] = getVertex(triVerts[i], triPoints[i]); } // Get tex. coord. index triVerts[0] = smFace[BaseMesh.FaceMembers.TEXCOORD0.ordinal()]; triVerts[1] = smFace[BaseMesh.FaceMembers.TEXCOORD1.ordinal()]; triVerts[2] = smFace[BaseMesh.FaceMembers.TEXCOORD2.ordinal()]; for (int i = 0; i < 3; i++) { triTexCoords[i] = getTVertex(triVerts[i], triTexCoords[i]); } MeshUtil.computeTBNNormalized(triPoints[0], triPoints[1], triPoints[2], triTexCoords[0], triTexCoords[1], triTexCoords[2], triNormals); for (int j = 0; j < 3; ++j) { pool[poolIndex] = (pool[poolIndex] == null) ? new MeshVertex() : pool[poolIndex]; for (int i = 0; i < 3; ++i) { pool[poolIndex].norm[i].set(triNormals[i]); } pool[poolIndex].smGroup = smFace[BaseMesh.FaceMembers.SMOOTHING_GROUP.ordinal()]; pool[poolIndex].fIdx = f; pool[poolIndex].tVert = triVerts[j]; pool[poolIndex].index = MeshVertex.IDX_UNDEFINED; int ii = j == 0 ? BaseMesh.FaceMembers.POINT0.ordinal() : j == 1 ? BaseMesh.FaceMembers.POINT1.ordinal() : BaseMesh.FaceMembers.POINT2.ordinal(); int pIdx = smFace[ii]; pool[poolIndex].pVert = pIdx; indexBuffer[index + j] = pIdx; pool[poolIndex].next = pVertex[pIdx]; pVertex[pIdx] = pool[poolIndex]; poolIndex++; } } } private void buildVSQuat(Vec3f[] tm, Quat4f quat) { Vec3f v = MeshTempState.getInstance().vec3f1; v.cross(tm[1], tm[2]); float d = tm[0].dot(v); if (d < 0) { tm[2].mul(-1); } MeshUtil.buildQuat(tm, quat); // This will interfer with degenerated triangle unit test. // assert (quat.w >= 0); if (d < 0) { if (quat.w == 0) { quat.w = MeshUtil.MAGIC_SMALL; } quat.scale(-1); } } private void buildVertexBuffer(MeshVertex[] pVerts, float[] vertexBuffer) { Quat4f quat = MeshTempState.getInstance().quat; int idLast = 0; for (int i = 0, index = 0; i < nVerts; ++i) { MeshVertex v = pVerts[i]; for (; v != null; v = v.next) { if (v.index == idLast) { int ind = v.pVert * 3; vertexBuffer[index++] = pos[ind]; vertexBuffer[index++] = pos[ind + 1]; vertexBuffer[index++] = pos[ind + 2]; ind = v.tVert * 2; vertexBuffer[index++] = uv[ind]; vertexBuffer[index++] = uv[ind + 1]; buildVSQuat(v.norm, quat); vertexBuffer[index++] = quat.x; vertexBuffer[index++] = quat.y; vertexBuffer[index++] = quat.z; vertexBuffer[index++] = quat.w; idLast++; } } } } private void buildIndexBuffer(MeshVertex[] pool, int[] indexBuffer, short[] indexBufferShort) { for (int i = 0; i < nFaces; ++i) { int index = i * 3; if (indexBuffer[index] != MeshVertex.IDX_UNDEFINED) { for (int j = 0; j < 3; ++j) { assert (pool[index].fIdx == i); if (indexBufferShort != null) { indexBufferShort[index + j] = (short) pool[index + j].index; } else { indexBuffer[index + j] = pool[index + j].index; } pool[index + j].next = null; // release reference } } else { for (int j = 0; j < 3; ++j) { if (indexBufferShort != null) { indexBufferShort[index + j] = 0; } else { indexBuffer[index + j] = 0; } } } } } public int getNumVerts() { return nVerts; } public int getNumTVerts() { return nTVerts; } public int getNumFaces() { return nFaces; } public Vec3f getVertex(int pIdx, Vec3f vertex) { if (vertex == null) { vertex = new Vec3f(); } int index = pIdx * 3; vertex.set(pos[index], pos[index + 1], pos[index + 2]); return vertex; } public Vec2f getTVertex(int tIdx, Vec2f texCoord) { if (texCoord == null) { texCoord = new Vec2f(); } int index = tIdx * 2; texCoord.set(uv[index], uv[index + 1]); return texCoord; } private void checkSmoothingGroup() { if (smoothing == null || smoothing.length == 0) { // all smooth allSameSmoothing = true; allHardEdges = false; return; } for (int i = 0; i + 1 < smoothing.length; i++) { if (smoothing[i] != smoothing[i + 1]) { // various SmGroup allSameSmoothing = false; allHardEdges = false; return; } } if (smoothing[0] == 0) { // all hard edges allSameSmoothing = false; allHardEdges = true; } else { // all belongs to one group == all smooth allSameSmoothing = true; allHardEdges = false; } } public int[] getFace(int fIdx, int[] face) { int index = fIdx * 6; if ((face == null) || (face.length < FACE_MEMBERS_SIZE)) { face = new int[FACE_MEMBERS_SIZE]; } // Note: Order matter, [0, 5] == FaceMembers' points and texcoords for (int i = 0; i < 6; i++) { face[i] = faces[index + i]; } // Note: Order matter, 6 == FaceMembers.SMOOTHING_GROUP.ordinal() // There is a total of 32 smoothing groups. // Assign to 1st smoothing group if smoothing is null. face[6] = smoothing != null ? smoothing[fIdx] : 1; return face; } class MeshGeomComp2VB { private final int key; // point or texCoord index private final int loc; // the first index into vertex buffer private int[] locs; private int validLocs; MeshGeomComp2VB(int key, int loc) { assert loc >= 0; this.key = key; this.loc = loc; locs = null; validLocs = 0; } void addLoc(int loc) { if (locs == null) { locs = new int[3]; // edge of mesh case locs[0] = this.loc; locs[1] = loc; this.validLocs = 2; } else if (locs.length > validLocs) { locs[validLocs] = loc; validLocs++; } else { int[] temp = new int[validLocs * 2]; System.arraycopy(locs, 0, temp, 0, locs.length); locs = temp; locs[validLocs] = loc; validLocs++; } } int getKey() { return key; } int getLoc() { return loc; } int[] getLocs() { return locs; } int getValidLocs() { return validLocs; } } }