mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-02-08 19:30:28 -05:00
5113 lines
216 KiB
C
5113 lines
216 KiB
C
![]() |
/**********************************************************************************************
|
||
|
*
|
||
|
* rmodels - Basic functions to draw 3d shapes and load and draw 3d models
|
||
|
*
|
||
|
* CONFIGURATION:
|
||
|
*
|
||
|
* #define SUPPORT_MODULE_RMODELS
|
||
|
* rmodels module is included in the build
|
||
|
*
|
||
|
* #define SUPPORT_FILEFORMAT_OBJ
|
||
|
* #define SUPPORT_FILEFORMAT_MTL
|
||
|
* #define SUPPORT_FILEFORMAT_IQM
|
||
|
* #define SUPPORT_FILEFORMAT_GLTF
|
||
|
* #define SUPPORT_FILEFORMAT_VOX
|
||
|
* Selected desired fileformats to be supported for model data loading.
|
||
|
*
|
||
|
* #define SUPPORT_MESH_GENERATION
|
||
|
* Support procedural mesh generation functions, uses external par_shapes.h library
|
||
|
* NOTE: Some generated meshes DO NOT include generated texture coordinates
|
||
|
*
|
||
|
*
|
||
|
* LICENSE: zlib/libpng
|
||
|
*
|
||
|
* Copyright (c) 2013-2022 Ramon Santamaria (@raysan5)
|
||
|
*
|
||
|
* 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" // Declares module functions
|
||
|
|
||
|
// Check if config flags have been externally provided on compilation line
|
||
|
#if !defined(EXTERNAL_CONFIG_FLAGS)
|
||
|
#include "config.h" // Defines module configuration flags
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_MODULE_RMODELS)
|
||
|
|
||
|
#include "utils.h" // Required for: TRACELOG(), LoadFileData(), LoadFileText(), SaveFileText()
|
||
|
#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2
|
||
|
#include "raymath.h" // Required for: Vector3, Quaternion and Matrix functionality
|
||
|
|
||
|
#include <stdio.h> // Required for: sprintf()
|
||
|
#include <stdlib.h> // Required for: malloc(), free()
|
||
|
#include <string.h> // Required for: memcmp(), strlen()
|
||
|
#include <math.h> // Required for: sinf(), cosf(), sqrtf(), fabsf()
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_OBJ) || defined(SUPPORT_FILEFORMAT_MTL)
|
||
|
#define TINYOBJ_MALLOC RL_MALLOC
|
||
|
#define TINYOBJ_CALLOC RL_CALLOC
|
||
|
#define TINYOBJ_REALLOC RL_REALLOC
|
||
|
#define TINYOBJ_FREE RL_FREE
|
||
|
|
||
|
#define TINYOBJ_LOADER_C_IMPLEMENTATION
|
||
|
#include "external/tinyobj_loader_c.h" // OBJ/MTL file formats loading
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||
|
#define CGLTF_MALLOC RL_MALLOC
|
||
|
#define CGLTF_FREE RL_FREE
|
||
|
|
||
|
#define CGLTF_IMPLEMENTATION
|
||
|
#include "external/cgltf.h" // glTF file format loading
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_VOX)
|
||
|
#define VOX_MALLOC RL_MALLOC
|
||
|
#define VOX_CALLOC RL_CALLOC
|
||
|
#define VOX_REALLOC RL_REALLOC
|
||
|
#define VOX_FREE RL_FREE
|
||
|
|
||
|
#define VOX_LOADER_IMPLEMENTATION
|
||
|
#include "external/vox_loader.h" // VOX file format loading (MagikaVoxel)
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_MESH_GENERATION)
|
||
|
#define PAR_MALLOC(T, N) ((T*)RL_MALLOC(N*sizeof(T)))
|
||
|
#define PAR_CALLOC(T, N) ((T*)RL_CALLOC(N*sizeof(T), 1))
|
||
|
#define PAR_REALLOC(T, BUF, N) ((T*)RL_REALLOC(BUF, sizeof(T)*(N)))
|
||
|
#define PAR_FREE RL_FREE
|
||
|
|
||
|
#define PAR_SHAPES_IMPLEMENTATION
|
||
|
#include "external/par_shapes.h" // Shapes 3d parametric generation
|
||
|
#endif
|
||
|
|
||
|
#if defined(_WIN32)
|
||
|
#include <direct.h> // Required for: _chdir() [Used in LoadOBJ()]
|
||
|
#define CHDIR _chdir
|
||
|
#else
|
||
|
#include <unistd.h> // Required for: chdir() (POSIX) [Used in LoadOBJ()]
|
||
|
#define CHDIR chdir
|
||
|
#endif
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Defines and Macros
|
||
|
//----------------------------------------------------------------------------------
|
||
|
#ifndef MAX_MATERIAL_MAPS
|
||
|
#define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported
|
||
|
#endif
|
||
|
#ifndef MAX_MESH_VERTEX_BUFFERS
|
||
|
#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh
|
||
|
#endif
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Types and Structures Definition
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// ...
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Global Variables Definition
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// ...
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Module specific Functions Declaration
|
||
|
//----------------------------------------------------------------------------------
|
||
|
#if defined(SUPPORT_FILEFORMAT_OBJ)
|
||
|
static Model LoadOBJ(const char *fileName); // Load OBJ mesh data
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||
|
static Model LoadIQM(const char *fileName); // Load IQM mesh data
|
||
|
static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount); // Load IQM animation data
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||
|
static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
|
||
|
//static ModelAnimation *LoadModelAnimationGLTF(const char *fileName, unsigned int *animCount); // Load GLTF animation data
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_VOX)
|
||
|
static Model LoadVOX(const char *filename); // Load VOX mesh data
|
||
|
#endif
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Module Functions Definition
|
||
|
//----------------------------------------------------------------------------------
|
||
|
|
||
|
// Draw a line in 3D world space
|
||
|
void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color)
|
||
|
{
|
||
|
// WARNING: Be careful with internal buffer vertex alignment
|
||
|
// when using RL_LINES or RL_TRIANGLES, data is aligned to fit
|
||
|
// lines-triangles-quads in the same indexed buffers!!!
|
||
|
rlCheckRenderBatchLimit(8);
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
rlVertex3f(startPos.x, startPos.y, startPos.z);
|
||
|
rlVertex3f(endPos.x, endPos.y, endPos.z);
|
||
|
rlEnd();
|
||
|
}
|
||
|
|
||
|
// Draw a point in 3D space, actually a small line
|
||
|
void DrawPoint3D(Vector3 position, Color color)
|
||
|
{
|
||
|
rlCheckRenderBatchLimit(8);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(position.x, position.y, position.z);
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
rlVertex3f(0.0f, 0.0f, 0.0f);
|
||
|
rlVertex3f(0.0f, 0.0f, 0.1f);
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw a circle in 3D world space
|
||
|
void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color)
|
||
|
{
|
||
|
rlCheckRenderBatchLimit(2*36);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(center.x, center.y, center.z);
|
||
|
rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
for (int i = 0; i < 360; i += 10)
|
||
|
{
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radius, cosf(DEG2RAD*i)*radius, 0.0f);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 10))*radius, cosf(DEG2RAD*(i + 10))*radius, 0.0f);
|
||
|
}
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw a color-filled triangle (vertex in counter-clockwise order!)
|
||
|
void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color)
|
||
|
{
|
||
|
rlCheckRenderBatchLimit(3);
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
rlVertex3f(v1.x, v1.y, v1.z);
|
||
|
rlVertex3f(v2.x, v2.y, v2.z);
|
||
|
rlVertex3f(v3.x, v3.y, v3.z);
|
||
|
rlEnd();
|
||
|
}
|
||
|
|
||
|
// Draw a triangle strip defined by points
|
||
|
void DrawTriangleStrip3D(Vector3 *points, int pointCount, Color color)
|
||
|
{
|
||
|
if (pointCount >= 3)
|
||
|
{
|
||
|
rlCheckRenderBatchLimit(3*(pointCount - 2));
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 2; i < pointCount; i++)
|
||
|
{
|
||
|
if ((i%2) == 0)
|
||
|
{
|
||
|
rlVertex3f(points[i].x, points[i].y, points[i].z);
|
||
|
rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z);
|
||
|
rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rlVertex3f(points[i].x, points[i].y, points[i].z);
|
||
|
rlVertex3f(points[i - 1].x, points[i - 1].y, points[i - 1].z);
|
||
|
rlVertex3f(points[i - 2].x, points[i - 2].y, points[i - 2].z);
|
||
|
}
|
||
|
}
|
||
|
rlEnd();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw cube
|
||
|
// NOTE: Cube position is the center position
|
||
|
void DrawCube(Vector3 position, float width, float height, float length, Color color)
|
||
|
{
|
||
|
float x = 0.0f;
|
||
|
float y = 0.0f;
|
||
|
float z = 0.0f;
|
||
|
|
||
|
rlCheckRenderBatchLimit(36);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
// NOTE: Transformation is applied in inverse order (scale -> rotate -> translate)
|
||
|
rlTranslatef(position.x, position.y, position.z);
|
||
|
//rlRotatef(45, 0, 1, 0);
|
||
|
//rlScalef(1.0f, 1.0f, 1.0f); // NOTE: Vertices are directly scaled on definition
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
// Front face
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left
|
||
|
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right
|
||
|
|
||
|
// Back face
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Left
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left
|
||
|
|
||
|
// Top face
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right
|
||
|
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right
|
||
|
|
||
|
// Bottom face
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left
|
||
|
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Right
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2); // Top Left
|
||
|
|
||
|
// Right face
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left
|
||
|
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left
|
||
|
|
||
|
// Left face
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right
|
||
|
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Left
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Left
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2); // Bottom Right
|
||
|
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 length, Color color)
|
||
|
{
|
||
|
float x = 0.0f;
|
||
|
float y = 0.0f;
|
||
|
float z = 0.0f;
|
||
|
|
||
|
rlCheckRenderBatchLimit(36);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(position.x, position.y, position.z);
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
// Front face -----------------------------------------------------
|
||
|
// Bottom line
|
||
|
rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom left
|
||
|
rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom right
|
||
|
|
||
|
// Left line
|
||
|
rlVertex3f(x+width/2, y-height/2, z+length/2); // Bottom right
|
||
|
rlVertex3f(x+width/2, y+height/2, z+length/2); // Top right
|
||
|
|
||
|
// Top line
|
||
|
rlVertex3f(x+width/2, y+height/2, z+length/2); // Top right
|
||
|
rlVertex3f(x-width/2, y+height/2, z+length/2); // Top left
|
||
|
|
||
|
// Right line
|
||
|
rlVertex3f(x-width/2, y+height/2, z+length/2); // Top left
|
||
|
rlVertex3f(x-width/2, y-height/2, z+length/2); // Bottom left
|
||
|
|
||
|
// Back face ------------------------------------------------------
|
||
|
// Bottom line
|
||
|
rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom left
|
||
|
rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom right
|
||
|
|
||
|
// Left line
|
||
|
rlVertex3f(x+width/2, y-height/2, z-length/2); // Bottom right
|
||
|
rlVertex3f(x+width/2, y+height/2, z-length/2); // Top right
|
||
|
|
||
|
// Top line
|
||
|
rlVertex3f(x+width/2, y+height/2, z-length/2); // Top right
|
||
|
rlVertex3f(x-width/2, y+height/2, z-length/2); // Top left
|
||
|
|
||
|
// Right line
|
||
|
rlVertex3f(x-width/2, y+height/2, z-length/2); // Top left
|
||
|
rlVertex3f(x-width/2, y-height/2, z-length/2); // Bottom left
|
||
|
|
||
|
// Top face -------------------------------------------------------
|
||
|
// Left line
|
||
|
rlVertex3f(x-width/2, y+height/2, z+length/2); // Top left front
|
||
|
rlVertex3f(x-width/2, y+height/2, z-length/2); // Top left back
|
||
|
|
||
|
// Right line
|
||
|
rlVertex3f(x+width/2, y+height/2, z+length/2); // Top right front
|
||
|
rlVertex3f(x+width/2, y+height/2, z-length/2); // Top right back
|
||
|
|
||
|
// Bottom face ---------------------------------------------------
|
||
|
// Left line
|
||
|
rlVertex3f(x-width/2, y-height/2, z+length/2); // Top left front
|
||
|
rlVertex3f(x-width/2, y-height/2, z-length/2); // Top left back
|
||
|
|
||
|
// Right line
|
||
|
rlVertex3f(x+width/2, y-height/2, z+length/2); // Top right front
|
||
|
rlVertex3f(x+width/2, y-height/2, z-length/2); // Top right back
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw cube wires (vector version)
|
||
|
void DrawCubeWiresV(Vector3 position, Vector3 size, Color color)
|
||
|
{
|
||
|
DrawCubeWires(position, size.x, size.y, size.z, color);
|
||
|
}
|
||
|
|
||
|
// Draw cube
|
||
|
// NOTE: Cube position is the center position
|
||
|
void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float height, float length, Color color)
|
||
|
{
|
||
|
float x = position.x;
|
||
|
float y = position.y;
|
||
|
float z = position.z;
|
||
|
|
||
|
rlCheckRenderBatchLimit(36);
|
||
|
|
||
|
rlSetTexture(texture.id);
|
||
|
|
||
|
//rlPushMatrix();
|
||
|
// NOTE: Transformation is applied in inverse order (scale -> rotate -> translate)
|
||
|
//rlTranslatef(2.0f, 0.0f, 0.0f);
|
||
|
//rlRotatef(45, 0, 1, 0);
|
||
|
//rlScalef(2.0f, 2.0f, 2.0f);
|
||
|
|
||
|
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 + length/2); // Bottom Left Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/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 - length/2); // Bottom Right Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z - length/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 - length/2); // Top Left Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Bottom Left Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Bottom Right Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/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 - length/2); // Top Right Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y - height/2, z - length/2); // Top Left Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/2); // Bottom Left Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/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 - length/2); // Bottom Right Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z - length/2); // Top Right Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x + width/2, y + height/2, z + length/2); // Top Left Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + width/2, y - height/2, z + length/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 - length/2); // Bottom Left Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(x - width/2, y - height/2, z + length/2); // Bottom Right Of The Texture and Quad
|
||
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z + length/2); // Top Right Of The Texture and Quad
|
||
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(x - width/2, y + height/2, z - length/2); // Top Left Of The Texture and Quad
|
||
|
rlEnd();
|
||
|
//rlPopMatrix();
|
||
|
|
||
|
rlSetTexture(0);
|
||
|
}
|
||
|
|
||
|
// Draw cube with texture piece applied to all faces
|
||
|
void DrawCubeTextureRec(Texture2D texture, Rectangle source, Vector3 position, float width, float height, float length, Color color)
|
||
|
{
|
||
|
float x = position.x;
|
||
|
float y = position.y;
|
||
|
float z = position.z;
|
||
|
float texWidth = (float)texture.width;
|
||
|
float texHeight = (float)texture.height;
|
||
|
|
||
|
rlCheckRenderBatchLimit(36);
|
||
|
|
||
|
rlSetTexture(texture.id);
|
||
|
|
||
|
rlBegin(RL_QUADS);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
// Front face
|
||
|
rlNormal3f(0.0f, 0.0f, 1.0f);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2);
|
||
|
|
||
|
// Back face
|
||
|
rlNormal3f(0.0f, 0.0f, - 1.0f);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2);
|
||
|
|
||
|
// Top face
|
||
|
rlNormal3f(0.0f, 1.0f, 0.0f);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2);
|
||
|
|
||
|
// Bottom face
|
||
|
rlNormal3f(0.0f, - 1.0f, 0.0f);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2);
|
||
|
|
||
|
// Right face
|
||
|
rlNormal3f(1.0f, 0.0f, 0.0f);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z - length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z - length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x + width/2, y + height/2, z + length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x + width/2, y - height/2, z + length/2);
|
||
|
|
||
|
// Left face
|
||
|
rlNormal3f( - 1.0f, 0.0f, 0.0f);
|
||
|
rlTexCoord2f(source.x/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z - length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, (source.y + source.height)/texHeight);
|
||
|
rlVertex3f(x - width/2, y - height/2, z + length/2);
|
||
|
rlTexCoord2f((source.x + source.width)/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z + length/2);
|
||
|
rlTexCoord2f(source.x/texWidth, source.y/texHeight);
|
||
|
rlVertex3f(x - width/2, y + height/2, z - length/2);
|
||
|
|
||
|
rlEnd();
|
||
|
|
||
|
rlSetTexture(0);
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
{
|
||
|
int numVertex = (rings + 2)*slices*6;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
// NOTE: Transformation is applied in inverse order (scale -> translate)
|
||
|
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
|
||
|
rlScalef(radius, radius, radius);
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 0; i < (rings + 2); i++)
|
||
|
{
|
||
|
for (int j = 0; j < slices; j++)
|
||
|
{
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
|
||
|
}
|
||
|
}
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw sphere wires
|
||
|
void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color)
|
||
|
{
|
||
|
int numVertex = (rings + 2)*slices*6;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
// NOTE: Transformation is applied in inverse order (scale -> translate)
|
||
|
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
|
||
|
rlScalef(radius, radius, radius);
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 0; i < (rings + 2); i++)
|
||
|
{
|
||
|
for (int j = 0; j < slices; j++)
|
||
|
{
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
|
||
|
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)),
|
||
|
sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)),
|
||
|
cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices)));
|
||
|
}
|
||
|
}
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw a cylinder
|
||
|
// NOTE: It could be also used for pyramid and cone
|
||
|
void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color)
|
||
|
{
|
||
|
if (sides < 3) sides = 3;
|
||
|
|
||
|
int numVertex = sides*6;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(position.x, position.y, position.z);
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
if (radiusTop > 0)
|
||
|
{
|
||
|
// Draw Body -------------------------------------------------------------------------------------
|
||
|
for (int i = 0; i < 360; i += 360/sides)
|
||
|
{
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); //Bottom Right
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); //Top Right
|
||
|
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); //Top Left
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); //Top Right
|
||
|
}
|
||
|
|
||
|
// Draw Cap --------------------------------------------------------------------------------------
|
||
|
for (int i = 0; i < 360; i += 360/sides)
|
||
|
{
|
||
|
rlVertex3f(0, height, 0);
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Draw Cone -------------------------------------------------------------------------------------
|
||
|
for (int i = 0; i < 360; i += 360/sides)
|
||
|
{
|
||
|
rlVertex3f(0, height, 0);
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw Base -----------------------------------------------------------------------------------------
|
||
|
for (int i = 0; i < 360; i += 360/sides)
|
||
|
{
|
||
|
rlVertex3f(0, 0, 0);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom);
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom);
|
||
|
}
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw a cylinder with base at startPos and top at endPos
|
||
|
// NOTE: It could be also used for pyramid and cone
|
||
|
void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color)
|
||
|
{
|
||
|
if (sides < 3) sides = 3;
|
||
|
|
||
|
int numVertex = sides*6;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
|
||
|
if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0)) return;
|
||
|
|
||
|
// Construct a basis of the base and the top face:
|
||
|
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
|
||
|
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
|
||
|
|
||
|
float baseAngle = (2.0f*PI)/sides;
|
||
|
|
||
|
rlBegin(RL_TRIANGLES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 0; i < sides; i++) {
|
||
|
// compute the four vertices
|
||
|
float s1 = sinf(baseAngle*(i + 0))*startRadius;
|
||
|
float c1 = cosf(baseAngle*(i + 0))*startRadius;
|
||
|
Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z };
|
||
|
float s2 = sinf(baseAngle*(i + 1))*startRadius;
|
||
|
float c2 = cosf(baseAngle*(i + 1))*startRadius;
|
||
|
Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z };
|
||
|
float s3 = sinf(baseAngle*(i + 0))*endRadius;
|
||
|
float c3 = cosf(baseAngle*(i + 0))*endRadius;
|
||
|
Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z };
|
||
|
float s4 = sinf(baseAngle*(i + 1))*endRadius;
|
||
|
float c4 = cosf(baseAngle*(i + 1))*endRadius;
|
||
|
Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z };
|
||
|
|
||
|
if (startRadius > 0) { //
|
||
|
rlVertex3f(startPos.x, startPos.y, startPos.z); // |
|
||
|
rlVertex3f(w2.x, w2.y, w2.z); // T0
|
||
|
rlVertex3f(w1.x, w1.y, w1.z); // |
|
||
|
} //
|
||
|
// w2 x.-----------x startPos
|
||
|
rlVertex3f(w1.x, w1.y, w1.z); // | |\'. T0 /
|
||
|
rlVertex3f(w2.x, w2.y, w2.z); // T1 | \ '. /
|
||
|
rlVertex3f(w3.x, w3.y, w3.z); // | |T \ '. /
|
||
|
// | 2 \ T 'x w1
|
||
|
rlVertex3f(w2.x, w2.y, w2.z); // | w4 x.---\-1-|---x endPos
|
||
|
rlVertex3f(w4.x, w4.y, w4.z); // T2 '. \ |T3/
|
||
|
rlVertex3f(w3.x, w3.y, w3.z); // | '. \ | /
|
||
|
// '.\|/
|
||
|
if (endRadius > 0) { // 'x w3
|
||
|
rlVertex3f(endPos.x, endPos.y, endPos.z); // |
|
||
|
rlVertex3f(w3.x, w3.y, w3.z); // T3
|
||
|
rlVertex3f(w4.x, w4.y, w4.z); // |
|
||
|
} //
|
||
|
}
|
||
|
rlEnd();
|
||
|
}
|
||
|
|
||
|
// Draw a wired cylinder
|
||
|
// NOTE: It could be also used for pyramid and cone
|
||
|
void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int sides, Color color)
|
||
|
{
|
||
|
if (sides < 3) sides = 3;
|
||
|
|
||
|
int numVertex = sides*8;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(position.x, position.y, position.z);
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 0; i < 360; i += 360/sides)
|
||
|
{
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom);
|
||
|
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom);
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop);
|
||
|
|
||
|
rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop);
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop);
|
||
|
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop);
|
||
|
rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom);
|
||
|
}
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
|
||
|
// Draw a wired cylinder with base at startPos and top at endPos
|
||
|
// NOTE: It could be also used for pyramid and cone
|
||
|
void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color)
|
||
|
{
|
||
|
if (sides < 3) sides = 3;
|
||
|
|
||
|
int numVertex = sides*6;
|
||
|
rlCheckRenderBatchLimit(numVertex);
|
||
|
|
||
|
Vector3 direction = { endPos.x - startPos.x, endPos.y - startPos.y, endPos.z - startPos.z };
|
||
|
if ((direction.x == 0) && (direction.y == 0) && (direction.z == 0))return;
|
||
|
|
||
|
// Construct a basis of the base and the top face:
|
||
|
Vector3 b1 = Vector3Normalize(Vector3Perpendicular(direction));
|
||
|
Vector3 b2 = Vector3Normalize(Vector3CrossProduct(b1, direction));
|
||
|
|
||
|
float baseAngle = (2.0f*PI)/sides;
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
for (int i = 0; i < sides; i++) {
|
||
|
// compute the four vertices
|
||
|
float s1 = sinf(baseAngle*(i + 0))*startRadius;
|
||
|
float c1 = cosf(baseAngle*(i + 0))*startRadius;
|
||
|
Vector3 w1 = { startPos.x + s1*b1.x + c1*b2.x, startPos.y + s1*b1.y + c1*b2.y, startPos.z + s1*b1.z + c1*b2.z };
|
||
|
float s2 = sinf(baseAngle*(i + 1))*startRadius;
|
||
|
float c2 = cosf(baseAngle*(i + 1))*startRadius;
|
||
|
Vector3 w2 = { startPos.x + s2*b1.x + c2*b2.x, startPos.y + s2*b1.y + c2*b2.y, startPos.z + s2*b1.z + c2*b2.z };
|
||
|
float s3 = sinf(baseAngle*(i + 0))*endRadius;
|
||
|
float c3 = cosf(baseAngle*(i + 0))*endRadius;
|
||
|
Vector3 w3 = { endPos.x + s3*b1.x + c3*b2.x, endPos.y + s3*b1.y + c3*b2.y, endPos.z + s3*b1.z + c3*b2.z };
|
||
|
float s4 = sinf(baseAngle*(i + 1))*endRadius;
|
||
|
float c4 = cosf(baseAngle*(i + 1))*endRadius;
|
||
|
Vector3 w4 = { endPos.x + s4*b1.x + c4*b2.x, endPos.y + s4*b1.y + c4*b2.y, endPos.z + s4*b1.z + c4*b2.z };
|
||
|
|
||
|
rlVertex3f(w1.x, w1.y, w1.z);
|
||
|
rlVertex3f(w2.x, w2.y, w2.z);
|
||
|
|
||
|
rlVertex3f(w1.x, w1.y, w1.z);
|
||
|
rlVertex3f(w3.x, w3.y, w3.z);
|
||
|
|
||
|
rlVertex3f(w3.x, w3.y, w3.z);
|
||
|
rlVertex3f(w4.x, w4.y, w4.z);
|
||
|
}
|
||
|
rlEnd();
|
||
|
}
|
||
|
|
||
|
|
||
|
// Draw a plane
|
||
|
void DrawPlane(Vector3 centerPos, Vector2 size, Color color)
|
||
|
{
|
||
|
rlCheckRenderBatchLimit(4);
|
||
|
|
||
|
// NOTE: Plane is always created on XZ ground
|
||
|
rlPushMatrix();
|
||
|
rlTranslatef(centerPos.x, centerPos.y, centerPos.z);
|
||
|
rlScalef(size.x, 1.0f, size.y);
|
||
|
|
||
|
rlBegin(RL_QUADS);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
rlNormal3f(0.0f, 1.0f, 0.0f);
|
||
|
|
||
|
rlVertex3f(-0.5f, 0.0f, -0.5f);
|
||
|
rlVertex3f(-0.5f, 0.0f, 0.5f);
|
||
|
rlVertex3f(0.5f, 0.0f, 0.5f);
|
||
|
rlVertex3f(0.5f, 0.0f, -0.5f);
|
||
|
rlEnd();
|
||
|
rlPopMatrix();
|
||
|
}
|
||
|
|
||
|
// Draw a ray line
|
||
|
void DrawRay(Ray ray, Color color)
|
||
|
{
|
||
|
float scale = 10000;
|
||
|
|
||
|
rlBegin(RL_LINES);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
rlColor4ub(color.r, color.g, color.b, color.a);
|
||
|
|
||
|
rlVertex3f(ray.position.x, ray.position.y, ray.position.z);
|
||
|
rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale);
|
||
|
rlEnd();
|
||
|
}
|
||
|
|
||
|
// Draw a grid centered at (0, 0, 0)
|
||
|
void DrawGrid(int slices, float spacing)
|
||
|
{
|
||
|
int halfSlices = slices/2;
|
||
|
|
||
|
rlCheckRenderBatchLimit((slices + 2)*4);
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
// Load model from files (mesh and material)
|
||
|
Model LoadModel(const char *fileName)
|
||
|
{
|
||
|
Model model = { 0 };
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_OBJ)
|
||
|
if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName);
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||
|
if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName);
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||
|
if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName);
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_VOX)
|
||
|
if (IsFileExtension(fileName, ".vox")) model = LoadVOX(fileName);
|
||
|
#endif
|
||
|
|
||
|
// Make sure model transform is set to identity matrix!
|
||
|
model.transform = MatrixIdentity();
|
||
|
|
||
|
if (model.meshCount == 0)
|
||
|
{
|
||
|
model.meshCount = 1;
|
||
|
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
#if defined(SUPPORT_MESH_GENERATION)
|
||
|
TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data, default to cube mesh", fileName);
|
||
|
model.meshes[0] = GenMeshCube(1.0f, 1.0f, 1.0f);
|
||
|
#else
|
||
|
TRACELOG(LOG_WARNING, "MESH: [%s] Failed to load mesh data", fileName);
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false);
|
||
|
}
|
||
|
|
||
|
if (model.materialCount == 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to load material data, default to white material", fileName);
|
||
|
|
||
|
model.materialCount = 1;
|
||
|
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
model.materials[0] = LoadMaterialDefault();
|
||
|
|
||
|
if (model.meshMaterial == NULL) model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
}
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
// Load model from generated mesh
|
||
|
// WARNING: A shallow copy of mesh is generated, passed by value,
|
||
|
// as long as struct contains pointers to data and some values, we get a copy
|
||
|
// of mesh pointing to same data as original version... be careful!
|
||
|
Model LoadModelFromMesh(Mesh mesh)
|
||
|
{
|
||
|
Model model = { 0 };
|
||
|
|
||
|
model.transform = MatrixIdentity();
|
||
|
|
||
|
model.meshCount = 1;
|
||
|
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
model.meshes[0] = mesh;
|
||
|
|
||
|
model.materialCount = 1;
|
||
|
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
model.materials[0] = LoadMaterialDefault();
|
||
|
|
||
|
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
model.meshMaterial[0] = 0; // First material index
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
// Unload model (meshes/materials) from memory (RAM and/or VRAM)
|
||
|
// NOTE: This function takes care of all model elements, for a detailed control
|
||
|
// over them, use UnloadMesh() and UnloadMaterial()
|
||
|
void UnloadModel(Model model)
|
||
|
{
|
||
|
// Unload meshes
|
||
|
for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]);
|
||
|
|
||
|
// Unload materials maps
|
||
|
// NOTE: As the user could be sharing shaders and textures between models,
|
||
|
// we don't unload the material but just free it's maps,
|
||
|
// the user is responsible for freeing models shaders and textures
|
||
|
for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps);
|
||
|
|
||
|
// Unload arrays
|
||
|
RL_FREE(model.meshes);
|
||
|
RL_FREE(model.materials);
|
||
|
RL_FREE(model.meshMaterial);
|
||
|
|
||
|
// Unload animation data
|
||
|
RL_FREE(model.bones);
|
||
|
RL_FREE(model.bindPose);
|
||
|
|
||
|
TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM");
|
||
|
}
|
||
|
|
||
|
// Unload model (but not meshes) from memory (RAM and/or VRAM)
|
||
|
void UnloadModelKeepMeshes(Model model)
|
||
|
{
|
||
|
// Unload materials maps
|
||
|
// NOTE: As the user could be sharing shaders and textures between models,
|
||
|
// we don't unload the material but just free it's maps,
|
||
|
// the user is responsible for freeing models shaders and textures
|
||
|
for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps);
|
||
|
|
||
|
// Unload arrays
|
||
|
RL_FREE(model.meshes);
|
||
|
RL_FREE(model.materials);
|
||
|
RL_FREE(model.meshMaterial);
|
||
|
|
||
|
// Unload animation data
|
||
|
RL_FREE(model.bones);
|
||
|
RL_FREE(model.bindPose);
|
||
|
|
||
|
TRACELOG(LOG_INFO, "MODEL: Unloaded model (but not meshes) from RAM and VRAM");
|
||
|
}
|
||
|
|
||
|
// Compute model bounding box limits (considers all meshes)
|
||
|
BoundingBox GetModelBoundingBox(Model model)
|
||
|
{
|
||
|
BoundingBox bounds = { 0 };
|
||
|
|
||
|
if (model.meshCount > 0)
|
||
|
{
|
||
|
Vector3 temp = { 0 };
|
||
|
bounds = GetMeshBoundingBox(model.meshes[0]);
|
||
|
|
||
|
for (int i = 1; i < model.meshCount; i++)
|
||
|
{
|
||
|
BoundingBox tempBounds = GetMeshBoundingBox(model.meshes[i]);
|
||
|
|
||
|
temp.x = (bounds.min.x < tempBounds.min.x)? bounds.min.x : tempBounds.min.x;
|
||
|
temp.y = (bounds.min.y < tempBounds.min.y)? bounds.min.y : tempBounds.min.y;
|
||
|
temp.z = (bounds.min.z < tempBounds.min.z)? bounds.min.z : tempBounds.min.z;
|
||
|
bounds.min = temp;
|
||
|
|
||
|
temp.x = (bounds.max.x > tempBounds.max.x)? bounds.max.x : tempBounds.max.x;
|
||
|
temp.y = (bounds.max.y > tempBounds.max.y)? bounds.max.y : tempBounds.max.y;
|
||
|
temp.z = (bounds.max.z > tempBounds.max.z)? bounds.max.z : tempBounds.max.z;
|
||
|
bounds.max = temp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bounds;
|
||
|
}
|
||
|
|
||
|
// Upload vertex data into a VAO (if supported) and VBO
|
||
|
void UploadMesh(Mesh *mesh, bool dynamic)
|
||
|
{
|
||
|
if (mesh->vaoId > 0)
|
||
|
{
|
||
|
// Check if mesh has already been loaded in GPU
|
||
|
TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
|
||
|
|
||
|
mesh->vaoId = 0; // Vertex Array Object
|
||
|
mesh->vboId[0] = 0; // Vertex buffer: positions
|
||
|
mesh->vboId[1] = 0; // Vertex buffer: texcoords
|
||
|
mesh->vboId[2] = 0; // Vertex buffer: normals
|
||
|
mesh->vboId[3] = 0; // Vertex buffer: colors
|
||
|
mesh->vboId[4] = 0; // Vertex buffer: tangents
|
||
|
mesh->vboId[5] = 0; // Vertex buffer: texcoords2
|
||
|
mesh->vboId[6] = 0; // Vertex buffer: indices
|
||
|
|
||
|
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
|
||
|
mesh->vaoId = rlLoadVertexArray();
|
||
|
rlEnableVertexArray(mesh->vaoId);
|
||
|
|
||
|
// NOTE: Attributes must be uploaded considering default locations points
|
||
|
|
||
|
// Enable vertex attributes: position (shader-location = 0)
|
||
|
void *vertices = mesh->animVertices != NULL ? mesh->animVertices : mesh->vertices;
|
||
|
mesh->vboId[0] = rlLoadVertexBuffer(vertices, mesh->vertexCount*3*sizeof(float), dynamic);
|
||
|
rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(0);
|
||
|
|
||
|
// Enable vertex attributes: texcoords (shader-location = 1)
|
||
|
mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic);
|
||
|
rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(1);
|
||
|
|
||
|
if (mesh->normals != NULL)
|
||
|
{
|
||
|
// Enable vertex attributes: normals (shader-location = 2)
|
||
|
void *normals = mesh->animNormals != NULL ? mesh->animNormals : mesh->normals;
|
||
|
mesh->vboId[2] = rlLoadVertexBuffer(normals, mesh->vertexCount*3*sizeof(float), dynamic);
|
||
|
rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(2);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Default color vertex attribute set to WHITE
|
||
|
float value[3] = { 1.0f, 1.0f, 1.0f };
|
||
|
rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3);
|
||
|
rlDisableVertexAttribute(2);
|
||
|
}
|
||
|
|
||
|
if (mesh->colors != NULL)
|
||
|
{
|
||
|
// Enable vertex attribute: color (shader-location = 3)
|
||
|
mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic);
|
||
|
rlSetVertexAttribute(3, 4, RL_UNSIGNED_BYTE, 1, 0, 0);
|
||
|
rlEnableVertexAttribute(3);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Default color vertex attribute set to WHITE
|
||
|
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||
|
rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4);
|
||
|
rlDisableVertexAttribute(3);
|
||
|
}
|
||
|
|
||
|
if (mesh->tangents != NULL)
|
||
|
{
|
||
|
// Enable vertex attribute: tangent (shader-location = 4)
|
||
|
mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic);
|
||
|
rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(4);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Default tangents vertex attribute
|
||
|
float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||
|
rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4);
|
||
|
rlDisableVertexAttribute(4);
|
||
|
}
|
||
|
|
||
|
if (mesh->texcoords2 != NULL)
|
||
|
{
|
||
|
// Enable vertex attribute: texcoord2 (shader-location = 5)
|
||
|
mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic);
|
||
|
rlSetVertexAttribute(5, 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(5);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Default texcoord2 vertex attribute
|
||
|
float value[2] = { 0.0f, 0.0f };
|
||
|
rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2);
|
||
|
rlDisableVertexAttribute(5);
|
||
|
}
|
||
|
|
||
|
if (mesh->indices != NULL)
|
||
|
{
|
||
|
mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic);
|
||
|
}
|
||
|
|
||
|
if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId);
|
||
|
else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)");
|
||
|
|
||
|
rlDisableVertexArray();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Update mesh vertex data in GPU for a specific buffer index
|
||
|
void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset)
|
||
|
{
|
||
|
rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset);
|
||
|
}
|
||
|
|
||
|
// Draw a 3d mesh with material and transform
|
||
|
void DrawMesh(Mesh mesh, Material material, Matrix transform)
|
||
|
{
|
||
|
#if defined(GRAPHICS_API_OPENGL_11)
|
||
|
#define GL_VERTEX_ARRAY 0x8074
|
||
|
#define GL_NORMAL_ARRAY 0x8075
|
||
|
#define GL_COLOR_ARRAY 0x8076
|
||
|
#define GL_TEXTURE_COORD_ARRAY 0x8078
|
||
|
|
||
|
rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id);
|
||
|
|
||
|
rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices);
|
||
|
rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords);
|
||
|
rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals);
|
||
|
rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors);
|
||
|
|
||
|
rlPushMatrix();
|
||
|
rlMultMatrixf(MatrixToFloat(transform));
|
||
|
rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r,
|
||
|
material.maps[MATERIAL_MAP_DIFFUSE].color.g,
|
||
|
material.maps[MATERIAL_MAP_DIFFUSE].color.b,
|
||
|
material.maps[MATERIAL_MAP_DIFFUSE].color.a);
|
||
|
|
||
|
if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices);
|
||
|
else rlDrawVertexArray(0, mesh.vertexCount);
|
||
|
rlPopMatrix();
|
||
|
|
||
|
rlDisableStatePointer(GL_VERTEX_ARRAY);
|
||
|
rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY);
|
||
|
rlDisableStatePointer(GL_NORMAL_ARRAY);
|
||
|
rlDisableStatePointer(GL_COLOR_ARRAY);
|
||
|
|
||
|
rlDisableTexture();
|
||
|
#endif
|
||
|
|
||
|
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
|
||
|
// Bind shader program
|
||
|
rlEnableShader(material.shader.id);
|
||
|
|
||
|
// Send required data to shader (matrices, values)
|
||
|
//-----------------------------------------------------
|
||
|
// Upload to shader material.colDiffuse
|
||
|
if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1)
|
||
|
{
|
||
|
float values[4] = {
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f
|
||
|
};
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1);
|
||
|
}
|
||
|
|
||
|
// Upload to shader material.colSpecular (if location available)
|
||
|
if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1)
|
||
|
{
|
||
|
float values[4] = {
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f
|
||
|
};
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1);
|
||
|
}
|
||
|
|
||
|
// Get a copy of current matrices to work with,
|
||
|
// just in case stereo render is required and we need to modify them
|
||
|
// NOTE: At this point the modelview matrix just contains the view matrix (camera)
|
||
|
// That's because BeginMode3D() sets it and there is no model-drawing function
|
||
|
// that modifies it, all use rlPushMatrix() and rlPopMatrix()
|
||
|
Matrix matModel = MatrixIdentity();
|
||
|
Matrix matView = rlGetMatrixModelview();
|
||
|
Matrix matModelView = MatrixIdentity();
|
||
|
Matrix matProjection = rlGetMatrixProjection();
|
||
|
|
||
|
// Upload view and projection matrices (if locations available)
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView);
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection);
|
||
|
|
||
|
// Model transformation matrix is send to shader uniform location: SHADER_LOC_MATRIX_MODEL
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], transform);
|
||
|
|
||
|
// Accumulate several model transformations:
|
||
|
// transform: model transformation provided (includes DrawModel() params combined with model.transform)
|
||
|
// rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack
|
||
|
matModel = MatrixMultiply(transform, rlGetMatrixTransform());
|
||
|
|
||
|
// Get model-view matrix
|
||
|
matModelView = MatrixMultiply(matModel, matView);
|
||
|
|
||
|
// Upload model normal matrix (if locations available)
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel)));
|
||
|
//-----------------------------------------------------
|
||
|
|
||
|
// Bind active texture maps (if available)
|
||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||
|
{
|
||
|
if (material.maps[i].texture.id > 0)
|
||
|
{
|
||
|
// Select current shader texture slot
|
||
|
rlActiveTextureSlot(i);
|
||
|
|
||
|
// Enable texture for active slot
|
||
|
if ((i == MATERIAL_MAP_IRRADIANCE) ||
|
||
|
(i == MATERIAL_MAP_PREFILTER) ||
|
||
|
(i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id);
|
||
|
else rlEnableTexture(material.maps[i].texture.id);
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try binding vertex array objects (VAO)
|
||
|
// or use VBOs if not possible
|
||
|
if (!rlEnableVertexArray(mesh.vaoId))
|
||
|
{
|
||
|
// Bind mesh VBO data: vertex position (shader-location = 0)
|
||
|
rlEnableVertexBuffer(mesh.vboId[0]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]);
|
||
|
|
||
|
// Bind mesh VBO data: vertex texcoords (shader-location = 1)
|
||
|
rlEnableVertexBuffer(mesh.vboId[1]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]);
|
||
|
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1)
|
||
|
{
|
||
|
// Bind mesh VBO data: vertex normals (shader-location = 2)
|
||
|
rlEnableVertexBuffer(mesh.vboId[2]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]);
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex colors (shader-location = 3, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1)
|
||
|
{
|
||
|
if (mesh.vboId[3] != 0)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[3]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Set default value for unused attribute
|
||
|
// NOTE: Required when using default shader and no VAO support
|
||
|
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||
|
rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4);
|
||
|
rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex tangents (shader-location = 4, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[4]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]);
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[5]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]);
|
||
|
}
|
||
|
|
||
|
if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]);
|
||
|
}
|
||
|
|
||
|
int eyeCount = 1;
|
||
|
if (rlIsStereoRenderEnabled()) eyeCount = 2;
|
||
|
|
||
|
for (int eye = 0; eye < eyeCount; eye++)
|
||
|
{
|
||
|
// Calculate model-view-projection matrix (MVP)
|
||
|
Matrix matModelViewProjection = MatrixIdentity();
|
||
|
if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection);
|
||
|
else
|
||
|
{
|
||
|
// Setup current eye viewport (half screen width)
|
||
|
rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight());
|
||
|
matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye));
|
||
|
}
|
||
|
|
||
|
// Send combined model-view-projection matrix to shader
|
||
|
rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection);
|
||
|
|
||
|
// Draw mesh
|
||
|
if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0);
|
||
|
else rlDrawVertexArray(0, mesh.vertexCount);
|
||
|
}
|
||
|
|
||
|
// Unbind all binded texture maps
|
||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||
|
{
|
||
|
// Select current shader texture slot
|
||
|
rlActiveTextureSlot(i);
|
||
|
|
||
|
// Disable texture for active slot
|
||
|
if ((i == MATERIAL_MAP_IRRADIANCE) ||
|
||
|
(i == MATERIAL_MAP_PREFILTER) ||
|
||
|
(i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap();
|
||
|
else rlDisableTexture();
|
||
|
}
|
||
|
|
||
|
// Disable all possible vertex array objects (or VBOs)
|
||
|
rlDisableVertexArray();
|
||
|
rlDisableVertexBuffer();
|
||
|
rlDisableVertexBufferElement();
|
||
|
|
||
|
// Disable shader program
|
||
|
rlDisableShader();
|
||
|
|
||
|
// Restore rlgl internal modelview and projection matrices
|
||
|
rlSetMatrixModelview(matView);
|
||
|
rlSetMatrixProjection(matProjection);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Draw multiple mesh instances with material and different transforms
|
||
|
void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances)
|
||
|
{
|
||
|
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
|
||
|
// Instancing required variables
|
||
|
float16 *instanceTransforms = NULL;
|
||
|
unsigned int instancesVboId = 0;
|
||
|
|
||
|
// Bind shader program
|
||
|
rlEnableShader(material.shader.id);
|
||
|
|
||
|
// Send required data to shader (matrices, values)
|
||
|
//-----------------------------------------------------
|
||
|
// Upload to shader material.colDiffuse
|
||
|
if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1)
|
||
|
{
|
||
|
float values[4] = {
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f,
|
||
|
(float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f
|
||
|
};
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1);
|
||
|
}
|
||
|
|
||
|
// Upload to shader material.colSpecular (if location available)
|
||
|
if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1)
|
||
|
{
|
||
|
float values[4] = {
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f,
|
||
|
(float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f
|
||
|
};
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1);
|
||
|
}
|
||
|
|
||
|
// Get a copy of current matrices to work with,
|
||
|
// just in case stereo render is required and we need to modify them
|
||
|
// NOTE: At this point the modelview matrix just contains the view matrix (camera)
|
||
|
// That's because BeginMode3D() sets it and there is no model-drawing function
|
||
|
// that modifies it, all use rlPushMatrix() and rlPopMatrix()
|
||
|
Matrix matModel = MatrixIdentity();
|
||
|
Matrix matView = rlGetMatrixModelview();
|
||
|
Matrix matModelView = MatrixIdentity();
|
||
|
Matrix matProjection = rlGetMatrixProjection();
|
||
|
|
||
|
// Upload view and projection matrices (if locations available)
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView);
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection);
|
||
|
|
||
|
// Create instances buffer
|
||
|
instanceTransforms = (float16 *)RL_MALLOC(instances*sizeof(float16));
|
||
|
|
||
|
// Fill buffer with instances transformations as float16 arrays
|
||
|
for (int i = 0; i < instances; i++) instanceTransforms[i] = MatrixToFloatV(transforms[i]);
|
||
|
|
||
|
// Enable mesh VAO to attach new buffer
|
||
|
rlEnableVertexArray(mesh.vaoId);
|
||
|
|
||
|
// This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData().
|
||
|
// It isn't clear which would be reliably faster in all cases and on all platforms,
|
||
|
// anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems
|
||
|
// no faster, since we're transferring all the transform matrices anyway
|
||
|
instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false);
|
||
|
|
||
|
// Instances transformation matrices are send to shader attribute location: SHADER_LOC_MATRIX_MODEL
|
||
|
for (unsigned int i = 0; i < 4; i++)
|
||
|
{
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 4, RL_FLOAT, 0, sizeof(Matrix), (void *)(i*sizeof(Vector4)));
|
||
|
rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 1);
|
||
|
}
|
||
|
|
||
|
rlDisableVertexBuffer();
|
||
|
rlDisableVertexArray();
|
||
|
|
||
|
// Accumulate internal matrix transform (push/pop) and view matrix
|
||
|
// NOTE: In this case, model instance transformation must be computed in the shader
|
||
|
matModelView = MatrixMultiply(rlGetMatrixTransform(), matView);
|
||
|
|
||
|
// Upload model normal matrix (if locations available)
|
||
|
if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModel)));
|
||
|
//-----------------------------------------------------
|
||
|
|
||
|
// Bind active texture maps (if available)
|
||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||
|
{
|
||
|
if (material.maps[i].texture.id > 0)
|
||
|
{
|
||
|
// Select current shader texture slot
|
||
|
rlActiveTextureSlot(i);
|
||
|
|
||
|
// Enable texture for active slot
|
||
|
if ((i == MATERIAL_MAP_IRRADIANCE) ||
|
||
|
(i == MATERIAL_MAP_PREFILTER) ||
|
||
|
(i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id);
|
||
|
else rlEnableTexture(material.maps[i].texture.id);
|
||
|
|
||
|
rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try binding vertex array objects (VAO)
|
||
|
// or use VBOs if not possible
|
||
|
if (!rlEnableVertexArray(mesh.vaoId))
|
||
|
{
|
||
|
// Bind mesh VBO data: vertex position (shader-location = 0)
|
||
|
rlEnableVertexBuffer(mesh.vboId[0]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]);
|
||
|
|
||
|
// Bind mesh VBO data: vertex texcoords (shader-location = 1)
|
||
|
rlEnableVertexBuffer(mesh.vboId[1]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]);
|
||
|
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1)
|
||
|
{
|
||
|
// Bind mesh VBO data: vertex normals (shader-location = 2)
|
||
|
rlEnableVertexBuffer(mesh.vboId[2]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]);
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex colors (shader-location = 3, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1)
|
||
|
{
|
||
|
if (mesh.vboId[3] != 0)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[3]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Set default value for unused attribute
|
||
|
// NOTE: Required when using default shader and no VAO support
|
||
|
float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||
|
rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC4, 4);
|
||
|
rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex tangents (shader-location = 4, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[4]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]);
|
||
|
}
|
||
|
|
||
|
// Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available)
|
||
|
if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1)
|
||
|
{
|
||
|
rlEnableVertexBuffer(mesh.vboId[5]);
|
||
|
rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]);
|
||
|
}
|
||
|
|
||
|
if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]);
|
||
|
}
|
||
|
|
||
|
int eyeCount = 1;
|
||
|
if (rlIsStereoRenderEnabled()) eyeCount = 2;
|
||
|
|
||
|
for (int eye = 0; eye < eyeCount; eye++)
|
||
|
{
|
||
|
// Calculate model-view-projection matrix (MVP)
|
||
|
Matrix matModelViewProjection = MatrixIdentity();
|
||
|
if (eyeCount == 1) matModelViewProjection = MatrixMultiply(matModelView, matProjection);
|
||
|
else
|
||
|
{
|
||
|
// Setup current eye viewport (half screen width)
|
||
|
rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight());
|
||
|
matModelViewProjection = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye));
|
||
|
}
|
||
|
|
||
|
// Send combined model-view-projection matrix to shader
|
||
|
rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matModelViewProjection);
|
||
|
|
||
|
// Draw mesh instanced
|
||
|
if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances);
|
||
|
else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances);
|
||
|
}
|
||
|
|
||
|
// Unbind all binded texture maps
|
||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||
|
{
|
||
|
// Select current shader texture slot
|
||
|
rlActiveTextureSlot(i);
|
||
|
|
||
|
// Disable texture for active slot
|
||
|
if ((i == MATERIAL_MAP_IRRADIANCE) ||
|
||
|
(i == MATERIAL_MAP_PREFILTER) ||
|
||
|
(i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap();
|
||
|
else rlDisableTexture();
|
||
|
}
|
||
|
|
||
|
// Disable all possible vertex array objects (or VBOs)
|
||
|
rlDisableVertexArray();
|
||
|
rlDisableVertexBuffer();
|
||
|
rlDisableVertexBufferElement();
|
||
|
|
||
|
// Disable shader program
|
||
|
rlDisableShader();
|
||
|
|
||
|
// Remove instance transforms buffer
|
||
|
rlUnloadVertexBuffer(instancesVboId);
|
||
|
RL_FREE(instanceTransforms);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Unload mesh from memory (RAM and VRAM)
|
||
|
void UnloadMesh(Mesh mesh)
|
||
|
{
|
||
|
// Unload rlgl mesh vboId data
|
||
|
rlUnloadVertexArray(mesh.vaoId);
|
||
|
|
||
|
if (mesh.vboId != NULL) for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]);
|
||
|
RL_FREE(mesh.vboId);
|
||
|
|
||
|
RL_FREE(mesh.vertices);
|
||
|
RL_FREE(mesh.texcoords);
|
||
|
RL_FREE(mesh.normals);
|
||
|
RL_FREE(mesh.colors);
|
||
|
RL_FREE(mesh.tangents);
|
||
|
RL_FREE(mesh.texcoords2);
|
||
|
RL_FREE(mesh.indices);
|
||
|
|
||
|
RL_FREE(mesh.animVertices);
|
||
|
RL_FREE(mesh.animNormals);
|
||
|
RL_FREE(mesh.boneWeights);
|
||
|
RL_FREE(mesh.boneIds);
|
||
|
}
|
||
|
|
||
|
// Export mesh data to file
|
||
|
bool ExportMesh(Mesh mesh, const char *fileName)
|
||
|
{
|
||
|
bool success = false;
|
||
|
|
||
|
if (IsFileExtension(fileName, ".obj"))
|
||
|
{
|
||
|
// Estimated data size, it should be enough...
|
||
|
int dataSize = mesh.vertexCount*(int)strlen("v 0000.00f 0000.00f 0000.00f") +
|
||
|
mesh.vertexCount*(int)strlen("vt 0.000f 0.00f") +
|
||
|
mesh.vertexCount*(int)strlen("vn 0.000f 0.00f 0.00f") +
|
||
|
mesh.triangleCount*(int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000");
|
||
|
|
||
|
// NOTE: Text data buffer size is estimated considering mesh data size
|
||
|
char *txtData = (char *)RL_CALLOC(dataSize*2 + 2000, sizeof(char));
|
||
|
|
||
|
int byteCount = 0;
|
||
|
byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // feedback and support: ray[at]raylib.com //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2022 Ramon Santamaria (@raysan5) //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# // //\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n");
|
||
|
byteCount += sprintf(txtData + byteCount, "# Vertex Count: %i\n", mesh.vertexCount);
|
||
|
byteCount += sprintf(txtData + byteCount, "# Triangle Count: %i\n\n", mesh.triangleCount);
|
||
|
|
||
|
byteCount += sprintf(txtData + byteCount, "g mesh\n");
|
||
|
|
||
|
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
|
||
|
{
|
||
|
byteCount += sprintf(txtData + byteCount, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]);
|
||
|
}
|
||
|
|
||
|
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2)
|
||
|
{
|
||
|
byteCount += sprintf(txtData + byteCount, "vt %.3f %.3f\n", mesh.texcoords[v], mesh.texcoords[v + 1]);
|
||
|
}
|
||
|
|
||
|
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
|
||
|
{
|
||
|
byteCount += sprintf(txtData + byteCount, "vn %.3f %.3f %.3f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]);
|
||
|
}
|
||
|
|
||
|
if (mesh.indices != NULL)
|
||
|
{
|
||
|
for (int i = 0, v = 0; i < mesh.triangleCount; i++, v += 3)
|
||
|
{
|
||
|
byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n",
|
||
|
mesh.indices[v] + 1, mesh.indices[v] + 1, mesh.indices[v] + 1,
|
||
|
mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1, mesh.indices[v + 1] + 1,
|
||
|
mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1, mesh.indices[v + 2] + 1);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int i = 0, v = 1; i < mesh.triangleCount; i++, v += 3)
|
||
|
{
|
||
|
byteCount += sprintf(txtData + byteCount, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", v, v, v, v + 1, v + 1, v + 1, v + 2, v + 2, v + 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
byteCount += sprintf(txtData + byteCount, "\n");
|
||
|
|
||
|
// NOTE: Text data length exported is determined by '\0' (NULL) character
|
||
|
success = SaveFileText(fileName, txtData);
|
||
|
|
||
|
RL_FREE(txtData);
|
||
|
}
|
||
|
else if (IsFileExtension(fileName, ".raw"))
|
||
|
{
|
||
|
// TODO: Support additional file formats to export mesh vertex data
|
||
|
}
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
// Load materials from model file
|
||
|
Material *LoadMaterials(const char *fileName, int *materialCount)
|
||
|
{
|
||
|
Material *materials = NULL;
|
||
|
unsigned int count = 0;
|
||
|
|
||
|
// TODO: Support IQM and GLTF for materials parsing
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_MTL)
|
||
|
if (IsFileExtension(fileName, ".mtl"))
|
||
|
{
|
||
|
tinyobj_material_t *mats = NULL;
|
||
|
|
||
|
int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
|
||
|
if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName);
|
||
|
|
||
|
// TODO: Process materials to return
|
||
|
|
||
|
tinyobj_materials_free(mats, count);
|
||
|
}
|
||
|
#else
|
||
|
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load material file", fileName);
|
||
|
#endif
|
||
|
|
||
|
// Set materials shader to default (DIFFUSE, SPECULAR, NORMAL)
|
||
|
if (materials != NULL)
|
||
|
{
|
||
|
for (unsigned int i = 0; i < count; i++)
|
||
|
{
|
||
|
materials[i].shader.id = rlGetShaderIdDefault();
|
||
|
materials[i].shader.locs = rlGetShaderLocsDefault();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*materialCount = count;
|
||
|
return materials;
|
||
|
}
|
||
|
|
||
|
// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
||
|
Material LoadMaterialDefault(void)
|
||
|
{
|
||
|
Material material = { 0 };
|
||
|
material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap));
|
||
|
|
||
|
// Using rlgl default shader
|
||
|
material.shader.id = rlGetShaderIdDefault();
|
||
|
material.shader.locs = rlGetShaderLocsDefault();
|
||
|
|
||
|
// Using rlgl default texture (1x1 pixel, UNCOMPRESSED_R8G8B8A8, 1 mipmap)
|
||
|
material.maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
|
||
|
//material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set
|
||
|
//material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set
|
||
|
|
||
|
material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color
|
||
|
material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color
|
||
|
|
||
|
return material;
|
||
|
}
|
||
|
|
||
|
// Unload material from memory
|
||
|
void UnloadMaterial(Material material)
|
||
|
{
|
||
|
// Unload material shader (avoid unloading default shader, managed by raylib)
|
||
|
if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader);
|
||
|
|
||
|
// Unload loaded texture maps (avoid unloading default texture, managed by raylib)
|
||
|
if (material.maps != NULL)
|
||
|
{
|
||
|
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||
|
{
|
||
|
if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RL_FREE(material.maps);
|
||
|
}
|
||
|
|
||
|
// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...)
|
||
|
// NOTE: Previous texture should be manually unloaded
|
||
|
void SetMaterialTexture(Material *material, int mapType, Texture2D texture)
|
||
|
{
|
||
|
material->maps[mapType].texture = texture;
|
||
|
}
|
||
|
|
||
|
// Set the material for a mesh
|
||
|
void SetModelMeshMaterial(Model *model, int meshId, int materialId)
|
||
|
{
|
||
|
if (meshId >= model->meshCount) TRACELOG(LOG_WARNING, "MESH: Id greater than mesh count");
|
||
|
else if (materialId >= model->materialCount) TRACELOG(LOG_WARNING, "MATERIAL: Id greater than material count");
|
||
|
else model->meshMaterial[meshId] = materialId;
|
||
|
}
|
||
|
|
||
|
// Load model animations from file
|
||
|
ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCount)
|
||
|
{
|
||
|
ModelAnimation *animations = NULL;
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||
|
if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount);
|
||
|
#endif
|
||
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||
|
//if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationGLTF(fileName, animCount);
|
||
|
#endif
|
||
|
|
||
|
return animations;
|
||
|
}
|
||
|
|
||
|
// Update model animated vertex data (positions and normals) for a given frame
|
||
|
// NOTE: Updated data is uploaded to GPU
|
||
|
void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
|
||
|
{
|
||
|
if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL))
|
||
|
{
|
||
|
if (frame >= anim.frameCount) frame = frame%anim.frameCount;
|
||
|
|
||
|
for (int m = 0; m < model.meshCount; m++)
|
||
|
{
|
||
|
Mesh mesh = model.meshes[m];
|
||
|
if (mesh.boneIds == NULL || mesh.boneWeights == NULL)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: UpdateModelAnimation Mesh %i has no connection to bones",m);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
bool updated = false; // set to true when anim vertex information is updated
|
||
|
Vector3 animVertex = { 0 };
|
||
|
Vector3 animNormal = { 0 };
|
||
|
|
||
|
Vector3 inTranslation = { 0 };
|
||
|
Quaternion inRotation = { 0 };
|
||
|
// Vector3 inScale = { 0 };
|
||
|
|
||
|
Vector3 outTranslation = { 0 };
|
||
|
Quaternion outRotation = { 0 };
|
||
|
Vector3 outScale = { 0 };
|
||
|
|
||
|
int boneId = 0;
|
||
|
int boneCounter = 0;
|
||
|
float boneWeight = 0.0;
|
||
|
|
||
|
const int vValues = mesh.vertexCount*3;
|
||
|
for (int vCounter = 0; vCounter < vValues; vCounter+=3)
|
||
|
{
|
||
|
mesh.animVertices[vCounter] = 0;
|
||
|
mesh.animVertices[vCounter + 1] = 0;
|
||
|
mesh.animVertices[vCounter + 2] = 0;
|
||
|
|
||
|
if (mesh.animNormals!=NULL)
|
||
|
{
|
||
|
mesh.animNormals[vCounter] = 0;
|
||
|
mesh.animNormals[vCounter + 1] = 0;
|
||
|
mesh.animNormals[vCounter + 2] = 0;
|
||
|
}
|
||
|
|
||
|
// Iterates over 4 bones per vertex
|
||
|
for (int j = 0; j < 4; j++, boneCounter++)
|
||
|
{
|
||
|
boneWeight = mesh.boneWeights[boneCounter];
|
||
|
// early stop when no transformation will be applied
|
||
|
if (boneWeight == 0.0f)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
boneId = mesh.boneIds[boneCounter];
|
||
|
//int boneIdParent = model.bones[boneId].parent;
|
||
|
inTranslation = model.bindPose[boneId].translation;
|
||
|
inRotation = model.bindPose[boneId].rotation;
|
||
|
// inScale = model.bindPose[boneId].scale;
|
||
|
outTranslation = anim.framePoses[frame][boneId].translation;
|
||
|
outRotation = anim.framePoses[frame][boneId].rotation;
|
||
|
outScale = anim.framePoses[frame][boneId].scale;
|
||
|
|
||
|
// Vertices processing
|
||
|
// NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position)
|
||
|
animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] };
|
||
|
animVertex = Vector3Multiply(animVertex, outScale);
|
||
|
animVertex = Vector3Subtract(animVertex, inTranslation);
|
||
|
animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
|
||
|
animVertex = Vector3Add(animVertex, outTranslation);
|
||
|
// animVertex = Vector3Transform(animVertex, model.transform);
|
||
|
mesh.animVertices[vCounter] += animVertex.x*boneWeight;
|
||
|
mesh.animVertices[vCounter + 1] += animVertex.y*boneWeight;
|
||
|
mesh.animVertices[vCounter + 2] += animVertex.z*boneWeight;
|
||
|
updated = true;
|
||
|
|
||
|
// Normals processing
|
||
|
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
|
||
|
if (mesh.normals != NULL)
|
||
|
{
|
||
|
animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] };
|
||
|
animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
|
||
|
mesh.animNormals[vCounter] += animNormal.x*boneWeight;
|
||
|
mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight;
|
||
|
mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Upload new vertex data to GPU for model drawing
|
||
|
// Only update data when values changed.
|
||
|
if (updated){
|
||
|
rlUpdateVertexBuffer(mesh.vboId[0], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); // Update vertex position
|
||
|
rlUpdateVertexBuffer(mesh.vboId[2], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); // Update vertex normals
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Unload animation array data
|
||
|
void UnloadModelAnimations(ModelAnimation *animations, unsigned int count)
|
||
|
{
|
||
|
for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]);
|
||
|
RL_FREE(animations);
|
||
|
}
|
||
|
|
||
|
// Unload animation data
|
||
|
void UnloadModelAnimation(ModelAnimation anim)
|
||
|
{
|
||
|
for (int i = 0; i < anim.frameCount; i++) RL_FREE(anim.framePoses[i]);
|
||
|
|
||
|
RL_FREE(anim.bones);
|
||
|
RL_FREE(anim.framePoses);
|
||
|
}
|
||
|
|
||
|
// Check model animation skeleton match
|
||
|
// NOTE: Only number of bones and parent connections are checked
|
||
|
bool IsModelAnimationValid(Model model, ModelAnimation anim)
|
||
|
{
|
||
|
int result = true;
|
||
|
|
||
|
if (model.boneCount != anim.boneCount) result = false;
|
||
|
else
|
||
|
{
|
||
|
for (int i = 0; i < model.boneCount; i++)
|
||
|
{
|
||
|
if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
#if defined(SUPPORT_MESH_GENERATION)
|
||
|
// Generate polygonal mesh
|
||
|
Mesh GenMeshPoly(int sides, float radius)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if (sides < 3) return mesh;
|
||
|
|
||
|
int vertexCount = sides*3;
|
||
|
|
||
|
// Vertices definition
|
||
|
Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
|
||
|
|
||
|
float d = 0.0f, dStep = 360.0f/sides;
|
||
|
for (int v = 0; v < vertexCount; v += 3)
|
||
|
{
|
||
|
vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f };
|
||
|
vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius };
|
||
|
vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius };
|
||
|
d += dStep;
|
||
|
}
|
||
|
|
||
|
// Normals definition
|
||
|
Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
|
||
|
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up;
|
||
|
|
||
|
// TexCoords definition
|
||
|
Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2));
|
||
|
for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f };
|
||
|
|
||
|
mesh.vertexCount = vertexCount;
|
||
|
mesh.triangleCount = sides;
|
||
|
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
|
||
|
// Mesh vertices position array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.vertices[3*i] = vertices[i].x;
|
||
|
mesh.vertices[3*i + 1] = vertices[i].y;
|
||
|
mesh.vertices[3*i + 2] = vertices[i].z;
|
||
|
}
|
||
|
|
||
|
// Mesh texcoords array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.texcoords[2*i] = texcoords[i].x;
|
||
|
mesh.texcoords[2*i + 1] = texcoords[i].y;
|
||
|
}
|
||
|
|
||
|
// Mesh normals array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.normals[3*i] = normals[i].x;
|
||
|
mesh.normals[3*i + 1] = normals[i].y;
|
||
|
mesh.normals[3*i + 2] = normals[i].z;
|
||
|
}
|
||
|
|
||
|
RL_FREE(vertices);
|
||
|
RL_FREE(normals);
|
||
|
RL_FREE(texcoords);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
// NOTE: mesh.vboId array is allocated inside UploadMesh()
|
||
|
UploadMesh(&mesh, false);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate plane mesh (with subdivisions)
|
||
|
Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
#define CUSTOM_MESH_GEN_PLANE
|
||
|
#if defined(CUSTOM_MESH_GEN_PLANE)
|
||
|
resX++;
|
||
|
resZ++;
|
||
|
|
||
|
// Vertices definition
|
||
|
int vertexCount = resX*resZ; // vertices get reused for the faces
|
||
|
|
||
|
Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
|
||
|
for (int z = 0; z < resZ; z++)
|
||
|
{
|
||
|
// [-length/2, length/2]
|
||
|
float zPos = ((float)z/(resZ - 1) - 0.5f)*length;
|
||
|
for (int x = 0; x < resX; x++)
|
||
|
{
|
||
|
// [-width/2, width/2]
|
||
|
float xPos = ((float)x/(resX - 1) - 0.5f)*width;
|
||
|
vertices[x + z*resX] = (Vector3){ xPos, 0.0f, zPos };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Normals definition
|
||
|
Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3));
|
||
|
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up;
|
||
|
|
||
|
// TexCoords definition
|
||
|
Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2));
|
||
|
for (int v = 0; v < resZ; v++)
|
||
|
{
|
||
|
for (int u = 0; u < resX; u++)
|
||
|
{
|
||
|
texcoords[u + v*resX] = (Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Triangles definition (indices)
|
||
|
int numFaces = (resX - 1)*(resZ - 1);
|
||
|
int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int));
|
||
|
int t = 0;
|
||
|
for (int face = 0; face < numFaces; face++)
|
||
|
{
|
||
|
// Retrieve lower left corner from face ind
|
||
|
int i = face % (resX - 1) + (face/(resZ - 1)*resX);
|
||
|
|
||
|
triangles[t++] = i + resX;
|
||
|
triangles[t++] = i + 1;
|
||
|
triangles[t++] = i;
|
||
|
|
||
|
triangles[t++] = i + resX;
|
||
|
triangles[t++] = i + resX + 1;
|
||
|
triangles[t++] = i + 1;
|
||
|
}
|
||
|
|
||
|
mesh.vertexCount = vertexCount;
|
||
|
mesh.triangleCount = numFaces*2;
|
||
|
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.indices = (unsigned short *)RL_MALLOC(mesh.triangleCount*3*sizeof(unsigned short));
|
||
|
|
||
|
// Mesh vertices position array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.vertices[3*i] = vertices[i].x;
|
||
|
mesh.vertices[3*i + 1] = vertices[i].y;
|
||
|
mesh.vertices[3*i + 2] = vertices[i].z;
|
||
|
}
|
||
|
|
||
|
// Mesh texcoords array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.texcoords[2*i] = texcoords[i].x;
|
||
|
mesh.texcoords[2*i + 1] = texcoords[i].y;
|
||
|
}
|
||
|
|
||
|
// Mesh normals array
|
||
|
for (int i = 0; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
mesh.normals[3*i] = normals[i].x;
|
||
|
mesh.normals[3*i + 1] = normals[i].y;
|
||
|
mesh.normals[3*i + 2] = normals[i].z;
|
||
|
}
|
||
|
|
||
|
// Mesh indices array initialization
|
||
|
for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i];
|
||
|
|
||
|
RL_FREE(vertices);
|
||
|
RL_FREE(normals);
|
||
|
RL_FREE(texcoords);
|
||
|
RL_FREE(triangles);
|
||
|
|
||
|
#else // Use par_shapes library to generate plane mesh
|
||
|
|
||
|
par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!!
|
||
|
par_shapes_scale(plane, width, length, 1.0f);
|
||
|
par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
par_shapes_translate(plane, -width/2, 0.0f, length/2);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = plane->ntriangles*3;
|
||
|
mesh.triangleCount = plane->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = plane->points[plane->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = plane->normals[plane->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(plane);
|
||
|
#endif
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generated cuboid mesh
|
||
|
Mesh GenMeshCube(float width, float height, float length)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
#define CUSTOM_MESH_GEN_CUBE
|
||
|
#if defined(CUSTOM_MESH_GEN_CUBE)
|
||
|
float vertices[] = {
|
||
|
-width/2, -height/2, length/2,
|
||
|
width/2, -height/2, length/2,
|
||
|
width/2, height/2, length/2,
|
||
|
-width/2, height/2, length/2,
|
||
|
-width/2, -height/2, -length/2,
|
||
|
-width/2, height/2, -length/2,
|
||
|
width/2, height/2, -length/2,
|
||
|
width/2, -height/2, -length/2,
|
||
|
-width/2, height/2, -length/2,
|
||
|
-width/2, height/2, length/2,
|
||
|
width/2, height/2, length/2,
|
||
|
width/2, height/2, -length/2,
|
||
|
-width/2, -height/2, -length/2,
|
||
|
width/2, -height/2, -length/2,
|
||
|
width/2, -height/2, length/2,
|
||
|
-width/2, -height/2, length/2,
|
||
|
width/2, -height/2, -length/2,
|
||
|
width/2, height/2, -length/2,
|
||
|
width/2, height/2, length/2,
|
||
|
width/2, -height/2, length/2,
|
||
|
-width/2, -height/2, -length/2,
|
||
|
-width/2, -height/2, length/2,
|
||
|
-width/2, height/2, length/2,
|
||
|
-width/2, height/2, -length/2
|
||
|
};
|
||
|
|
||
|
float texcoords[] = {
|
||
|
0.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 1.0f,
|
||
|
0.0f, 1.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 1.0f,
|
||
|
0.0f, 1.0f,
|
||
|
0.0f, 0.0f,
|
||
|
0.0f, 1.0f,
|
||
|
0.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 1.0f,
|
||
|
1.0f, 1.0f,
|
||
|
0.0f, 1.0f,
|
||
|
0.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 1.0f,
|
||
|
0.0f, 1.0f,
|
||
|
0.0f, 0.0f,
|
||
|
0.0f, 0.0f,
|
||
|
1.0f, 0.0f,
|
||
|
1.0f, 1.0f,
|
||
|
0.0f, 1.0f
|
||
|
};
|
||
|
|
||
|
float normals[] = {
|
||
|
0.0f, 0.0f, 1.0f,
|
||
|
0.0f, 0.0f, 1.0f,
|
||
|
0.0f, 0.0f, 1.0f,
|
||
|
0.0f, 0.0f, 1.0f,
|
||
|
0.0f, 0.0f,-1.0f,
|
||
|
0.0f, 0.0f,-1.0f,
|
||
|
0.0f, 0.0f,-1.0f,
|
||
|
0.0f, 0.0f,-1.0f,
|
||
|
0.0f, 1.0f, 0.0f,
|
||
|
0.0f, 1.0f, 0.0f,
|
||
|
0.0f, 1.0f, 0.0f,
|
||
|
0.0f, 1.0f, 0.0f,
|
||
|
0.0f,-1.0f, 0.0f,
|
||
|
0.0f,-1.0f, 0.0f,
|
||
|
0.0f,-1.0f, 0.0f,
|
||
|
0.0f,-1.0f, 0.0f,
|
||
|
1.0f, 0.0f, 0.0f,
|
||
|
1.0f, 0.0f, 0.0f,
|
||
|
1.0f, 0.0f, 0.0f,
|
||
|
1.0f, 0.0f, 0.0f,
|
||
|
-1.0f, 0.0f, 0.0f,
|
||
|
-1.0f, 0.0f, 0.0f,
|
||
|
-1.0f, 0.0f, 0.0f,
|
||
|
-1.0f, 0.0f, 0.0f
|
||
|
};
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float));
|
||
|
memcpy(mesh.vertices, vertices, 24*3*sizeof(float));
|
||
|
|
||
|
mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float));
|
||
|
memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float));
|
||
|
|
||
|
mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float));
|
||
|
memcpy(mesh.normals, normals, 24*3*sizeof(float));
|
||
|
|
||
|
mesh.indices = (unsigned short *)RL_MALLOC(36*sizeof(unsigned short));
|
||
|
|
||
|
int k = 0;
|
||
|
|
||
|
// Indices can be initialized right now
|
||
|
for (int i = 0; i < 36; i += 6)
|
||
|
{
|
||
|
mesh.indices[i] = 4*k;
|
||
|
mesh.indices[i + 1] = 4*k + 1;
|
||
|
mesh.indices[i + 2] = 4*k + 2;
|
||
|
mesh.indices[i + 3] = 4*k;
|
||
|
mesh.indices[i + 4] = 4*k + 2;
|
||
|
mesh.indices[i + 5] = 4*k + 3;
|
||
|
|
||
|
k++;
|
||
|
}
|
||
|
|
||
|
mesh.vertexCount = 24;
|
||
|
mesh.triangleCount = 12;
|
||
|
|
||
|
#else // Use par_shapes library to generate cube mesh
|
||
|
/*
|
||
|
// Platonic solids:
|
||
|
par_shapes_mesh* par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid)
|
||
|
par_shapes_mesh* par_shapes_create_cube(); // 6 sides polyhedron (cube)
|
||
|
par_shapes_mesh* par_shapes_create_octahedron(); // 8 sides polyhedron (dyamond)
|
||
|
par_shapes_mesh* par_shapes_create_dodecahedron(); // 12 sides polyhedron
|
||
|
par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron
|
||
|
*/
|
||
|
// Platonic solid generation: cube (6 sides)
|
||
|
// NOTE: No normals/texcoords generated by default
|
||
|
par_shapes_mesh *cube = par_shapes_create_cube();
|
||
|
cube->tcoords = PAR_MALLOC(float, 2*cube->npoints);
|
||
|
for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f;
|
||
|
par_shapes_scale(cube, width, height, length);
|
||
|
par_shapes_translate(cube, -width/2, 0.0f, -length/2);
|
||
|
par_shapes_compute_normals(cube);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = cube->ntriangles*3;
|
||
|
mesh.triangleCount = cube->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = cube->points[cube->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = cube->normals[cube->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(cube);
|
||
|
#endif
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate sphere mesh (standard sphere)
|
||
|
Mesh GenMeshSphere(float radius, int rings, int slices)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if ((rings >= 3) && (slices >= 3))
|
||
|
{
|
||
|
par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings);
|
||
|
par_shapes_scale(sphere, radius, radius, radius);
|
||
|
// NOTE: Soft normals are computed internally
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = sphere->ntriangles*3;
|
||
|
mesh.triangleCount = sphere->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(sphere);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate hemi-sphere mesh (half sphere, no bottom cap)
|
||
|
Mesh GenMeshHemiSphere(float radius, int rings, int slices)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if ((rings >= 3) && (slices >= 3))
|
||
|
{
|
||
|
if (radius < 0.0f) radius = 0.0f;
|
||
|
|
||
|
par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings);
|
||
|
par_shapes_scale(sphere, radius, radius, radius);
|
||
|
// NOTE: Soft normals are computed internally
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = sphere->ntriangles*3;
|
||
|
mesh.triangleCount = sphere->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(sphere);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate cylinder mesh
|
||
|
Mesh GenMeshCylinder(float radius, float height, int slices)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if (slices >= 3)
|
||
|
{
|
||
|
// Instance a cylinder that sits on the Z=0 plane using the given tessellation
|
||
|
// levels across the UV domain. Think of "slices" like a number of pizza
|
||
|
// slices, and "stacks" like a number of stacked rings.
|
||
|
// Height and radius are both 1.0, but they can easily be changed with par_shapes_scale
|
||
|
par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8);
|
||
|
par_shapes_scale(cylinder, radius, radius, height);
|
||
|
par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
|
||
|
// Generate an orientable disk shape (top cap)
|
||
|
par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 });
|
||
|
capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints);
|
||
|
for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f;
|
||
|
par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
par_shapes_rotate(capTop, 90*DEG2RAD, (float[]){ 0, 1, 0 });
|
||
|
par_shapes_translate(capTop, 0, height, 0);
|
||
|
|
||
|
// Generate an orientable disk shape (bottom cap)
|
||
|
par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 });
|
||
|
capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints);
|
||
|
for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f;
|
||
|
par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
par_shapes_rotate(capBottom, -90*DEG2RAD, (float[]){ 0, 1, 0 });
|
||
|
|
||
|
par_shapes_merge_and_free(cylinder, capTop);
|
||
|
par_shapes_merge_and_free(cylinder, capBottom);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = cylinder->ntriangles*3;
|
||
|
mesh.triangleCount = cylinder->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(cylinder);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate cone/pyramid mesh
|
||
|
Mesh GenMeshCone(float radius, float height, int slices)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if (slices >= 3)
|
||
|
{
|
||
|
// Instance a cone that sits on the Z=0 plane using the given tessellation
|
||
|
// levels across the UV domain. Think of "slices" like a number of pizza
|
||
|
// slices, and "stacks" like a number of stacked rings.
|
||
|
// Height and radius are both 1.0, but they can easily be changed with par_shapes_scale
|
||
|
par_shapes_mesh *cone = par_shapes_create_cone(slices, 8);
|
||
|
par_shapes_scale(cone, radius, radius, height);
|
||
|
par_shapes_rotate(cone, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
par_shapes_rotate(cone, PI/2.0f, (float[]){ 0, 1, 0 });
|
||
|
|
||
|
// Generate an orientable disk shape (bottom cap)
|
||
|
par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 });
|
||
|
capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints);
|
||
|
for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f;
|
||
|
par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 });
|
||
|
|
||
|
par_shapes_merge_and_free(cone, capBottom);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(cone->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = cone->ntriangles*3;
|
||
|
mesh.triangleCount = cone->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = cone->points[cone->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = cone->points[cone->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = cone->points[cone->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = cone->normals[cone->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = cone->normals[cone->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = cone->normals[cone->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = cone->tcoords[cone->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = cone->tcoords[cone->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(cone);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cone");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate torus mesh
|
||
|
Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if ((sides >= 3) && (radSeg >= 3))
|
||
|
{
|
||
|
if (radius > 1.0f) radius = 1.0f;
|
||
|
else if (radius < 0.1f) radius = 0.1f;
|
||
|
|
||
|
// Create a donut that sits on the Z=0 plane with the specified inner radius
|
||
|
// The outer radius can be controlled with par_shapes_scale
|
||
|
par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius);
|
||
|
par_shapes_scale(torus, size/2, size/2, size/2);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = torus->ntriangles*3;
|
||
|
mesh.triangleCount = torus->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = torus->points[torus->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = torus->normals[torus->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(torus);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate trefoil knot mesh
|
||
|
Mesh GenMeshKnot(float radius, float size, int radSeg, int sides)
|
||
|
{
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
if ((sides >= 3) && (radSeg >= 3))
|
||
|
{
|
||
|
if (radius > 3.0f) radius = 3.0f;
|
||
|
else if (radius < 0.5f) radius = 0.5f;
|
||
|
|
||
|
par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius);
|
||
|
par_shapes_scale(knot, size, size, size);
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float));
|
||
|
|
||
|
mesh.vertexCount = knot->ntriangles*3;
|
||
|
mesh.triangleCount = knot->ntriangles;
|
||
|
|
||
|
for (int k = 0; k < mesh.vertexCount; k++)
|
||
|
{
|
||
|
mesh.vertices[k*3] = knot->points[knot->triangles[k]*3];
|
||
|
mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1];
|
||
|
mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.normals[k*3] = knot->normals[knot->triangles[k]*3];
|
||
|
mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1];
|
||
|
mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2];
|
||
|
|
||
|
mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2];
|
||
|
mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1];
|
||
|
}
|
||
|
|
||
|
par_shapes_free_mesh(knot);
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot");
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate a mesh from heightmap
|
||
|
// NOTE: Vertex data is uploaded to GPU
|
||
|
Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
|
||
|
{
|
||
|
#define GRAY_VALUE(c) ((c.r+c.g+c.b)/3)
|
||
|
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
int mapX = heightmap.width;
|
||
|
int mapZ = heightmap.height;
|
||
|
|
||
|
Color *pixels = LoadImageColors(heightmap);
|
||
|
|
||
|
// NOTE: One vertex per pixel
|
||
|
mesh.triangleCount = (mapX-1)*(mapZ-1)*2; // One quad every four pixels
|
||
|
|
||
|
mesh.vertexCount = mesh.triangleCount*3;
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
|
||
|
mesh.colors = NULL;
|
||
|
|
||
|
int vCounter = 0; // Used to count vertices float by float
|
||
|
int tcCounter = 0; // Used to count texcoords float by float
|
||
|
int nCounter = 0; // Used to count normals float by float
|
||
|
|
||
|
int trisCounter = 0;
|
||
|
|
||
|
Vector3 scaleFactor = { size.x/mapX, size.y/255.0f, size.z/mapZ };
|
||
|
|
||
|
Vector3 vA = { 0 };
|
||
|
Vector3 vB = { 0 };
|
||
|
Vector3 vC = { 0 };
|
||
|
Vector3 vN = { 0 };
|
||
|
|
||
|
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
|
||
|
mesh.vertices[vCounter] = (float)x*scaleFactor.x;
|
||
|
mesh.vertices[vCounter + 1] = (float)GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y;
|
||
|
mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z;
|
||
|
|
||
|
mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x;
|
||
|
mesh.vertices[vCounter + 4] = (float)GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y;
|
||
|
mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z;
|
||
|
|
||
|
mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x;
|
||
|
mesh.vertices[vCounter + 7] = (float)GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y;
|
||
|
mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z;
|
||
|
|
||
|
// another triangle - 3 vertex
|
||
|
mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6];
|
||
|
mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7];
|
||
|
mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8];
|
||
|
|
||
|
mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3];
|
||
|
mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4];
|
||
|
mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5];
|
||
|
|
||
|
mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x;
|
||
|
mesh.vertices[vCounter + 16] = (float)GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y;
|
||
|
mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z;
|
||
|
vCounter += 18; // 6 vertex, 18 floats
|
||
|
|
||
|
// Fill texcoords array with data
|
||
|
//--------------------------------------------------------------
|
||
|
mesh.texcoords[tcCounter] = (float)x/(mapX - 1);
|
||
|
mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1);
|
||
|
|
||
|
mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1);
|
||
|
mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1);
|
||
|
|
||
|
mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1);
|
||
|
mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1);
|
||
|
|
||
|
mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4];
|
||
|
mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5];
|
||
|
|
||
|
mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2];
|
||
|
mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3];
|
||
|
|
||
|
mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1);
|
||
|
mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1);
|
||
|
tcCounter += 12; // 6 texcoords, 12 floats
|
||
|
|
||
|
// Fill normals array with data
|
||
|
//--------------------------------------------------------------
|
||
|
for (int i = 0; i < 18; i += 9)
|
||
|
{
|
||
|
vA.x = mesh.vertices[nCounter + i];
|
||
|
vA.y = mesh.vertices[nCounter + i + 1];
|
||
|
vA.z = mesh.vertices[nCounter + i + 2];
|
||
|
|
||
|
vB.x = mesh.vertices[nCounter + i + 3];
|
||
|
vB.y = mesh.vertices[nCounter + i + 4];
|
||
|
vB.z = mesh.vertices[nCounter + i + 5];
|
||
|
|
||
|
vC.x = mesh.vertices[nCounter + i + 6];
|
||
|
vC.y = mesh.vertices[nCounter + i + 7];
|
||
|
vC.z = mesh.vertices[nCounter + i + 8];
|
||
|
|
||
|
vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA)));
|
||
|
|
||
|
mesh.normals[nCounter + i] = vN.x;
|
||
|
mesh.normals[nCounter + i + 1] = vN.y;
|
||
|
mesh.normals[nCounter + i + 2] = vN.z;
|
||
|
|
||
|
mesh.normals[nCounter + i + 3] = vN.x;
|
||
|
mesh.normals[nCounter + i + 4] = vN.y;
|
||
|
mesh.normals[nCounter + i + 5] = vN.z;
|
||
|
|
||
|
mesh.normals[nCounter + i + 6] = vN.x;
|
||
|
mesh.normals[nCounter + i + 7] = vN.y;
|
||
|
mesh.normals[nCounter + i + 8] = vN.z;
|
||
|
}
|
||
|
|
||
|
nCounter += 18; // 6 vertex, 18 floats
|
||
|
trisCounter += 2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UnloadImageColors(pixels); // Unload pixels color data
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
// Generate a cubes mesh from pixel data
|
||
|
// NOTE: Vertex data is uploaded to GPU
|
||
|
Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
|
||
|
{
|
||
|
#define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
|
||
|
|
||
|
Mesh mesh = { 0 };
|
||
|
|
||
|
Color *pixels = LoadImageColors(cubicmap);
|
||
|
|
||
|
int mapWidth = cubicmap.width;
|
||
|
int mapHeight = cubicmap.height;
|
||
|
|
||
|
// NOTE: Max possible number of triangles numCubes*(12 triangles by cube)
|
||
|
int maxTriangles = cubicmap.width*cubicmap.height*12;
|
||
|
|
||
|
int vCounter = 0; // Used to count vertices
|
||
|
int tcCounter = 0; // Used to count texcoords
|
||
|
int nCounter = 0; // Used to count normals
|
||
|
|
||
|
float w = cubeSize.x;
|
||
|
float h = cubeSize.z;
|
||
|
float h2 = cubeSize.y;
|
||
|
|
||
|
Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3));
|
||
|
Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2));
|
||
|
Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3));
|
||
|
|
||
|
// Define the 6 normals of the cube, we will combine them accordingly later...
|
||
|
Vector3 n1 = { 1.0f, 0.0f, 0.0f };
|
||
|
Vector3 n2 = { -1.0f, 0.0f, 0.0f };
|
||
|
Vector3 n3 = { 0.0f, 1.0f, 0.0f };
|
||
|
Vector3 n4 = { 0.0f, -1.0f, 0.0f };
|
||
|
Vector3 n5 = { 0.0f, 0.0f, -1.0f };
|
||
|
Vector3 n6 = { 0.0f, 0.0f, 1.0f };
|
||
|
|
||
|
// NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6)
|
||
|
typedef struct RectangleF {
|
||
|
float x;
|
||
|
float y;
|
||
|
float width;
|
||
|
float height;
|
||
|
} RectangleF;
|
||
|
|
||
|
RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f };
|
||
|
RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f };
|
||
|
RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f };
|
||
|
RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f };
|
||
|
RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f };
|
||
|
RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f };
|
||
|
|
||
|
for (int z = 0; z < mapHeight; ++z)
|
||
|
{
|
||
|
for (int x = 0; x < mapWidth; ++x)
|
||
|
{
|
||
|
// Define the 8 vertex of the cube, we will combine them accordingly later...
|
||
|
Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) };
|
||
|
Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) };
|
||
|
Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) };
|
||
|
Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) };
|
||
|
Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) };
|
||
|
Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) };
|
||
|
Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) };
|
||
|
Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) };
|
||
|
|
||
|
// We check pixel color to be WHITE -> draw full cube
|
||
|
if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE))
|
||
|
{
|
||
|
// Define triangles and checking collateral cubes
|
||
|
//------------------------------------------------
|
||
|
|
||
|
// Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4)
|
||
|
// WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside
|
||
|
mapVertices[vCounter] = v1;
|
||
|
mapVertices[vCounter + 1] = v2;
|
||
|
mapVertices[vCounter + 2] = v3;
|
||
|
mapVertices[vCounter + 3] = v1;
|
||
|
mapVertices[vCounter + 4] = v3;
|
||
|
mapVertices[vCounter + 5] = v4;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n3;
|
||
|
mapNormals[nCounter + 1] = n3;
|
||
|
mapNormals[nCounter + 2] = n3;
|
||
|
mapNormals[nCounter + 3] = n3;
|
||
|
mapNormals[nCounter + 4] = n3;
|
||
|
mapNormals[nCounter + 5] = n3;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y };
|
||
|
tcCounter += 6;
|
||
|
|
||
|
// Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8)
|
||
|
mapVertices[vCounter] = v6;
|
||
|
mapVertices[vCounter + 1] = v8;
|
||
|
mapVertices[vCounter + 2] = v7;
|
||
|
mapVertices[vCounter + 3] = v6;
|
||
|
mapVertices[vCounter + 4] = v5;
|
||
|
mapVertices[vCounter + 5] = v8;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n4;
|
||
|
mapNormals[nCounter + 1] = n4;
|
||
|
mapNormals[nCounter + 2] = n4;
|
||
|
mapNormals[nCounter + 3] = n4;
|
||
|
mapNormals[nCounter + 4] = n4;
|
||
|
mapNormals[nCounter + 5] = n4;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
|
||
|
// Checking cube on bottom of current cube
|
||
|
if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1))
|
||
|
{
|
||
|
// Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8
|
||
|
// NOTE: Collateral occluded faces are not generated
|
||
|
mapVertices[vCounter] = v2;
|
||
|
mapVertices[vCounter + 1] = v7;
|
||
|
mapVertices[vCounter + 2] = v3;
|
||
|
mapVertices[vCounter + 3] = v3;
|
||
|
mapVertices[vCounter + 4] = v7;
|
||
|
mapVertices[vCounter + 5] = v8;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n6;
|
||
|
mapNormals[nCounter + 1] = n6;
|
||
|
mapNormals[nCounter + 2] = n6;
|
||
|
mapNormals[nCounter + 3] = n6;
|
||
|
mapNormals[nCounter + 4] = n6;
|
||
|
mapNormals[nCounter + 5] = n6;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
}
|
||
|
|
||
|
// Checking cube on top of current cube
|
||
|
if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0))
|
||
|
{
|
||
|
// Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5
|
||
|
// NOTE: Collateral occluded faces are not generated
|
||
|
mapVertices[vCounter] = v1;
|
||
|
mapVertices[vCounter + 1] = v5;
|
||
|
mapVertices[vCounter + 2] = v6;
|
||
|
mapVertices[vCounter + 3] = v1;
|
||
|
mapVertices[vCounter + 4] = v4;
|
||
|
mapVertices[vCounter + 5] = v5;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n5;
|
||
|
mapNormals[nCounter + 1] = n5;
|
||
|
mapNormals[nCounter + 2] = n5;
|
||
|
mapNormals[nCounter + 3] = n5;
|
||
|
mapNormals[nCounter + 4] = n5;
|
||
|
mapNormals[nCounter + 5] = n5;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
}
|
||
|
|
||
|
// Checking cube on right of current cube
|
||
|
if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1))
|
||
|
{
|
||
|
// Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5
|
||
|
// NOTE: Collateral occluded faces are not generated
|
||
|
mapVertices[vCounter] = v3;
|
||
|
mapVertices[vCounter + 1] = v8;
|
||
|
mapVertices[vCounter + 2] = v4;
|
||
|
mapVertices[vCounter + 3] = v4;
|
||
|
mapVertices[vCounter + 4] = v8;
|
||
|
mapVertices[vCounter + 5] = v5;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n1;
|
||
|
mapNormals[nCounter + 1] = n1;
|
||
|
mapNormals[nCounter + 2] = n1;
|
||
|
mapNormals[nCounter + 3] = n1;
|
||
|
mapNormals[nCounter + 4] = n1;
|
||
|
mapNormals[nCounter + 5] = n1;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
}
|
||
|
|
||
|
// Checking cube on left of current cube
|
||
|
if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0))
|
||
|
{
|
||
|
// Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7
|
||
|
// NOTE: Collateral occluded faces are not generated
|
||
|
mapVertices[vCounter] = v1;
|
||
|
mapVertices[vCounter + 1] = v7;
|
||
|
mapVertices[vCounter + 2] = v2;
|
||
|
mapVertices[vCounter + 3] = v1;
|
||
|
mapVertices[vCounter + 4] = v6;
|
||
|
mapVertices[vCounter + 5] = v7;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n2;
|
||
|
mapNormals[nCounter + 1] = n2;
|
||
|
mapNormals[nCounter + 2] = n2;
|
||
|
mapNormals[nCounter + 3] = n2;
|
||
|
mapNormals[nCounter + 4] = n2;
|
||
|
mapNormals[nCounter + 5] = n2;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
}
|
||
|
}
|
||
|
// We check pixel color to be BLACK, we will only draw floor and roof
|
||
|
else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK))
|
||
|
{
|
||
|
// Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4)
|
||
|
mapVertices[vCounter] = v1;
|
||
|
mapVertices[vCounter + 1] = v3;
|
||
|
mapVertices[vCounter + 2] = v2;
|
||
|
mapVertices[vCounter + 3] = v1;
|
||
|
mapVertices[vCounter + 4] = v4;
|
||
|
mapVertices[vCounter + 5] = v3;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n4;
|
||
|
mapNormals[nCounter + 1] = n4;
|
||
|
mapNormals[nCounter + 2] = n4;
|
||
|
mapNormals[nCounter + 3] = n4;
|
||
|
mapNormals[nCounter + 4] = n4;
|
||
|
mapNormals[nCounter + 5] = n4;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height };
|
||
|
tcCounter += 6;
|
||
|
|
||
|
// Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8)
|
||
|
mapVertices[vCounter] = v6;
|
||
|
mapVertices[vCounter + 1] = v7;
|
||
|
mapVertices[vCounter + 2] = v8;
|
||
|
mapVertices[vCounter + 3] = v6;
|
||
|
mapVertices[vCounter + 4] = v8;
|
||
|
mapVertices[vCounter + 5] = v5;
|
||
|
vCounter += 6;
|
||
|
|
||
|
mapNormals[nCounter] = n3;
|
||
|
mapNormals[nCounter + 1] = n3;
|
||
|
mapNormals[nCounter + 2] = n3;
|
||
|
mapNormals[nCounter + 3] = n3;
|
||
|
mapNormals[nCounter + 4] = n3;
|
||
|
mapNormals[nCounter + 5] = n3;
|
||
|
nCounter += 6;
|
||
|
|
||
|
mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
|
||
|
mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height };
|
||
|
mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
|
||
|
mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y };
|
||
|
mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height };
|
||
|
mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y };
|
||
|
tcCounter += 6;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Move data from mapVertices temp arays to vertices float array
|
||
|
mesh.vertexCount = vCounter;
|
||
|
mesh.triangleCount = vCounter/3;
|
||
|
|
||
|
mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float));
|
||
|
mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float));
|
||
|
mesh.colors = NULL;
|
||
|
|
||
|
int fCounter = 0;
|
||
|
|
||
|
// Move vertices data
|
||
|
for (int i = 0; i < vCounter; i++)
|
||
|
{
|
||
|
mesh.vertices[fCounter] = mapVertices[i].x;
|
||
|
mesh.vertices[fCounter + 1] = mapVertices[i].y;
|
||
|
mesh.vertices[fCounter + 2] = mapVertices[i].z;
|
||
|
fCounter += 3;
|
||
|
}
|
||
|
|
||
|
fCounter = 0;
|
||
|
|
||
|
// Move normals data
|
||
|
for (int i = 0; i < nCounter; i++)
|
||
|
{
|
||
|
mesh.normals[fCounter] = mapNormals[i].x;
|
||
|
mesh.normals[fCounter + 1] = mapNormals[i].y;
|
||
|
mesh.normals[fCounter + 2] = mapNormals[i].z;
|
||
|
fCounter += 3;
|
||
|
}
|
||
|
|
||
|
fCounter = 0;
|
||
|
|
||
|
// Move texcoords data
|
||
|
for (int i = 0; i < tcCounter; i++)
|
||
|
{
|
||
|
mesh.texcoords[fCounter] = mapTexcoords[i].x;
|
||
|
mesh.texcoords[fCounter + 1] = mapTexcoords[i].y;
|
||
|
fCounter += 2;
|
||
|
}
|
||
|
|
||
|
RL_FREE(mapVertices);
|
||
|
RL_FREE(mapNormals);
|
||
|
RL_FREE(mapTexcoords);
|
||
|
|
||
|
UnloadImageColors(pixels); // Unload pixels color data
|
||
|
|
||
|
// Upload vertex data to GPU (static mesh)
|
||
|
UploadMesh(&mesh, false);
|
||
|
|
||
|
return mesh;
|
||
|
}
|
||
|
#endif // SUPPORT_MESH_GENERATION
|
||
|
|
||
|
// Compute mesh bounding box limits
|
||
|
// NOTE: minVertex and maxVertex should be transformed by model transform matrix
|
||
|
BoundingBox GetMeshBoundingBox(Mesh mesh)
|
||
|
{
|
||
|
// Get min and max vertex to construct bounds (AABB)
|
||
|
Vector3 minVertex = { 0 };
|
||
|
Vector3 maxVertex = { 0 };
|
||
|
|
||
|
if (mesh.vertices != NULL)
|
||
|
{
|
||
|
minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
||
|
maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
|
||
|
|
||
|
for (int i = 1; i < mesh.vertexCount; i++)
|
||
|
{
|
||
|
minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
|
||
|
maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create the bounding box
|
||
|
BoundingBox box = { 0 };
|
||
|
box.min = minVertex;
|
||
|
box.max = maxVertex;
|
||
|
|
||
|
return box;
|
||
|
}
|
||
|
|
||
|
// Compute mesh tangents
|
||
|
// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
|
||
|
// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
|
||
|
void GenMeshTangents(Mesh *mesh)
|
||
|
{
|
||
|
if (mesh->tangents == NULL) mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
|
||
|
else
|
||
|
{
|
||
|
RL_FREE(mesh->tangents);
|
||
|
mesh->tangents = (float *)RL_MALLOC(mesh->vertexCount*4*sizeof(float));
|
||
|
}
|
||
|
|
||
|
Vector3 *tan1 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3));
|
||
|
Vector3 *tan2 = (Vector3 *)RL_MALLOC(mesh->vertexCount*sizeof(Vector3));
|
||
|
|
||
|
for (int i = 0; i < mesh->vertexCount; i += 3)
|
||
|
{
|
||
|
// Get triangle vertices
|
||
|
Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
|
||
|
Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
|
||
|
Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
|
||
|
|
||
|
// Get triangle texcoords
|
||
|
Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
|
||
|
Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
|
||
|
Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
|
||
|
|
||
|
float x1 = v2.x - v1.x;
|
||
|
float y1 = v2.y - v1.y;
|
||
|
float z1 = v2.z - v1.z;
|
||
|
float x2 = v3.x - v1.x;
|
||
|
float y2 = v3.y - v1.y;
|
||
|
float z2 = v3.z - v1.z;
|
||
|
|
||
|
float s1 = uv2.x - uv1.x;
|
||
|
float t1 = uv2.y - uv1.y;
|
||
|
float s2 = uv3.x - uv1.x;
|
||
|
float t2 = uv3.y - uv1.y;
|
||
|
|
||
|
float div = s1*t2 - s2*t1;
|
||
|
float r = (div == 0.0f)? 0.0f : 1.0f/div;
|
||
|
|
||
|
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
|
||
|
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
|
||
|
|
||
|
tan1[i + 0] = sdir;
|
||
|
tan1[i + 1] = sdir;
|
||
|
tan1[i + 2] = sdir;
|
||
|
|
||
|
tan2[i + 0] = tdir;
|
||
|
tan2[i + 1] = tdir;
|
||
|
tan2[i + 2] = tdir;
|
||
|
}
|
||
|
|
||
|
// Compute tangents considering normals
|
||
|
for (int i = 0; i < mesh->vertexCount; i++)
|
||
|
{
|
||
|
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
||
|
Vector3 tangent = tan1[i];
|
||
|
|
||
|
// TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
|
||
|
#if defined(COMPUTE_TANGENTS_METHOD_01)
|
||
|
Vector3 tmp = Vector3Subtract(tangent, Vector3Scale(normal, Vector3DotProduct(normal, tangent)));
|
||
|
tmp = Vector3Normalize(tmp);
|
||
|
mesh->tangents[i*4 + 0] = tmp.x;
|
||
|
mesh->tangents[i*4 + 1] = tmp.y;
|
||
|
mesh->tangents[i*4 + 2] = tmp.z;
|
||
|
mesh->tangents[i*4 + 3] = 1.0f;
|
||
|
#else
|
||
|
Vector3OrthoNormalize(&normal, &tangent);
|
||
|
mesh->tangents[i*4 + 0] = tangent.x;
|
||
|
mesh->tangents[i*4 + 1] = tangent.y;
|
||
|
mesh->tangents[i*4 + 2] = tangent.z;
|
||
|
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
RL_FREE(tan1);
|
||
|
RL_FREE(tan2);
|
||
|
|
||
|
if (mesh->vboId != NULL)
|
||
|
{
|
||
|
if (mesh->vboId[SHADER_LOC_VERTEX_TANGENT] != 0)
|
||
|
{
|
||
|
// Upate existing vertex buffer
|
||
|
rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_TANGENT], mesh->tangents, mesh->vertexCount*4*sizeof(float), 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Load a new tangent attributes buffer
|
||
|
mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false);
|
||
|
}
|
||
|
|
||
|
rlEnableVertexArray(mesh->vaoId);
|
||
|
rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0);
|
||
|
rlEnableVertexAttribute(4);
|
||
|
rlDisableVertexArray();
|
||
|
}
|
||
|
|
||
|
TRACELOG(LOG_INFO, "MESH: Tangents data computed and uploaded for provided mesh");
|
||
|
}
|
||
|
|
||
|
// Compute mesh binormals (aka bitangent)
|
||
|
void GenMeshBinormals(Mesh *mesh)
|
||
|
{
|
||
|
for (int i = 0; i < mesh->vertexCount; i++)
|
||
|
{
|
||
|
//Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
||
|
//Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
|
||
|
//Vector3 binormal = Vector3Scale(Vector3CrossProduct(normal, tangent), mesh->tangents[i*4 + 3]);
|
||
|
|
||
|
// TODO: Register computed binormal in mesh->binormal?
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw a model (with texture if set)
|
||
|
void DrawModel(Model model, Vector3 position, float scale, Color tint)
|
||
|
{
|
||
|
Vector3 vScale = { scale, scale, scale };
|
||
|
Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f };
|
||
|
|
||
|
DrawModelEx(model, position, rotationAxis, 0.0f, vScale, tint);
|
||
|
}
|
||
|
|
||
|
// Draw a model with extended parameters
|
||
|
void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
|
||
|
{
|
||
|
// Calculate transformation matrix from function parameters
|
||
|
// Get transform matrix (rotation -> scale -> translation)
|
||
|
Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
|
||
|
Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
|
||
|
Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
|
||
|
|
||
|
Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
|
||
|
|
||
|
// Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform)
|
||
|
model.transform = MatrixMultiply(model.transform, matTransform);
|
||
|
|
||
|
for (int i = 0; i < model.meshCount; i++)
|
||
|
{
|
||
|
Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color;
|
||
|
|
||
|
Color colorTint = WHITE;
|
||
|
colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f);
|
||
|
colorTint.g = (unsigned char)((((float)color.g/255.0)*((float)tint.g/255.0))*255.0f);
|
||
|
colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f);
|
||
|
colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f);
|
||
|
|
||
|
model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint;
|
||
|
DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform);
|
||
|
model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw a model wires (with texture if set)
|
||
|
void DrawModelWires(Model model, Vector3 position, float scale, Color tint)
|
||
|
{
|
||
|
rlEnableWireMode();
|
||
|
|
||
|
DrawModel(model, position, scale, tint);
|
||
|
|
||
|
rlDisableWireMode();
|
||
|
}
|
||
|
|
||
|
// Draw a model wires (with texture if set) with extended parameters
|
||
|
void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
|
||
|
{
|
||
|
rlEnableWireMode();
|
||
|
|
||
|
DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint);
|
||
|
|
||
|
rlDisableWireMode();
|
||
|
}
|
||
|
|
||
|
// Draw a billboard
|
||
|
void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint)
|
||
|
{
|
||
|
Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height };
|
||
|
|
||
|
DrawBillboardRec(camera, texture, source, position, (Vector2){ size, size }, tint);
|
||
|
}
|
||
|
|
||
|
// Draw a billboard (part of a texture defined by a rectangle)
|
||
|
void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint)
|
||
|
{
|
||
|
// NOTE: Billboard locked on axis-Y
|
||
|
Vector3 up = { 0.0f, 1.0f, 0.0f };
|
||
|
|
||
|
DrawBillboardPro(camera, texture, source, position, up, size, Vector2Zero(), 0.0f, tint);
|
||
|
}
|
||
|
|
||
|
void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint)
|
||
|
{
|
||
|
// NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width
|
||
|
Vector2 sizeRatio = { size.y, size.x*(float)source.height/source.width };
|
||
|
|
||
|
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
|
||
|
|
||
|
Vector3 right = { matView.m0, matView.m4, matView.m8 };
|
||
|
//Vector3 up = { matView.m1, matView.m5, matView.m9 };
|
||
|
|
||
|
Vector3 rightScaled = Vector3Scale(right, sizeRatio.x/2);
|
||
|
Vector3 upScaled = Vector3Scale(up, sizeRatio.y/2);
|
||
|
|
||
|
Vector3 p1 = Vector3Add(rightScaled, upScaled);
|
||
|
Vector3 p2 = Vector3Subtract(rightScaled, upScaled);
|
||
|
|
||
|
Vector3 topLeft = Vector3Scale(p2, -1);
|
||
|
Vector3 topRight = p1;
|
||
|
Vector3 bottomRight = p2;
|
||
|
Vector3 bottomLeft = Vector3Scale(p1, -1);
|
||
|
|
||
|
if (rotation != 0.0f)
|
||
|
{
|
||
|
float sinRotation = sinf(rotation*DEG2RAD);
|
||
|
float cosRotation = cosf(rotation*DEG2RAD);
|
||
|
|
||
|
// NOTE: (-1, 1) is the range where origin.x, origin.y is inside the texture
|
||
|
float rotateAboutX = sizeRatio.x*origin.x/2;
|
||
|
float rotateAboutY = sizeRatio.y*origin.y/2;
|
||
|
|
||
|
float xtvalue, ytvalue;
|
||
|
float rotatedX, rotatedY;
|
||
|
|
||
|
xtvalue = Vector3DotProduct(right, topLeft) - rotateAboutX; // Project points to x and y coordinates on the billboard plane
|
||
|
ytvalue = Vector3DotProduct(up, topLeft) - rotateAboutY;
|
||
|
rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; // Rotate about the point origin
|
||
|
rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY;
|
||
|
topLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); // Translate back to cartesian coordinates
|
||
|
|
||
|
xtvalue = Vector3DotProduct(right, topRight) - rotateAboutX;
|
||
|
ytvalue = Vector3DotProduct(up, topRight) - rotateAboutY;
|
||
|
rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX;
|
||
|
rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY;
|
||
|
topRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX));
|
||
|
|
||
|
xtvalue = Vector3DotProduct(right, bottomRight) - rotateAboutX;
|
||
|
ytvalue = Vector3DotProduct(up, bottomRight) - rotateAboutY;
|
||
|
rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX;
|
||
|
rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY;
|
||
|
bottomRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX));
|
||
|
|
||
|
xtvalue = Vector3DotProduct(right, bottomLeft)-rotateAboutX;
|
||
|
ytvalue = Vector3DotProduct(up, bottomLeft)-rotateAboutY;
|
||
|
rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX;
|
||
|
rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY;
|
||
|
bottomLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX));
|
||
|
}
|
||
|
|
||
|
// Translate points to the draw center (position)
|
||
|
topLeft = Vector3Add(topLeft, position);
|
||
|
topRight = Vector3Add(topRight, position);
|
||
|
bottomRight = Vector3Add(bottomRight, position);
|
||
|
bottomLeft = Vector3Add(bottomLeft, position);
|
||
|
|
||
|
rlCheckRenderBatchLimit(4);
|
||
|
|
||
|
rlSetTexture(texture.id);
|
||
|
|
||
|
rlBegin(RL_QUADS);
|
||
|
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
|
||
|
|
||
|
// Bottom-left corner for texture and quad
|
||
|
rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height);
|
||
|
rlVertex3f(topLeft.x, topLeft.y, topLeft.z);
|
||
|
|
||
|
// Top-left corner for texture and quad
|
||
|
rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height);
|
||
|
rlVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z);
|
||
|
|
||
|
// Top-right corner for texture and quad
|
||
|
rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height);
|
||
|
rlVertex3f(bottomRight.x, bottomRight.y, bottomRight.z);
|
||
|
|
||
|
// Bottom-right corner for texture and quad
|
||
|
rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height);
|
||
|
rlVertex3f(topRight.x, topRight.y, topRight.z);
|
||
|
rlEnd();
|
||
|
|
||
|
rlSetTexture(0);
|
||
|
}
|
||
|
|
||
|
// Draw a bounding box with wires
|
||
|
void DrawBoundingBox(BoundingBox box, Color color)
|
||
|
{
|
||
|
Vector3 size = { 0 };
|
||
|
|
||
|
size.x = fabsf(box.max.x - box.min.x);
|
||
|
size.y = fabsf(box.max.y - box.min.y);
|
||
|
size.z = fabsf(box.max.z - box.min.z);
|
||
|
|
||
|
Vector3 center = { box.min.x + size.x/2.0f, box.min.y + size.y/2.0f, box.min.z + size.z/2.0f };
|
||
|
|
||
|
DrawCubeWires(center, size.x, size.y, size.z, color);
|
||
|
}
|
||
|
|
||
|
// Check collision between two spheres
|
||
|
bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2)
|
||
|
{
|
||
|
bool collision = false;
|
||
|
|
||
|
// Simple way to check for collision, just checking distance between two points
|
||
|
// Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution
|
||
|
/*
|
||
|
float dx = center1.x - center2.x; // X distance between centers
|
||
|
float dy = center1.y - center2.y; // Y distance between centers
|
||
|
float dz = center1.z - center2.z; // Z distance between centers
|
||
|
|
||
|
float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers
|
||
|
|
||
|
if (distance <= (radius1 + radius2)) collision = true;
|
||
|
*/
|
||
|
|
||
|
// Check for distances squared to avoid sqrtf()
|
||
|
if (Vector3DotProduct(Vector3Subtract(center2, center1), Vector3Subtract(center2, center1)) <= (radius1 + radius2)*(radius1 + radius2)) collision = true;
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Check collision between two boxes
|
||
|
// NOTE: Boxes are defined by two points minimum and maximum
|
||
|
bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2)
|
||
|
{
|
||
|
bool collision = true;
|
||
|
|
||
|
if ((box1.max.x >= box2.min.x) && (box1.min.x <= box2.max.x))
|
||
|
{
|
||
|
if ((box1.max.y < box2.min.y) || (box1.min.y > box2.max.y)) collision = false;
|
||
|
if ((box1.max.z < box2.min.z) || (box1.min.z > box2.max.z)) collision = false;
|
||
|
}
|
||
|
else collision = false;
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Check collision between box and sphere
|
||
|
bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius)
|
||
|
{
|
||
|
bool collision = false;
|
||
|
|
||
|
float dmin = 0;
|
||
|
|
||
|
if (center.x < box.min.x) dmin += powf(center.x - box.min.x, 2);
|
||
|
else if (center.x > box.max.x) dmin += powf(center.x - box.max.x, 2);
|
||
|
|
||
|
if (center.y < box.min.y) dmin += powf(center.y - box.min.y, 2);
|
||
|
else if (center.y > box.max.y) dmin += powf(center.y - box.max.y, 2);
|
||
|
|
||
|
if (center.z < box.min.z) dmin += powf(center.z - box.min.z, 2);
|
||
|
else if (center.z > box.max.z) dmin += powf(center.z - box.max.z, 2);
|
||
|
|
||
|
if (dmin <= (radius*radius)) collision = true;
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and sphere
|
||
|
RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius)
|
||
|
{
|
||
|
RayCollision collision = { 0 };
|
||
|
|
||
|
Vector3 raySpherePos = Vector3Subtract(center, ray.position);
|
||
|
float vector = Vector3DotProduct(raySpherePos, ray.direction);
|
||
|
float distance = Vector3Length(raySpherePos);
|
||
|
float d = radius*radius - (distance*distance - vector*vector);
|
||
|
|
||
|
collision.hit = d >= 0.0f;
|
||
|
|
||
|
// Check if ray origin is inside the sphere to calculate the correct collision point
|
||
|
if (distance < radius)
|
||
|
{
|
||
|
collision.distance = vector + sqrtf(d);
|
||
|
|
||
|
// Calculate collision point
|
||
|
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
|
||
|
|
||
|
// Calculate collision normal (pointing outwards)
|
||
|
collision.normal = Vector3Negate(Vector3Normalize(Vector3Subtract(collision.point, center)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
collision.distance = vector - sqrtf(d);
|
||
|
|
||
|
// Calculate collision point
|
||
|
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
|
||
|
|
||
|
// Calculate collision normal (pointing inwards)
|
||
|
collision.normal = Vector3Normalize(Vector3Subtract(collision.point, center));
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and box
|
||
|
RayCollision GetRayCollisionBox(Ray ray, BoundingBox box)
|
||
|
{
|
||
|
RayCollision collision = { 0 };
|
||
|
|
||
|
// Note: If ray.position is inside the box, the distance is negative (as if the ray was reversed)
|
||
|
// Reversing ray.direction will give use the correct result.
|
||
|
bool insideBox = (ray.position.x > box.min.x) && (ray.position.x < box.max.x) &&
|
||
|
(ray.position.y > box.min.y) && (ray.position.y < box.max.y) &&
|
||
|
(ray.position.z > box.min.z) && (ray.position.z < box.max.z);
|
||
|
|
||
|
if (insideBox) ray.direction = Vector3Negate(ray.direction);
|
||
|
|
||
|
float t[11] = { 0 };
|
||
|
|
||
|
t[8] = 1.0f/ray.direction.x;
|
||
|
t[9] = 1.0f/ray.direction.y;
|
||
|
t[10] = 1.0f/ray.direction.z;
|
||
|
|
||
|
t[0] = (box.min.x - ray.position.x)*t[8];
|
||
|
t[1] = (box.max.x - ray.position.x)*t[8];
|
||
|
t[2] = (box.min.y - ray.position.y)*t[9];
|
||
|
t[3] = (box.max.y - ray.position.y)*t[9];
|
||
|
t[4] = (box.min.z - ray.position.z)*t[10];
|
||
|
t[5] = (box.max.z - ray.position.z)*t[10];
|
||
|
t[6] = (float)fmax(fmax(fmin(t[0], t[1]), fmin(t[2], t[3])), fmin(t[4], t[5]));
|
||
|
t[7] = (float)fmin(fmin(fmax(t[0], t[1]), fmax(t[2], t[3])), fmax(t[4], t[5]));
|
||
|
|
||
|
collision.hit = !((t[7] < 0) || (t[6] > t[7]));
|
||
|
collision.distance = t[6];
|
||
|
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, collision.distance));
|
||
|
|
||
|
// Get box center point
|
||
|
collision.normal = Vector3Lerp(box.min, box.max, 0.5f);
|
||
|
// Get vector center point->hit point
|
||
|
collision.normal = Vector3Subtract(collision.point, collision.normal);
|
||
|
// Scale vector to unit cube
|
||
|
// NOTE: We use an additional .01 to fix numerical errors
|
||
|
collision.normal = Vector3Scale(collision.normal, 2.01f);
|
||
|
collision.normal = Vector3Divide(collision.normal, Vector3Subtract(box.max, box.min));
|
||
|
// The relevant elemets of the vector are now slightly larger than 1.0f (or smaller than -1.0f)
|
||
|
// and the others are somewhere between -1.0 and 1.0 casting to int is exactly our wanted normal!
|
||
|
collision.normal.x = (float)((int)collision.normal.x);
|
||
|
collision.normal.y = (float)((int)collision.normal.y);
|
||
|
collision.normal.z = (float)((int)collision.normal.z);
|
||
|
|
||
|
collision.normal = Vector3Normalize(collision.normal);
|
||
|
|
||
|
if (insideBox)
|
||
|
{
|
||
|
// Reset ray.direction
|
||
|
ray.direction = Vector3Negate(ray.direction);
|
||
|
// Fix result
|
||
|
collision.distance *= -1.0f;
|
||
|
collision.normal = Vector3Negate(collision.normal);
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and mesh
|
||
|
RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform)
|
||
|
{
|
||
|
RayCollision collision = { 0 };
|
||
|
|
||
|
// Check if mesh vertex data on CPU for testing
|
||
|
if (mesh.vertices != NULL)
|
||
|
{
|
||
|
int triangleCount = mesh.triangleCount;
|
||
|
|
||
|
// Test against all triangles in mesh
|
||
|
for (int i = 0; i < triangleCount; i++)
|
||
|
{
|
||
|
Vector3 a, b, c;
|
||
|
Vector3* vertdata = (Vector3*)mesh.vertices;
|
||
|
|
||
|
if (mesh.indices)
|
||
|
{
|
||
|
a = vertdata[mesh.indices[i*3 + 0]];
|
||
|
b = vertdata[mesh.indices[i*3 + 1]];
|
||
|
c = vertdata[mesh.indices[i*3 + 2]];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
a = vertdata[i*3 + 0];
|
||
|
b = vertdata[i*3 + 1];
|
||
|
c = vertdata[i*3 + 2];
|
||
|
}
|
||
|
|
||
|
a = Vector3Transform(a, transform);
|
||
|
b = Vector3Transform(b, transform);
|
||
|
c = Vector3Transform(c, transform);
|
||
|
|
||
|
RayCollision triHitInfo = GetRayCollisionTriangle(ray, a, b, c);
|
||
|
|
||
|
if (triHitInfo.hit)
|
||
|
{
|
||
|
// Save the closest hit triangle
|
||
|
if ((!collision.hit) || (collision.distance > triHitInfo.distance)) collision = triHitInfo;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and model
|
||
|
RayCollision GetRayCollisionModel(Ray ray, Model model)
|
||
|
{
|
||
|
RayCollision collision = { 0 };
|
||
|
|
||
|
for (int m = 0; m < model.meshCount; m++)
|
||
|
{
|
||
|
RayCollision meshHitInfo = GetRayCollisionMesh(ray, model.meshes[m], model.transform);
|
||
|
|
||
|
if (meshHitInfo.hit)
|
||
|
{
|
||
|
// Save the closest hit mesh
|
||
|
if ((!collision.hit) || (collision.distance > meshHitInfo.distance)) collision = meshHitInfo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and triangle
|
||
|
// NOTE: The points are expected to be in counter-clockwise winding
|
||
|
// NOTE: Based on https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
|
||
|
RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3)
|
||
|
{
|
||
|
#define EPSILON 0.000001 // A small number
|
||
|
|
||
|
RayCollision collision = { 0 };
|
||
|
Vector3 edge1 = { 0 };
|
||
|
Vector3 edge2 = { 0 };
|
||
|
Vector3 p, q, tv;
|
||
|
float det, invDet, u, v, t;
|
||
|
|
||
|
// Find vectors for two edges sharing V1
|
||
|
edge1 = Vector3Subtract(p2, p1);
|
||
|
edge2 = Vector3Subtract(p3, p1);
|
||
|
|
||
|
// Begin calculating determinant - also used to calculate u parameter
|
||
|
p = Vector3CrossProduct(ray.direction, edge2);
|
||
|
|
||
|
// If determinant is near zero, ray lies in plane of triangle or ray is parallel to plane of triangle
|
||
|
det = Vector3DotProduct(edge1, p);
|
||
|
|
||
|
// Avoid culling!
|
||
|
if ((det > -EPSILON) && (det < EPSILON)) return collision;
|
||
|
|
||
|
invDet = 1.0f/det;
|
||
|
|
||
|
// Calculate distance from V1 to ray origin
|
||
|
tv = Vector3Subtract(ray.position, p1);
|
||
|
|
||
|
// Calculate u parameter and test bound
|
||
|
u = Vector3DotProduct(tv, p)*invDet;
|
||
|
|
||
|
// The intersection lies outside of the triangle
|
||
|
if ((u < 0.0f) || (u > 1.0f)) return collision;
|
||
|
|
||
|
// Prepare to test v parameter
|
||
|
q = Vector3CrossProduct(tv, edge1);
|
||
|
|
||
|
// Calculate V parameter and test bound
|
||
|
v = Vector3DotProduct(ray.direction, q)*invDet;
|
||
|
|
||
|
// The intersection lies outside of the triangle
|
||
|
if ((v < 0.0f) || ((u + v) > 1.0f)) return collision;
|
||
|
|
||
|
t = Vector3DotProduct(edge2, q)*invDet;
|
||
|
|
||
|
if (t > EPSILON)
|
||
|
{
|
||
|
// Ray hit, get hit point and normal
|
||
|
collision.hit = true;
|
||
|
collision.distance = t;
|
||
|
collision.normal = Vector3Normalize(Vector3CrossProduct(edge1, edge2));
|
||
|
collision.point = Vector3Add(ray.position, Vector3Scale(ray.direction, t));
|
||
|
}
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
// Get collision info between ray and quad
|
||
|
// NOTE: The points are expected to be in counter-clockwise winding
|
||
|
RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
|
||
|
{
|
||
|
RayCollision collision = { 0 };
|
||
|
|
||
|
collision = GetRayCollisionTriangle(ray, p1, p2, p4);
|
||
|
|
||
|
if (!collision.hit) collision = GetRayCollisionTriangle(ray, p2, p3, p4);
|
||
|
|
||
|
return collision;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
// Module specific Functions Definition
|
||
|
//----------------------------------------------------------------------------------
|
||
|
#if defined(SUPPORT_FILEFORMAT_OBJ)
|
||
|
// Load OBJ mesh data
|
||
|
//
|
||
|
// Keep the following information in mind when reading this
|
||
|
// - A mesh is created for every material present in the obj file
|
||
|
// - the model.meshCount is therefore the materialCount returned from tinyobj
|
||
|
// - the mesh is automatically triangulated by tinyobj
|
||
|
static Model LoadOBJ(const char *fileName)
|
||
|
{
|
||
|
Model model = { 0 };
|
||
|
|
||
|
tinyobj_attrib_t attrib = { 0 };
|
||
|
tinyobj_shape_t *meshes = NULL;
|
||
|
unsigned int meshCount = 0;
|
||
|
|
||
|
tinyobj_material_t *materials = NULL;
|
||
|
unsigned int materialCount = 0;
|
||
|
|
||
|
char *fileText = LoadFileText(fileName);
|
||
|
|
||
|
if (fileText != NULL)
|
||
|
{
|
||
|
unsigned int dataSize = (unsigned int)strlen(fileText);
|
||
|
char currentDir[1024] = { 0 };
|
||
|
strcpy(currentDir, GetWorkingDirectory());
|
||
|
const char *workingDir = GetDirectoryPath(fileName);
|
||
|
if (CHDIR(workingDir) != 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir);
|
||
|
}
|
||
|
|
||
|
unsigned int flags = TINYOBJ_FLAG_TRIANGULATE;
|
||
|
int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileText, dataSize, flags);
|
||
|
|
||
|
if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName);
|
||
|
else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes/%i materials", fileName, meshCount, materialCount);
|
||
|
|
||
|
model.meshCount = materialCount;
|
||
|
|
||
|
// Init model materials array
|
||
|
if (materialCount > 0)
|
||
|
{
|
||
|
model.materialCount = materialCount;
|
||
|
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
model.meshCount = 1;
|
||
|
TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material");
|
||
|
}
|
||
|
|
||
|
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
// Count the faces for each material
|
||
|
int *matFaces = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
// iff no materials are present use all faces on one mesh
|
||
|
if (materialCount > 0)
|
||
|
{
|
||
|
for (unsigned int fi = 0; fi < attrib.num_faces; fi++)
|
||
|
{
|
||
|
//tinyobj_vertex_index_t face = attrib.faces[fi];
|
||
|
int idx = attrib.material_ids[fi];
|
||
|
matFaces[idx]++;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
matFaces[0] = attrib.num_faces;
|
||
|
}
|
||
|
|
||
|
//--------------------------------------
|
||
|
// Create the material meshes
|
||
|
|
||
|
// Running counts/indexes for each material mesh as we are
|
||
|
// building them at the same time
|
||
|
int *vCount = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
int *vtCount = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
int *vnCount = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
int *faceCount = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
// Allocate space for each of the material meshes
|
||
|
for (int mi = 0; mi < model.meshCount; mi++)
|
||
|
{
|
||
|
model.meshes[mi].vertexCount = matFaces[mi]*3;
|
||
|
model.meshes[mi].triangleCount = matFaces[mi];
|
||
|
model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
|
||
|
model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float));
|
||
|
model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
|
||
|
model.meshMaterial[mi] = mi;
|
||
|
}
|
||
|
|
||
|
// Scan through the combined sub meshes and pick out each material mesh
|
||
|
for (unsigned int af = 0; af < attrib.num_faces; af++)
|
||
|
{
|
||
|
int mm = attrib.material_ids[af]; // mesh material for this face
|
||
|
if (mm == -1) { mm = 0; } // no material object..
|
||
|
|
||
|
// Get indices for the face
|
||
|
tinyobj_vertex_index_t idx0 = attrib.faces[3*af + 0];
|
||
|
tinyobj_vertex_index_t idx1 = attrib.faces[3*af + 1];
|
||
|
tinyobj_vertex_index_t idx2 = attrib.faces[3*af + 2];
|
||
|
|
||
|
// Fill vertices buffer (float) using vertex index of the face
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3;
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3;
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3;
|
||
|
|
||
|
if (attrib.num_texcoords > 0)
|
||
|
{
|
||
|
// Fill texcoords buffer (float) using vertex index of the face
|
||
|
// NOTE: Y-coordinate must be flipped upside-down to account for
|
||
|
// raylib's upside down textures...
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0];
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2;
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0];
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2;
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0];
|
||
|
model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2;
|
||
|
}
|
||
|
|
||
|
if (attrib.num_normals > 0)
|
||
|
{
|
||
|
// Fill normals buffer (float) using vertex index of the face
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3;
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3;
|
||
|
for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Init model materials
|
||
|
for (unsigned int m = 0; m < materialCount; m++)
|
||
|
{
|
||
|
// Init material to default
|
||
|
// NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE
|
||
|
model.materials[m] = LoadMaterialDefault();
|
||
|
|
||
|
// Get default texture, in case no texture is defined
|
||
|
// NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8
|
||
|
model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
|
||
|
|
||
|
if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd
|
||
|
|
||
|
model.materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3];
|
||
|
model.materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f;
|
||
|
|
||
|
if (materials[m].specular_texname != NULL) model.materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks
|
||
|
model.materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3];
|
||
|
model.materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f;
|
||
|
|
||
|
if (materials[m].bump_texname != NULL) model.materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump
|
||
|
model.materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE;
|
||
|
model.materials[m].maps[MATERIAL_MAP_NORMAL].value = materials[m].shininess;
|
||
|
|
||
|
model.materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3];
|
||
|
|
||
|
if (materials[m].displacement_texname != NULL) model.materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp
|
||
|
}
|
||
|
|
||
|
tinyobj_attrib_free(&attrib);
|
||
|
tinyobj_shapes_free(meshes, meshCount);
|
||
|
tinyobj_materials_free(materials, materialCount);
|
||
|
|
||
|
UnloadFileText(fileText);
|
||
|
|
||
|
RL_FREE(matFaces);
|
||
|
RL_FREE(vCount);
|
||
|
RL_FREE(vtCount);
|
||
|
RL_FREE(vnCount);
|
||
|
RL_FREE(faceCount);
|
||
|
|
||
|
if (CHDIR(currentDir) != 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||
|
// Load IQM mesh data
|
||
|
static Model LoadIQM(const char *fileName)
|
||
|
{
|
||
|
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
|
||
|
#define IQM_VERSION 2 // only IQM version 2 supported
|
||
|
|
||
|
#define BONE_NAME_LENGTH 32 // BoneInfo name string length
|
||
|
#define MESH_NAME_LENGTH 32 // Mesh name string length
|
||
|
#define MATERIAL_NAME_LENGTH 32 // Material name string length
|
||
|
|
||
|
unsigned int fileSize = 0;
|
||
|
unsigned char *fileData = LoadFileData(fileName, &fileSize);
|
||
|
unsigned char *fileDataPtr = fileData;
|
||
|
|
||
|
// IQM file structs
|
||
|
//-----------------------------------------------------------------------------------
|
||
|
typedef struct IQMHeader {
|
||
|
char magic[16];
|
||
|
unsigned int version;
|
||
|
unsigned int filesize;
|
||
|
unsigned int flags;
|
||
|
unsigned int num_text, ofs_text;
|
||
|
unsigned int num_meshes, ofs_meshes;
|
||
|
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
|
||
|
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
|
||
|
unsigned int num_joints, ofs_joints;
|
||
|
unsigned int num_poses, ofs_poses;
|
||
|
unsigned int num_anims, ofs_anims;
|
||
|
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
|
||
|
unsigned int num_comment, ofs_comment;
|
||
|
unsigned int num_extensions, ofs_extensions;
|
||
|
} IQMHeader;
|
||
|
|
||
|
typedef struct IQMMesh {
|
||
|
unsigned int name;
|
||
|
unsigned int material;
|
||
|
unsigned int first_vertex, num_vertexes;
|
||
|
unsigned int first_triangle, num_triangles;
|
||
|
} IQMMesh;
|
||
|
|
||
|
typedef struct IQMTriangle {
|
||
|
unsigned int vertex[3];
|
||
|
} IQMTriangle;
|
||
|
|
||
|
typedef struct IQMJoint {
|
||
|
unsigned int name;
|
||
|
int parent;
|
||
|
float translate[3], rotate[4], scale[3];
|
||
|
} IQMJoint;
|
||
|
|
||
|
typedef struct IQMVertexArray {
|
||
|
unsigned int type;
|
||
|
unsigned int flags;
|
||
|
unsigned int format;
|
||
|
unsigned int size;
|
||
|
unsigned int offset;
|
||
|
} IQMVertexArray;
|
||
|
|
||
|
// NOTE: Below IQM structures are not used but listed for reference
|
||
|
/*
|
||
|
typedef struct IQMAdjacency {
|
||
|
unsigned int triangle[3];
|
||
|
} IQMAdjacency;
|
||
|
|
||
|
typedef struct IQMPose {
|
||
|
int parent;
|
||
|
unsigned int mask;
|
||
|
float channeloffset[10];
|
||
|
float channelscale[10];
|
||
|
} IQMPose;
|
||
|
|
||
|
typedef struct IQMAnim {
|
||
|
unsigned int name;
|
||
|
unsigned int first_frame, num_frames;
|
||
|
float framerate;
|
||
|
unsigned int flags;
|
||
|
} IQMAnim;
|
||
|
|
||
|
typedef struct IQMBounds {
|
||
|
float bbmin[3], bbmax[3];
|
||
|
float xyradius, radius;
|
||
|
} IQMBounds;
|
||
|
*/
|
||
|
//-----------------------------------------------------------------------------------
|
||
|
|
||
|
// IQM vertex data types
|
||
|
enum {
|
||
|
IQM_POSITION = 0,
|
||
|
IQM_TEXCOORD = 1,
|
||
|
IQM_NORMAL = 2,
|
||
|
IQM_TANGENT = 3, // NOTE: Tangents unused by default
|
||
|
IQM_BLENDINDEXES = 4,
|
||
|
IQM_BLENDWEIGHTS = 5,
|
||
|
IQM_COLOR = 6,
|
||
|
IQM_CUSTOM = 0x10 // NOTE: Custom vertex values unused by default
|
||
|
};
|
||
|
|
||
|
Model model = { 0 };
|
||
|
|
||
|
IQMMesh *imesh = NULL;
|
||
|
IQMTriangle *tri = NULL;
|
||
|
IQMVertexArray *va = NULL;
|
||
|
IQMJoint *ijoint = NULL;
|
||
|
|
||
|
float *vertex = NULL;
|
||
|
float *normal = NULL;
|
||
|
float *text = NULL;
|
||
|
char *blendi = NULL;
|
||
|
unsigned char *blendw = NULL;
|
||
|
unsigned char *color = NULL;
|
||
|
|
||
|
// In case file can not be read, return an empty model
|
||
|
if (fileDataPtr == NULL) return model;
|
||
|
|
||
|
// Read IQM header
|
||
|
IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
|
||
|
|
||
|
if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
if (iqmHeader->version != IQM_VERSION)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
//fileDataPtr += sizeof(IQMHeader); // Move file data pointer
|
||
|
|
||
|
// Meshes data processing
|
||
|
imesh = RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET);
|
||
|
//fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile);
|
||
|
memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh));
|
||
|
|
||
|
model.meshCount = iqmHeader->num_meshes;
|
||
|
model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
|
||
|
model.materialCount = model.meshCount;
|
||
|
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
char name[MESH_NAME_LENGTH] = { 0 };
|
||
|
char material[MATERIAL_NAME_LENGTH] = { 0 };
|
||
|
|
||
|
for (int i = 0; i < model.meshCount; i++)
|
||
|
{
|
||
|
//fseek(iqmFile, iqmHeader->ofs_text + imesh[i].name, SEEK_SET);
|
||
|
//fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile);
|
||
|
memcpy(name, fileDataPtr + iqmHeader->ofs_text + imesh[i].name, MESH_NAME_LENGTH*sizeof(char));
|
||
|
|
||
|
//fseek(iqmFile, iqmHeader->ofs_text + imesh[i].material, SEEK_SET);
|
||
|
//fread(material, sizeof(char)*MATERIAL_NAME_LENGTH, 1, iqmFile);
|
||
|
memcpy(material, fileDataPtr + iqmHeader->ofs_text + imesh[i].material, MATERIAL_NAME_LENGTH*sizeof(char));
|
||
|
|
||
|
model.materials[i] = LoadMaterialDefault();
|
||
|
|
||
|
TRACELOG(LOG_DEBUG, "MODEL: [%s] mesh name (%s), material (%s)", fileName, name, material);
|
||
|
|
||
|
model.meshes[i].vertexCount = imesh[i].num_vertexes;
|
||
|
|
||
|
model.meshes[i].vertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex positions
|
||
|
model.meshes[i].normals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); // Default vertex normals
|
||
|
model.meshes[i].texcoords = RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); // Default vertex texcoords
|
||
|
|
||
|
model.meshes[i].boneIds = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(unsigned char)); // Up-to 4 bones supported!
|
||
|
model.meshes[i].boneWeights = RL_CALLOC(model.meshes[i].vertexCount*4, sizeof(float)); // Up-to 4 bones supported!
|
||
|
|
||
|
model.meshes[i].triangleCount = imesh[i].num_triangles;
|
||
|
model.meshes[i].indices = RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short));
|
||
|
|
||
|
// Animated verted data, what we actually process for rendering
|
||
|
// NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning)
|
||
|
model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
|
||
|
model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float));
|
||
|
}
|
||
|
|
||
|
// Triangles data processing
|
||
|
tri = RL_MALLOC(iqmHeader->num_triangles*sizeof(IQMTriangle));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_triangles, SEEK_SET);
|
||
|
//fread(tri, iqmHeader->num_triangles*sizeof(IQMTriangle), 1, iqmFile);
|
||
|
memcpy(tri, fileDataPtr + iqmHeader->ofs_triangles, iqmHeader->num_triangles*sizeof(IQMTriangle));
|
||
|
|
||
|
for (int m = 0; m < model.meshCount; m++)
|
||
|
{
|
||
|
int tcounter = 0;
|
||
|
|
||
|
for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++)
|
||
|
{
|
||
|
// IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order,
|
||
|
// expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes
|
||
|
// NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default
|
||
|
model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex;
|
||
|
model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex;
|
||
|
model.meshes[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex;
|
||
|
tcounter += 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Vertex arrays data processing
|
||
|
va = RL_MALLOC(iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_vertexarrays, SEEK_SET);
|
||
|
//fread(va, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray), 1, iqmFile);
|
||
|
memcpy(va, fileDataPtr + iqmHeader->ofs_vertexarrays, iqmHeader->num_vertexarrays*sizeof(IQMVertexArray));
|
||
|
|
||
|
for (unsigned int i = 0; i < iqmHeader->num_vertexarrays; i++)
|
||
|
{
|
||
|
switch (va[i].type)
|
||
|
{
|
||
|
case IQM_POSITION:
|
||
|
{
|
||
|
vertex = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(vertex, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile);
|
||
|
memcpy(vertex, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
int vCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
|
||
|
{
|
||
|
model.meshes[m].vertices[vCounter] = vertex[i];
|
||
|
model.meshes[m].animVertices[vCounter] = vertex[i];
|
||
|
vCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IQM_NORMAL:
|
||
|
{
|
||
|
normal = RL_MALLOC(iqmHeader->num_vertexes*3*sizeof(float));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(normal, iqmHeader->num_vertexes*3*sizeof(float), 1, iqmFile);
|
||
|
memcpy(normal, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*3*sizeof(float));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
int vCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
|
||
|
{
|
||
|
model.meshes[m].normals[vCounter] = normal[i];
|
||
|
model.meshes[m].animNormals[vCounter] = normal[i];
|
||
|
vCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IQM_TEXCOORD:
|
||
|
{
|
||
|
text = RL_MALLOC(iqmHeader->num_vertexes*2*sizeof(float));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(text, iqmHeader->num_vertexes*2*sizeof(float), 1, iqmFile);
|
||
|
memcpy(text, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*2*sizeof(float));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
int vCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++)
|
||
|
{
|
||
|
model.meshes[m].texcoords[vCounter] = text[i];
|
||
|
vCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IQM_BLENDINDEXES:
|
||
|
{
|
||
|
blendi = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(char));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(blendi, iqmHeader->num_vertexes*4*sizeof(char), 1, iqmFile);
|
||
|
memcpy(blendi, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(char));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
int boneCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
|
||
|
{
|
||
|
model.meshes[m].boneIds[boneCounter] = blendi[i];
|
||
|
boneCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IQM_BLENDWEIGHTS:
|
||
|
{
|
||
|
blendw = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile);
|
||
|
memcpy(blendw, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
int boneCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
|
||
|
{
|
||
|
model.meshes[m].boneWeights[boneCounter] = blendw[i]/255.0f;
|
||
|
boneCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
case IQM_COLOR:
|
||
|
{
|
||
|
color = RL_MALLOC(iqmHeader->num_vertexes*4*sizeof(unsigned char));
|
||
|
//fseek(iqmFile, va[i].offset, SEEK_SET);
|
||
|
//fread(blendw, iqmHeader->num_vertexes*4*sizeof(unsigned char), 1, iqmFile);
|
||
|
memcpy(color, fileDataPtr + va[i].offset, iqmHeader->num_vertexes*4*sizeof(unsigned char));
|
||
|
|
||
|
for (unsigned int m = 0; m < iqmHeader->num_meshes; m++)
|
||
|
{
|
||
|
model.meshes[m].colors = RL_CALLOC(model.meshes[m].vertexCount*4, sizeof(unsigned char));
|
||
|
|
||
|
int vCounter = 0;
|
||
|
for (unsigned int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
|
||
|
{
|
||
|
model.meshes[m].colors[vCounter] = color[i];
|
||
|
vCounter++;
|
||
|
}
|
||
|
}
|
||
|
} break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Bones (joints) data processing
|
||
|
ijoint = RL_MALLOC(iqmHeader->num_joints*sizeof(IQMJoint));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_joints, SEEK_SET);
|
||
|
//fread(ijoint, iqmHeader->num_joints*sizeof(IQMJoint), 1, iqmFile);
|
||
|
memcpy(ijoint, fileDataPtr + iqmHeader->ofs_joints, iqmHeader->num_joints*sizeof(IQMJoint));
|
||
|
|
||
|
model.boneCount = iqmHeader->num_joints;
|
||
|
model.bones = RL_MALLOC(iqmHeader->num_joints*sizeof(BoneInfo));
|
||
|
model.bindPose = RL_MALLOC(iqmHeader->num_joints*sizeof(Transform));
|
||
|
|
||
|
for (unsigned int i = 0; i < iqmHeader->num_joints; i++)
|
||
|
{
|
||
|
// Bones
|
||
|
model.bones[i].parent = ijoint[i].parent;
|
||
|
//fseek(iqmFile, iqmHeader->ofs_text + ijoint[i].name, SEEK_SET);
|
||
|
//fread(model.bones[i].name, BONE_NAME_LENGTH*sizeof(char), 1, iqmFile);
|
||
|
memcpy(model.bones[i].name, fileDataPtr + iqmHeader->ofs_text + ijoint[i].name, BONE_NAME_LENGTH*sizeof(char));
|
||
|
|
||
|
// Bind pose (base pose)
|
||
|
model.bindPose[i].translation.x = ijoint[i].translate[0];
|
||
|
model.bindPose[i].translation.y = ijoint[i].translate[1];
|
||
|
model.bindPose[i].translation.z = ijoint[i].translate[2];
|
||
|
|
||
|
model.bindPose[i].rotation.x = ijoint[i].rotate[0];
|
||
|
model.bindPose[i].rotation.y = ijoint[i].rotate[1];
|
||
|
model.bindPose[i].rotation.z = ijoint[i].rotate[2];
|
||
|
model.bindPose[i].rotation.w = ijoint[i].rotate[3];
|
||
|
|
||
|
model.bindPose[i].scale.x = ijoint[i].scale[0];
|
||
|
model.bindPose[i].scale.y = ijoint[i].scale[1];
|
||
|
model.bindPose[i].scale.z = ijoint[i].scale[2];
|
||
|
}
|
||
|
|
||
|
// Build bind pose from parent joints
|
||
|
for (int i = 0; i < model.boneCount; i++)
|
||
|
{
|
||
|
if (model.bones[i].parent >= 0)
|
||
|
{
|
||
|
model.bindPose[i].rotation = QuaternionMultiply(model.bindPose[model.bones[i].parent].rotation, model.bindPose[i].rotation);
|
||
|
model.bindPose[i].translation = Vector3RotateByQuaternion(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].rotation);
|
||
|
model.bindPose[i].translation = Vector3Add(model.bindPose[i].translation, model.bindPose[model.bones[i].parent].translation);
|
||
|
model.bindPose[i].scale = Vector3Multiply(model.bindPose[i].scale, model.bindPose[model.bones[i].parent].scale);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RL_FREE(fileData);
|
||
|
|
||
|
RL_FREE(imesh);
|
||
|
RL_FREE(tri);
|
||
|
RL_FREE(va);
|
||
|
RL_FREE(vertex);
|
||
|
RL_FREE(normal);
|
||
|
RL_FREE(text);
|
||
|
RL_FREE(blendi);
|
||
|
RL_FREE(blendw);
|
||
|
RL_FREE(ijoint);
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
// Load IQM animation data
|
||
|
static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount)
|
||
|
{
|
||
|
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
|
||
|
#define IQM_VERSION 2 // only IQM version 2 supported
|
||
|
|
||
|
unsigned int fileSize = 0;
|
||
|
unsigned char *fileData = LoadFileData(fileName, &fileSize);
|
||
|
unsigned char *fileDataPtr = fileData;
|
||
|
|
||
|
typedef struct IQMHeader {
|
||
|
char magic[16];
|
||
|
unsigned int version;
|
||
|
unsigned int filesize;
|
||
|
unsigned int flags;
|
||
|
unsigned int num_text, ofs_text;
|
||
|
unsigned int num_meshes, ofs_meshes;
|
||
|
unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
|
||
|
unsigned int num_triangles, ofs_triangles, ofs_adjacency;
|
||
|
unsigned int num_joints, ofs_joints;
|
||
|
unsigned int num_poses, ofs_poses;
|
||
|
unsigned int num_anims, ofs_anims;
|
||
|
unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
|
||
|
unsigned int num_comment, ofs_comment;
|
||
|
unsigned int num_extensions, ofs_extensions;
|
||
|
} IQMHeader;
|
||
|
|
||
|
typedef struct IQMPose {
|
||
|
int parent;
|
||
|
unsigned int mask;
|
||
|
float channeloffset[10];
|
||
|
float channelscale[10];
|
||
|
} IQMPose;
|
||
|
|
||
|
typedef struct IQMAnim {
|
||
|
unsigned int name;
|
||
|
unsigned int first_frame, num_frames;
|
||
|
float framerate;
|
||
|
unsigned int flags;
|
||
|
} IQMAnim;
|
||
|
|
||
|
// In case file can not be read, return an empty model
|
||
|
if (fileDataPtr == NULL) return NULL;
|
||
|
|
||
|
// Read IQM header
|
||
|
IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr;
|
||
|
|
||
|
if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (iqmHeader->version != IQM_VERSION)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// Get bones data
|
||
|
IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET);
|
||
|
//fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile);
|
||
|
memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose));
|
||
|
|
||
|
// Get animations data
|
||
|
*animCount = iqmHeader->num_anims;
|
||
|
IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET);
|
||
|
//fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile);
|
||
|
memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim));
|
||
|
|
||
|
ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation));
|
||
|
|
||
|
// frameposes
|
||
|
unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
|
||
|
//fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET);
|
||
|
//fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile);
|
||
|
memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short));
|
||
|
|
||
|
for (unsigned int a = 0; a < iqmHeader->num_anims; a++)
|
||
|
{
|
||
|
animations[a].frameCount = anim[a].num_frames;
|
||
|
animations[a].boneCount = iqmHeader->num_poses;
|
||
|
animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo));
|
||
|
animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *));
|
||
|
// animations[a].framerate = anim.framerate; // TODO: Use framerate?
|
||
|
|
||
|
for (unsigned int j = 0; j < iqmHeader->num_poses; j++)
|
||
|
{
|
||
|
strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
|
||
|
animations[a].bones[j].parent = poses[j].parent;
|
||
|
}
|
||
|
|
||
|
for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform));
|
||
|
|
||
|
int dcounter = anim[a].first_frame*iqmHeader->num_framechannels;
|
||
|
|
||
|
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
|
||
|
{
|
||
|
for (unsigned int i = 0; i < iqmHeader->num_poses; i++)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
|
||
|
|
||
|
if (poses[i].mask & 0x01)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
|
||
|
|
||
|
if (poses[i].mask & 0x02)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
|
||
|
|
||
|
if (poses[i].mask & 0x04)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
|
||
|
|
||
|
if (poses[i].mask & 0x08)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
|
||
|
|
||
|
if (poses[i].mask & 0x10)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
|
||
|
|
||
|
if (poses[i].mask & 0x20)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
|
||
|
|
||
|
if (poses[i].mask & 0x40)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
|
||
|
|
||
|
if (poses[i].mask & 0x80)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
|
||
|
|
||
|
if (poses[i].mask & 0x100)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
|
||
|
|
||
|
if (poses[i].mask & 0x200)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
|
||
|
dcounter++;
|
||
|
}
|
||
|
|
||
|
animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Build frameposes
|
||
|
for (unsigned int frame = 0; frame < anim[a].num_frames; frame++)
|
||
|
{
|
||
|
for (int i = 0; i < animations[a].boneCount; i++)
|
||
|
{
|
||
|
if (animations[a].bones[i].parent >= 0)
|
||
|
{
|
||
|
animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
|
||
|
animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
|
||
|
animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
|
||
|
animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RL_FREE(fileData);
|
||
|
|
||
|
RL_FREE(framedata);
|
||
|
RL_FREE(poses);
|
||
|
RL_FREE(anim);
|
||
|
|
||
|
return animations;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||
|
// Load image from different glTF provided methods (uri, path, buffer_view)
|
||
|
static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath)
|
||
|
{
|
||
|
Image image = { 0 };
|
||
|
|
||
|
if (cgltfImage->uri != NULL) // Check if image data is provided as a uri (base64 or path)
|
||
|
{
|
||
|
if ((strlen(cgltfImage->uri) > 5) &&
|
||
|
(cgltfImage->uri[0] == 'd') &&
|
||
|
(cgltfImage->uri[1] == 'a') &&
|
||
|
(cgltfImage->uri[2] == 't') &&
|
||
|
(cgltfImage->uri[3] == 'a') &&
|
||
|
(cgltfImage->uri[4] == ':')) // Check if image is provided as base64 text data
|
||
|
{
|
||
|
// Data URI Format: data:<mediatype>;base64,<data>
|
||
|
|
||
|
// Find the comma
|
||
|
int i = 0;
|
||
|
while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++;
|
||
|
|
||
|
if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image");
|
||
|
else
|
||
|
{
|
||
|
int base64Size = (int)strlen(cgltfImage->uri + i + 1);
|
||
|
int outSize = 3*(base64Size/4); // TODO: Consider padding (-numberOfPaddingCharacters)
|
||
|
void *data = NULL;
|
||
|
|
||
|
cgltf_options options = { 0 };
|
||
|
cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data);
|
||
|
|
||
|
if (result == cgltf_result_success)
|
||
|
{
|
||
|
image = LoadImageFromMemory(".png", (unsigned char *)data, outSize);
|
||
|
cgltf_free((cgltf_data*)data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else // Check if image is provided as image path
|
||
|
{
|
||
|
image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri));
|
||
|
}
|
||
|
}
|
||
|
else if (cgltfImage->buffer_view->buffer->data != NULL) // Check if image is provided as data buffer
|
||
|
{
|
||
|
unsigned char *data = RL_MALLOC(cgltfImage->buffer_view->size);
|
||
|
int offset = (int)cgltfImage->buffer_view->offset;
|
||
|
int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1;
|
||
|
|
||
|
// Copy buffer data to memory for loading
|
||
|
for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++)
|
||
|
{
|
||
|
data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset];
|
||
|
offset += stride;
|
||
|
}
|
||
|
|
||
|
// Check mime_type for image: (cgltfImage->mime_type == "image/png")
|
||
|
// NOTE: Detected that some models define mime_type as "image\\/png"
|
||
|
if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) ||
|
||
|
(strcmp(cgltfImage->mime_type, "image/png") == 0)) image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size);
|
||
|
else if ((strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) ||
|
||
|
(strcmp(cgltfImage->mime_type, "image/jpeg") == 0)) image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size);
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri));
|
||
|
|
||
|
RL_FREE(data);
|
||
|
}
|
||
|
|
||
|
return image;
|
||
|
}
|
||
|
|
||
|
// Load glTF file into model struct, .gltf and .glb supported
|
||
|
static Model LoadGLTF(const char *fileName)
|
||
|
{
|
||
|
/*********************************************************************************************
|
||
|
|
||
|
Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend)
|
||
|
Reviewed by Ramon Santamaria (@raysan5)
|
||
|
|
||
|
FEATURES:
|
||
|
- Supports .gltf and .glb files
|
||
|
- Supports embedded (base64) or external textures
|
||
|
- Supports PBR metallic/roughness flow, loads material textures, values and colors
|
||
|
PBR specular/glossiness flow and extended texture flows not supported
|
||
|
- Supports multiple meshes per model (every primitives is loaded as a separate mesh)
|
||
|
|
||
|
RESTRICTIONS:
|
||
|
- Only triangle meshes supported
|
||
|
- Vertex attibute types and formats supported:
|
||
|
> Vertices (position): vec3: float
|
||
|
> Normals: vec3: float
|
||
|
> Texcoords: vec2: float
|
||
|
> Colors: vec4: u8, u16, f32 (normalized)
|
||
|
> Indices: u16, u32 (truncated to u16)
|
||
|
- Node hierarchies or transforms not supported
|
||
|
|
||
|
***********************************************************************************************/
|
||
|
|
||
|
// Macro to simplify attributes loading code
|
||
|
#define LOAD_ATTRIBUTE(accesor, numComp, dataType, dstPtr) \
|
||
|
{ \
|
||
|
int n = 0; \
|
||
|
dataType *buffer = (dataType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(dataType) + accesor->offset/sizeof(dataType); \
|
||
|
for (unsigned int k = 0; k < accesor->count; k++) \
|
||
|
{\
|
||
|
for (int l = 0; l < numComp; l++) \
|
||
|
{\
|
||
|
dstPtr[numComp*k + l] = buffer[n + l];\
|
||
|
}\
|
||
|
n += (int)(accesor->stride/sizeof(dataType));\
|
||
|
}\
|
||
|
}
|
||
|
|
||
|
Model model = { 0 };
|
||
|
|
||
|
// glTF file loading
|
||
|
unsigned int dataSize = 0;
|
||
|
unsigned char *fileData = LoadFileData(fileName, &dataSize);
|
||
|
|
||
|
if (fileData == NULL) return model;
|
||
|
|
||
|
// glTF data loading
|
||
|
cgltf_options options = { 0 };
|
||
|
cgltf_data *data = NULL;
|
||
|
cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
|
||
|
|
||
|
if (result == cgltf_result_success)
|
||
|
{
|
||
|
if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName);
|
||
|
else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName);
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName);
|
||
|
|
||
|
TRACELOG(LOG_INFO, " > Meshes count: %i", data->meshes_count);
|
||
|
TRACELOG(LOG_INFO, " > Materials count: %i (+1 default)", data->materials_count);
|
||
|
TRACELOG(LOG_DEBUG, " > Buffers count: %i", data->buffers_count);
|
||
|
TRACELOG(LOG_DEBUG, " > Images count: %i", data->images_count);
|
||
|
TRACELOG(LOG_DEBUG, " > Textures count: %i", data->textures_count);
|
||
|
|
||
|
// Force reading data buffers (fills buffer_view->buffer->data)
|
||
|
// NOTE: If an uri is defined to base64 data or external path, it's automatically loaded -> TODO: Verify this assumption
|
||
|
result = cgltf_load_buffers(&options, data, fileName);
|
||
|
if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName);
|
||
|
|
||
|
int primitivesCount = 0;
|
||
|
// NOTE: We will load every primitive in the glTF as a separate raylib mesh
|
||
|
for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count;
|
||
|
|
||
|
// Load our model data: meshes and materials
|
||
|
model.meshCount = primitivesCount;
|
||
|
model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int*)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
|
||
|
|
||
|
// NOTE: We keep an extra slot for default material, in case some mesh requires it
|
||
|
model.materialCount = (int)data->materials_count + 1;
|
||
|
model.materials = RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0)
|
||
|
|
||
|
// Load mesh-material indices, by default all meshes are mapped to material index: 0
|
||
|
model.meshMaterial = RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
// Load materials data
|
||
|
//----------------------------------------------------------------------------------------------------
|
||
|
for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++)
|
||
|
{
|
||
|
model.materials[j] = LoadMaterialDefault();
|
||
|
const char *texPath = GetDirectoryPath(fileName);
|
||
|
|
||
|
// Check glTF material flow: PBR metallic/roughness flow
|
||
|
// NOTE: Alternatively, materials can follow PBR specular/glossiness flow
|
||
|
if (data->materials[i].has_pbr_metallic_roughness)
|
||
|
{
|
||
|
// Load base color texture (albedo)
|
||
|
if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
|
||
|
{
|
||
|
Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath);
|
||
|
if (imAlbedo.data != NULL)
|
||
|
{
|
||
|
model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo);
|
||
|
UnloadImage(imAlbedo);
|
||
|
}
|
||
|
}
|
||
|
// Load base color factor (tint)
|
||
|
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255);
|
||
|
|
||
|
// Load metallic/roughness texture
|
||
|
if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
|
||
|
{
|
||
|
Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath);
|
||
|
if (imMetallicRoughness.data != NULL)
|
||
|
{
|
||
|
model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imMetallicRoughness);
|
||
|
UnloadImage(imMetallicRoughness);
|
||
|
}
|
||
|
|
||
|
// Load metallic/roughness material properties
|
||
|
float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
|
||
|
model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
|
||
|
|
||
|
float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
|
||
|
model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic;
|
||
|
}
|
||
|
|
||
|
// Load normal texture
|
||
|
if (data->materials[i].normal_texture.texture)
|
||
|
{
|
||
|
Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath);
|
||
|
if (imNormal.data != NULL)
|
||
|
{
|
||
|
model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal);
|
||
|
UnloadImage(imNormal);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load ambient occlusion texture
|
||
|
if (data->materials[i].occlusion_texture.texture)
|
||
|
{
|
||
|
Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath);
|
||
|
if (imOcclusion.data != NULL)
|
||
|
{
|
||
|
model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion);
|
||
|
UnloadImage(imOcclusion);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load emissive texture
|
||
|
if (data->materials[i].emissive_texture.texture)
|
||
|
{
|
||
|
Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath);
|
||
|
if (imEmissive.data != NULL)
|
||
|
{
|
||
|
model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive);
|
||
|
UnloadImage(imEmissive);
|
||
|
}
|
||
|
|
||
|
// Load emissive color factor
|
||
|
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
|
||
|
model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Other possible materials not supported by raylib pipeline:
|
||
|
// has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen
|
||
|
}
|
||
|
|
||
|
// Load meshes data
|
||
|
//----------------------------------------------------------------------------------------------------
|
||
|
for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++)
|
||
|
{
|
||
|
// NOTE: meshIndex accumulates primitives
|
||
|
|
||
|
for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
|
||
|
{
|
||
|
// NOTE: We only support primitives defined by triangles
|
||
|
// Other alternatives: points, lines, line_strip, triangle_strip
|
||
|
if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue;
|
||
|
|
||
|
// NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...),
|
||
|
// Only some formats for each attribute type are supported, read info at the top of this function!
|
||
|
|
||
|
for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
|
||
|
{
|
||
|
// Check the different attributes for every pimitive
|
||
|
if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
// WARNING: SPECS: POSITION accessor MUST have its min and max properties defined.
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3))
|
||
|
{
|
||
|
// Init raylib mesh vertices to copy glTF attribute data
|
||
|
model.meshes[meshIndex].vertexCount = (int)attribute->count;
|
||
|
model.meshes[meshIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float));
|
||
|
|
||
|
// Load 3 components of float data type into mesh.vertices
|
||
|
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName);
|
||
|
}
|
||
|
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3))
|
||
|
{
|
||
|
// Init raylib mesh normals to copy glTF attribute data
|
||
|
model.meshes[meshIndex].normals = RL_MALLOC(attribute->count*3*sizeof(float));
|
||
|
|
||
|
// Load 3 components of float data type into mesh.normals
|
||
|
LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName);
|
||
|
}
|
||
|
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh tangent to copy glTF attribute data
|
||
|
model.meshes[meshIndex].tangents = RL_MALLOC(attribute->count*4*sizeof(float));
|
||
|
|
||
|
// Load 4 components of float data type into mesh.tangents
|
||
|
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName);
|
||
|
}
|
||
|
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_0
|
||
|
{
|
||
|
// TODO: Support additional texture coordinates: TEXCOORD_1 -> mesh.texcoords2
|
||
|
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec2))
|
||
|
{
|
||
|
// Init raylib mesh texcoords to copy glTF attribute data
|
||
|
model.meshes[meshIndex].texcoords = RL_MALLOC(attribute->count*2*sizeof(float));
|
||
|
|
||
|
// Load 3 components of float data type into mesh.texcoords
|
||
|
LOAD_ATTRIBUTE(attribute, 2, float, model.meshes[meshIndex].texcoords)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName);
|
||
|
}
|
||
|
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_0
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
// WARNING: SPECS: All components of each COLOR_n accessor element MUST be clamped to [0.0, 1.0] range.
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh color to copy glTF attribute data
|
||
|
model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
|
||
|
|
||
|
// Load 4 components of unsigned char data type into mesh.colors
|
||
|
LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].colors)
|
||
|
}
|
||
|
else if ((attribute->component_type == cgltf_component_type_r_16u) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh color to copy glTF attribute data
|
||
|
model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
|
||
|
|
||
|
// Load data into a temp buffer to be converted to raylib data type
|
||
|
unsigned short *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned short));
|
||
|
LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp);
|
||
|
|
||
|
// Convert data to raylib color data type (4 bytes)
|
||
|
for (int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f);
|
||
|
|
||
|
RL_FREE(temp);
|
||
|
}
|
||
|
else if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh color to copy glTF attribute data
|
||
|
model.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
|
||
|
|
||
|
// Load data into a temp buffer to be converted to raylib data type
|
||
|
float *temp = RL_MALLOC(attribute->count*4*sizeof(float));
|
||
|
LOAD_ATTRIBUTE(attribute, 4, float, temp);
|
||
|
|
||
|
// Convert data to raylib color data type (4 bytes), we expect the color data normalized
|
||
|
for (int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (unsigned char)(temp[c]*255.0f);
|
||
|
|
||
|
RL_FREE(temp);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName);
|
||
|
}
|
||
|
|
||
|
// NOTE: Attributes related to animations are processed separately
|
||
|
}
|
||
|
|
||
|
// Load primitive indices data (if provided)
|
||
|
if (data->meshes[i].primitives[p].indices != NULL)
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].indices;
|
||
|
|
||
|
model.meshes[meshIndex].triangleCount = (int)attribute->count/3;
|
||
|
|
||
|
if (attribute->component_type == cgltf_component_type_r_16u)
|
||
|
{
|
||
|
// Init raylib mesh indices to copy glTF attribute data
|
||
|
model.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short));
|
||
|
|
||
|
// Load unsigned short data type into mesh.indices
|
||
|
LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[meshIndex].indices)
|
||
|
}
|
||
|
else if (attribute->component_type == cgltf_component_type_r_32u)
|
||
|
{
|
||
|
// Init raylib mesh indices to copy glTF attribute data
|
||
|
model.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short));
|
||
|
|
||
|
// Load data into a temp buffer to be converted to raylib data type
|
||
|
unsigned int *temp = RL_MALLOC(attribute->count*sizeof(unsigned int));
|
||
|
LOAD_ATTRIBUTE(attribute, 1, unsigned int, temp);
|
||
|
|
||
|
// Convert data to raylib indices data type (unsigned short)
|
||
|
for (int d = 0; d < attribute->count; d++) model.meshes[meshIndex].indices[d] = (unsigned short)temp[d];
|
||
|
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName);
|
||
|
|
||
|
RL_FREE(temp);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName);
|
||
|
}
|
||
|
else model.meshes[meshIndex].triangleCount = model.meshes[meshIndex].vertexCount/3; // Unindexed mesh
|
||
|
|
||
|
// Assign to the primitive mesh the corresponding material index
|
||
|
// NOTE: If no material defined, mesh uses the already assigned default material (index: 0)
|
||
|
for (int m = 0; m < data->materials_count; m++)
|
||
|
{
|
||
|
// The primitive actually keeps the pointer to the corresponding material,
|
||
|
// raylib instead assigns to the mesh the by its index, as loaded in model.materials array
|
||
|
// To get the index, we check if material pointers match and we assign the corresponding index,
|
||
|
// skipping index 0, the default material
|
||
|
if (&data->materials[m] == data->meshes[i].primitives[p].material)
|
||
|
{
|
||
|
model.meshMaterial[meshIndex] = m + 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
meshIndex++; // Move to next mesh
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
// TODO: Load glTF meshes animation data
|
||
|
// REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins
|
||
|
// REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes
|
||
|
//----------------------------------------------------------------------------------------------------
|
||
|
for (unsigned int i = 0, meshIndex = 0; i < data->meshes_count; i++)
|
||
|
{
|
||
|
for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
|
||
|
{
|
||
|
// NOTE: We only support primitives defined by triangles
|
||
|
if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue;
|
||
|
|
||
|
for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
|
||
|
{
|
||
|
// NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib
|
||
|
|
||
|
if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16)
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh bone ids to copy glTF attribute data
|
||
|
model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char));
|
||
|
|
||
|
// Load 4 components of unsigned char data type into mesh.boneIds
|
||
|
LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIds)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported, use vec4 u8", fileName);
|
||
|
}
|
||
|
else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4 / u8, u16, f32)
|
||
|
{
|
||
|
cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
|
||
|
|
||
|
if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4))
|
||
|
{
|
||
|
// Init raylib mesh bone weight to copy glTF attribute data
|
||
|
model.meshes[meshIndex].boneWeights = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float));
|
||
|
|
||
|
// Load 4 components of float data type into mesh.boneWeights
|
||
|
LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].boneWeights)
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
meshIndex++; // Move to next mesh
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
// Free all cgltf loaded data
|
||
|
cgltf_free(data);
|
||
|
}
|
||
|
else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
|
||
|
|
||
|
// WARNING: cgltf requires the file pointer available while reading data
|
||
|
UnloadFileData(fileData);
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if defined(SUPPORT_FILEFORMAT_VOX)
|
||
|
// Load VOX (MagicaVoxel) mesh data
|
||
|
static Model LoadVOX(const char *fileName)
|
||
|
{
|
||
|
Model model = { 0 };
|
||
|
|
||
|
int nbvertices = 0;
|
||
|
int meshescount = 0;
|
||
|
unsigned int fileSize = 0;
|
||
|
unsigned char *fileData = NULL;
|
||
|
|
||
|
// Read vox file into buffer
|
||
|
fileData = LoadFileData(fileName, &fileSize);
|
||
|
if (fileData == 0)
|
||
|
{
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX file", fileName);
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
// Read and build voxarray description
|
||
|
VoxArray3D voxarray = { 0 };
|
||
|
int ret = Vox_LoadFromMemory(fileData, fileSize, &voxarray);
|
||
|
|
||
|
if (ret != VOX_SUCCESS)
|
||
|
{
|
||
|
// Error
|
||
|
UnloadFileData(fileData);
|
||
|
|
||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load VOX data", fileName);
|
||
|
return model;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Success: Compute meshes count
|
||
|
nbvertices = voxarray.vertices.used;
|
||
|
meshescount = 1 + (nbvertices/65536);
|
||
|
|
||
|
TRACELOG(LOG_INFO, "MODEL: [%s] VOX data loaded successfully : %i vertices/%i meshes", fileName, nbvertices, meshescount);
|
||
|
}
|
||
|
|
||
|
// Build models from meshes
|
||
|
model.transform = MatrixIdentity();
|
||
|
|
||
|
model.meshCount = meshescount;
|
||
|
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||
|
|
||
|
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||
|
|
||
|
model.materialCount = 1;
|
||
|
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
|
||
|
model.materials[0] = LoadMaterialDefault();
|
||
|
|
||
|
// Init model meshes
|
||
|
int verticesRemain = voxarray.vertices.used;
|
||
|
int verticesMax = 65532; // 5461 voxels x 12 vertices per voxel -> 65532 (must be inf 65536)
|
||
|
|
||
|
// 6*4 = 12 vertices per voxel
|
||
|
Vector3 *pvertices = (Vector3 *)voxarray.vertices.array;
|
||
|
Color *pcolors = (Color *)voxarray.colors.array;
|
||
|
|
||
|
unsigned short *pindices = voxarray.indices.array; // 5461*6*6 = 196596 indices max per mesh
|
||
|
|
||
|
int size = 0;
|
||
|
|
||
|
for (int i = 0; i < meshescount; i++)
|
||
|
{
|
||
|
Mesh *pmesh = &model.meshes[i];
|
||
|
memset(pmesh, 0, sizeof(Mesh));
|
||
|
|
||
|
// Copy vertices
|
||
|
pmesh->vertexCount = (int)fmin(verticesMax, verticesRemain);
|
||
|
|
||
|
size = pmesh->vertexCount*sizeof(float)*3;
|
||
|
pmesh->vertices = RL_MALLOC(size);
|
||
|
memcpy(pmesh->vertices, pvertices, size);
|
||
|
|
||
|
// Copy indices
|
||
|
// TODO: Compute globals indices array
|
||
|
size = voxarray.indices.used*sizeof(unsigned short);
|
||
|
pmesh->indices = RL_MALLOC(size);
|
||
|
memcpy(pmesh->indices, pindices, size);
|
||
|
|
||
|
pmesh->triangleCount = (pmesh->vertexCount/4)*2;
|
||
|
|
||
|
// Copy colors
|
||
|
size = pmesh->vertexCount*sizeof(Color);
|
||
|
pmesh->colors = RL_MALLOC(size);
|
||
|
memcpy(pmesh->colors, pcolors, size);
|
||
|
|
||
|
// First material index
|
||
|
model.meshMaterial[i] = 0;
|
||
|
|
||
|
verticesRemain -= verticesMax;
|
||
|
pvertices += verticesMax;
|
||
|
pcolors += verticesMax;
|
||
|
}
|
||
|
|
||
|
// Free buffers
|
||
|
Vox_FreeArrays(&voxarray);
|
||
|
UnloadFileData(fileData);
|
||
|
|
||
|
return model;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#endif // SUPPORT_MODULE_RMODELS
|