commit 6024d7470c4ee4c4ec63c5627eca45fb98b1dc56 Author: Michael Savage <mikejsavage@gmail.com> Date: Tue Jul 21 23:08:26 +0200 Initial commit Diffstat:
.gitignore | | | 4 | ++++ |
Makefile | | | 14 | ++++++++++++++ |
bsp.cc | | | 379 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
bsp.h | | | 208 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
gl.cc | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
gl.h | | | 12 | ++++++++++++ |
int.h | | | 16 | ++++++++++++++++ |
stb_easy_font.h | | | 220 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +*.o +bsp +ttvfs +*.bsp diff --git a/Makefile b/Makefile @@ -0,0 +1,14 @@ +all: bsp + +SRCS = bsp.cc gl.cc +OBJS := $(patsubst %.cc,%.o,$(SRCS)) + +CXXFLAGS += -std=c++11 -O2 -Wall -Wextra +LDFLAGS += -lm -lGL -lglfw +LDFLAGS += -lGLU + +bsp: $(OBJS) + $(CXX) $(OBJS) $(LDFLAGS) -o bsp + +clean: + rm -f bsp $(OBJS) diff --git a/bsp.cc b/bsp.cc @@ -0,0 +1,379 @@ +#include <iostream> +#include <fstream> +#include <math.h> +#include <string.h> + +#include <glm/glm.hpp> + +#include <GL/glu.h> +#include <GLFW/glfw3.h> + +#include "stb_easy_font.h" +#include "gl.h" +#include "bsp.h" +#include "int.h" + +const float EPSILON = 1.0 / 32.0; + +void glterrible() { + printf( "glterrible\n" ); + GLenum err = glGetError(); + while(err!=GL_NO_ERROR) { + const char * error; + + switch(err) { + case GL_INVALID_OPERATION: error="INVALID_OPERATION"; break; + case GL_INVALID_ENUM: error="INVALID_ENUM"; break; + case GL_INVALID_VALUE: error="INVALID_VALUE"; break; + case GL_OUT_OF_MEMORY: error="OUT_OF_MEMORY"; break; + case GL_INVALID_FRAMEBUFFER_OPERATION: error="INVALID_FRAMEBUFFER_OPERATION"; break; + } + + printf( "GL error: %s\n", error ); + err=glGetError(); + } +} + +// TODO: bit twiddling? +bool same_sign( const float a, const float b ) { + return a * b >= 0; +} + +float point_plane_distance( const glm::vec3 & point, const glm::vec3 & normal, float d ) { + return glm::dot( point, normal ) - d; +} + +void BSP::pick_color( const BSP_Face & face ) { + if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_olive033") == 0) { + glColor3f(0, 0.33, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_olive066") == 0) { + glColor3f(0, 0.66, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_olive100") == 0) { + glColor3f(0, 1, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_olive100_fade") == 0) { + glColor3f(0.25, 1, 0.25); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_red100_fade") == 0) { + glColor3f(1, 0.25, 0.25); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_red100") == 0) { + glColor3f(1, 0, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_red066") == 0) { + glColor3f(0.66, 0, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_red033") == 0) { + glColor3f(0.33, 0, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_purple033") == 0) { + glColor3f(0.33, 0, 0.33); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_purple066") == 0) { + glColor3f(0.66, 0, 0.66); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_purple100") == 0) { + glColor3f(1, 0, 1); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_purple100_fade") == 0) { + glColor3f(1, 0.25, 1); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_cyan033") == 0) { + glColor3f(0, 0.33, 0.33); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_cyan066") == 0) { + glColor3f(0, 0.66, 0.66); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_cyan100") == 0) { + glColor3f(0, 1, 1); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/wall_cyan100_fade") == 0) { + glColor3f(0.25, 1, 1); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/struc_lightgrey") == 0) { + //glColor3f(0.85, 0.85, 0.85); + glColor3f(0.15, 0.15, 0.15); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/struc_darkgrey") == 0) { + glColor3f(0.5, 0.5, 0.5); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/sky_black") == 0) { + glColor3f(0, 0, 0); + } else if(strcmp(textures[face.texture].name, "textures/acidwdm2/ink") == 0) { + //glColor3f(0, 0, 0); + glColor3f( 0, 0, 0 ); + } else { + //glColor3f( 1, 1, 1 ); + glColor3f( 0, 0, 0 ); + } +} + +template< typename T > +void BSP::load_lump( u32 & num_ts, T *& ts, BSP_Lump lump ) { + const BSP_Header * const header = reinterpret_cast< BSP_Header * >( contents ); + const BSP_HeaderLump & hl = header->lumps[ lump ]; + + assert( hl.len % sizeof( T ) == 0 ); + + num_ts = hl.len / sizeof( T ); + ts = reinterpret_cast< T * >( contents + hl.off ); + + printf( "%d: ok. off %u len %u num %u\n", lump, hl.off, hl.len, num_ts ); +} + +void BSP::trace_seg_brush( const BSP_Brush & brush, BSP_Intersection & bis ) { + float near = -1.0; + float far = 1.0; + + for( u32 i = 0; i < brush.num_sides; i++ ) { + const BSP_BrushSide & side = brush_sides[ i + brush.init_side ]; + const BSP_Plane & plane = planes[ side.plane ]; + + const float sd = point_plane_distance( bis.start, plane.n, plane.d ); + const float ed = point_plane_distance( bis.end, plane.n, plane.d ); + + // both points infront of plane - we are outside the brush + if( sd > 0 && ed > 0 ) { + return; + } + + // both points behind plane - we intersect with another side + if( sd <= 0 && ed <= 0 ) { + continue; + } + + const bool entering = sd > ed; + const float eps = entering ? -EPSILON : EPSILON; + const float t = ( sd + eps ) / ( sd - ed ); + + if( entering ) { + if( t > near ) { + near = t; + bis.is.plane = &plane; + bis.hit = true; + } + } + else { + far = fminf( t, far ); + } + } + + // TODO if near < 0, near = 0? + if( near < 0 ) printf( "near < 0: %.3f\n", near ); + + if( near < far && near > -1.0 && near < bis.is.t ) { + if( near >= 0 ) { + bis.is.t = near; + } + } +} + +void BSP::trace_seg_leaf( const i32 leaf_idx, BSP_Intersection & bis ) { + const BSP_Leaf & leaf = leaves[ leaf_idx ]; + + for( u32 i = 0; i < leaf.num_brushes; i++ ) { + const BSP_Brush & brush = brushes[ leaf_brushes[ i + leaf.init_brush ] ]; + const BSP_Texture & texture = textures[ brush.texture ]; + + if( texture.content_flags & 1 ) { + trace_seg_brush( brush, bis ); + } + } +} + +void BSP::trace_seg_tree( const i32 node_idx, const glm::vec3 & start, const glm::vec3 & end, const float t1, const float t2, BSP_Intersection & bis ) { + if( node_idx < 0 ) { + trace_seg_leaf( -( node_idx + 1 ), bis ); + return; + } + + const BSP_Node & node = nodes[ node_idx ]; + const BSP_Plane & plane = planes[ node.plane ]; + + const float sd = point_plane_distance( start, plane.n, plane.d ); + const float ed = point_plane_distance( end, plane.n, plane.d ); + + if( same_sign( sd, ed ) ) { + trace_seg_tree( sd >= 0 ? node.pos_child : node.neg_child, start, end, t1, t2, bis ); + return; + } + + const bool pos_to_neg = sd > ed; + const float id = 1 / ( sd - ed ); + const float f1 = ( sd + EPSILON ) * id; + float f2; + + if( pos_to_neg ) { + f2 = ( sd - EPSILON ) * id; + } + else { + f2 = ( sd + EPSILON ) * id; + } + + // clamp f1/f2? + if( f1 < 0 || f1 > 1 || f2 < 0 || f2 > 1 ) printf( "%.2f %.2f\n", f1, f2 ); + + const float m1 = t1 + f1 * ( t2 - t1 ); + const float m2 = t1 + f2 * ( t2 - t1 ); + + const glm::vec3 mid1 = start + f1 * ( end - start ); + const glm::vec3 mid2 = start + f2 * ( end - start ); + + trace_seg_tree( pos_to_neg ? node.pos_child : node.neg_child, start, mid1, t1, m1, bis ); + trace_seg_tree( pos_to_neg ? node.neg_child : node.pos_child, mid2, end, m2, t2, bis ); +} + +bool BSP::trace_seg( const glm::vec3 & start, const glm::vec3 & end, Intersection & is ) { + BSP_Intersection bis = { }; + bis.is.t = 1; + bis.start = start; + bis.end = end; + + trace_seg_tree( 0, start, end, 0, 1, bis ); + + if( bis.hit ) { + bis.is.pos = start + bis.is.t * ( end - start ); + } + is = bis.is; + + return bis.hit; +} + +BSP_Leaf & BSP::position_to_leaf( const glm::vec3 & pos ) { + i32 node_idx = 0; + + do { + const BSP_Node & node = nodes[ node_idx ]; + const BSP_Plane & plane = planes[ node.plane ]; + + const float dist = point_plane_distance( pos, plane.n, plane.d ); + + node_idx = dist >= 0 ? node.pos_child : node.neg_child; + } while( node_idx >= 0 ); + + return leaves[ -( node_idx + 1 ) ]; +} + +void BSP::render_leaf( const BSP_Leaf & leaf ) { + for( u32 i = 0; i < leaf.num_faces; i++ ) { + const BSP_LeafFace & leaf_face = leaf_faces[ i + leaf.init_face ]; + const BSP_Face & face = faces[ leaf_face ]; + + pick_color( face ); + + glVertexPointer( 3, GL_FLOAT, sizeof( BSP_Vertex ), &vertices[ face.init_vert ].pos ); + glDrawElements( GL_TRIANGLES, face.num_mesh_verts, GL_UNSIGNED_INT, &mesh_verts[ face.init_mesh_vert ] ); + } +} + +BSP::BSP( const std::string filename ) { + std::ifstream file( filename, std::ifstream::binary ); + + assert( file.is_open() ); + + file.seekg( 0, file.end ); + ssize_t len = file.tellg(); + file.seekg( 0, file.beg ); + + contents = new char[ len ]; + file.read( contents, len ); + + assert( ( file.rdstate() & std::ifstream::failbit ) == 0 ); + + file.close(); + + load_lump( num_textures, textures, LUMP_TEXTURES ); + load_lump( num_planes, planes, LUMP_PLANES ); + load_lump( num_nodes, nodes, LUMP_NODES ); + load_lump( num_leaves, leaves, LUMP_LEAVES ); + load_lump( num_leaf_faces, leaf_faces, LUMP_LEAFFACES ); + load_lump( num_leaf_brushes, leaf_brushes, LUMP_LEAFBRUSHES ); + load_lump( num_brushes, brushes, LUMP_BRUSHES ); + load_lump( num_brush_sides, brush_sides, LUMP_BRUSHSIDES ); + load_lump( num_vertices, vertices, LUMP_VERTICES ); + load_lump( num_mesh_verts, mesh_verts, LUMP_MESHVERTS ); + load_lump( num_faces, faces, LUMP_FACES ); + load_lump( num_vis, vis, LUMP_VISIBILITY ); +} + +BSP::~BSP() { + delete contents; +} + +void BSP::render( const glm::vec3 & camera ) { + // const i32 cluster = position_to_leaf( camera ).cluster; + + for( u32 i = 0; i < num_leaves; i++ ) { + const BSP_Leaf & leaf = leaves[ i ]; + // const i32 other_cluster = leaf.cluster; + // const i32 vis_idx = cluster * num_visdata + other_cluster / 8; + + // printf( "%d\n", vis_idx ); + + render_leaf( leaf ); + } +} + +glm::vec3 d2r( const glm::vec3 & degrees ) { + return degrees * static_cast< float >( M_PI / 180 ); +} + +glm::vec3 angles_to_vector( const glm::vec3 & angles ) { + return glm::vec3( + -sin( angles.y ) * sin( angles.x ), + -cos( angles.y ) * sin( angles.x ), + -cos( angles.x ) + ); +} + +int main() { + BSP bsp( "acidwdm2.bsp" ); + GLFWwindow * const window = GL::init(); + + const glm::vec3 start( 0, -100, 450 ); + const glm::vec3 angles( -90, 135, 0 ); + const glm::vec3 end = start + angles_to_vector( d2r( angles ) ) * 1000.0f; + + Intersection tt; + bool hit = bsp.trace_seg( start, end, tt ); + + printf( "%.3f: %s %.1f %.1f %.1f\n", tt.t, hit ? "yes" : "no", tt.pos.x, tt.pos.y, tt.pos.z ); + + while( !glfwWindowShouldClose( window ) ) { + float now = glfwGetTime(); + + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + glLoadIdentity(); + + // TODO: do matrices like a big boy + gluPerspective( 120.0f, 800.0f / 600.0f, 0.1f, 10000.0f ); + glRotatef( angles.x, 1.0, 0.0, 0.0 ); + glRotatef( angles.y, 0.0, 0.0, 1.0 ); + glTranslatef( -start.x, -start.y, -start.z ); + + bsp.render( start ); + + const glm::vec3 test( 353.553, -453.553, 450.000 ); + glColor3f( 1, 1, 1 ); + glLineWidth( 2 ); + glBegin( GL_LINES ); + glVertex3f( test.x, test.y, test.z + 10 ); + glVertex3f( test.x, test.y, test.z - 10 ); + + glVertex3f( test.x, test.y + 10, test.z ); + glVertex3f( test.x, test.y - 10, test.z ); + + glVertex3f( test.x + 10, test.y, test.z ); + glVertex3f( test.x - 10, test.y, test.z ); + glEnd(); + + glLoadIdentity(); + + glBegin( GL_TRIANGLE_STRIP ); + glColor3f( 0.2, 0.2, 0.2 ); + glVertex2f( -1, 1 ); + glVertex2f( 1, 1 ); + glVertex2f( -1, 0.95 ); + glVertex2f( 1, 0.95 ); + glEnd(); + + static char buffer[99999]; // ~500 chars + int num_quads = stb_easy_font_print(2, 2, "hello", NULL, buffer, sizeof(buffer)); + + glOrtho( 0, 640, 480, 0, -1, 1 ); + glColor3f(1,1,1); + glVertexPointer(2, GL_FLOAT, 16, buffer); + glDrawArrays(GL_QUADS, 0, num_quads*4); + + glfwSwapBuffers( window ); + glfwPollEvents(); + } + + GL::term(); + + return 0; +} diff --git a/bsp.h b/bsp.h @@ -0,0 +1,208 @@ +#ifndef _BSP_H_ +#define _BSP_H_ + +#include "int.h" + +enum BSP_Lump { + LUMP_ENTITIES = 0, + LUMP_TEXTURES, + LUMP_PLANES, + LUMP_NODES, + LUMP_LEAVES, + LUMP_LEAFFACES, + LUMP_LEAFBRUSHES, + LUMP_MODELS, + LUMP_BRUSHES, + LUMP_BRUSHSIDES, + LUMP_VERTICES, + LUMP_MESHVERTS, + LUMP_FOGS, + LUMP_FACES, + LUMP_LIGHTING, + LUMP_LIGHTGRID, + LUMP_VISIBILITY, + LUMP_LIGHTARRAY, + LUMP_MAX, +}; + +struct BSP_HeaderLump { + u32 off; + u32 len; +}; + +struct BSP_Header { + char ibsp[ 4 ]; + u32 version; + + BSP_HeaderLump lumps[ LUMP_MAX ]; +}; + +struct BSP_Texture { + char name[ 64 ]; + i32 surface_flags; + i32 content_flags; +}; + +struct BSP_Plane { + glm::vec3 n; + float d; +}; + +struct BSP_Node { + u32 plane; + + i32 pos_child; + i32 neg_child; + + i32 mins[ 3 ]; + i32 maxs[ 3 ]; +}; + +struct BSP_Leaf { + i32 cluster; + i32 area; + + i32 mins[ 3 ]; + i32 maxs[ 3 ]; + + u32 init_face; + u32 num_faces; + + u32 init_brush; + u32 num_brushes; +}; + +typedef u32 BSP_LeafFace; +typedef u32 BSP_LeafBrush; + +// LUMP_MODELS + +struct BSP_Brush { + u32 init_side; + u32 num_sides; + + i32 texture; +}; + +struct BSP_BrushSide { + u32 plane; + i32 texture; +}; + +struct BSP_Vertex { + glm::vec3 pos; + float uv[ 2 ][ 2 ]; + glm::vec3 normal; + u8 rgba[ 4 ]; +}; + +typedef i32 BSP_MeshVert; + +// LUMP_FOG + +struct BSP_Face { + i32 texture; + i32 effect; + i32 type; + + i32 init_vert; + i32 num_verts; + i32 init_mesh_vert; + i32 num_mesh_verts; + + i32 lightmap; + i32 lmpos[ 2 ]; + i32 lmsize[ 2 ]; + glm::vec3 lmorigin; + glm::vec3 a; + glm::vec3 b; + + glm::vec3 normal; + + i32 c; i32 d; +}; + +// LUMP_LIGHTING +// LUMP_LIGHTGRID + +struct BSP_Vis { + u8 visdata; +}; + +// LUMP_LIGHTARRAY + +struct Intersection { + const BSP_Plane * plane; + glm::vec3 pos; + float t; +}; + +struct BSP_Intersection { + Intersection is; + glm::vec3 start; + glm::vec3 end; + bool hit; +}; + +class BSP { +private: + char * contents; + + u32 num_textures; + BSP_Texture * textures; + + u32 num_planes; + BSP_Plane * planes; + + u32 num_nodes; + BSP_Node * nodes; + + u32 num_leaves; + BSP_Leaf * leaves; + + u32 num_leaf_faces; + BSP_LeafFace * leaf_faces; + + u32 num_leaf_brushes; + BSP_LeafBrush * leaf_brushes; + + u32 num_brushes; + BSP_Brush * brushes; + + u32 num_brush_sides; + BSP_BrushSide * brush_sides; + + u32 num_vertices; + BSP_Vertex * vertices; + + u32 num_mesh_verts; + BSP_MeshVert * mesh_verts; + + u32 num_faces; + BSP_Face * faces; + + u32 num_vis; + BSP_Vis * vis; + + void pick_color( const BSP_Face & face ); + + template< typename T > void load_lump( u32 & num_ts, T *& ts, BSP_Lump lump ); + + void trace_seg_brush( const BSP_Brush & brush, BSP_Intersection & bis ); + void trace_seg_leaf( const i32 leaf_idx, BSP_Intersection & bis ); + void trace_seg_tree( const i32 node_idx, const glm::vec3 & start, const glm::vec3 & end, const float t1, const float t2, BSP_Intersection & bis ); + + BSP_Leaf & position_to_leaf( const glm::vec3 & pos ); + + void render_leaf( const BSP_Leaf & leaf ); + +public: + BSP( std::string filename ); + ~BSP(); + + bool trace_seg( const glm::vec3 & start, const glm::vec3 & end, Intersection & is ); + + void render( const glm::vec3 & camera ); +}; + +#endif // _BSP_H_ diff --git a/gl.cc b/gl.cc @@ -0,0 +1,58 @@ +#include <err.h> +#include <GLFW/glfw3.h> + +#include "gl.h" + +static const int WIDTH = 640; +static const int HEIGHT = 480; + +static const int BITS_RED = 8; +static const int BITS_GREEN = 8; +static const int BITS_BLUE = 8; +static const int BITS_ALPHA = 8; +static const int BITS_DEPTH = 24; +static const int BITS_STENCIL = 8; + +void error_handler( const int code, const char * const message ) { + warnx( "GLFW error %d: %s", code, message ); +} + +GLFWwindow * GL::init() { + glfwSetErrorCallback( error_handler ); + + if( !glfwInit() ) { + errx( 1, "glfwInit" ); + } + + glfwWindowHint( GLFW_RESIZABLE, 0 ); + + GLFWwindow * const window = glfwCreateWindow( WIDTH, HEIGHT, "bsp", nullptr, nullptr ); + if( !window ) { + errx( 1, "glfwCreateWindow" ); + } + + glfwMakeContextCurrent( window ); + + // TODO: do matrices like a big boy + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + + // TODO: do matrices like a big boy + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + + glEnable( GL_CULL_FACE ); + glCullFace( GL_FRONT ); + + glEnableClientState( GL_VERTEX_ARRAY ); + + return window; +} + +void GL::term() { + glfwTerminate(); +} + diff --git a/gl.h b/gl.h @@ -0,0 +1,12 @@ +#ifndef _GL_H_ +#define _GL_H_ + +#include <GLFW/glfw3.h> + +class GL { +public: + static GLFWwindow * init(); + static void term(); +}; + +#endif // _GL_H_ diff --git a/int.h b/int.h @@ -0,0 +1,16 @@ +#ifndef _INT_H_ +#define _INT_H_ + +#include <cstdint> + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#endif // _INT_H_ diff --git a/stb_easy_font.h b/stb_easy_font.h @@ -0,0 +1,220 @@ +// stb_easy_font.h - v0.5 - bitmap font for 3D rendering - public domain +// Sean Barrett, Feb 2015 +// +// Easy-to-deploy, +// reasonably compact, +// extremely inefficient performance-wise, +// crappy-looking, +// ASCII-only, +// bitmap font for use in 3D APIs. +// +// Intended for when you just want to get some text displaying +// in a 3D app as quickly as possible. +// +// Doesn't use any textures, instead builds characters out of quads. +// +// DOCUMENTATION: +// +// int stb_easy_font_width(char *text) +// +// Takes a string without newlines and returns the horizontal size. +// +// int stb_easy_font_print(float x, float y, +// char *text, unsigned char color[4], +// void *vertex_buffer, int vbuf_size) +// +// Takes a string (which can contain '\n') and fills out a +// vertex buffer with renderable data to draw the string. +// Output data assumes increasing x is rightwards, increasing y +// is downwards. +// +// The vertex data is divided into quads, i.e. there are four +// vertices in the vertex buffer for each quad. +// +// The vertices are stored in an interleaved format: +// +// x:float +// y:float +// z:float +// color:uint8[4] +// +// You can ignore z and color if you get them from elsewhere +// This format was chosen in the hopes it would make it +// easier for you to reuse existing buffer-drawing code. +// +// If you pass in NULL for color, it becomes 255,255,255,255. +// +// Returns the number of quads. +// +// If the buffer isn't large enough, it will truncate. +// Expect it to use an average of ~270 bytes per character. +// +// If your API doesn't draw quads, build a reusable index +// list that allows you to render quads as indexed triangles. +// +// void stb_easy_font_spacing(float spacing) +// +// Use positive values to expand the space between characters, +// and small negative values (no smaller than -1.5) to contract +// the space between characters. +// +// E.g. spacing = 1 adds one "pixel" of spacing between the +// characters. spacing = -1 is reasonable but feels a bit too +// compact to me; -0.5 is a reasonable compromise as long as +// you're scaling the font up. +// +// SAMPLE CODE: +// +// Here's sample code for old OpenGL; it's a lot more complicated +// to make work on modern APIs, and that's your problem. +// +#if 0 +void print_string(float x, float y, char *text, float r, float g, float b) +{ + static char buffer[99999]; // ~500 chars + int num_quads; + + num_quads = stb_easy_font_print(x, y, text, NULL, buffer, sizeof(buffer)); + + glColor3f(r,g,b); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 16, buffer); + glDrawArrays(GL_QUADS, 0, num_quads*4); + glDisableClientState(GL_VERTEX_ARRAY); +} +#endif + +#ifndef INCLUDE_STB_EASY_FONT_H +#define INCLUDE_STB_EASY_FONT_H + +#include <stdlib.h> + +struct { + unsigned char advance; + unsigned char h_seg; + unsigned char v_seg; +} stb_easy_font_charinfo[96] = { + { 5, 0, 0 }, { 3, 0, 0 }, { 5, 1, 1 }, { 7, 1, 4 }, + { 7, 3, 7 }, { 7, 6, 12 }, { 7, 8, 19 }, { 4, 16, 21 }, + { 4, 17, 22 }, { 4, 19, 23 }, { 23, 21, 24 }, { 23, 22, 31 }, + { 20, 23, 34 }, { 22, 23, 36 }, { 19, 24, 36 }, { 21, 25, 36 }, + { 6, 25, 39 }, { 6, 27, 43 }, { 6, 28, 45 }, { 6, 30, 49 }, + { 6, 33, 53 }, { 6, 34, 57 }, { 6, 40, 58 }, { 6, 46, 59 }, + { 6, 47, 62 }, { 6, 55, 64 }, { 19, 57, 68 }, { 20, 59, 68 }, + { 21, 61, 69 }, { 22, 66, 69 }, { 21, 68, 69 }, { 7, 73, 69 }, + { 9, 75, 74 }, { 6, 78, 81 }, { 6, 80, 85 }, { 6, 83, 90 }, + { 6, 85, 91 }, { 6, 87, 95 }, { 6, 90, 96 }, { 7, 92, 97 }, + { 6, 96,102 }, { 5, 97,106 }, { 6, 99,107 }, { 6,100,110 }, + { 6,100,115 }, { 7,101,116 }, { 6,101,121 }, { 6,101,125 }, + { 6,102,129 }, { 7,103,133 }, { 6,104,140 }, { 6,105,145 }, + { 7,107,149 }, { 6,108,151 }, { 7,109,155 }, { 7,109,160 }, + { 7,109,165 }, { 7,118,167 }, { 6,118,172 }, { 4,120,176 }, + { 6,122,177 }, { 4,122,181 }, { 23,124,182 }, { 22,129,182 }, + { 4,130,182 }, { 22,131,183 }, { 6,133,187 }, { 22,135,191 }, + { 6,137,192 }, { 22,139,196 }, { 5,144,197 }, { 22,147,198 }, + { 6,150,202 }, { 19,151,206 }, { 21,152,207 }, { 6,155,209 }, + { 3,160,210 }, { 23,160,211 }, { 22,164,216 }, { 22,165,220 }, + { 22,167,224 }, { 22,169,228 }, { 21,171,232 }, { 21,173,233 }, + { 5,178,233 }, { 22,179,234 }, { 23,180,238 }, { 23,180,243 }, + { 23,180,248 }, { 22,189,248 }, { 22,191,252 }, { 5,196,252 }, + { 3,203,252 }, { 5,203,253 }, { 22,210,253 }, { 0,214,253 }, +}; + +unsigned char stb_easy_font_hseg[214] = { + 97,37,69,84,28,51,2,18,10,49,98,41,65,25,81,105,33,9,97,1,97,37,37,36, + 81,10,98,107,3,100,3,99,58,51,4,99,58,8,73,81,10,50,98,8,73,81,4,10,50, + 98,8,25,33,65,81,10,50,17,65,97,25,33,25,49,9,65,20,68,1,65,25,49,41, + 11,105,13,101,76,10,50,10,50,98,11,99,10,98,11,50,99,11,50,11,99,8,57, + 58,3,99,99,107,10,10,11,10,99,11,5,100,41,65,57,41,65,9,17,81,97,3,107, + 9,97,1,97,33,25,9,25,41,100,41,26,82,42,98,27,83,42,98,26,51,82,8,41, + 35,8,10,26,82,114,42,1,114,8,9,73,57,81,41,97,18,8,8,25,26,26,82,26,82, + 26,82,41,25,33,82,26,49,73,35,90,17,81,41,65,57,41,65,25,81,90,114,20, + 84,73,57,41,49,25,33,65,81,9,97,1,97,25,33,65,81,57,33,25,41,25, +}; + +unsigned char stb_easy_font_vseg[253] = { + 4,2,8,10,15,8,15,33,8,15,8,73,82,73,57,41,82,10,82,18,66,10,21,29,1,65, + 27,8,27,9,65,8,10,50,97,74,66,42,10,21,57,41,29,25,14,81,73,57,26,8,8, + 26,66,3,8,8,15,19,21,90,58,26,18,66,18,105,89,28,74,17,8,73,57,26,21, + 8,42,41,42,8,28,22,8,8,30,7,8,8,26,66,21,7,8,8,29,7,7,21,8,8,8,59,7,8, + 8,15,29,8,8,14,7,57,43,10,82,7,7,25,42,25,15,7,25,41,15,21,105,105,29, + 7,57,57,26,21,105,73,97,89,28,97,7,57,58,26,82,18,57,57,74,8,30,6,8,8, + 14,3,58,90,58,11,7,74,43,74,15,2,82,2,42,75,42,10,67,57,41,10,7,2,42, + 74,106,15,2,35,8,8,29,7,8,8,59,35,51,8,8,15,35,30,35,8,8,30,7,8,8,60, + 36,8,45,7,7,36,8,43,8,44,21,8,8,44,35,8,8,43,23,8,8,43,35,8,8,31,21,15, + 20,8,8,28,18,58,89,58,26,21,89,73,89,29,20,8,8,30,7, +}; + +typedef struct +{ + unsigned char c[4]; +} stb_easy_font_color; + +static int stb_easy_font_draw_segs(float x, float y, unsigned char *segs, int num_segs, int vertical, stb_easy_font_color c, char *vbuf, int vbuf_size, int offset) +{ + int i,j; + for (i=0; i < num_segs; ++i) { + int len = segs[i] & 7; + x += (float) ((segs[i] >> 3) & 1); + if (len && offset+64 <= vbuf_size) { + float y0 = y + (float) (segs[i]>>4); + for (j=0; j < 4; ++j) { + * (float *) (vbuf+offset+0) = x + (j==1 || j==2 ? (vertical ? 1 : len) : 0); + * (float *) (vbuf+offset+4) = y0 + ( j >= 2 ? (vertical ? len : 1) : 0); + * (float *) (vbuf+offset+8) = 0.f; + * (stb_easy_font_color *) (vbuf+offset+12) = c; + offset += 16; + } + } + } + return offset; +} + +float stb_easy_font_spacing_val = 0; +static void stb_easy_font_spacing(float spacing) +{ + stb_easy_font_spacing_val = spacing; +} + +static int stb_easy_font_print(float x, float y, char *text, unsigned char color[4], void *vertex_buffer, int vbuf_size) +{ + char *vbuf = (char *) vertex_buffer; + float start_x = x; + int offset = 0; + + stb_easy_font_color c = { 255,255,255,255 }; // use structure copying to avoid needing depending on memcpy() + if (color) { c.c[0] = color[0]; c.c[1] = color[1]; c.c[2] = color[2]; c.c[3] = color[3]; } + + while (*text && offset < vbuf_size) { + if (*text == '\n') { + y += 12; + x = start_x; + } else { + unsigned char advance = stb_easy_font_charinfo[*text-32].advance; + float y_ch = advance & 16 ? y+1 : y; + int h_seg, v_seg, num_h, num_v; + h_seg = stb_easy_font_charinfo[*text-32 ].h_seg; + v_seg = stb_easy_font_charinfo[*text-32 ].v_seg; + num_h = stb_easy_font_charinfo[*text-32+1].h_seg - h_seg; + num_v = stb_easy_font_charinfo[*text-32+1].v_seg - v_seg; + offset = stb_easy_font_draw_segs(x, y_ch, &stb_easy_font_hseg[h_seg], num_h, 0, c, vbuf, vbuf_size, offset); + offset = stb_easy_font_draw_segs(x, y_ch, &stb_easy_font_vseg[v_seg], num_v, 1, c, vbuf, vbuf_size, offset); + x += advance & 15; + x += stb_easy_font_spacing_val; + } + ++text; + } + return (unsigned) offset/64; +} + +static int stb_easy_font_width(char *text) +{ + float len = 0; + while (*text) { + len += stb_easy_font_charinfo[*text-32].advance & 15; + len += stb_easy_font_spacing_val; + ++text; + } + return (int) ceil(len); +} +#endif