/********************************************************************************************* * * raylib.models * * Basic functions to draw 3d shapes and load/draw 3d models (.OBJ) * * Copyright (c) 2013 Ramon Santamaria (Ray San - raysan@raysanweb.com) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #include "raylib.h" #include // OpenGL functions #include // Standard input/output functions, used to read model files data #include // Declares malloc() and free() for memory management #include // Used for sin, cos, tan #include "raymath.h" // Required for data type Matrix and Matrix functions #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Nop... //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- #ifdef USE_OPENGL_11 struct Model { int numVertices; Vector3 *vertices; Vector2 *texcoords; Vector3 *normals; }; #else struct Model { int numVertices; Vector3 *vertices; Vector2 *texcoords; Vector3 *normals; }; /* struct Model { GLUint vaoId; Matrix transform; int polyMode; } */ #endif //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- // It's lonely here... //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- static float GetHeightValue(Color pixel); //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- // Draw cube // NOTE: Cube position is the center position void DrawCube(Vector3 position, float width, float height, float lenght, Color color) { // THIS WORKS! /* Matrix mat = MatrixTranslate(2.0, 0.0, 0.0); MatrixTranspose(&mat); VectorTransform(&position, mat); PrintMatrix(mat); */ float x = position.x; float y = position.y; float z = position.z; rlPushMatrix(); // NOTE: Be careful! Function order matters (scale, translate, rotate) //rlScalef(2.0f, 2.0f, 2.0f); //rlTranslatef(2.0f, 0.0f, 0.0f); rlRotatef(45, 0, 1, 0); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); // Front Face rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x-width/2, y-height/2, z+lenght/2); // Bottom Left Of The Texture and Quad rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x+width/2, y-height/2, z+lenght/2); // Bottom Right Of The Texture and Quad rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x+width/2, y+height/2, z+lenght/2); // Top Right Of The Texture and Quad rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x-width/2, y+height/2, z+lenght/2); // Top Left Of The Texture and Quad // Back Face rlNormal3f( 0.0f, 0.0f,-1.0f); // Normal Pointing Away From Viewer rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x-width/2, y-height/2, z-lenght/2); // Bottom Right Of The Texture and Quad rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x-width/2, y+height/2, z-lenght/2); // Top Right Of The Texture and Quad rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x+width/2, y+height/2, z-lenght/2); // Top Left Of The Texture and Quad rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x+width/2, y-height/2, z-lenght/2); // Bottom Left Of The Texture and Quad // Top Face rlNormal3f( 0.0f, 1.0f, 0.0f); // Normal Pointing Up rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x-width/2, y+height/2, z-lenght/2); // Top Left Of The Texture and Quad rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x-width/2, y+height/2, z+lenght/2); // Bottom Left Of The Texture and Quad rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x+width/2, y+height/2, z+lenght/2); // Bottom Right Of The Texture and Quad rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x+width/2, y+height/2, z-lenght/2); // Top Right Of The Texture and Quad // Bottom Face rlNormal3f( 0.0f,-1.0f, 0.0f); // Normal Pointing Down rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x-width/2, y-height/2, z-lenght/2); // Top Right Of The Texture and Quad rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x+width/2, y-height/2, z-lenght/2); // Top Left Of The Texture and Quad rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x+width/2, y-height/2, z+lenght/2); // Bottom Left Of The Texture and Quad rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x-width/2, y-height/2, z+lenght/2); // Bottom Right Of The Texture and Quad // Right face rlNormal3f( 1.0f, 0.0f, 0.0f); // Normal Pointing Right rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x+width/2, y-height/2, z-lenght/2); // Bottom Right Of The Texture and Quad rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x+width/2, y+height/2, z-lenght/2); // Top Right Of The Texture and Quad rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x+width/2, y+height/2, z+lenght/2); // Top Left Of The Texture and Quad rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x+width/2, y-height/2, z+lenght/2); // Bottom Left Of The Texture and Quad // Left Face rlNormal3f(-1.0f, 0.0f, 0.0f); // Normal Pointing Left rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x-width/2, y-height/2, z-lenght/2); // Bottom Left Of The Texture and Quad rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x-width/2, y-height/2, z+lenght/2); // Bottom Right Of The Texture and Quad rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x-width/2, y+height/2, z+lenght/2); // Top Right Of The Texture and Quad rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x-width/2, y+height/2, z-lenght/2); // Top Left Of The Texture and Quad rlEnd(); rlPopMatrix(); } // Draw cube (Vector version) void DrawCubeV(Vector3 position, Vector3 size, Color color) { DrawCube(position, size.x, size.y, size.z, color); } // Draw cube wires void DrawCubeWires(Vector3 position, float width, float height, float lenght, Color color) { // TODO: Draw cube using RL_LINES! //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //DrawCube(position, width, height, lenght, color); //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } // Draw sphere void DrawSphere(Vector3 centerPos, float radius, Color color) { DrawSphereEx(centerPos, radius, 16, 16, color); } // Draw sphere with extended parameters void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) { float lat0, z0, zr0; float lat1, z1, zr1; float lng, x, y; // TODO: Review vertex translate/rotate/scale mechanism rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlRotatef(90, 1, 0, 0); rlScalef(radius, radius, radius); rlBegin(GL_QUAD_STRIP); rlColor4ub(color.r, color.g, color.b, color.a); for(int i = 0; i <= rings; i++) { lat0 = PI * (-0.5 + (float)(i - 1) / rings); z0 = sin(lat0); zr0 = cos(lat0); lat1 = PI * (-0.5 + (float)i / rings); z1 = sin(lat1); zr1 = cos(lat1); for(int j = 0; j <= slices; j++) { lng = 2 * PI * (float)(j - 1) / slices; x = cos(lng); y = sin(lng); rlNormal3f(x * zr0, y * zr0, z0); rlVertex3f(x * zr0, y * zr0, z0); rlNormal3f(x * zr1, y * zr1, z1); rlVertex3f(x * zr1, y * zr1, z1); } } rlEnd(); rlPopMatrix(); } // Draw sphere wires void DrawSphereWires(Vector3 centerPos, float radius, Color color) { // TODO: Draw sphere using RL_LINES! //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //DrawSphere(centerPos, radius, color); //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } // Draw a cylinder/cone void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color) // Could be used for pyramid and cone! { Vector3 a = { position.x, position.y + height, position.z }; Vector3 d = { 0.0f, 1.0f, 0.0f }; Vector3 p; Vector3 c = { a.x + (-d.x * height), a.y + (-d.y * height), a.z + (-d.z * height) }; //= a + (-d * h); Vector3 e0 = VectorPerpendicular(d); Vector3 e1 = VectorCrossProduct(e0, d); float angInc = 360.0 / slices * DEG2RAD; if (radiusTop == 0) // Draw pyramid or cone { //d – axis defined as a normalized vector from base to apex //a – position of apex (top point) //h – height //rd – radius of directrix //n – number of radial "slices" // TODO: Review drawing to use RL_TRIANGLES // Draw cone top rlBegin(GL_TRIANGLE_FAN); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex3f(a.x, a.y, a.z); for (int i = 0; i <= slices; i++) { float rad = angInc * i; p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusBottom); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusBottom); p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusBottom); rlVertex3f(p.x, p.y, p.z); } rlEnd(); // Draw cone bottom rlBegin(GL_TRIANGLE_FAN); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex3f(c.x, c.y, c.z); for (int i = slices; i >= 0; i--) { float rad = angInc * i; p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusBottom); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusBottom); p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusBottom); rlVertex3f(p.x, p.y, p.z); } rlEnd(); } else // Draw cylinder { // TODO: Review drawing to use RL_TRIANGLES // Draw cylinder top (pointed cap) rlBegin(GL_TRIANGLE_FAN); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex3f(c.x, c.y + height, c.z); for (int i = slices; i >= 0; i--) { float rad = angInc * i; p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusTop); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusTop) + height; p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusTop); rlVertex3f(p.x, p.y, p.z); } rlEnd(); // Draw cylinder sides rlBegin(GL_TRIANGLE_STRIP); rlColor4ub(color.r, color.g, color.b, color.a); for (int i = slices; i >= 0; i--) { float rad = angInc * i; p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusTop); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusTop) + height; p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusTop); rlVertex3f(p.x, p.y, p.z); p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusBottom); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusBottom); p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusBottom); rlVertex3f(p.x, p.y, p.z); } rlEnd(); // Draw cylinder bottom rlBegin(GL_TRIANGLE_FAN); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex3f(c.x, c.y, c.z); for (int i = slices; i >= 0; i--) { float rad = angInc * i; p.x = c.x + (((e0.x * cos(rad)) + (e1.x * sin(rad))) * radiusBottom); p.y = c.y + (((e0.y * cos(rad)) + (e1.y * sin(rad))) * radiusBottom); p.z = c.z + (((e0.z * cos(rad)) + (e1.z * sin(rad))) * radiusBottom); rlVertex3f(p.x, p.y, p.z); } rlEnd(); } } // Draw a cylinder/cone wires void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color) { // TODO: Draw sphere using RL_LINES! //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); DrawCylinder(position, radiusTop, radiusBottom, height, slices, color); //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } // Draw a plane void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color) { // TODO: Review vertex translate/rotate/scale mechanism // NOTE: Plane is always created on XZ ground and then rotated rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... rlRotatef(rotation.x, 1, 0, 0); rlRotatef(rotation.y, 0, 1, 0); rlRotatef(rotation.z, 0, 0, 1); rlScalef(size.x, 1.0f, size.y); rlBegin(GL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(-0.5f, 0.0f, -0.5f); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(0.5f, 0.0f, -0.5f); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(0.5f, 0.0f, 0.5f); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(-0.5f, 0.0f, 0.5f); rlEnd(); rlPopMatrix(); } // Draw a plane with divisions void DrawPlaneEx(Vector3 centerPos, Vector2 size, Vector3 rotation, int slicesX, int slicesZ, Color color) { float quadWidth = size.x / slicesX; float quadLenght = size.y / slicesZ; float texPieceW = 1 / size.x; float texPieceH = 1 / size.y; // TODO: Review vertex translate/rotate/scale mechanism // NOTE: Plane is always created on XZ ground and then rotated rlPushMatrix(); rlTranslatef(-size.x / 2, 0.0f, -size.y / 2); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... rlRotatef(rotation.x, 1, 0, 0); rlRotatef(rotation.y, 0, 1, 0); rlRotatef(rotation.z, 0, 0, 1); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); for (int z = 0; z < slicesZ; z++) { for (int x = 0; x < slicesX; x++) { // Draw the plane quad by quad (with textcoords) rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH); rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght); rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH); rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght); rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH + texPieceH); rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght + quadLenght); rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH + texPieceH); rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght + quadLenght); } } rlEnd(); rlPopMatrix(); } // Draw a grid centered at (0, 0, 0) void DrawGrid(int slices, float spacing) { int halfSlices = slices / 2; rlBegin(RL_LINES); for(int i = -halfSlices; i <= halfSlices; i++) { if (i == 0) { rlColor3f(0.5f, 0.5f, 0.5f); rlColor3f(0.5f, 0.5f, 0.5f); rlColor3f(0.5f, 0.5f, 0.5f); rlColor3f(0.5f, 0.5f, 0.5f); } else { rlColor3f(0.75f, 0.75f, 0.75f); rlColor3f(0.75f, 0.75f, 0.75f); rlColor3f(0.75f, 0.75f, 0.75f); rlColor3f(0.75f, 0.75f, 0.75f); } rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); rlVertex3f((float)i*spacing, 0.0f, (float)halfSlices*spacing); rlVertex3f((float)-halfSlices*spacing, 0.0f, (float)i*spacing); rlVertex3f((float)halfSlices*spacing, 0.0f, (float)i*spacing); } rlEnd(); } // Draw gizmo (with or without orbits) void DrawGizmo(Vector3 position, bool orbits) { // NOTE: RGB = XYZ float lenght = 1.0f; float radius = 1.0f; //glEnable(GL_LINE_SMOOTH); // Smoothies circle outline (anti-aliasing applied) //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // Best quality for line smooth (anti-aliasing best algorithm) // GL_LINE_SMOOTH is very poorly supported on desktop GL. // A lot of drivers ignore it, so most people avoid using... rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); //glRotatef(rotation, 0, 1, 0); rlScalef(lenght, lenght, lenght); rlBegin(GL_LINES); rlColor3f(1.0f, 0.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f); rlVertex3f(1.0f, 0.0f, 0.0f); rlColor3f(0.0f, 1.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f); rlVertex3f(0.0f, 1.0f, 0.0f); rlColor3f(0.0f, 0.0f, 1.0f); rlVertex3f(0.0f, 0.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 1.0f); rlEnd(); if (orbits) { rlBegin(GL_LINE_LOOP); rlColor4f(1.0f, 0.0f, 0.0f, 0.4f); for (int i=0; i < 360; i++) rlVertex3f(sin(DEG2RAD*i) * radius, 0, cos(DEG2RAD*i) * radius); rlEnd(); rlBegin(GL_LINE_LOOP); rlColor4f(0.0f, 1.0f, 0.0f, 0.4f); for (int i=0; i < 360; i++) rlVertex3f(sin(DEG2RAD*i) * radius, cos(DEG2RAD*i) * radius, 0); rlEnd(); rlBegin(GL_LINE_LOOP); rlColor4f(0.0f, 0.0f, 1.0f, 0.4f); for (int i=0; i < 360; i++) rlVertex3f(0, sin(DEG2RAD*i) * radius, cos(DEG2RAD*i) * radius); rlEnd(); } rlPopMatrix(); //glDisable(GL_LINE_SMOOTH); } // Load a 3d model (.OBJ) // TODO: Add comments explaining this function process Model LoadModel(const char *fileName) { struct Model model; char dataType; char comments[200]; int numVertex = 0; int numNormals = 0; int numTexCoords = 0; int numTriangles = 0; FILE* objFile; objFile = fopen(fileName, "rt"); while(!feof(objFile)) { fscanf(objFile, "%c", &dataType); switch(dataType) { case '#': // It's a comment { fgets(comments, 200, objFile); } break; case 'v': { fscanf(objFile, "%c", &dataType); if (dataType == 't') // Read texCoord { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); while (dataType == 'v') { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); } if (dataType == '#') { fscanf(objFile, "%i", &numTexCoords); } else printf("Ouch! Something was wrong..."); fgets(comments, 200, objFile); } else if (dataType == 'n') // Read normals { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); while (dataType == 'v') { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); } if (dataType == '#') { fscanf(objFile, "%i", &numNormals); } else printf("Ouch! Something was wrong..."); fgets(comments, 200, objFile); } else // Read vertex { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); while (dataType == 'v') { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); } if (dataType == '#') { fscanf(objFile, "%i", &numVertex); } else printf("Ouch! Something was wrong..."); fgets(comments, 200, objFile); } } break; case 'f': { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); while (dataType == 'f') { fgets(comments, 200, objFile); fscanf(objFile, "%c", &dataType); } if (dataType == '#') { fscanf(objFile, "%i", &numTriangles); } else printf("Ouch! Something was wrong..."); fgets(comments, 200, objFile); } break; default: break; } } Vector3 midVertices[numVertex]; Vector3 midNormals[numNormals]; Vector2 midTexCoords[numTexCoords]; model.numVertices = numTriangles*3; model.vertices = (Vector3 *)malloc(model.numVertices * sizeof(Vector3)); model.normals = (Vector3 *)malloc(model.numVertices * sizeof(Vector3)); model.texcoords = (Vector2 *)malloc(model.numVertices * sizeof(Vector2)); int countVertex = 0; int countNormals = 0; int countTexCoords = 0; int countMaxVertex = 0; rewind(objFile); while(!feof(objFile)) { fscanf(objFile, "%c", &dataType); switch(dataType) { case '#': { fgets(comments, 200, objFile); } break; case 'v': { fscanf(objFile, "%c", &dataType); if (dataType == 't') // Read texCoord { float useless = 0; fscanf(objFile, "%f %f %f", &midTexCoords[countTexCoords].x, &midTexCoords[countTexCoords].y, &useless); countTexCoords++; fscanf(objFile, "%c", &dataType); } else if (dataType == 'n') // Read normals { fscanf(objFile, "%f %f %f", &midNormals[countNormals].x, &midNormals[countNormals].y, &midNormals[countNormals].z ); countNormals++; fscanf(objFile, "%c", &dataType); } else // Read vertex { fscanf(objFile, "%f %f %f", &midVertices[countVertex].x, &midVertices[countVertex].y, &midVertices[countVertex].z ); countVertex++; fscanf(objFile, "%c", &dataType); } } break; case 'f': { int vNum, vtNum, vnNum; fscanf(objFile, "%c", &dataType); fscanf(objFile, "%i/%i/%i", &vNum, &vtNum, &vnNum); model.vertices[countMaxVertex] = midVertices[vNum-1]; model.normals[countMaxVertex] = midNormals[vnNum-1]; model.texcoords[countMaxVertex].x = midTexCoords[vtNum-1].x; model.texcoords[countMaxVertex].y = -midTexCoords[vtNum-1].y; countMaxVertex++; fscanf(objFile, "%i/%i/%i", &vNum, &vtNum, &vnNum); model.vertices[countMaxVertex] = midVertices[vNum-1]; model.normals[countMaxVertex] = midNormals[vnNum-1]; model.texcoords[countMaxVertex].x = midTexCoords[vtNum-1].x; model.texcoords[countMaxVertex].y = -midTexCoords[vtNum-1].y; countMaxVertex++; fscanf(objFile, "%i/%i/%i", &vNum, &vtNum, &vnNum); model.vertices[countMaxVertex] = midVertices[vNum-1]; model.normals[countMaxVertex] = midNormals[vnNum-1]; model.texcoords[countMaxVertex].x = midTexCoords[vtNum-1].x; model.texcoords[countMaxVertex].y = -midTexCoords[vtNum-1].y; countMaxVertex++; } break; default: break; } } fclose(objFile); #ifdef USE_OPENGL_33 // TODO: Use loaded data to generate VAO #endif return model; } // Load a heightmap image as a 3d model Model LoadHeightmap(Image heightmap, float maxHeight) { Model model; int mapX = heightmap.width; int mapZ = heightmap.height; // NOTE: One vertex per pixel // TODO: Consider resolution when generating model data? int numTriangles = (mapX-1)*(mapZ-1)*2; // One quad every four pixels model.numVertices = numTriangles*3; model.vertices = (Vector3 *)malloc(model.numVertices * sizeof(Vector3)); model.normals = (Vector3 *)malloc(model.numVertices * sizeof(Vector3)); model.texcoords = (Vector2 *)malloc(model.numVertices * sizeof(Vector2)); int vCounter = 0; int trisCounter = 0; float scaleFactor = maxHeight/255; // TODO: Review scaleFactor calculation for(int z = 0; z < mapZ-1; z++) { for(int x = 0; x < mapX-1; x++) { // Fill vertices array with data //---------------------------------------------------------- // one triangle - 3 vertex model.vertices[vCounter].x = x; model.vertices[vCounter].y = GetHeightValue(heightmap.pixels[x + z*mapX])*scaleFactor; model.vertices[vCounter].z = z; model.vertices[vCounter+1].x = x; model.vertices[vCounter+1].y = GetHeightValue(heightmap.pixels[x + (z+1)*mapX])*scaleFactor; model.vertices[vCounter+1].z = z+1; model.vertices[vCounter+2].x = x+1; model.vertices[vCounter+2].y = GetHeightValue(heightmap.pixels[(x+1) + z*mapX])*scaleFactor; model.vertices[vCounter+2].z = z; // another triangle - 3 vertex model.vertices[vCounter+3] = model.vertices[vCounter+2]; model.vertices[vCounter+4] = model.vertices[vCounter+1]; model.vertices[vCounter+5].x = x+1; model.vertices[vCounter+5].y = GetHeightValue(heightmap.pixels[(x+1) + (z+1)*mapX])*scaleFactor; model.vertices[vCounter+5].z = z+1; // Fill texcoords array with data //-------------------------------------------------------------- model.texcoords[vCounter].x = (float)x / (mapX-1); model.texcoords[vCounter].y = (float)z / (mapZ-1); model.texcoords[vCounter+1].x = (float)x / (mapX-1); model.texcoords[vCounter+1].y = (float)(z+1) / (mapZ-1); model.texcoords[vCounter+2].x = (float)(x+1) / (mapX-1); model.texcoords[vCounter+2].y = (float)z / (mapZ-1); model.texcoords[vCounter+3] = model.texcoords[vCounter+2]; model.texcoords[vCounter+4] = model.texcoords[vCounter+1]; model.texcoords[vCounter+5].x = (float)(x+1) / (mapX-1); model.texcoords[vCounter+5].y = (float)(z+1) / (mapZ-1); // Fill normals array with data //-------------------------------------------------------------- // TODO: Review normals calculation model.normals[vCounter] = (Vector3){ 0.0f, 1.0f, 0.0f }; model.normals[vCounter+1] = (Vector3){ 0.0f, 1.0f, 0.0f }; model.normals[vCounter+2] = (Vector3){ 0.0f, 1.0f, 0.0f }; model.normals[vCounter+3] = (Vector3){ 0.0f, 1.0f, 0.0f }; model.normals[vCounter+4] = (Vector3){ 0.0f, 1.0f, 0.0f }; model.normals[vCounter+5] = (Vector3){ 0.0f, 1.0f, 0.0f }; vCounter += 6; trisCounter += 2; } } #ifdef USE_OPENGL_33 // TODO: Use loaded data to generate VAO #endif return model; } // Unload 3d model from memory void UnloadModel(Model model) { free(model.vertices); free(model.texcoords); free(model.normals); } // Draw a model void DrawModel(Model model, Vector3 position, float scale, Color color) { // NOTE: For models we use Vertex Arrays (OpenGL 1.1) //static int rotation = 0; // NOTE: Add OpenGL 3.3+ VAOs-based drawing! --> Move this stuff to rlgl? glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array glVertexPointer(3, GL_FLOAT, 0, model.vertices); // Pointer to vertex coords array glTexCoordPointer(2, GL_FLOAT, 0, model.texcoords); // Pointer to texture coords array glNormalPointer(GL_FLOAT, 0, model.normals); // Pointer to normals array //glColorPointer(4, GL_UNSIGNED_BYTE, 0, model.colors); // Pointer to colors array (NOT USED) rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); //glRotatef(rotation * GetFrameTime(), 0, 1, 0); rlScalef(scale, scale, scale); rlColor4ub(color.r, color.g, color.b, color.a); glDrawArrays(GL_TRIANGLES, 0, model.numVertices); rlPopMatrix(); glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array //rotation += 10; // Model drawing in OpenGL 3.3+, transform is passed to shader /* glUseProgram(shaderProgram); // Use our shader Matrix modelview = MatrixMultiply(model.transform, view); glUniformMatrix4fv(projectionMatrixLoc, 1, false, GetMatrixVector(projection)); glUniformMatrix4fv(modelviewMatrixLoc, 1, false, GetMatrixVector(modelview)); glUniform1i(textureLoc, 0); glBindVertexArray(model.vaoId); glBindTexture(GL_TEXTURE_2D, model.texId); glDrawArrays(GL_TRIANGLES, 0, model.numVertices); glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures glBindVertexArray(0); // Unbind VAO */ } // Draw a textured model void DrawModelEx(Model model, Texture2D texture, Vector3 position, float scale, Color tint) { rlEnableTexture(texture.glId); DrawModel(model, position, scale, tint); rlDisableTexture(); } // Draw a model wires void DrawModelWires(Model model, Vector3 position, float scale, Color color) { // TODO: Draw model using RL_LINES... or look for a way to deal with polygon mode! //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); DrawModel(model, position, scale, color); //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } // Draw a billboard void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint) { // NOTE: Billboard size will maintain texture aspect ratio, size will be billboard width Vector2 sizeRatio = { size, size * (float)texture.height/texture.width }; Matrix viewMatrix = MatrixLookAt(camera.position, camera.target, camera.up); MatrixTranspose(&viewMatrix); Vector3 right = { viewMatrix.m0, viewMatrix.m4, viewMatrix.m8 }; Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; /* d-------c | | | * | | | a-------b */ VectorScale(&right, sizeRatio.x/2); VectorScale(&up, sizeRatio.y/2); Vector3 p1 = VectorAdd(right, up); Vector3 p2 = VectorSubtract(right, up); Vector3 a = VectorSubtract(center, p2); Vector3 b = VectorAdd(center, p1); Vector3 c = VectorAdd(center, p2); Vector3 d = VectorSubtract(center, p1); rlEnableTexture(texture.glId); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); rlNormal3f(0.0f, 1.0f, 0.0f); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(a.x, a.y, a.z); rlTexCoord2f(1.0f, 0.0f); rlVertex3f(b.x, b.y, b.z); rlTexCoord2f(1.0f, 1.0f); rlVertex3f(c.x, c.y, c.z); rlTexCoord2f(0.0f, 1.0f); rlVertex3f(d.x, d.y, d.z); rlEnd(); rlDisableTexture(); } // Draw a billboard (part of a texture defined by a rectangle) void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint) { // NOTE: Billboard size will maintain sourceRec aspect ratio, size will represent billboard width Vector2 sizeRatio = { size, size * (float)sourceRec.height/sourceRec.width }; Matrix viewMatrix = MatrixLookAt(camera.position, camera.target, camera.up); MatrixTranspose(&viewMatrix); Vector3 right = { viewMatrix.m0, viewMatrix.m4, viewMatrix.m8 }; Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; /* d-------c | | | * | | | a-------b */ VectorScale(&right, sizeRatio.x/2); VectorScale(&up, sizeRatio.y/2); Vector3 p1 = VectorAdd(right, up); Vector3 p2 = VectorSubtract(right, up); Vector3 a = VectorSubtract(center, p2); Vector3 b = VectorAdd(center, p1); Vector3 c = VectorAdd(center, p2); Vector3 d = VectorSubtract(center, p1); rlEnableTexture(texture.glId); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); // Bottom-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)sourceRec.y / texture.height); rlVertex3f(a.x, a.y, a.z); // Bottom-right corner for texture and quad rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)sourceRec.y / texture.height); rlVertex3f(b.x, b.y, b.z); // Top-right corner for texture and quad rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(c.x, c.y, c.z); // Top-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(d.x, d.y, d.z); rlEnd(); rlDisableTexture(); } // Get current vertex y altitude (proportional to pixel colors in grayscale) static float GetHeightValue(Color pixel) { return (((float)pixel.r + (float)pixel.g + (float)pixel.b)/3); }