#region CPL License /* Nuclex Framework Copyright (C) 2002-2008 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using TextVertex = Microsoft.Xna.Framework.Graphics.VertexPositionNormalTexture; namespace Nuclex.Fonts { /// Stores the vertices for an extruded string mesh /// /// The extruded mesh will always be extruded by 1.0 units centered about the /// middle of the extrusion. This allows you to scale the text's extrusion /// level at rendering time for free using the transformation matrix of the /// rendered text. /// public class ExtrudedText : Text { /// Initializes a new extruded string mesh /// Font from which the vertices will be taken /// String of which to build a mesh internal ExtrudedText(VectorFont font, string text) { resizeVertexAndIndexArrays(font, text); buildStringMesh(font, text); } /// Builds the combined mesh for the letters in this string /// Vector font to take the vertex data from /// String of which a mesh is to be built private void buildStringMesh(VectorFont font, string text) { Vector2 position = Vector2.Zero; int baseVertexIndex = 0; // base index in the vertex array int baseIndexIndex = 0; // base index in the index array for(int characterIndex = 0; characterIndex < text.Length; ++characterIndex) { int fontCharacterIndex; // Only add this character to the mesh if there is an actual font character // for this unicode symbol in the font (characters not imported by the user // will be silently skipped -- this is the only sane option imho) if(font.CharacterMap.TryGetValue(text[characterIndex], out fontCharacterIndex)) { VectorFontCharacter character = font.Characters[fontCharacterIndex]; // Import all of the vertices of this font (we need both the outline vertices // as well as the mesh supporting vertices). Each vertex is imported two times, // once with a z coordinate of -0.5 and once with a z coordinate of +0.5 for(int vertexIndex = 0; vertexIndex < character.Vertices.Count; ++vertexIndex) { Vector2 adjustedPosition = character.Vertices[vertexIndex] + position; Vector3 frontPosition = new Vector3(adjustedPosition, -0.5f); Vector3 backPosition = new Vector3(adjustedPosition, +0.5f); Vector2 textureCoordinates = new Vector2( (frontPosition.X / font.LineHeight + frontPosition.Z) / 2.0f, frontPosition.Y / font.LineHeight ); this.vertices[baseVertexIndex + vertexIndex * 2 + 0] = new TextVertex( frontPosition, Vector3.Forward, textureCoordinates ); this.vertices[baseVertexIndex + vertexIndex * 2 + 1] = new TextVertex( backPosition, Vector3.Backward, textureCoordinates ); } // Transform the outline index ranges into single big line list pointing // to the vertices we just imported from the font for(int index = 0; index < character.Outlines.Count; ++index) { VectorFontCharacter.Outline outline = character.Outlines[index]; int startIndex, endIndex; // Set up the indices for the outline exluding the final connection from // the outline's end to its start for(int lineIndex = 0; lineIndex < outline.VertexCount - 1; ++lineIndex) { startIndex = baseVertexIndex + (outline.StartVertexIndex + lineIndex) * 2; endIndex = startIndex + 2; this.indices[baseIndexIndex + lineIndex * 6 + 0] = (short)(startIndex + 0); this.indices[baseIndexIndex + lineIndex * 6 + 1] = (short)(startIndex + 1); this.indices[baseIndexIndex + lineIndex * 6 + 2] = (short)(endIndex + 0); this.indices[baseIndexIndex + lineIndex * 6 + 3] = (short)(endIndex + 0); this.indices[baseIndexIndex + lineIndex * 6 + 4] = (short)(startIndex + 1); this.indices[baseIndexIndex + lineIndex * 6 + 5] = (short)(endIndex + 1); } int lastLineIndex = outline.VertexCount - 1; startIndex = baseVertexIndex + outline.StartVertexIndex * 2; endIndex = startIndex + lastLineIndex * 2; this.indices[baseIndexIndex + lastLineIndex * 6 + 0] = (short)(startIndex + 0); this.indices[baseIndexIndex + lastLineIndex * 6 + 1] = (short)(startIndex + 1); this.indices[baseIndexIndex + lastLineIndex * 6 + 2] = (short)(endIndex + 0); this.indices[baseIndexIndex + lastLineIndex * 6 + 3] = (short)(endIndex + 0); this.indices[baseIndexIndex + lastLineIndex * 6 + 4] = (short)(startIndex + 1); this.indices[baseIndexIndex + lastLineIndex * 6 + 5] = (short)(endIndex + 1); // Advance the index pointer for the next run baseIndexIndex += outline.VertexCount * 6; } // Add the indices for the vertices of font's faces, once for the front face and // once for the back face we generated. for(int faceIndex = 0; faceIndex < character.Faces.Count; ++faceIndex) { this.indices[baseIndexIndex + faceIndex * 6 + 0] = (short)(character.Faces[faceIndex].FirstVertexIndex * 2 + baseVertexIndex); this.indices[baseIndexIndex + faceIndex * 6 + 1] = (short)(character.Faces[faceIndex].SecondVertexIndex * 2 + baseVertexIndex); this.indices[baseIndexIndex + faceIndex * 6 + 2] = (short)(character.Faces[faceIndex].ThirdVertexIndex * 2 + baseVertexIndex); this.indices[baseIndexIndex + faceIndex * 6 + 3] = (short)(character.Faces[faceIndex].FirstVertexIndex * 2 + baseVertexIndex + 1); this.indices[baseIndexIndex + faceIndex * 6 + 4] = (short)(character.Faces[faceIndex].SecondVertexIndex * 2 + baseVertexIndex + 1); this.indices[baseIndexIndex + faceIndex * 6 + 5] = (short)(character.Faces[faceIndex].ThirdVertexIndex * 2 + baseVertexIndex + 1); } // Adjust the base vertex index for the next character baseIndexIndex += character.Faces.Count * 6; baseVertexIndex += character.Vertices.Count * 2; // Update the position to the next character position += character.Advancement; } // if } // for this.width = position.X; this.height = font.LineHeight; this.primitiveType = PrimitiveType.TriangleList; } /// Reserves the required space in the vertex and index arrays /// Font the vertices for the letters will be taken from /// String of which a mesh will be built private void resizeVertexAndIndexArrays(VectorFont font, string text) { int vertexCount = 0; int indexCount = 0; // Count the vertices and indices requires for all characters in the string for(int index = 0; index < text.Length; ++index) { int fontCharacterIndex; // Try to find the current character in the font's character map. If it isn't // there, we'll ignore it, just like the mesh creation routine does. if(font.CharacterMap.TryGetValue(text[index], out fontCharacterIndex)) { VectorFontCharacter character = font.Characters[fontCharacterIndex]; vertexCount += character.Vertices.Count * 2; // multiply by 2 for front and indexCount += character.Faces.Count * 3 * 2; // back face of the mesh // There may be empty characters (characters without a visual representation // in the font, so we need to check this before accessing the outline array if(character.Outlines.Count > 0) { VectorFontCharacter.Outline finalOutline = character.Outlines[character.Outlines.Count - 1]; int outlineVertexCount = finalOutline.StartVertexIndex + finalOutline.VertexCount; indexCount += outlineVertexCount * 6; // 2 triangles each } } } this.vertices = new VertexPositionNormalTexture[vertexCount]; this.indices = new short[indexCount]; } } } // namespace Nuclex.Fonts