public float getLineWidth(CharSequence text) { // This method will probably always be a bit of a maintenance // nightmare since it basis its calculation on a different // routine than the Letters class. The ideal situation would // be to abstract out letter position and size into its own // class that both BitmapFont and Letters could use for // positioning. // If getLineWidth() here ever again returns a different value // than Letters does with the same text then it might be better // just to create a Letters object for the sole purpose of // getting a text size. It's less efficient but at least it // would be accurate. // And here I am mucking around in here again... // // A font character has a few values that are pertinent to the // line width: // xOffset // xAdvance // kerningAmount(nextChar) // // The way BitmapText ultimately works is that the first character // starts with xOffset included (ie: it is rendered at -xOffset). // Its xAdvance is wider to accomodate that initial offset. // The cursor position is advanced by xAdvance each time. // // So, a width should be calculated in a similar way. Start with // -xOffset + xAdvance for the first character and then each subsequent // character is just xAdvance more 'width'. // // The kerning amount from one character to the next affects the // cursor position of that next character and thus the ultimate width // and so must be factored in also. float lineWidth = 0f; float maxLineWidth = 0f; char lastChar = 0; boolean firstCharOfLine = true; // float sizeScale = (float) block.getSize() / charSet.getRenderedSize(); float sizeScale = 1f; for (int i = 0; i < text.length(); i++) { char theChar = text.charAt(i); if (theChar == '\n') { maxLineWidth = Math.max(maxLineWidth, lineWidth); lineWidth = 0f; firstCharOfLine = true; continue; } BitmapCharacter c = charSet.getCharacter((int) theChar); if (c != null) { if (theChar == '\\' && i < text.length() - 1 && text.charAt(i + 1) == '#') { if (i + 5 < text.length() && text.charAt(i + 5) == '#') { i += 5; continue; } else if (i + 8 < text.length() && text.charAt(i + 8) == '#') { i += 8; continue; } } if (!firstCharOfLine) { lineWidth += findKerningAmount(lastChar, theChar) * sizeScale; } else { // The first character needs to add in its xOffset but it // is the only one... and negative offsets = postive width // because we're trying to account for the part that hangs // over the left. So we subtract. lineWidth -= c.getXOffset() * sizeScale; firstCharOfLine = false; } float xAdvance = c.getXAdvance() * sizeScale; // If this is the last character, then we really should have // only add its width. The advance may include extra spacing // that we don't care about. if (i == text.length() - 1) { lineWidth += c.getWidth() * sizeScale; // Since theh width includes the xOffset then we need // to take it out again by adding it, ie: offset the width // we just added by the appropriate amount. lineWidth += c.getXOffset() * sizeScale; } else { lineWidth += xAdvance; } } } return Math.max(maxLineWidth, lineWidth); }
/** * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation * with null checks that would slow down the process even if tangents don't have to be computed. * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos * and norm * * @param maxWeightsPerVert maximum number of weights per vertex * @param mesh the mesh * @param offsetMatrices the offsetMaytrices to apply * @param tb the tangent vertexBuffer */ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { int maxWeightsPerVert = mesh.getMaxNumWeights(); if (maxWeightsPerVert <= 0) { throw new IllegalStateException("Max weights per vert is incorrectly set!"); } int fourMinusMaxWeights = 4 - maxWeightsPerVert; // NOTE: This code assumes the vertex buffer is in bind pose // resetToBind() has been called this frame VertexBuffer vb = mesh.getBuffer(Type.Position); FloatBuffer fvb = (FloatBuffer) vb.getData(); fvb.rewind(); VertexBuffer nb = mesh.getBuffer(Type.Normal); FloatBuffer fnb = (FloatBuffer) nb.getData(); fnb.rewind(); FloatBuffer ftb = (FloatBuffer) tb.getData(); ftb.rewind(); // get boneIndexes and weights for mesh ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); ib.rewind(); wb.rewind(); float[] weights = wb.array(); byte[] indices = ib.array(); int idxWeights = 0; TempVars vars = TempVars.get(); float[] posBuf = vars.skinPositions; float[] normBuf = vars.skinNormals; float[] tanBuf = vars.skinTangents; int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length)); int bufLength = 0; int tanLength = 0; for (int i = iterations - 1; i >= 0; i--) { // read next set of positions and normals from native buffer bufLength = Math.min(posBuf.length, fvb.remaining()); tanLength = Math.min(tanBuf.length, ftb.remaining()); fvb.get(posBuf, 0, bufLength); fnb.get(normBuf, 0, bufLength); ftb.get(tanBuf, 0, tanLength); int verts = bufLength / 3; int idxPositions = 0; // tangents has their own index because of the 4 components int idxTangents = 0; // iterate vertices and apply skinning transform for each effecting bone for (int vert = verts - 1; vert >= 0; vert--) { float nmx = normBuf[idxPositions]; float vtx = posBuf[idxPositions++]; float nmy = normBuf[idxPositions]; float vty = posBuf[idxPositions++]; float nmz = normBuf[idxPositions]; float vtz = posBuf[idxPositions++]; float tnx = tanBuf[idxTangents++]; float tny = tanBuf[idxTangents++]; float tnz = tanBuf[idxTangents++]; // skipping the 4th component of the tangent since it doesn't have to be transformed idxTangents++; float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0; for (int w = maxWeightsPerVert - 1; w >= 0; w--) { float weight = weights[idxWeights]; Matrix4f mat = offsetMatrices[indices[idxWeights++]]; rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight; rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight; rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight; rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight; rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight; } idxWeights += fourMinusMaxWeights; idxPositions -= 3; normBuf[idxPositions] = rnx; posBuf[idxPositions++] = rx; normBuf[idxPositions] = rny; posBuf[idxPositions++] = ry; normBuf[idxPositions] = rnz; posBuf[idxPositions++] = rz; idxTangents -= 4; tanBuf[idxTangents++] = rtx; tanBuf[idxTangents++] = rty; tanBuf[idxTangents++] = rtz; // once again skipping the 4th component of the tangent idxTangents++; } fvb.position(fvb.position() - bufLength); fvb.put(posBuf, 0, bufLength); fnb.position(fnb.position() - bufLength); fnb.put(normBuf, 0, bufLength); ftb.position(ftb.position() - tanLength); ftb.put(tanBuf, 0, tanLength); } vars.release(); vb.updateData(fvb); nb.updateData(fnb); tb.updateData(ftb); }
/** * Merges all geometries in the collection into the output mesh. Does not take into account * materials. * * @param geometries * @param outMesh */ private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) { int[] compsForBuf = new int[VertexBuffer.Type.values().length]; VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length]; int totalVerts = 0; int totalTris = 0; int totalLodLevels = 0; int maxWeights = -1; Mesh.Mode mode = null; for (Geometry geom : geometries) { totalVerts += geom.getVertexCount(); totalTris += geom.getTriangleCount(); totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); if (maxVertCount < geom.getVertexCount()) { maxVertCount = geom.getVertexCount(); } Mesh.Mode listMode; int components; switch (geom.getMesh().getMode()) { case Points: listMode = Mesh.Mode.Points; components = 1; break; case LineLoop: case LineStrip: case Lines: listMode = Mesh.Mode.Lines; components = 2; break; case TriangleFan: case TriangleStrip: case Triangles: listMode = Mesh.Mode.Triangles; components = 3; break; default: throw new UnsupportedOperationException(); } for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); } maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); if (mode != null && mode != listMode) { throw new UnsupportedOperationException( "Cannot combine different" + " primitive types: " + mode + " != " + listMode); } mode = listMode; compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; } outMesh.setMaxNumWeights(maxWeights); outMesh.setMode(mode); if (totalVerts >= 65536) { // make sure we create an UnsignedInt buffer so // we can fit all of the meshes formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; } else { formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort; } // generate output buffers based on retrieved info for (int i = 0; i < compsForBuf.length; i++) { if (compsForBuf[i] == 0) { continue; } Buffer data; if (i == VertexBuffer.Type.Index.ordinal()) { data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); } else { data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); } VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]); vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data); outMesh.setBuffer(vb); } int globalVertIndex = 0; int globalTriIndex = 0; for (Geometry geom : geometries) { Mesh inMesh = geom.getMesh(); if (!isBatch(geom)) { geom.batch(this, globalVertIndex); } int geomVertCount = inMesh.getVertexCount(); int geomTriCount = inMesh.getTriangleCount(); for (int bufType = 0; bufType < compsForBuf.length; bufType++) { VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]); VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]); if (outBuf == null) { continue; } if (VertexBuffer.Type.Index.ordinal() == bufType) { int components = compsForBuf[bufType]; IndexBuffer inIdx = inMesh.getIndicesAsList(); IndexBuffer outIdx = outMesh.getIndexBuffer(); for (int tri = 0; tri < geomTriCount; tri++) { for (int comp = 0; comp < components; comp++) { int idx = inIdx.get(tri * components + comp) + globalVertIndex; outIdx.put((globalTriIndex + tri) * components + comp, idx); } } } else if (VertexBuffer.Type.Position.ordinal() == bufType) { FloatBuffer inPos = (FloatBuffer) inBuf.getData(); FloatBuffer outPos = (FloatBuffer) outBuf.getData(); doCopyBuffer(inPos, globalVertIndex, outPos, 3); } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) { FloatBuffer inPos = (FloatBuffer) inBuf.getData(); FloatBuffer outPos = (FloatBuffer) outBuf.getData(); doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]); if (VertexBuffer.Type.Tangent.ordinal() == bufType) { useTangents = true; } } else { inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); // for (int vert = 0; vert < geomVertCount; vert++) { // int curGlobalVertIndex = globalVertIndex + vert; // inBuf.copyElement(vert, outBuf, curGlobalVertIndex); // } } } globalVertIndex += geomVertCount; globalTriIndex += geomTriCount; } }
public void reshape(ViewPort vp, int w, int h) { // this has no effect at first init but is useful when resizing the canvas with multi views Camera cam = vp.getCamera(); cam.setViewPort(left, right, bottom, top); // resizing the camera to fit the new viewport and saving original dimensions cam.resize(w, h, false); left = cam.getViewPortLeft(); right = cam.getViewPortRight(); top = cam.getViewPortTop(); bottom = cam.getViewPortBottom(); originalWidth = w; originalHeight = h; cam.setViewPort(0, 1, 0, 1); // computing real dimension of the viewport and resizing he camera width = (int) (w * (Math.abs(right - left))); height = (int) (h * (Math.abs(bottom - top))); width = Math.max(1, width); height = Math.max(1, height); cam.resize(width, height, false); cameraInit = true; computeDepth = false; if (renderFrameBuffer == null) { outputBuffer = viewPort.getOutputFrameBuffer(); } Collection<Caps> caps = renderer.getCaps(); // antialiasing on filters only supported in opengl 3 due to depth read problem if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) { renderFrameBufferMS = new FrameBuffer(width, height, numSamples); if (caps.contains(Caps.OpenGL31)) { Texture2D msColor = new Texture2D(width, height, numSamples, Format.RGBA8); Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth); renderFrameBufferMS.setDepthTexture(msDepth); renderFrameBufferMS.setColorTexture(msColor); filterTexture = msColor; depthTexture = msDepth; } else { renderFrameBufferMS.setDepthBuffer(Format.Depth); renderFrameBufferMS.setColorBuffer(Format.RGBA8); } } if (numSamples <= 1 || !caps.contains(Caps.OpenGL31)) { renderFrameBuffer = new FrameBuffer(width, height, 1); renderFrameBuffer.setDepthBuffer(Format.Depth); filterTexture = new Texture2D(width, height, Format.RGBA8); renderFrameBuffer.setColorTexture(filterTexture); } for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) { Filter filter = it.next(); initFilter(filter, vp); } if (renderFrameBufferMS != null) { viewPort.setOutputFrameBuffer(renderFrameBufferMS); } else { viewPort.setOutputFrameBuffer(renderFrameBuffer); } }