Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
#include "bmf_loader.h"
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
|
||||
//BMF Specification (version 0):
|
||||
//uint32 == "BMF "
|
||||
//uint32: version number (0 for current version)
|
||||
//
|
||||
//uint32: vertex count
|
||||
//Repeat <vertex count>:
|
||||
// float x,y,z: vertex[n] coordinates
|
||||
//
|
||||
//uint32: normal count
|
||||
//Repeat <normal count>:
|
||||
// float x,y,z: normal[n] values
|
||||
//
|
||||
//uint32: uv count
|
||||
//Repeat <uv count>:
|
||||
// float u,v: uv[n] coordinates
|
||||
//
|
||||
//uint32: face count
|
||||
//Repeat <face count>:
|
||||
// If <vertex count> <= 0xffff
|
||||
// uint16 v1,v2,v3: face[n] vertex indices
|
||||
// Else
|
||||
// uint32 v1,v2,v3: face[n] vertex indices
|
||||
//
|
||||
// If <normal count> > 0
|
||||
// If <normal count> <= 0xffff
|
||||
// uint16 n1,n2,n3: face[n] normal indices
|
||||
// Else
|
||||
// uint32 n1,n2,n3: face[n] normal indices
|
||||
//
|
||||
// If <uv count> > 0
|
||||
// If <uv count> <= 0xffff
|
||||
// uint16 u1,u2,u3: face[n] uv indices
|
||||
// Else
|
||||
// uint32 u1,u2,u3: face[n] uv indices
|
||||
|
||||
//First 4 characters of any Binary Mesh File
|
||||
const char* bmfHead = "BMF ";
|
||||
|
||||
namespace render {
|
||||
|
||||
struct UV {
|
||||
float u, v;
|
||||
};
|
||||
|
||||
struct VertexIndex {
|
||||
unsigned a, b, c;
|
||||
|
||||
VertexIndex() : a(0), b(0), c(0) {}
|
||||
VertexIndex(unsigned A, unsigned B, unsigned C) : a(A), b(B), c(C) {}
|
||||
|
||||
bool operator<(const VertexIndex& other) const {
|
||||
return memcmp(this, &other, sizeof(unsigned) * 3) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void loadBinaryMesh(const char* filename, Mesh& mesh) {
|
||||
static_assert(sizeof(vec3f) == 12, "vec3f must be the size of 3 floats");
|
||||
static_assert(sizeof(UV) == 8, "UV must be the size of 2 floats");
|
||||
|
||||
std::vector<vec3f> vertices;
|
||||
std::vector<vec3f> normals;
|
||||
std::vector<UV> uvs;
|
||||
|
||||
std::ifstream file(filename, std::ios_base::binary | std::ios_base::in);
|
||||
if(!file.is_open())
|
||||
return;
|
||||
|
||||
char buff[4];
|
||||
file.read(buff, 4);
|
||||
if(file.fail() || strncmp(bmfHead, buff, 4) != 0)
|
||||
return;
|
||||
|
||||
unsigned version;
|
||||
file.read((char*)&version, sizeof(version));
|
||||
if(file.fail() || version != 0)
|
||||
return;
|
||||
|
||||
//Vertices
|
||||
unsigned count;
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail() || count == 0)
|
||||
return;
|
||||
|
||||
vertices.resize(count);
|
||||
file.read((char*)&vertices.front(), sizeof(vec3f) * count);
|
||||
|
||||
//Normals
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
normals.resize(count);
|
||||
file.read((char*)&normals.front(), sizeof(vec3f) * count);
|
||||
|
||||
//UVs
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
uvs.resize(count);
|
||||
file.read((char*)&uvs.front(), sizeof(UV) * count);
|
||||
|
||||
//Faces
|
||||
file.read((char*)&count, sizeof(count));
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
std::map<VertexIndex,unsigned> vertexMap;
|
||||
|
||||
mesh.faces.reserve(count);
|
||||
mesh.vertices.reserve(count);
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
Mesh::Face face;
|
||||
Vertex vertex[3];
|
||||
VertexIndex vertIndices[3];
|
||||
|
||||
//Load vertex position indices
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(vertices.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= vertices.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].position = vertices[index];
|
||||
vertIndices[j].a = index;
|
||||
}
|
||||
|
||||
//Load vertex normal indices
|
||||
if(!normals.empty()) {
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(normals.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= normals.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].normal = normals[index];
|
||||
vertIndices[j].b = index;
|
||||
}
|
||||
}
|
||||
|
||||
//Load vertex uv indices
|
||||
if(!uvs.empty()) {
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
unsigned index;
|
||||
|
||||
if(uvs.size() <= 0xffff) {
|
||||
unsigned short v;
|
||||
file.read((char*)&v, sizeof(v));
|
||||
index = v;
|
||||
}
|
||||
else {
|
||||
file.read((char*)&index, sizeof(index));
|
||||
}
|
||||
|
||||
if(index >= uvs.size())
|
||||
index = 0;
|
||||
|
||||
vertex[j].u = uvs[index].u;
|
||||
vertex[j].v = uvs[index].v;
|
||||
vertIndices[j].c = index;
|
||||
}
|
||||
}
|
||||
|
||||
if(file.fail())
|
||||
return;
|
||||
|
||||
//Automatically fuse identical vertices and store results in mesh
|
||||
unsigned indices[3];
|
||||
for(unsigned j = 0; j < 3; ++j) {
|
||||
auto previous = vertexMap.find(vertIndices[j]);
|
||||
if(previous != vertexMap.end()) {
|
||||
indices[j] = previous->second;
|
||||
}
|
||||
else {
|
||||
indices[j] = (unsigned)mesh.vertices.size();
|
||||
mesh.vertices.push_back(vertex[j]);
|
||||
vertexMap[vertIndices[j]] = indices[j];
|
||||
}
|
||||
}
|
||||
|
||||
face.a = indices[0];
|
||||
face.b = indices[1];
|
||||
face.c = indices[2];
|
||||
mesh.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
bool saveBinaryMesh(const char* filename, Mesh& mesh) {
|
||||
std::ofstream file(filename, std::ios_base::binary | std::ios_base::out);
|
||||
if(!file.is_open())
|
||||
return false;
|
||||
|
||||
file.write(bmfHead, 4);
|
||||
unsigned version = 0;
|
||||
file.write((char*)&version, sizeof(version));
|
||||
|
||||
//TODO: Writes duplicate data unnecessarily
|
||||
|
||||
unsigned count;
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
file.write((char*)&mesh.vertices[i].position, sizeof(vec3f));
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i)
|
||||
file.write((char*)&mesh.vertices[i].normal, sizeof(vec3f));
|
||||
|
||||
count = (unsigned)mesh.vertices.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
file.write((char*)&mesh.vertices[i].u, sizeof(float));
|
||||
file.write((char*)&mesh.vertices[i].v, sizeof(float));
|
||||
}
|
||||
|
||||
bool shortIndices = mesh.vertices.size() <= 0xffff;
|
||||
|
||||
count = (unsigned)mesh.faces.size();
|
||||
file.write((char*)&count, sizeof(count));
|
||||
for(unsigned i = 0; i < count; ++i) {
|
||||
Mesh::Face face = mesh.faces[i];
|
||||
if(shortIndices) {
|
||||
unsigned short data[] = {face.a, face.b, face.c, face.a, face.b, face.c, face.a, face.b, face.c};
|
||||
file.write((char*)data, sizeof(data));
|
||||
}
|
||||
else {
|
||||
unsigned data[] = {face.a, face.b, face.c, face.a, face.b, face.c, face.a, face.b, face.c};
|
||||
file.write((char*)data, sizeof(data));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
void loadBinaryMesh(const char* filename, Mesh& mesh);
|
||||
bool saveBinaryMesh(const char* filename, Mesh& mesh);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,409 @@
|
||||
#include "render/camera.h"
|
||||
#include "constants.h"
|
||||
#include <stdio.h>
|
||||
|
||||
namespace render {
|
||||
|
||||
const double pctPerSecond = 0.9999;
|
||||
|
||||
Camera::Camera()
|
||||
: radius(300), qd_yaw(0), qd_pitch(0), qd_roll(0), qd_zoom(1), qd_zoom_min_distance(0), positionBound(vec3d(-1e7), vec3d(1e7)), maxDist(1e7),
|
||||
qd_abs_yaw(0), qd_abs_pitch(0), zNear(1), zFar(1000), fov(0.8), aspect(1), objectCamera(false), linearZoom(false), lockedRotation(true)
|
||||
{
|
||||
}
|
||||
|
||||
void Camera::yaw(double radians, bool snap) {
|
||||
if(snap)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::up(), radians);
|
||||
else
|
||||
qd_yaw += radians;
|
||||
}
|
||||
|
||||
void Camera::pitch(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::right(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_pitch += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_yaw(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_abs_yaw += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_yaw_to(double radians, bool snap) {
|
||||
if(snap) {
|
||||
double amount = getYaw() - radians;
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), amount);
|
||||
}
|
||||
else
|
||||
qd_abs_yaw = getYaw() - radians;
|
||||
}
|
||||
|
||||
void Camera::abs_pitch(double radians, bool snap) {
|
||||
if(snap) {
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), radians);
|
||||
rotation.normalize();
|
||||
}
|
||||
else {
|
||||
qd_abs_pitch += radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::abs_pitch_to(double radians, bool snap) {
|
||||
if(snap) {
|
||||
double amount = getPitch() - radians;
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), amount);
|
||||
}
|
||||
else {
|
||||
qd_abs_pitch = getPitch() - radians;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::roll(double radians, bool snap) {
|
||||
if(snap)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::front(), radians);
|
||||
else
|
||||
qd_roll += radians;
|
||||
}
|
||||
|
||||
void Camera::zoom(double factor) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_point = vec3d();
|
||||
qd_zoom_min_distance = 0;
|
||||
}
|
||||
|
||||
void Camera::zoomTo(double factor, const vec3d& towards, double minDistance) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_point = towards;
|
||||
qd_zoom_line = vec3d();
|
||||
qd_zoom_min_distance = minDistance;
|
||||
}
|
||||
|
||||
void Camera::zoomAlong(double factor, const vec3d& line) {
|
||||
qd_zoom *= factor;
|
||||
qd_zoom_line = line;
|
||||
qd_zoom_point = vec3d();
|
||||
qd_zoom_min_distance = 0;
|
||||
}
|
||||
|
||||
void Camera::setRadius(double amount) {
|
||||
radius = amount;
|
||||
if(radius > maxDist)
|
||||
radius = maxDist;
|
||||
if(radius < 0)
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
double Camera::getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
void Camera::move_world(const vec3d& motion) {
|
||||
qd_world_motion += motion * radius;
|
||||
}
|
||||
|
||||
void Camera::move_world_abs(const vec3d& motion) {
|
||||
qd_world_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::move_cam(const vec3d& motion) {
|
||||
qd_cam_motion += motion * radius;
|
||||
}
|
||||
|
||||
void Camera::move_cam_abs(const vec3d& motion) {
|
||||
qd_cam_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::move_abs(const vec3d& motion) {
|
||||
qd_abs_motion += motion;
|
||||
}
|
||||
|
||||
void Camera::setPositionBound(const vec3d& minimum, const vec3d& maximum) {
|
||||
positionBound = AABBoxd(minimum, maximum);
|
||||
}
|
||||
|
||||
void Camera::setMaxDistance(double dist) {
|
||||
maxDist = dist;
|
||||
}
|
||||
|
||||
void Camera::setRenderConstraints(double ZNear, double ZFar, double FOV, double Aspect, double w, double h) {
|
||||
zNear = ZNear;
|
||||
zFar = ZFar;
|
||||
fov = FOV * (twopi / 360.0);
|
||||
aspect = Aspect;
|
||||
pxWidth = w;
|
||||
pxHeight = h;
|
||||
}
|
||||
|
||||
bool Camera::inverted() {
|
||||
return getUp().y <= 0;
|
||||
}
|
||||
|
||||
vec3d Camera::getPosition() const {
|
||||
if(objectCamera)
|
||||
return center;
|
||||
else
|
||||
return center + (rotation * vec3d::front(-radius));
|
||||
}
|
||||
|
||||
vec3d Camera::getMovedPosition(vec3d pos, double pct) const {
|
||||
//Camera motion
|
||||
if(qd_cam_motion.getLength() > 0.00001) {
|
||||
vec3d cam_x = rotation * vec3d::right(); cam_x.normalize();
|
||||
vec3d cam_y = rotation * vec3d::up(); cam_y.normalize();
|
||||
vec3d cam_z = rotation * vec3d::front(); cam_z.normalize();
|
||||
|
||||
pos += (cam_x * qd_cam_motion.x * pct) + (cam_y * qd_cam_motion.y * pct) + (cam_z * qd_cam_motion.z * pct);
|
||||
}
|
||||
|
||||
//World motion
|
||||
if(qd_world_motion.getLength() > 0.00001) {
|
||||
vec3d world_x = rotation * vec3d::right(); world_x.y = 0; world_x.normalize();
|
||||
vec3d world_y = rotation * vec3d::up(); world_y.x = 0; world_y.z = 0; world_y.normalize();
|
||||
vec3d world_z = rotation * vec3d::front(); world_z.y = 0; world_z.normalize();
|
||||
|
||||
pos += (world_x * qd_world_motion.x * pct) + (world_y * qd_world_motion.y * pct) + (world_z * qd_world_motion.z * pct);
|
||||
}
|
||||
|
||||
//Absolute motion
|
||||
if(qd_abs_motion.getLength() > 0.00001)
|
||||
pos += qd_abs_motion * pct;
|
||||
|
||||
pos = pos.elementMax(positionBound.minimum).elementMin(positionBound.maximum);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
vec3d Camera::getFinalPosition() const {
|
||||
return getMovedPosition(getPosition(), 1.0);
|
||||
}
|
||||
|
||||
vec3d Camera::getFacing() const {
|
||||
return rotation * vec3d::front();
|
||||
}
|
||||
|
||||
vec3d Camera::getRight() const {
|
||||
return rotation * vec3d::right();
|
||||
}
|
||||
|
||||
vec3d Camera::getUp() const {
|
||||
return (rotation * vec3d::up()).normalized();
|
||||
}
|
||||
|
||||
quaterniond Camera::getRotation() const {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
vec3d Camera::getLookAt() const {
|
||||
return center;
|
||||
}
|
||||
|
||||
vec3d Camera::getFinalLookAt() const {
|
||||
return getMovedPosition(getLookAt(), 1.0);
|
||||
}
|
||||
|
||||
double Camera::getDistance() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
double Camera::getYaw() const {
|
||||
vec3d rotated = rotation * vec3d::front();
|
||||
return atan2(rotated.z, rotated.x);
|
||||
}
|
||||
|
||||
double Camera::getPitch() const {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double Camera::getRoll() const {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
vec2i Camera::screenPos(const vec3d& point) const {
|
||||
//Transform to camera space
|
||||
vec3d transformed = rotation.inverted() * (point - getPosition());
|
||||
|
||||
//Transform to OpenGL's space
|
||||
vec3d converted = vec3d(vec3d::right().dot(transformed), vec3d::up().dot(transformed), -vec3d::front().dot(transformed));
|
||||
|
||||
Matrix projection = Matrix::projection(fov / (twopi / 360.0), aspect, zNear, zFar);
|
||||
|
||||
//Apply projection and return final result (Left-Right and Top-Bottom are x[-1,1] and y[-1,1])
|
||||
vec4d pos = projection * vec4d(converted.x, converted.y, converted.z, 1.0);
|
||||
pos.x /= -pos.w; pos.y /= -pos.w;
|
||||
|
||||
return vec2i((int)(pxWidth * (pos.x + 1.0) * 0.5), (int)(pxHeight * (pos.y + 1.0) * 0.5));
|
||||
}
|
||||
|
||||
double Camera::screenAngle(const vec3d& toPoint) const {
|
||||
quaterniond inv = rotation.inverted();
|
||||
vec3d cam = inv * center;
|
||||
vec3d pos = inv * toPoint;
|
||||
|
||||
vec2d flatOffset(cam.x - pos.x, pos.y - cam.y);
|
||||
return flatOffset.radians() / pi;
|
||||
}
|
||||
|
||||
line3dd Camera::screenToRay(double x, double y) const {
|
||||
double tan_fov = tan(fov/2.0);
|
||||
|
||||
vec3d view_dir =
|
||||
vec3d::front(1.0)
|
||||
+ vec3d::right(tan_fov * 2.0 * (0.5-x) * aspect)
|
||||
+ vec3d::up(tan_fov * 2.0 * (0.5-y));
|
||||
|
||||
view_dir = rotation * view_dir.normalized();
|
||||
|
||||
vec3d start = view_dir * zNear, end = view_dir * zFar;
|
||||
|
||||
vec3d camPos = getPosition();
|
||||
start += camPos; end += camPos;
|
||||
|
||||
return line3dd(start, end);
|
||||
}
|
||||
|
||||
void Camera::setLockedRotation(bool locked) {
|
||||
lockedRotation = locked;
|
||||
}
|
||||
|
||||
void Camera::animatePercentage(double pct) {
|
||||
{ //Rotation
|
||||
auto prevRot = rotation;
|
||||
auto rotLock = [&prevRot,this]() {
|
||||
if(lockedRotation && (rotation * vec3d::up()).y < 0.001)
|
||||
rotation = prevRot;
|
||||
else
|
||||
prevRot = rotation;
|
||||
};
|
||||
|
||||
if(fabs(qd_abs_pitch) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::right(), pct * qd_abs_pitch);
|
||||
qd_abs_pitch *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
if(fabs(qd_pitch) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::right(), pct * qd_pitch);
|
||||
qd_pitch *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
if(fabs(qd_roll) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::front(), pct * qd_roll);
|
||||
qd_roll *= 1.0 - pct;
|
||||
|
||||
rotLock();
|
||||
|
||||
//Quaternion rotations
|
||||
if(fabs(qd_yaw) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(vec3d::up(), pct * qd_yaw);
|
||||
qd_yaw *= 1.0 - pct;
|
||||
|
||||
//Absolute rotations around world axes
|
||||
if(fabs(qd_abs_yaw) > 0.00001)
|
||||
rotation = rotation * quaterniond::fromAxisAngle(rotation.inverted() * vec3d::up(), pct * qd_abs_yaw);
|
||||
qd_abs_yaw *= 1.0 - pct;
|
||||
|
||||
rotation.normalize();
|
||||
}
|
||||
|
||||
center = getMovedPosition(center, pct);
|
||||
|
||||
//Move the center of the camera
|
||||
qd_cam_motion *= 1.0 - pct;
|
||||
qd_world_motion *= 1.0 - pct;
|
||||
qd_abs_motion *= 1.0 - pct;
|
||||
|
||||
//Zoom level
|
||||
if(fabs(qd_zoom-1) > 0.00001) {
|
||||
if(linearZoom) {
|
||||
double dest = (qd_zoom * radius);
|
||||
double zoomDist = dest - radius;
|
||||
radius += pct * zoomDist;
|
||||
qd_zoom = dest / radius;
|
||||
}
|
||||
else {
|
||||
double doZoom = pow(qd_zoom, pct);
|
||||
qd_zoom /= doZoom;
|
||||
radius *= doZoom;
|
||||
|
||||
if(!qd_zoom_line.zero()) {
|
||||
//Screw you and your entire 3D vector family
|
||||
}
|
||||
else if(!qd_zoom_point.zero()) {
|
||||
center = qd_zoom_point.interpolate(center, doZoom);
|
||||
}
|
||||
}
|
||||
|
||||
if(radius > maxDist)
|
||||
radius = maxDist;
|
||||
if(radius < qd_zoom_min_distance)
|
||||
radius = qd_zoom_min_distance;
|
||||
}
|
||||
else {
|
||||
qd_zoom = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::animate(double seconds) {
|
||||
//Interpolate the camera's current state to its destination state
|
||||
if(seconds <= 0)
|
||||
return;
|
||||
|
||||
double pct = 1.0 - pow(1.0 - pctPerSecond,seconds);
|
||||
animatePercentage(pct);
|
||||
}
|
||||
|
||||
void Camera::snap() {
|
||||
animatePercentage(1.0);
|
||||
}
|
||||
|
||||
void Camera::snapTranslation() {
|
||||
center = getMovedPosition(center, 1.0);
|
||||
|
||||
//Move the center of the camera
|
||||
qd_cam_motion *= 0.0;
|
||||
qd_world_motion *= 0.0;
|
||||
qd_abs_motion *= 0.0;
|
||||
}
|
||||
|
||||
void Camera::toLookAt(vec3d& cameraPosition, vec3d& cameraLookAt, vec3d& cameraUp) const {
|
||||
cameraPosition = getPosition();
|
||||
cameraLookAt = cameraPosition + getFacing();
|
||||
cameraUp = getUp();
|
||||
}
|
||||
|
||||
void Camera::setObjectCamera(bool ObjectCamera) {
|
||||
objectCamera = ObjectCamera;
|
||||
}
|
||||
|
||||
void Camera::setLinearZoom(bool linear) {
|
||||
linearZoom = linear;
|
||||
}
|
||||
|
||||
void Camera::resetRotation() {
|
||||
//TODO: Make this calculate the required yaw/pitch/roll to
|
||||
//reset back to defaults, so that this can be animated instead
|
||||
//of always needing to snap.
|
||||
rotation = quaterniond();
|
||||
qd_yaw = 0;
|
||||
qd_pitch = 0;
|
||||
qd_roll = 0;
|
||||
qd_zoom = 1;
|
||||
}
|
||||
|
||||
void Camera::resetZoom() {
|
||||
qd_zoom = 1;
|
||||
radius = 300;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "quaternion.h"
|
||||
#include "vec2.h"
|
||||
#include "line3d.h"
|
||||
#include "util/refcount.h"
|
||||
#include "aabbox.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class Camera : public AtomicRefCounted {
|
||||
private:
|
||||
quaterniond rotation;
|
||||
vec3d center;
|
||||
double radius;
|
||||
|
||||
AABBoxd positionBound;
|
||||
double maxDist;
|
||||
|
||||
vec3d qd_zoom_point;
|
||||
vec3d qd_zoom_line;
|
||||
|
||||
vec3d qd_world_motion;
|
||||
vec3d qd_cam_motion;
|
||||
vec3d qd_abs_motion;
|
||||
double qd_yaw, qd_pitch, qd_roll, qd_zoom;
|
||||
double qd_abs_yaw, qd_abs_pitch;
|
||||
double qd_zoom_min_distance;
|
||||
|
||||
double zNear, zFar, fov, aspect, pxWidth, pxHeight;
|
||||
|
||||
bool objectCamera;
|
||||
bool linearZoom;
|
||||
bool lockedRotation;
|
||||
|
||||
public:
|
||||
void yaw(double radians, bool snap = false);
|
||||
void pitch(double radians, bool snap = false);
|
||||
void abs_yaw(double radians, bool snap = false);
|
||||
void abs_pitch(double radians, bool snap = false);
|
||||
void roll(double radians, bool snap = false);
|
||||
void zoom(double factor);
|
||||
void snap();
|
||||
void snapTranslation();
|
||||
|
||||
void setRadius(double radius);
|
||||
double getRadius();
|
||||
|
||||
void abs_yaw_to(double yaw, bool snap = false);
|
||||
void abs_pitch_to(double pitch, bool snap = false);
|
||||
|
||||
void move_cam(const vec3d& motion);
|
||||
void move_cam_abs(const vec3d& motion);
|
||||
void move_world(const vec3d& motion);
|
||||
void move_world_abs(const vec3d& motion);
|
||||
void move_abs(const vec3d& motion);
|
||||
|
||||
void zoomTo(double factor, const vec3d& towards, double minDistance = 0);
|
||||
void zoomAlong(double factor, const vec3d& line);
|
||||
|
||||
void animatePercentage(double pct);
|
||||
void animate(double seconds);
|
||||
|
||||
void setLinearZoom(bool linear);
|
||||
void setLockedRotation(bool locked);
|
||||
|
||||
void setPositionBound(const vec3d& minimum, const vec3d& maximum);
|
||||
void setMaxDistance(double dist);
|
||||
|
||||
//Returns the angle in physical screen space
|
||||
//that a particular point is directed in from the
|
||||
//center of the screen
|
||||
double screenAngle(const vec3d& toPoint) const;
|
||||
vec2i screenPos(const vec3d& point) const;
|
||||
|
||||
//Returns a ray starting at the near Z plane, ending at the
|
||||
//far Z plane, with each endpoint being at <x,y> on the screen in
|
||||
//normalized coordinates [0,1]
|
||||
line3dd screenToRay(double x, double y) const;
|
||||
|
||||
vec3d getMovedPosition(vec3d pos, double pct) const;
|
||||
vec3d getFinalPosition() const;
|
||||
vec3d getPosition() const;
|
||||
vec3d getFacing() const;
|
||||
vec3d getUp() const;
|
||||
vec3d getRight() const;
|
||||
quaterniond getRotation() const;
|
||||
|
||||
vec3d getLookAt() const;
|
||||
vec3d getFinalLookAt() const;
|
||||
double getDistance() const;
|
||||
|
||||
double getYaw() const;
|
||||
double getPitch() const;
|
||||
double getRoll() const;
|
||||
|
||||
//Returns whether the camera is currently upside down
|
||||
bool inverted();
|
||||
|
||||
void setObjectCamera(bool ObjectCamera);
|
||||
void toLookAt(vec3d& cameraPosition, vec3d& cameraLookAt, vec3d& cameraUp) const;
|
||||
void setRenderConstraints(double ZNear, double ZFar, double FOV_degrees, double Aspect, double w, double h);
|
||||
void resetRotation();
|
||||
void resetZoom();
|
||||
|
||||
Camera();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
#include "scene/node.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/texture.h"
|
||||
#include "matrix.h"
|
||||
#include "mesh.h"
|
||||
#include "rect.h"
|
||||
#include "image.h"
|
||||
#include "color.h"
|
||||
#include "vec2.h"
|
||||
#include "line3d.h"
|
||||
|
||||
struct frustum;
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
enum PrimitiveType {
|
||||
PT_Lines,
|
||||
PT_LineStrip,
|
||||
PT_Triangles,
|
||||
PT_Quads,
|
||||
};
|
||||
|
||||
class Camera;
|
||||
|
||||
class RenderDriver {
|
||||
public:
|
||||
RenderState activeRenderState;
|
||||
scene::Node rootNode;
|
||||
vec3d cam_pos, cam_facing, cam_up;
|
||||
|
||||
virtual bool init() = 0;
|
||||
virtual void reportErrors(const char* context = nullptr) const = 0;
|
||||
|
||||
virtual const RenderState* getLastRenderState() const = 0;
|
||||
|
||||
virtual void setSkybox(const RenderState* mat) = 0;
|
||||
virtual void setSkyboxMesh(const RenderMesh* mesh) = 0;
|
||||
virtual void setScreenSize(int w, int h) = 0;
|
||||
virtual void setFOV(double fov) = 0;
|
||||
virtual void setNearFarPlanes(double near, double far) = 0;
|
||||
|
||||
virtual const frustum& getViewFrustum() const = 0;
|
||||
virtual void setCameraData(Camera& camera) = 0;
|
||||
|
||||
virtual void setDefaultRenderState() = 0;
|
||||
virtual void set2DRenderState() = 0;
|
||||
virtual void switchToRenderState(const RenderState&) = 0;
|
||||
|
||||
virtual void setTransformation(const Matrix&) = 0;
|
||||
virtual void setTransformationAbs(const Matrix&) = 0;
|
||||
virtual void setTransformationIdentity() = 0;
|
||||
virtual void setBBTransform(vec3d pos, double width, double rot) = 0;
|
||||
virtual void resetTransformation() = 0;
|
||||
|
||||
virtual void getInverseView(float* mat3) const = 0;
|
||||
|
||||
virtual void getBillboardVecs(vec3d& upLeft, vec3d& upRight, double rotation = 0) const = 0;
|
||||
virtual void getBillboardVecs(const vec3d& from, vec3d& upLeft, vec3d& upRight, double rotation = 0) const = 0;
|
||||
|
||||
virtual void clearRenderPrepared() = 0;
|
||||
virtual bool isRenderPrepared() = 0;
|
||||
virtual void prepareRender3D(Camera& camera, const recti* clip = 0) = 0;
|
||||
virtual void prepareRender2D() = 0;
|
||||
virtual void renderWorld() = 0;
|
||||
|
||||
virtual RenderMesh* createMesh(const Mesh& mesh) = 0;
|
||||
virtual Shader* createShader() = 0;
|
||||
virtual ShaderProgram* createShaderProgram(const char* vertex_shader, const char* fragment_shader) = 0;
|
||||
virtual Texture* createTexture(Image& image, bool mipmap = true, bool cachePixels = false) = 0;
|
||||
static Texture* createTexture();
|
||||
static Texture* createCubemap();
|
||||
|
||||
virtual Texture* createRenderTarget(const vec2i& size) = 0;
|
||||
virtual void setRenderTarget(Texture* texture, bool intermediate = false) = 0;
|
||||
|
||||
virtual Image* getScreen(int x, int y, int w, int h) = 0;
|
||||
|
||||
virtual void drawFPSGraph(const recti& location) = 0;
|
||||
|
||||
virtual void drawBillboard(vec3d center, double width) = 0;
|
||||
|
||||
virtual void drawLine(line3dd line, Color start, Color end) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const RenderState* mat,
|
||||
const vec2<float>* vertices,
|
||||
const vec2<float>* textureCoords = 0,
|
||||
const Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const vec2<float>* vertices,
|
||||
const vec2<float>* textureCoords,
|
||||
const Color* colors
|
||||
) = 0;
|
||||
|
||||
virtual void drawQuad(
|
||||
const vec3d* vertices,
|
||||
const vec2<float>* textureCoords,
|
||||
const Color* colors
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
const recti& rectangle,
|
||||
const RenderState* mat,
|
||||
Color color,
|
||||
const recti* clip = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
recti rectangle,
|
||||
const RenderState* mat = 0,
|
||||
const recti* sourceRect = 0,
|
||||
const Color* color = 0,
|
||||
const recti* clip = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawRectangle(
|
||||
recti rectangle,
|
||||
const RenderState* mat,
|
||||
const recti* sourceRect,
|
||||
const Color* color,
|
||||
const recti* clip,
|
||||
double rotation
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
double rotation,
|
||||
Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
const recti& source,
|
||||
Color* color = 0
|
||||
) = 0;
|
||||
|
||||
virtual void drawBillboard(
|
||||
vec3d center,
|
||||
double width,
|
||||
const RenderState& mat,
|
||||
const recti& source,
|
||||
double rotation,
|
||||
Color color = Color()
|
||||
) = 0;
|
||||
|
||||
virtual void pushScreenClip(const recti& box) = 0;
|
||||
virtual void popScreenClip() = 0;
|
||||
|
||||
virtual ~RenderDriver() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "render/texture.h"
|
||||
#include "render/render_state.h"
|
||||
#include "render/driver.h"
|
||||
#include "color.h"
|
||||
#include "image.h"
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
|
||||
typedef int fontChar;
|
||||
|
||||
class Font {
|
||||
//Prevent copying the font
|
||||
Font(const Font& other) {}
|
||||
void operator=(const Font& other) {}
|
||||
protected:
|
||||
Font() : bold(0), italic(0) {}
|
||||
public:
|
||||
Font* bold;
|
||||
Font* italic;
|
||||
|
||||
virtual vec2i getDimension(const char* text) const {
|
||||
return vec2i();
|
||||
};
|
||||
|
||||
virtual vec2i getDimension(int c, int lastC) const {
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
virtual unsigned getBaseline() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual unsigned getLineHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void draw(render::RenderDriver* driver, const char* text,
|
||||
int x, int y, const Color* color = 0, const recti* clip = 0) const {
|
||||
};
|
||||
|
||||
virtual vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color = 0, const recti* clip = 0) const {
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
virtual ~Font() {
|
||||
};
|
||||
|
||||
static Font* createDummyFont();
|
||||
};
|
||||
|
||||
bool clipQuad(rectf pos, rectf source, const rectf* clip, vec2f* verts, vec2f* texcoords);
|
||||
|
||||
Font* loadFontFNT(render::RenderDriver* driver, const char* filename);
|
||||
|
||||
Font* loadFontFT2(render::RenderDriver& driver, const char* filename,
|
||||
std::vector<std::pair<int,int>>& pages, int size);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,368 @@
|
||||
#include "render/font.h"
|
||||
#include "render/driver.h"
|
||||
#include <stdio.h>
|
||||
#include "str_util.h"
|
||||
#include "vec2.h"
|
||||
#include "main/initialization.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace resource {
|
||||
extern render::Texture* queueImage(const std::string& abs_file, int priority, bool mipmap, bool cachePixels, bool cubemap = false);
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
class FontFNT : public Font {
|
||||
public:
|
||||
std::vector<render::RenderState*> textures;
|
||||
|
||||
struct Glyph {
|
||||
unsigned short x : 16, y : 16;
|
||||
bool draw : 1;
|
||||
unsigned char page : 3;
|
||||
unsigned char w : 8, h : 8;
|
||||
char wOff : 8, hOff : 8;
|
||||
unsigned char xAdv : 8;
|
||||
};
|
||||
|
||||
struct GlyphEntry {
|
||||
fontChar letter;
|
||||
Glyph glyph;
|
||||
|
||||
bool operator<(const GlyphEntry& other) const { return letter < other.letter; }
|
||||
GlyphEntry(fontChar Letter) : letter(Letter) {}
|
||||
GlyphEntry(fontChar Letter, Glyph& Glyph) : letter(Letter), glyph(Glyph) {}
|
||||
};
|
||||
|
||||
struct KernEntry {
|
||||
union {
|
||||
unsigned v;
|
||||
struct { fontChar left : 16, right : 16; };
|
||||
};
|
||||
int offset;
|
||||
|
||||
bool operator<(const KernEntry& other) const { return v < other.v; }
|
||||
KernEntry(fontChar Left, fontChar Right) : left(Left), right(Right) {}
|
||||
};
|
||||
|
||||
Glyph* lowChars;
|
||||
std::vector<GlyphEntry> hiChars;
|
||||
std::vector<KernEntry> kerning;
|
||||
|
||||
unsigned glyphHeight, textureHeight, textureWidth;
|
||||
|
||||
unsigned getBaseline() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
unsigned getLineHeight() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
void draw(render::RenderDriver* driver, const char* text, int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
int x = X, y = Y;
|
||||
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
unsigned lastTexture = (unsigned)textures.size();
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(c == L'\n') {
|
||||
y += glyphHeight;
|
||||
x = X;
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
x += k->offset;
|
||||
}
|
||||
lastC = c;
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f((float)(x + glyph.wOff), (float)(y + glyph.hOff)), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
if(glyph.page != lastTexture) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
driver->switchToRenderState(mat);
|
||||
lastTexture = glyph.page;
|
||||
}
|
||||
|
||||
driver->drawQuad(font_verts,font_tc,color ? colors : 0);
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
int x = X, y = Y;
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
x += k->offset;
|
||||
}
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f((float)(x + glyph.wOff), (float)(y + glyph.hOff)), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
driver->switchToRenderState(mat);
|
||||
|
||||
driver->drawQuad(font_verts,font_tc,color ? colors : 0);
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
return vec2i(x - X, y - Y);
|
||||
}
|
||||
|
||||
vec2i getDimension(const char* text) const {
|
||||
vec2i size(0, glyphHeight);
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
size.x += k->offset;
|
||||
}
|
||||
lastC = c;
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph)
|
||||
size.x += glyph->xAdv;
|
||||
++text;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
vec2i getDimension(int c, int lastC) const {
|
||||
vec2i size(0, glyphHeight);
|
||||
|
||||
if(lastC) {
|
||||
auto k = std::lower_bound(kerning.begin(), kerning.end(), KernEntry(lastC, c));
|
||||
if(k != kerning.end() && k->left == lastC && k->right == c)
|
||||
size.x += k->offset;
|
||||
}
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph)
|
||||
size.x += glyph->xAdv;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
FontFNT()
|
||||
: lowChars(new Glyph[256]), glyphHeight(0), textureHeight(1), textureWidth(1)
|
||||
{
|
||||
memset(lowChars, 0, sizeof(Glyph) * 256);
|
||||
}
|
||||
|
||||
~FontFNT()
|
||||
{
|
||||
delete[] lowChars;
|
||||
foreach(tex, textures)
|
||||
delete *tex;
|
||||
}
|
||||
};
|
||||
|
||||
Font* loadFontFNT(render::RenderDriver* driver, const char* filename) {
|
||||
auto file = fopen(filename, "r");
|
||||
if(file == 0)
|
||||
return 0;
|
||||
|
||||
auto slash = strrchr(filename, '/');
|
||||
if(auto bSlash = strrchr(filename, '\\'))
|
||||
if(bSlash > slash)
|
||||
slash = bSlash;
|
||||
|
||||
std::string folder(filename, slash ? slash + 1 - filename : 0);
|
||||
|
||||
//Skip first line
|
||||
do {
|
||||
int c = fgetc(file);
|
||||
if(c == L'\n' || c == L'\r' || c == EOF)
|
||||
break;
|
||||
} while(true);
|
||||
|
||||
if(feof(file)) {
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FontFNT* font = new FontFNT;
|
||||
|
||||
if(false) {
|
||||
failedLoad:
|
||||
delete font;
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pages, lineCheck;
|
||||
|
||||
if(fscanf(file,
|
||||
" common lineHeight=%i base=%*i scaleW=%i scaleH=%i pages=%i packed=%*i alphaChnl=%*i redChnl=%*i greenChnl=%*i blueChnl=%i ",
|
||||
&font->glyphHeight, &font->textureWidth, &font->textureHeight, &pages, &lineCheck) < 5
|
||||
|| pages == 0 || pages > 7 || font->glyphHeight <= 0)
|
||||
goto failedLoad;
|
||||
|
||||
int page; char pageName[256];
|
||||
while(pages--) {
|
||||
int args = fscanf(file,"page id=%i file=%255s ", &page, pageName);
|
||||
if(args < 2 || (unsigned int)page != font->textures.size())
|
||||
goto failedLoad;
|
||||
|
||||
std::string pageFile = folder + trim(pageName,"\"");
|
||||
|
||||
auto material = new render::RenderState();
|
||||
material->depthTest = render::DT_NoDepthTest;
|
||||
material->lighting = false;
|
||||
material->culling = render::FC_None;
|
||||
material->baseMat = render::MAT_Alpha;
|
||||
if(load_resources)
|
||||
material->textures[0] = resource::queueImage(pageFile, 20, true, false);
|
||||
|
||||
font->textures.push_back(material);
|
||||
}
|
||||
|
||||
int glyphs;
|
||||
if(fscanf(file, "chars count=%i ", &glyphs) != 1)
|
||||
goto failedLoad;
|
||||
|
||||
while(glyphs--) {
|
||||
int c, x, y, w, h, wOff, hOff, xAdv, page, channel;
|
||||
if(fscanf(file, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i chnl=%i ",
|
||||
&c, &x, &y, &w, &h, &wOff, &hOff, &xAdv, &page, &channel) != 10
|
||||
|| page < 0 || page >= (int)font->textures.size())
|
||||
goto failedLoad;
|
||||
FontFNT::Glyph glyph;
|
||||
glyph.draw = c != 32;
|
||||
glyph.x = x;
|
||||
glyph.y = y;
|
||||
glyph.w = w;
|
||||
glyph.h = h;
|
||||
glyph.wOff = wOff;
|
||||
glyph.hOff = hOff;
|
||||
glyph.xAdv = xAdv;
|
||||
glyph.page = page;
|
||||
|
||||
if(c <= 0xff)
|
||||
font->lowChars[c] = glyph;
|
||||
else
|
||||
font->hiChars.push_back(FontFNT::GlyphEntry(c,glyph));
|
||||
}
|
||||
|
||||
//Load kerning data
|
||||
int kerns;
|
||||
if(fscanf(file, "kernings count=%i ", &kerns) == 1) {
|
||||
while(kerns--) {
|
||||
int left, right, offset;
|
||||
if(fscanf(file, "kerning first=%i second=%i amount=%i ", &left, &right, &offset) == 3) {
|
||||
FontFNT::KernEntry kerning((fontChar)left, (fontChar)right);
|
||||
kerning.offset = offset;
|
||||
font->kerning.push_back(kerning);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(font->hiChars.begin(), font->hiChars.end());
|
||||
std::sort(font->kerning.begin(), font->kerning.end());
|
||||
return font;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,487 @@
|
||||
#include "render/font.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/vertexBuffer.h"
|
||||
#include "main/initialization.h"
|
||||
#include <stdio.h>
|
||||
#include "str_util.h"
|
||||
#include "vec2.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <ft2build.h>
|
||||
#else
|
||||
#include <freetype2/ft2build.h>
|
||||
#endif
|
||||
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_SIZES_H
|
||||
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
namespace resource {
|
||||
extern render::Texture* queueImage(Image* img, int priority, bool mipmap, bool cachePixels);
|
||||
};
|
||||
|
||||
namespace render {
|
||||
|
||||
Font* Font::createDummyFont() {
|
||||
return new Font();
|
||||
}
|
||||
|
||||
bool clipQuad(rectf pos, rectf source, const rectf* clip, vec2f* verts, vec2f* texcoords) {
|
||||
if(clip) {
|
||||
if(!clip->overlaps(pos))
|
||||
return false;
|
||||
|
||||
if(!clip->isRectInside(pos)) {
|
||||
//TODThere seem to be some oddities with the texture coordinates if a clip occurs
|
||||
rectf clipped = clip->clipAgainst(pos);
|
||||
source = source.clipProportional(pos, clipped);
|
||||
pos = clipped;
|
||||
}
|
||||
}
|
||||
|
||||
verts[0] = vec2f(pos.topLeft.x, pos.topLeft.y);
|
||||
verts[1] = vec2f(pos.botRight.x, pos.topLeft.y);
|
||||
verts[3] = vec2f(pos.topLeft.x, pos.botRight.y);
|
||||
verts[2] = vec2f(pos.botRight.x, pos.botRight.y);
|
||||
|
||||
texcoords[0] = vec2f(source.topLeft.x, source.topLeft.y);
|
||||
texcoords[1] = vec2f(source.botRight.x, source.topLeft.y);
|
||||
texcoords[3] = vec2f(source.topLeft.x, source.botRight.y);
|
||||
texcoords[2] = vec2f(source.botRight.x, source.botRight.y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class FontFT2 : public Font {
|
||||
public:
|
||||
std::vector<render::RenderState*> textures;
|
||||
|
||||
struct Glyph {
|
||||
unsigned short x : 16, y : 16;
|
||||
bool draw : 1;
|
||||
unsigned char page : 7;
|
||||
unsigned char w : 8, h : 8;
|
||||
char wOff : 8, hOff : 8;
|
||||
float xAdv;
|
||||
int glyph_index;
|
||||
};
|
||||
|
||||
struct GlyphEntry {
|
||||
fontChar letter;
|
||||
Glyph glyph;
|
||||
|
||||
bool operator<(const GlyphEntry& other) const { return letter < other.letter; }
|
||||
GlyphEntry(fontChar Letter) : letter(Letter) {}
|
||||
GlyphEntry(fontChar Letter, Glyph& Glyph) : letter(Letter), glyph(Glyph) {}
|
||||
};
|
||||
|
||||
Glyph* lowChars;
|
||||
FT_Face* face;
|
||||
FT_Size size;
|
||||
std::vector<GlyphEntry> hiChars;
|
||||
|
||||
double x_scale, y_scale;
|
||||
unsigned glyphHeight, baseLine, textureHeight, textureWidth;
|
||||
|
||||
void draw(render::RenderDriver* driver, const char* text, int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
float x = (float)X, y = (float)Y;
|
||||
Color col = color ? *color : Color();
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
unsigned lastTexture = (unsigned)textures.size();
|
||||
VertexBufferTCV* buffer = 0;
|
||||
|
||||
u8it it(text);
|
||||
fontChar lastC = 0;
|
||||
|
||||
while(fontChar c = it++) {
|
||||
if(c == L'\n') {
|
||||
y += glyphHeight;
|
||||
x = (float)X;
|
||||
lastC = 0;
|
||||
}
|
||||
else if(c == L'\t') {
|
||||
x += 32.f - fmod(x - X, 32.f);
|
||||
lastC = L' ';
|
||||
}
|
||||
else {
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c >= '0' && c <= '9') {
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0/*FT_KERNING_UNFITTED*/, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
x += ((float)kerning.x) / 64.f;
|
||||
}
|
||||
|
||||
lastC = glyph.glyph_index;
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f(x + (float)glyph.wOff, y + (float)glyph.hOff), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc)) {
|
||||
if(glyph.page != lastTexture) {
|
||||
auto& mat = *textures[glyph.page];
|
||||
buffer = VertexBufferTCV::fetch(&mat);
|
||||
lastTexture = glyph.page;
|
||||
}
|
||||
|
||||
auto* verts = buffer->request(1, PT_Quads);
|
||||
|
||||
for(unsigned i = 0; i < 4; ++i) {
|
||||
auto& v = verts[i];
|
||||
v.uv = font_tc[i];
|
||||
v.col = col;
|
||||
v.pos.set(font_verts[i].x, font_verts[i].y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2i drawChar(render::RenderDriver* driver, int c, int lastC,
|
||||
int X, int Y, const Color* color, const recti* clip = 0) const {
|
||||
float x = (float)X, y = (float)Y;
|
||||
Color colors[4];
|
||||
if(color)
|
||||
colors[0] = colors[1] = colors[2] = colors[3] = *color;
|
||||
|
||||
rectf fclip;
|
||||
if(clip)
|
||||
fclip = rectf(*clip);
|
||||
|
||||
float xFactor = 1.f / float(textureWidth), yFactor = 1.f / float(textureHeight);
|
||||
vec2f font_verts[4], font_tc[4];
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
Glyph glyph;
|
||||
|
||||
if(c <= 0xff)
|
||||
glyph = lowChars[c];
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c) {
|
||||
glyph = g->glyph;
|
||||
}
|
||||
else {
|
||||
glyph = lowChars['?'];
|
||||
}
|
||||
}
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c >= '0' && c <= '9') {
|
||||
lastC = 0;
|
||||
}
|
||||
else {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph.glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
x += ((float)kerning.x) / 64.f;
|
||||
}
|
||||
|
||||
lastC = glyph.glyph_index;
|
||||
}
|
||||
|
||||
if(glyph.draw) {
|
||||
rectf pos = rectf::area(vec2f(x + glyph.wOff, y + glyph.hOff), vec2f(glyph.w, glyph.h));
|
||||
rectf source = rectf::area(vec2f((float)glyph.x * xFactor, (float)glyph.y * yFactor),
|
||||
vec2f((float)glyph.w * xFactor, (float)glyph.h * yFactor));
|
||||
|
||||
if(clipQuad(pos, source, clip ? &fclip : 0, font_verts, font_tc))
|
||||
driver->drawQuad(textures[glyph.page], font_verts, font_tc, color ? colors : 0);
|
||||
}
|
||||
|
||||
x += glyph.xAdv;
|
||||
return vec2i((int)x - X, (int)y - Y);
|
||||
}
|
||||
|
||||
vec2i getDimension(const char* text) const {
|
||||
vec2i dim(0, glyphHeight);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
fontChar lastC = 0;
|
||||
u8it it(text);
|
||||
unsigned maxWidth = 0;
|
||||
|
||||
while(fontChar c = it++) {
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c == L'\n') {
|
||||
if(dim.x > (int)maxWidth)
|
||||
maxWidth = dim.x;
|
||||
dim.x = 0;
|
||||
dim.y += glyphHeight;
|
||||
}
|
||||
else if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph) {
|
||||
dim.x += (int)glyph->xAdv;
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c < '0' || c > '9') {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
dim.x += (unsigned)(kerning.x >> 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dim.x < (int)maxWidth)
|
||||
dim.x = maxWidth;
|
||||
return dim;
|
||||
}
|
||||
|
||||
vec2i getDimension(int c, int lastC) const {
|
||||
vec2i dim(0, glyphHeight);
|
||||
|
||||
FT_Activate_Size(size);
|
||||
|
||||
const Glyph* glyph = 0;
|
||||
|
||||
if(c <= 0xff) {
|
||||
glyph = &lowChars[c];
|
||||
}
|
||||
else {
|
||||
auto g = std::lower_bound(hiChars.begin(), hiChars.end(), c);
|
||||
if(g != hiChars.end() && g->letter == c)
|
||||
glyph = &g->glyph;
|
||||
else
|
||||
glyph = &lowChars['?'];
|
||||
}
|
||||
|
||||
if(glyph) {
|
||||
dim.x += (int)glyph->xAdv;
|
||||
|
||||
//Don't kern anything involving digits
|
||||
if(c < '0' || c > '9') {
|
||||
if(lastC) {
|
||||
FT_Vector kerning;
|
||||
FT_Get_Kerning(*face, lastC, glyph->glyph_index, 0, &kerning);
|
||||
|
||||
if(kerning.x != 0)
|
||||
dim.x += (unsigned)(kerning.x >> 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dim;
|
||||
}
|
||||
|
||||
unsigned getBaseline() const {
|
||||
return baseLine;
|
||||
}
|
||||
|
||||
unsigned getLineHeight() const {
|
||||
return glyphHeight;
|
||||
}
|
||||
|
||||
FontFT2()
|
||||
: lowChars(new Glyph[256]), glyphHeight(0), baseLine(0), textureHeight(1), textureWidth(1)
|
||||
{
|
||||
memset(lowChars, 0, sizeof(Glyph) * 256);
|
||||
}
|
||||
|
||||
~FontFT2()
|
||||
{
|
||||
FT_Done_Size(size);
|
||||
delete[] lowChars;
|
||||
for(auto tex = textures.begin(), end = textures.end(); tex != end; ++tex)
|
||||
delete *tex;
|
||||
}
|
||||
};
|
||||
|
||||
FT_Library* library = 0;
|
||||
std::map<std::string, FT_Face*> faces;
|
||||
const int PAGE_SIZE = 512;
|
||||
|
||||
Font* loadFontFT2(render::RenderDriver& driver, const char* filename,
|
||||
std::vector<std::pair<int,int>>& pages, int size) {
|
||||
FontFT2* font = new FontFT2();
|
||||
font->textureWidth = PAGE_SIZE;
|
||||
font->textureHeight = PAGE_SIZE;
|
||||
|
||||
std::vector<Image*> images;
|
||||
int cur_page = 0;
|
||||
|
||||
Image* img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
||||
images.push_back(img);
|
||||
|
||||
int x = 0, y = 0;
|
||||
|
||||
//Create the library if none exists
|
||||
if(!library) {
|
||||
library = new FT_Library();
|
||||
FT_Init_FreeType(library);
|
||||
}
|
||||
|
||||
//Read in the font face if necessary
|
||||
FT_Face* face = 0;
|
||||
|
||||
auto it = faces.find(filename);
|
||||
if(it != faces.end()) {
|
||||
face = it->second;
|
||||
}
|
||||
else {
|
||||
face = new FT_Face();
|
||||
if (int err = FT_New_Face(*library, filename, 0, face)) {
|
||||
fprintf(stderr, "Error loading font %s (code %d).\n", filename, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
faces[filename] = face;
|
||||
}
|
||||
|
||||
FT_New_Size(*face, &font->size);
|
||||
FT_Activate_Size(font->size);
|
||||
|
||||
FT_Set_Char_Size(*face, 0, size << 6, 96, 96);
|
||||
|
||||
font->x_scale = (double)((*face)->size->metrics.x_scale / 65536.0);
|
||||
font->y_scale = (double)((*face)->size->metrics.y_scale / 65536.0);
|
||||
font->glyphHeight = (unsigned)((*face)->size->metrics.height >> 6);
|
||||
font->face = face;
|
||||
|
||||
font->baseLine = font->glyphHeight + (unsigned)((*face)->size->metrics.descender >> 6);
|
||||
int lineHeight = 0;
|
||||
|
||||
for(auto page = pages.begin(), end = pages.end(); page != end; ++page) {
|
||||
int from = page->first;
|
||||
int to = page->second;
|
||||
|
||||
for(int ch = from; ch < to; ++ch) {
|
||||
int glyph_ind = FT_Get_Char_Index(*face, ch);
|
||||
|
||||
FontFT2::Glyph glyph;
|
||||
glyph.page = cur_page;
|
||||
|
||||
if(glyph_ind != 0) {
|
||||
FT_Load_Glyph(*face, glyph_ind, FT_LOAD_RENDER);
|
||||
|
||||
auto glp = (*face)->glyph;
|
||||
auto bmp = glp->bitmap;
|
||||
|
||||
//Collect data about glyph
|
||||
glyph.draw = true;
|
||||
glyph.glyph_index = glyph_ind;
|
||||
glyph.w = glp->bitmap.width;
|
||||
glyph.h = glp->bitmap.rows;
|
||||
glyph.wOff = glp->bitmap_left;
|
||||
glyph.hOff = font->baseLine - glp->bitmap_top;
|
||||
glyph.xAdv = ((float)glp->metrics.horiAdvance) / 64.f;
|
||||
|
||||
glyph.x = x;
|
||||
glyph.y = y;
|
||||
|
||||
if(glyph.h > lineHeight)
|
||||
lineHeight = glyph.h;
|
||||
|
||||
//Find a fitting spot on the image
|
||||
x += (unsigned)glyph.w + 2;
|
||||
if(x > PAGE_SIZE) {
|
||||
x = (unsigned)glyph.w + 2;
|
||||
glyph.x = 0;
|
||||
|
||||
y += lineHeight + 1;
|
||||
glyph.y += lineHeight + 1;
|
||||
lineHeight = glyph.h;
|
||||
|
||||
if(y + font->glyphHeight + 1 > (unsigned)PAGE_SIZE) {
|
||||
img = new Image(PAGE_SIZE, PAGE_SIZE, FMT_Alpha, 0xff);
|
||||
images.push_back(img);
|
||||
|
||||
y = 0;
|
||||
glyph.y = 0;
|
||||
|
||||
++cur_page;
|
||||
++glyph.page;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Put the glyph on the actual image
|
||||
unsigned char* buffer = bmp.buffer;
|
||||
for(int row = 0; row < bmp.rows; ++row, buffer += bmp.width)
|
||||
memcpy(&img->get_alpha(glyph.x, glyph.y + row), buffer, bmp.width);
|
||||
}
|
||||
else {
|
||||
glyph.draw = false;
|
||||
glyph.xAdv = 0;
|
||||
}
|
||||
|
||||
if(ch <= 0xff)
|
||||
font->lowChars[ch] = glyph;
|
||||
else
|
||||
font->hiChars.push_back(FontFT2::GlyphEntry(ch, glyph));
|
||||
}
|
||||
}
|
||||
|
||||
//Create the textures
|
||||
for(auto it = images.begin(), end = images.end(); it != end; ++it) {
|
||||
auto material = new render::RenderState();
|
||||
material->depthTest = render::DT_NoDepthTest;
|
||||
material->lighting = false;
|
||||
material->culling = render::FC_None;
|
||||
material->baseMat = MAT_Font;
|
||||
material->depthWrite = false;
|
||||
if(load_resources)
|
||||
material->textures[0] = resource::queueImage(*it, 20, true, false);
|
||||
|
||||
font->textures.push_back(material);
|
||||
}
|
||||
|
||||
std::sort(font->hiChars.begin(), font->hiChars.end());
|
||||
return font;
|
||||
}
|
||||
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "render/driver.h"
|
||||
|
||||
namespace render {
|
||||
RenderDriver* createGLDriver();
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "gl_framebuffer.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class DummyTexture : public Texture {
|
||||
GLuint glName;
|
||||
public:
|
||||
DummyTexture(GLuint name) : glName(name) {
|
||||
type = TT_2D;
|
||||
hasMipMaps = false;
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
virtual bool isPixelActive(vec2i px) const { return false; }
|
||||
|
||||
virtual void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0) {}
|
||||
virtual void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0) {}
|
||||
virtual void loadFinish(bool mipmap, unsigned lod = 0) {}
|
||||
|
||||
virtual void load(Image& image, bool mipmap = true, bool cachePixels = false) {}
|
||||
virtual void save(Image& image) const {}
|
||||
virtual void bind() { glBindTexture(GL_TEXTURE_2D, glName); }
|
||||
virtual unsigned getID() const { return glName; }
|
||||
|
||||
virtual unsigned getTextureBytes() const { return 0; }
|
||||
};
|
||||
|
||||
glFrameBuffer::glFrameBuffer(const vec2i& Size) {
|
||||
devices.render->reportErrors();
|
||||
|
||||
type = TT_2D;
|
||||
loaded = true;
|
||||
|
||||
size = Size;
|
||||
hasMipMaps = false;
|
||||
glGenFramebuffers(1, &buffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, buffer);
|
||||
prevRenderState.filterMin = TF_Nearest;
|
||||
prevRenderState.filterMag = TF_Nearest;
|
||||
|
||||
textures = new GLuint[2];
|
||||
glGenTextures(2, textures);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Size.width, Size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textures[1]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, Size.width, Size.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
devices.render->reportErrors("setting framebuffer parameters");
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
|
||||
|
||||
devices.render->reportErrors("creating framebuffer color attachment");
|
||||
|
||||
renderBuffers = new GLuint[1];
|
||||
//glGenRenderbuffers(1, renderBuffers);
|
||||
//glBindRenderbuffer(GL_RENDERBUFFER, renderBuffers[0]);
|
||||
//glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, Size.width, Size.height);
|
||||
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffers[0]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[1], 0);
|
||||
|
||||
depthTexture = new DummyTexture(textures[1]);
|
||||
|
||||
devices.render->reportErrors("creating framebuffer depth attachment");
|
||||
|
||||
auto state = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if(state != GL_FRAMEBUFFER_COMPLETE)
|
||||
error("Incomplete framebuffer: %d", state);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
glFrameBuffer::~glFrameBuffer() {
|
||||
glDeleteFramebuffers(1, &buffer);
|
||||
glDeleteTextures(2, textures); delete[] textures;
|
||||
//glDeleteRenderbuffers(1, renderBuffers);
|
||||
delete[] renderBuffers;
|
||||
delete depthTexture;
|
||||
}
|
||||
|
||||
void glFrameBuffer::bind() {
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
}
|
||||
|
||||
unsigned glFrameBuffer::getID() const {
|
||||
return textures[0];
|
||||
}
|
||||
|
||||
void glFrameBuffer::setAsTarget() {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer);
|
||||
glViewport(0,0,size.width, size.height);
|
||||
}
|
||||
|
||||
void glFrameBuffer::save(Image& image) const {
|
||||
image.resize(size.width, size.height, FMT_RGB);
|
||||
glBindTexture(GL_TEXTURE_2D, textures[0]);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, image.rgb);
|
||||
}
|
||||
|
||||
void glFrameBuffer::load(Image& img, bool mipmap, bool cachePixels) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
void glFrameBuffer::loadFinish(bool mipmap, unsigned lod) {
|
||||
throw "Cannot load framebuffer from image.";
|
||||
}
|
||||
|
||||
bool glFrameBuffer::isPixelActive(vec2i px) const {
|
||||
throw "Cannot get framebuffer pixels.";
|
||||
}
|
||||
|
||||
unsigned glFrameBuffer::getTextureBytes() const {
|
||||
//4 bytes for RGBA, 3 bytes for 24 bit z buffer
|
||||
return size.width * size.height * 7;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "texture.h"
|
||||
#include "compat/gl.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
class glFrameBuffer : public Texture {
|
||||
GLuint buffer;
|
||||
GLuint* textures;
|
||||
GLuint* renderBuffers;
|
||||
public:
|
||||
glFrameBuffer(const vec2i& Size);
|
||||
~glFrameBuffer();
|
||||
|
||||
Texture* depthTexture;
|
||||
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
void load(Image& img, bool mipmap = true, bool cachePixels = false);
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
void save(Image& img) const;
|
||||
unsigned getTextureBytes() const;
|
||||
void setAsTarget();
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
#include "compat/gl.h"
|
||||
#include "vertex.h"
|
||||
#include "vec3.h"
|
||||
#include "mesh.h"
|
||||
#include "render/render_mesh.h"
|
||||
#include "render/gl_mesh.h"
|
||||
#include "render/driver.h"
|
||||
#include "main/references.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace render {
|
||||
|
||||
bool vertexArraysAvailable = false, vertArraysChecked = false;
|
||||
|
||||
bool useVertexArrays() {
|
||||
if(vertArraysChecked)
|
||||
return vertexArraysAvailable;
|
||||
|
||||
vertexArraysAvailable = GLEW_ARB_vertex_array_object;
|
||||
vertArraysChecked = true;
|
||||
return vertexArraysAvailable;
|
||||
}
|
||||
|
||||
const RenderMesh* lastRenderedMesh = 0;
|
||||
|
||||
class GLMesh : public RenderMesh {
|
||||
bool valid;
|
||||
public:
|
||||
GLuint vertex_buffer;
|
||||
GLuint element_buffer;
|
||||
GLuint tangent_buffer;
|
||||
GLuint color_buffer;
|
||||
GLuint uv2_buffer;
|
||||
|
||||
GLuint vertex_array;
|
||||
|
||||
unsigned int vertices;
|
||||
unsigned int faces;
|
||||
|
||||
AABBoxf bbox;
|
||||
Mesh mesh;
|
||||
|
||||
double lod_distance;
|
||||
const RenderMesh* lod;
|
||||
|
||||
GLMesh() : valid(false), lod_distance(1), lod(0), tangent_buffer(0), vertex_array(0), color_buffer(0), uv2_buffer(0) {
|
||||
}
|
||||
|
||||
GLMesh(const Mesh& mesh) : valid(false), lod_distance(1), lod(0), tangent_buffer(0), vertex_array(0), color_buffer(0), uv2_buffer(0) {
|
||||
resetToMesh(mesh);
|
||||
}
|
||||
|
||||
~GLMesh() {
|
||||
clear();
|
||||
}
|
||||
|
||||
const Mesh& getMesh() const {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
if(!valid)
|
||||
return;
|
||||
|
||||
glDeleteVertexArrays(1, &vertex_array);
|
||||
glDeleteBuffers(1, &vertex_buffer);
|
||||
glDeleteBuffers(1, &element_buffer);
|
||||
if(tangent_buffer != 0) {
|
||||
glDeleteBuffers(1, &tangent_buffer);
|
||||
tangent_buffer = 0;
|
||||
}
|
||||
if(color_buffer != 0) {
|
||||
glDeleteBuffers(1, &color_buffer);
|
||||
color_buffer = 0;
|
||||
}
|
||||
if(uv2_buffer != 0) {
|
||||
glDeleteBuffers(1, &uv2_buffer);
|
||||
uv2_buffer = 0;
|
||||
}
|
||||
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void setupBuffers() const {
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,position));
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,u));
|
||||
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex,normal));
|
||||
|
||||
if(tangent_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tangent_buffer);
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(vec4f), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
if(color_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glEnableVertexAttribArray(4);
|
||||
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(Colorf), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(4);
|
||||
}
|
||||
|
||||
if(uv2_buffer) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, uv2_buffer);
|
||||
glEnableVertexAttribArray(5);
|
||||
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(vec4f), nullptr);
|
||||
}
|
||||
else {
|
||||
glDisableVertexAttribArray(5);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
}
|
||||
|
||||
void resetToMesh(const Mesh& mesh) {
|
||||
this->mesh = mesh;
|
||||
clear();
|
||||
|
||||
if(mesh.vertices.empty() || mesh.faces.empty())
|
||||
return;
|
||||
|
||||
devices.render->reportErrors();
|
||||
|
||||
// Generate vertex buffer
|
||||
vertices = (unsigned)mesh.vertices.size();
|
||||
glGenBuffers(1, &vertex_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices * sizeof(Vertex),
|
||||
&mesh.vertices[0], GL_STATIC_DRAW);
|
||||
|
||||
if(!mesh.tangents.empty()) {
|
||||
glGenBuffers(1, &tangent_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, tangent_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.tangents.size() * sizeof(vec4f), mesh.tangents.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
if(!mesh.colors.empty()) {
|
||||
glGenBuffers(1, &color_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, color_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.colors.size() * sizeof(Colorf), mesh.colors.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
if(!mesh.uvs2.empty()) {
|
||||
glGenBuffers(1, &uv2_buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, uv2_buffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, mesh.uvs2.size() * sizeof(vec4f), mesh.uvs2.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Generate element buffer
|
||||
faces = (unsigned)mesh.faces.size();
|
||||
glGenBuffers(1, &element_buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces * sizeof(Mesh::Face),
|
||||
&mesh.faces[0], GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
devices.render->reportErrors("Filling mesh buffers");
|
||||
|
||||
if(useVertexArrays()) {
|
||||
glGenVertexArrays(1, &vertex_array);
|
||||
glBindVertexArray(vertex_array);
|
||||
setupBuffers();
|
||||
glBindVertexArray(0);
|
||||
|
||||
devices.render->reportErrors("Setting up mesh vertex array");
|
||||
}
|
||||
|
||||
//Generate Bounding Box
|
||||
for(unsigned i = 0; i < vertices; ++i)
|
||||
bbox.addPoint(mesh.vertices[i].position);
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
const RenderMesh* selectLOD(double distance) const {
|
||||
if(lod && distance > lod_distance)
|
||||
return lod->selectLOD(distance);
|
||||
return this;
|
||||
}
|
||||
|
||||
void setLOD(double distance, const RenderMesh* mesh) {
|
||||
lod_distance = distance;
|
||||
lod = mesh;
|
||||
}
|
||||
|
||||
const AABBoxf& getBoundingBox() const {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
unsigned getMeshBytes() const {
|
||||
if(!valid)
|
||||
return 0;
|
||||
|
||||
return (vertices * sizeof(float) * 8) + (faces * 3 * sizeof(short));
|
||||
}
|
||||
|
||||
void render() const {
|
||||
if(lastRenderedMesh != this) {
|
||||
//We only need to check valid here, because we can't be the rendered mesh if we aren't valid
|
||||
if(!valid)
|
||||
return;
|
||||
lastRenderedMesh = this;
|
||||
if(vertex_array)
|
||||
glBindVertexArray(vertex_array);
|
||||
else
|
||||
setupBuffers();
|
||||
}
|
||||
|
||||
glDrawElements(
|
||||
GL_TRIANGLES, //Mode
|
||||
3 * faces, //Total amount of vertices
|
||||
GL_UNSIGNED_SHORT, //Index type
|
||||
(void*)0 //Offset
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
RenderMesh* createGLMesh(const Mesh& mesh) {
|
||||
return new GLMesh(mesh);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "render/render_mesh.h"
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
RenderMesh* createGLMesh(const Mesh& mesh);
|
||||
};
|
||||
@@ -0,0 +1,925 @@
|
||||
#include "render/shader.h"
|
||||
#include "render/gl_shader.h"
|
||||
#include "compat/gl.h"
|
||||
#include "compat/misc.h"
|
||||
#include "str_util.h"
|
||||
#include "files.h"
|
||||
#include "main/logging.h"
|
||||
#include "main/references.h"
|
||||
|
||||
extern unsigned frameNumber;
|
||||
|
||||
namespace render {
|
||||
|
||||
void preProcessShader(std::string& shader) {
|
||||
std::string output;
|
||||
size_t start = 0;
|
||||
while(start < shader.size()) {
|
||||
size_t pos = shader.find('#', start);
|
||||
if(pos == std::string::npos)
|
||||
break;
|
||||
|
||||
if(pos != start)
|
||||
output += shader.substr(start, pos-start);
|
||||
|
||||
if(shader.size() - pos > 3 && shader[pos+1] == '{' && shader[pos+2] == '{') {
|
||||
size_t endpos = shader.find("}}", pos, 2);
|
||||
if(endpos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string value = shader.substr(pos+3, endpos-pos-3);
|
||||
std::string replace;
|
||||
|
||||
if(value == "level:extreme") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 4) ? "true" : "false";
|
||||
}
|
||||
else if(value == "level:high") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 3) ? "true" : "false";
|
||||
}
|
||||
else if(value == "level:medium") {
|
||||
auto* sl = devices.settings.engine.getSetting("iShaderLevel");
|
||||
if(sl)
|
||||
output += (sl->getInteger() >= 2) ? "true" : "false";
|
||||
}
|
||||
else if(value == "fallback") {
|
||||
auto* fallback = devices.settings.engine.getSetting("bShaderFallback");
|
||||
if(fallback)
|
||||
output += (fallback->getBool()) ? "true" : "false";
|
||||
}
|
||||
else {
|
||||
auto* setting = devices.settings.engine.getSetting(value.c_str());
|
||||
if(!setting)
|
||||
setting = devices.settings.mod.getSetting(value.c_str());
|
||||
|
||||
if(setting) {
|
||||
switch(setting->type) {
|
||||
case GT_Bool:
|
||||
output += (setting->getBool()) ? "true" : "false";
|
||||
break;
|
||||
case GT_Integer:
|
||||
case GT_Enum:
|
||||
output += toString<int>(setting->getInteger());
|
||||
break;
|
||||
case GT_Double:
|
||||
output += toString<double>(setting->getDouble());
|
||||
break;
|
||||
case GT_String:
|
||||
output += *setting->getString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("ERROR: Could not find shader variable %s.", value.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
start = endpos + 2;
|
||||
}
|
||||
else if(shader.size() - pos > 11 && shader.compare(pos, 10, "#include \"") == 0) {
|
||||
size_t endpos = shader.find("\"", pos+10, 1);
|
||||
if(endpos == std::string::npos)
|
||||
break;
|
||||
|
||||
std::string value = shader.substr(pos+10, endpos-pos-10);
|
||||
std::string fname = devices.mods.resolve(value);
|
||||
if(!fileExists(fname)) {
|
||||
error("ERROR COMPILING SHADER: Could not find include file %s. (Resolved to %s)", value.c_str(), fname.c_str());
|
||||
}
|
||||
else {
|
||||
std::string includeContents = getFileContents(fname);
|
||||
preProcessShader(includeContents);
|
||||
output += includeContents;
|
||||
}
|
||||
|
||||
start = endpos + 1;
|
||||
}
|
||||
else {
|
||||
output += "#";
|
||||
start = pos + 1;
|
||||
}
|
||||
}
|
||||
if(start < shader.size())
|
||||
output += shader.substr(start, shader.size()-start);
|
||||
shader = output;
|
||||
}
|
||||
|
||||
class GLShader;
|
||||
class GLShaderProgram : public ShaderProgram {
|
||||
public:
|
||||
GLuint vertex_shader, fragment_shader, program;
|
||||
std::string vertex_file, fragment_file;
|
||||
|
||||
std::unordered_map<GLuint, unsigned> cached_uniforms;
|
||||
std::vector<float> mem_buffer;
|
||||
|
||||
const GLShader* lastShader;
|
||||
|
||||
GLShaderProgram(const std::string& vFile, const std::string& fFile)
|
||||
: vertex_file(vFile), fragment_file(fFile), program(0), lastShader(0)
|
||||
{
|
||||
}
|
||||
|
||||
~GLShaderProgram() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if(program != 0) {
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
}
|
||||
|
||||
mem_buffer.clear();
|
||||
cached_uniforms.clear();
|
||||
}
|
||||
|
||||
unsigned cacheUniform(GLuint position, size_t size) {
|
||||
auto it = cached_uniforms.find(position);
|
||||
if(it != cached_uniforms.end())
|
||||
return it->second;
|
||||
|
||||
unsigned pos = (unsigned)mem_buffer.size();
|
||||
cached_uniforms[position] = pos;
|
||||
|
||||
for(size_t i = 0; i < size; ++i)
|
||||
mem_buffer.push_back(0);
|
||||
return pos;
|
||||
}
|
||||
|
||||
GLuint compileShader(GLenum type, const std::string& fname, const std::string& source) {
|
||||
GLuint shader;
|
||||
|
||||
//Compile
|
||||
const GLchar* str_ptr = (const GLchar*)source.c_str();
|
||||
GLint str_len = (GLint)source.size();
|
||||
shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &str_ptr, &str_len);
|
||||
glCompileShader(shader);
|
||||
|
||||
//Check compile value
|
||||
GLint shader_ok;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok);
|
||||
|
||||
if(!shader_ok) {
|
||||
error("Failed to compile shader: %s", fname.c_str());
|
||||
|
||||
GLint log_length;
|
||||
char* log;
|
||||
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log);
|
||||
error("%s", log);
|
||||
|
||||
free(log);
|
||||
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
else if(getLogLevel() == LL_Info) {
|
||||
//Print warnings in verbose
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
//We're always provided at least an empty string, just ignore very small logs
|
||||
if(log_length > 8) {
|
||||
char* log;
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetShaderInfoLog(shader, log_length, NULL, log);
|
||||
error("Shader '%s' has warnings:\n%s", fname.c_str(), log);
|
||||
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
int compile() override {
|
||||
reset();
|
||||
//Compile vertex shader
|
||||
{
|
||||
auto contents = getFileContents(vertex_file);
|
||||
preProcessShader(contents);
|
||||
if(contents.empty()) {
|
||||
error("Could not open vertex shader, or the file was empty");
|
||||
return 1;
|
||||
}
|
||||
|
||||
vertex_shader = compileShader(GL_VERTEX_SHADER, vertex_file, contents);
|
||||
if(!vertex_shader)
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Compile fragment shader
|
||||
{
|
||||
auto contents = getFileContents(fragment_file);
|
||||
preProcessShader(contents);
|
||||
fragment_shader = compileShader(GL_FRAGMENT_SHADER, fragment_file, contents);
|
||||
if(!fragment_shader) {
|
||||
if(contents.empty())
|
||||
error("Could not open fragment shader, or the file was empty");
|
||||
glDeleteShader(vertex_shader);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
//Link entire program
|
||||
program = glCreateProgram();
|
||||
|
||||
glBindAttribLocation(program, 0, "in_vertex");
|
||||
glBindAttribLocation(program, 2, "in_normal");
|
||||
glBindAttribLocation(program, 1, "in_uv");
|
||||
glBindAttribLocation(program, 3, "in_tangent");
|
||||
glBindAttribLocation(program, 4, "in_color");
|
||||
glBindAttribLocation(program, 5, "in_uv2");
|
||||
|
||||
glAttachShader(program, vertex_shader);
|
||||
glAttachShader(program, fragment_shader);
|
||||
glLinkProgram(program);
|
||||
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
//Make sure it linked correctly
|
||||
GLint status;
|
||||
glGetProgramiv(program,GL_LINK_STATUS,&status);
|
||||
if(status == GL_FALSE) {
|
||||
error("Linking failed:");
|
||||
GLint log_length;
|
||||
char* log;
|
||||
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
log = (char*)malloc(log_length);
|
||||
|
||||
glGetProgramInfoLog(program, log_length, NULL, log);
|
||||
error("%s", log);
|
||||
|
||||
free(log);
|
||||
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class GLShader : public Shader {
|
||||
public:
|
||||
mutable unsigned lastBoundFrame;
|
||||
|
||||
std::vector<std::string> uniform_names;
|
||||
std::vector<GLuint> uniform_locations;
|
||||
std::vector<unsigned> uniform_vars;
|
||||
std::vector<unsigned> uniform_cache;
|
||||
|
||||
GLShader()
|
||||
{
|
||||
program = 0;
|
||||
constant = true;
|
||||
dynamicFloats = 0;
|
||||
lastBoundFrame = 0xffffffff;
|
||||
}
|
||||
|
||||
~GLShader() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
uniform_locations.clear();
|
||||
uniform_cache.clear();
|
||||
uniform_vars.clear();
|
||||
}
|
||||
|
||||
virtual int compile() {
|
||||
reset();
|
||||
|
||||
if(!program) {
|
||||
error("No associated program");
|
||||
return 4;
|
||||
}
|
||||
|
||||
auto glProgram = (GLShaderProgram*)program;
|
||||
if(glProgram->program == 0)
|
||||
return 4;
|
||||
|
||||
bool missingVars = false;
|
||||
for(unsigned i = 0; i < uniform_names.size(); ++i) {
|
||||
GLuint location = glGetUniformLocation(glProgram->program, uniform_names[i].c_str());
|
||||
|
||||
if(location == (GLuint)-1) {
|
||||
//NOTE: This is no longer considered an error, because dynamic shader levels
|
||||
//are going to be causing uniforms to be missing all the time.
|
||||
//
|
||||
//error("Unable to find uniform '%s'", uniform_names[i].c_str());
|
||||
//missingVars = true;
|
||||
}
|
||||
else {
|
||||
uniform_locations.push_back(location);
|
||||
uniform_vars.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < uniform_locations.size(); ++i) {
|
||||
auto& v = vars[uniform_vars[i]];
|
||||
size_t size = 0;
|
||||
switch(v.type) {
|
||||
case VT_int: case VT_float: size = 1; break;
|
||||
case VT_int2: case VT_float2: size = 2; break;
|
||||
case VT_int3: case VT_float3: size = 3; break;
|
||||
case VT_int4: case VT_float4: size = 4; break;
|
||||
case VT_mat3: size = 9; break;
|
||||
}
|
||||
|
||||
size *= v.count;
|
||||
uniform_cache.push_back(glProgram->cacheUniform(uniform_locations[i], size));
|
||||
}
|
||||
|
||||
if(missingVars)
|
||||
return 4;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void addVariable(const std::string& name, const Variable& var) {
|
||||
uniform_names.push_back(name);
|
||||
Shader::addVariable(name, var);
|
||||
}
|
||||
|
||||
virtual void bind(float* dynamic) const {
|
||||
if(!program) {
|
||||
glUseProgram(0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto glProgram = (GLShaderProgram*)program;
|
||||
if(auto programID = glProgram->program) {
|
||||
glUseProgram(programID);
|
||||
}
|
||||
else {
|
||||
glUseProgram(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bindConst = lastBoundFrame != frameNumber || glProgram->lastShader != this;
|
||||
lastBoundFrame = frameNumber;
|
||||
glProgram->lastShader = this;
|
||||
|
||||
if(dynamic) {
|
||||
float* floats = dynamic;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(!bindConst && var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&glProgram->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var.constant) {
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
case VT_mat3:
|
||||
if(var.constant) {
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats, var.count, var._args);
|
||||
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
}
|
||||
else if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, floats);
|
||||
memcpy(uniformMem, floats, var.count * 36);
|
||||
}
|
||||
floats += var.count * 9;
|
||||
}
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(!bindConst && var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&glProgram->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateDynamicVars() const {
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&((GLShaderProgram*)program)->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall)
|
||||
var._intcall(var._ints,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._ints, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, var._ints);
|
||||
memcpy(uniformMem, var._ints, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 4);
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 8);
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 12);
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall)
|
||||
var._floatcall(var._floats,var.count,var._args);
|
||||
if(memcmp(uniformMem, var._floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, var._floats);
|
||||
memcpy(uniformMem, var._floats, var.count * 36);
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void saveDynamicVars(float* buffer) const {
|
||||
float* floats = buffer;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall) {
|
||||
var._intcall((int*)floats,var.count,var._args);
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.count,var._args);
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall) {
|
||||
var._floatcall(floats,var.constant,var._args);
|
||||
floats += var.constant * 9;
|
||||
}
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadDynamicVars(float* buffer) const {
|
||||
float* floats = buffer;
|
||||
|
||||
for(size_t i = 0, cnt = uniform_locations.size(); i < cnt; ++i) {
|
||||
const Shader::Variable& var = vars[uniform_vars[i]];
|
||||
if(var.constant)
|
||||
continue;
|
||||
|
||||
GLuint uniform = uniform_locations[i];
|
||||
void* uniformMem = (void*)&((GLShaderProgram*)program)->mem_buffer[uniform_cache[i]];
|
||||
|
||||
switch(var.type) {
|
||||
case VT_invalid:
|
||||
break;
|
||||
|
||||
case VT_int:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_int2:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_int3:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_int4:
|
||||
if(var._intcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4iv(uniform, var.count, (int*)floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_float:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 4) != 0) {
|
||||
glUniform1fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 4);
|
||||
}
|
||||
floats += var.count;
|
||||
}
|
||||
break;
|
||||
case VT_float2:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 8) != 0) {
|
||||
glUniform2fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 8);
|
||||
}
|
||||
floats += var.count * 2;
|
||||
}
|
||||
break;
|
||||
case VT_float3:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 12) != 0) {
|
||||
glUniform3fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 12);
|
||||
}
|
||||
floats += var.count * 3;
|
||||
}
|
||||
break;
|
||||
case VT_float4:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 16) != 0) {
|
||||
glUniform4fv(uniform, var.count, floats);
|
||||
memcpy(uniformMem, floats, var.count * 16);
|
||||
}
|
||||
floats += var.count * 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case VT_mat3:
|
||||
if(var._floatcall) {
|
||||
if(memcmp(uniformMem, floats, var.count * 36) != 0) {
|
||||
glUniformMatrix3fv(uniform, var.count, false, floats);
|
||||
memcpy(uniformMem, floats, var.count * 36);
|
||||
}
|
||||
floats += var.count * 9;
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Shader* createGLShader() {
|
||||
return new GLShader();
|
||||
}
|
||||
|
||||
ShaderProgram* createGLShaderProgram(const char* vertex_shader, const char* fragment_shader) {
|
||||
return new GLShaderProgram(vertex_shader, fragment_shader);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "render/shader.h"
|
||||
|
||||
namespace render {
|
||||
Shader* createGLShader();
|
||||
ShaderProgram* createGLShaderProgram(const char* vertex_shader, const char* fragment_shader);
|
||||
};
|
||||
@@ -0,0 +1,423 @@
|
||||
#include "render/gl_texture.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace render {
|
||||
extern bool isIntelCard;
|
||||
|
||||
void GLTexture::bind() {
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
}
|
||||
|
||||
unsigned GLTexture::getID() const {
|
||||
return glName;
|
||||
}
|
||||
|
||||
GLTexture::GLTexture()
|
||||
: glName(0), pixelDepth(1)
|
||||
{
|
||||
type = TT_2D;
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
GLTexture::GLTexture(Image& image, bool mipmap, bool cachePixels)
|
||||
: glName(0), pixelDepth(1)
|
||||
{
|
||||
type = TT_2D;
|
||||
loaded = false;
|
||||
load(image, mipmap, cachePixels);
|
||||
}
|
||||
|
||||
void createTexture(Image& image, bool mipmap) {
|
||||
GLenum format;
|
||||
|
||||
unsigned levels = 1;
|
||||
if(mipmap) {
|
||||
unsigned smallDim = std::min(image.width, image.height);
|
||||
while(smallDim > 2) {
|
||||
smallDim /= 2;
|
||||
levels += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(GLEW_ARB_texture_storage) {
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE8;
|
||||
//{
|
||||
// GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
|
||||
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
||||
//}
|
||||
break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA8;
|
||||
//{
|
||||
// GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED};
|
||||
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask);
|
||||
//}
|
||||
break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB8; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA8; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
glTexStorage2D(GL_TEXTURE_2D, levels, format, image.width, image.height);
|
||||
|
||||
if(!isIntelCard)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
||||
}
|
||||
else {
|
||||
//Sized lum/alpha are from texture storage arb
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
unsigned w = image.width, h = image.height;
|
||||
for(unsigned i = 0; i < levels; ++i) {
|
||||
glTexImage2D(GL_TEXTURE_2D, i, format, w, h, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
||||
w /= 2;
|
||||
h /= 2;
|
||||
}
|
||||
|
||||
if(!isIntelCard)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels-1);
|
||||
}
|
||||
devices.render->reportErrors("creating texture");
|
||||
}
|
||||
|
||||
void loadTexture(Image& image, const recti& pixels, unsigned lod = 0) {
|
||||
devices.render->reportErrors();
|
||||
|
||||
GLenum format;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
assert((unsigned)(pixels.topLeft.x + pixels.getWidth()) <= image.width);
|
||||
assert((unsigned)(pixels.topLeft.y + pixels.getHeight()) <= image.height);
|
||||
|
||||
if(pixels.getSize() == vec2i(image.width, image.height)) {
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)image.grey; break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)image.grey; break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)image.rgb; break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)image.rgba; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
else if((unsigned)pixels.getWidth() == image.width) {
|
||||
assert(pixels.topLeft.x == 0);
|
||||
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)&image.get_grey(0, pixels.topLeft.y); break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)&image.get_alpha(0, pixels.topLeft.y); break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)&image.get_rgb(0, pixels.topLeft.y); break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)&image.get_rgba(0, pixels.topLeft.y); break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, pixels.topLeft.y, pixels.getWidth(), pixels.getHeight(), format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
else {
|
||||
//Must load line by line
|
||||
for(unsigned y = pixels.topLeft.y, endY = pixels.botRight.y; y < endY; ++y) {
|
||||
void* ptrPixels;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
ptrPixels = (void*)&image.get_grey(pixels.topLeft.x, y); break;
|
||||
case FMT_Alpha:
|
||||
ptrPixels = (void*)&image.get_alpha(pixels.topLeft.x, y); break;
|
||||
case FMT_RGB:
|
||||
ptrPixels = (void*)&image.get_rgb(pixels.topLeft.x, y); break;
|
||||
case FMT_RGBA:
|
||||
ptrPixels = (void*)&image.get_rgba(pixels.topLeft.x, y); break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
glTexSubImage2D(GL_TEXTURE_2D, lod, pixels.topLeft.x, y, pixels.getWidth(), 1, format, GL_UNSIGNED_BYTE, ptrPixels);
|
||||
}
|
||||
}
|
||||
devices.render->reportErrors("filling sub image");
|
||||
}
|
||||
|
||||
void GLTexture::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
devices.render->reportErrors();
|
||||
loaded = true;
|
||||
|
||||
if(lod == 0) {
|
||||
//Destroy old texture
|
||||
if(glName != 0) {
|
||||
glDeleteTextures(1, &glName);
|
||||
glName = 0;
|
||||
}
|
||||
|
||||
//Generate a new texture
|
||||
glGenTextures(1,&glName);
|
||||
|
||||
//Store texture size
|
||||
size = vec2i(image.width, image.height);
|
||||
|
||||
pixelDepth = ColorDepths[image.format];
|
||||
|
||||
if(cachePixels)
|
||||
activePixels.resize(size.x * size.y);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
if(lod == 0)
|
||||
createTexture(image, mipmap);
|
||||
}
|
||||
|
||||
void GLTexture::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
loadTexture(image, pixels, lod);
|
||||
|
||||
if(cachePixels && lod == 0) {
|
||||
for(int y = pixels.topLeft.y; y < pixels.getHeight(); ++y) {
|
||||
for(int x = pixels.topLeft.x; x < pixels.getWidth(); ++x) {
|
||||
unsigned char a = image.get(x, y).a;
|
||||
activePixels[y * size.width + x] = a != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLTexture::loadFinish(bool mipmap, unsigned lod) {
|
||||
devices.render->reportErrors();
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
|
||||
hasMipMaps = mipmap;
|
||||
if(mipmap) {
|
||||
//Set linear mipmap filter
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
|
||||
if(lod == 0) {
|
||||
if(size.width * size.height > (1024 * 1024))
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
else
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
else if(lod == 0) {
|
||||
//Set linear filter
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
else {
|
||||
//We're defining mipmap data now, switch back to mipmaping
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, lod);
|
||||
}
|
||||
|
||||
devices.render->reportErrors("finalizing texture");
|
||||
}
|
||||
|
||||
unsigned GLTexture::getTextureBytes() const {
|
||||
unsigned bytes = size.width * size.height * pixelDepth;
|
||||
if(hasMipMaps)
|
||||
bytes += bytes / 3;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void GLTexture::load(Image& image, bool mipmap, bool cachePixels) {
|
||||
loadStart(image, mipmap, cachePixels);
|
||||
loadPartial(image, recti(0,0, image.width, image.height), cachePixels);
|
||||
loadFinish(mipmap);
|
||||
}
|
||||
|
||||
void GLTexture::save(Image& image) const {
|
||||
image.resize(size.width, size.height, FMT_RGBA);
|
||||
glBindTexture(GL_TEXTURE_2D, glName);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.rgba);
|
||||
}
|
||||
|
||||
bool GLTexture::isPixelActive(vec2i px) const {
|
||||
unsigned index = px.y * size.width + px.x;
|
||||
if(activePixels.empty())
|
||||
return true;
|
||||
if(index >= activePixels.size())
|
||||
return false;
|
||||
return activePixels[index];
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture() {
|
||||
glDeleteTextures(1, &glName);
|
||||
}
|
||||
|
||||
bool GLCubeMap::isPixelActive(vec2i px) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLCubeMap::load(Image& image, bool mipmap, bool cachePixels) {
|
||||
devices.render->reportErrors();
|
||||
loaded = false;
|
||||
|
||||
vec2u tile = vec2u(image.width / 4, image.height / 3);
|
||||
if(tile.width != tile.height || tile.width == 0) {
|
||||
error("ERROR loading cubemap: Cubemap must have a 4:3 aspect ratio.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Destroy old texture
|
||||
if(glName != 0) {
|
||||
glDeleteTextures(1, &glName);
|
||||
glName = 0;
|
||||
}
|
||||
|
||||
//Generate a new texture
|
||||
glGenTextures(1,&glName);
|
||||
|
||||
//Store texture size
|
||||
size = vec2i(image.width, image.height);
|
||||
loaded = true;
|
||||
|
||||
pixelDepth = ColorDepths[image.format];
|
||||
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
||||
|
||||
unsigned char* data = image.grey;
|
||||
|
||||
GLenum format;
|
||||
switch(image.format) {
|
||||
case FMT_Grey:
|
||||
format = GL_LUMINANCE; break;
|
||||
case FMT_Alpha:
|
||||
format = GL_ALPHA; break;
|
||||
case FMT_RGB:
|
||||
format = GL_RGB; break;
|
||||
case FMT_RGBA:
|
||||
format = GL_RGBA; break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
|
||||
auto getTile = [&](unsigned x, unsigned y) -> Image* {
|
||||
vec2u top = vec2u(tile.width * x, tile.height * y);
|
||||
return image.crop(rect<unsigned>(top, top + tile));
|
||||
};
|
||||
|
||||
auto* img = getTile(1,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(2,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(1,2);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(3,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(0,1);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
img = getTile(1,0);
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, tile.width, tile.height, 0, format, GL_UNSIGNED_BYTE, img->grey);
|
||||
delete img;
|
||||
|
||||
devices.render->reportErrors("loading cubemap images");
|
||||
|
||||
hasMipMaps = mipmap;
|
||||
if(mipmap) {
|
||||
//Set linear mipmap filter
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
|
||||
if(size.width * size.height > (1024 * 1024))
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
||||
else
|
||||
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
|
||||
}
|
||||
else {
|
||||
//Set linear filter
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
|
||||
devices.render->reportErrors("finalizing texture");
|
||||
}
|
||||
|
||||
unsigned GLCubeMap::getTextureBytes() const {
|
||||
unsigned bytes = size.width * size.height * pixelDepth * 6;
|
||||
if(hasMipMaps)
|
||||
bytes += bytes / 3;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void GLCubeMap::save(Image& image) const {}
|
||||
|
||||
void GLCubeMap::bind() {
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, glName);
|
||||
}
|
||||
|
||||
unsigned GLCubeMap::getID() const {
|
||||
return glName;
|
||||
}
|
||||
|
||||
GLCubeMap::GLCubeMap(Image& image, bool mipmap) : glName(0), pixelDepth(1) {
|
||||
loaded = false;
|
||||
type = TT_Cubemap;
|
||||
load(image, mipmap);
|
||||
}
|
||||
|
||||
GLCubeMap::GLCubeMap() : glName(0), pixelDepth(0) {
|
||||
loaded = false;
|
||||
type = TT_Cubemap;
|
||||
}
|
||||
|
||||
GLCubeMap::~GLCubeMap() {
|
||||
glDeleteTextures(1, &glName);
|
||||
}
|
||||
|
||||
void GLCubeMap::loadStart(Image& image, bool mipmap, bool cachePixels, unsigned lod) {
|
||||
load(image, mipmap);
|
||||
}
|
||||
|
||||
void GLCubeMap::loadPartial(Image& image, const recti& pixels, bool cachePixels, unsigned lod) {
|
||||
}
|
||||
|
||||
void GLCubeMap::loadFinish(bool mipmap, unsigned lod) {
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "compat/gl.h"
|
||||
#include "render/texture.h"
|
||||
#include "image.h"
|
||||
#include "rect.h"
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
class GLTexture : public Texture {
|
||||
GLuint glName;
|
||||
unsigned pixelDepth;
|
||||
std::vector<bool> activePixels;
|
||||
public:
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void load(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
unsigned getTextureBytes() const;
|
||||
|
||||
void save(Image& image) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
|
||||
GLTexture(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
GLTexture();
|
||||
|
||||
~GLTexture();
|
||||
};
|
||||
|
||||
class GLCubeMap : public Texture {
|
||||
GLuint glName;
|
||||
unsigned pixelDepth;
|
||||
public:
|
||||
bool isPixelActive(vec2i px) const;
|
||||
void load(Image& image, bool mipmap = true, bool cachePixels = false);
|
||||
void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0);
|
||||
void loadFinish(bool mipmap, unsigned lod = 0);
|
||||
|
||||
unsigned getTextureBytes() const;
|
||||
|
||||
void save(Image& image) const;
|
||||
void bind();
|
||||
unsigned getID() const;
|
||||
|
||||
GLCubeMap(Image& image, bool mipmap = true);
|
||||
GLCubeMap();
|
||||
|
||||
~GLCubeMap();
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
#include "vertexBuffer.h"
|
||||
#include "compat/gl.h"
|
||||
#include "main/references.h"
|
||||
#include "compat/misc.h"
|
||||
#include <unordered_map>
|
||||
|
||||
const unsigned maxVerts = 2048, maxInds = 4096;
|
||||
|
||||
unsigned drawnSteps = 0, bufferFlushes = 0;
|
||||
|
||||
namespace render {
|
||||
|
||||
unsigned vbFloatLimit = 24, vbMaxSteps = 200;
|
||||
unsigned vbFlushCounts[FC_COUNT] = {0,0,0,0};
|
||||
|
||||
extern const RenderMesh* lastRenderedMesh;
|
||||
extern float* shaderUniforms;
|
||||
VertexBufferTCV tcv;
|
||||
threads::Mutex bufferLock;
|
||||
|
||||
extern bool useVertexArrays();
|
||||
|
||||
inline bool getRequestSize(unsigned& indexCount, unsigned& vertCount, unsigned polygons, unsigned polyType) {
|
||||
switch(polyType) {
|
||||
case PT_Lines:
|
||||
indexCount = polygons * 2;
|
||||
vertCount = polygons * 2;
|
||||
return true;
|
||||
case PT_LineStrip:
|
||||
indexCount = polygons * 2;
|
||||
vertCount = polygons + 1;
|
||||
return true;
|
||||
case PT_Triangles:
|
||||
indexCount = polygons * 3;
|
||||
vertCount = polygons * 3;
|
||||
return false;
|
||||
case PT_Quads:
|
||||
indexCount = polygons * 6;
|
||||
vertCount = polygons * 4;
|
||||
return false;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
void buildIndices(std::vector<unsigned short>& indices, unsigned indexCount, unsigned prevVerts, unsigned polygons, unsigned polyType) {
|
||||
//Fill out indices according to the polygon type
|
||||
auto prevInds = indices.size();
|
||||
indices.resize(prevInds + indexCount);
|
||||
unsigned short* pIndices = &indices.at(0);
|
||||
|
||||
switch(polyType) {
|
||||
case PT_Lines:
|
||||
for(unsigned i = 0; i < polygons * 2; ++i)
|
||||
pIndices[prevInds + i] = prevVerts + i;
|
||||
break;
|
||||
case PT_LineStrip:
|
||||
for(unsigned i = 0; i < polygons; ++i) {
|
||||
pIndices[prevInds + (i*2)] = prevVerts + i;
|
||||
pIndices[prevInds + (i*2) + 1] = prevVerts + i + 1;
|
||||
}
|
||||
break;
|
||||
case PT_Triangles:
|
||||
for(unsigned i = 0; i < polygons * 3; ++i)
|
||||
pIndices[prevInds + i] = prevVerts + i;
|
||||
break;
|
||||
case PT_Quads:
|
||||
for(unsigned i = 0; i < polygons; ++i) {
|
||||
auto off = prevInds + (i*6);
|
||||
auto vOff = prevVerts + (i*4);
|
||||
pIndices[off] = vOff;
|
||||
pIndices[off+1] = vOff+2;
|
||||
pIndices[off+2] = vOff+1;
|
||||
pIndices[off+3] = vOff;
|
||||
pIndices[off+4] = vOff+3;
|
||||
pIndices[off+5] = vOff+2;
|
||||
}
|
||||
break;
|
||||
NO_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
void renderVertexBuffers() {
|
||||
tcv.draw();
|
||||
}
|
||||
|
||||
VertexBufferTCV::VertexBufferTCV()
|
||||
: last(0)
|
||||
{
|
||||
vertices.reserve(maxVerts);
|
||||
indices.reserve(maxInds);
|
||||
steps.reserve(vbMaxSteps);
|
||||
|
||||
shaderBufferSize = 40000;
|
||||
shaderBuffer = new float[shaderBufferSize];
|
||||
pNextShaderBuffer = shaderBuffer;
|
||||
}
|
||||
|
||||
VertexBufferTCV::~VertexBufferTCV() {
|
||||
for(auto i = vertBuffer.begin(), end = vertBuffer.end(); i != end; ++i)
|
||||
glDeleteBuffers(1, &*i);
|
||||
for(auto i = indBuffer.begin(), end = indBuffer.end(); i != end; ++i)
|
||||
glDeleteBuffers(1, &*i);
|
||||
delete[] shaderBuffer;
|
||||
}
|
||||
|
||||
void VertexBufferTCV::flushRetainingLast(FlushCause reason) {
|
||||
RenderStep step = *last;
|
||||
float* prevBuffer = pNextShaderBuffer;
|
||||
|
||||
//We may have added a step that we decided we can't render, we just pop the empty step
|
||||
if(step.vertEnd == 0)
|
||||
steps.pop_back();
|
||||
|
||||
draw(reason);
|
||||
|
||||
step.vertStart = 0;
|
||||
step.vertEnd = 0;
|
||||
step.indexOffset = 0;
|
||||
|
||||
if(step.shaderCache) {
|
||||
memcpy(pNextShaderBuffer, step.shaderCache, (prevBuffer - step.shaderCache) * sizeof(float));
|
||||
auto count = prevBuffer - step.shaderCache;
|
||||
step.shaderCache = pNextShaderBuffer;
|
||||
pNextShaderBuffer += count;
|
||||
}
|
||||
|
||||
steps.push_back(step);
|
||||
last = &steps.back();
|
||||
}
|
||||
|
||||
void VertexBufferTCV::duplicateLast() {
|
||||
if(steps.size() != steps.capacity()) {
|
||||
steps.push_back(*last);
|
||||
last = &steps.back();
|
||||
|
||||
last->indexOffset = (unsigned short)indices.size();
|
||||
last->vertStart = (unsigned short)vertices.size();
|
||||
}
|
||||
else {
|
||||
flushRetainingLast(FC_StepLimit);
|
||||
}
|
||||
}
|
||||
|
||||
VertexTCV* VertexBufferTCV::request(unsigned polygons, unsigned polyType) {
|
||||
unsigned indexCount = 0;
|
||||
unsigned vertCount = 0;
|
||||
bool isLines = getRequestSize(indexCount, vertCount, polygons, polyType);
|
||||
|
||||
unsigned prevVerts = (unsigned)vertices.size();
|
||||
|
||||
if(prevVerts + vertCount > maxVerts
|
||||
|| indices.size() + indexCount > maxInds)
|
||||
{
|
||||
flushRetainingLast(FC_VertexLimit);
|
||||
last->lines = isLines;
|
||||
prevVerts = 0;
|
||||
}
|
||||
|
||||
if(last->lines == isLines) {
|
||||
vertices.resize(prevVerts + vertCount);
|
||||
buildIndices(indices, indexCount, prevVerts, polygons, polyType);
|
||||
if(last->vertEnd)
|
||||
last->vertEnd += vertCount;
|
||||
else
|
||||
last->vertEnd = last->vertStart + vertCount - 1;
|
||||
|
||||
return &vertices[prevVerts];
|
||||
}
|
||||
else if(last->vertEnd != 0) {
|
||||
duplicateLast();
|
||||
last->lines = isLines;
|
||||
vertices.resize(prevVerts + vertCount);
|
||||
buildIndices(indices, indexCount, prevVerts, polygons, polyType);
|
||||
last->vertEnd = last->vertStart + vertCount - 1;
|
||||
|
||||
return &vertices[prevVerts];
|
||||
}
|
||||
else {
|
||||
last->lines = isLines;
|
||||
vertices.resize(prevVerts + vertCount);
|
||||
buildIndices(indices, indexCount, prevVerts, polygons, polyType);
|
||||
last->vertEnd = last->vertStart + vertCount - 1;
|
||||
|
||||
return &vertices[prevVerts];
|
||||
}
|
||||
}
|
||||
|
||||
void VertexBufferTCV::draw(FlushCause reason) {
|
||||
if(vertices.empty())
|
||||
return;
|
||||
|
||||
if(vertBuffer.empty()) {
|
||||
GLuint buffs[24];
|
||||
glGenBuffers(24, buffs);
|
||||
for(unsigned i = 0; i < 12; ++i)
|
||||
vertBuffer.push_back(buffs[i]);
|
||||
for(unsigned i = 12; i < 24; ++i)
|
||||
indBuffer.push_back(buffs[i]);
|
||||
}
|
||||
|
||||
{
|
||||
lastRenderedMesh = reinterpret_cast<const render::RenderMesh*>(this);
|
||||
if(useVertexArrays())
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indBuffer.front());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertBuffer.front());
|
||||
|
||||
indBuffer.push_back(indBuffer.front());
|
||||
indBuffer.pop_front();
|
||||
vertBuffer.push_back(vertBuffer.front());
|
||||
vertBuffer.pop_front();
|
||||
|
||||
if(!useVertexArrays()) {
|
||||
glDisableVertexAttribArray(0);
|
||||
glDisableVertexAttribArray(1);
|
||||
glDisableVertexAttribArray(2);
|
||||
glDisableVertexAttribArray(3);
|
||||
glDisableVertexAttribArray(4);
|
||||
glDisableVertexAttribArray(5);
|
||||
}
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
glTexCoordPointer(2, GL_FLOAT, sizeof(VertexTCV), (void*)offsetof(VertexTCV, uv));
|
||||
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(VertexTCV), (void*)offsetof(VertexTCV, col));
|
||||
glVertexPointer(3, GL_FLOAT, sizeof(VertexTCV), (void*)offsetof(VertexTCV, pos));
|
||||
}
|
||||
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned short),
|
||||
&indices[0], GL_STREAM_DRAW);
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(VertexTCV), &vertices[0], GL_STREAM_DRAW);
|
||||
|
||||
drawnSteps += (unsigned)steps.size();
|
||||
bufferFlushes += 1;
|
||||
vbFlushCounts[reason] += 1;
|
||||
|
||||
RenderStep* pSteps = &steps.front();
|
||||
for(unsigned i = 0, cnt = (unsigned)steps.size(); i < cnt; ++i) {
|
||||
auto& step = pSteps[i];
|
||||
shaderUniforms = step.shaderCache;
|
||||
devices.render->switchToRenderState(*step.material);
|
||||
unsigned count = ((i+1) < cnt ? pSteps[i+1].indexOffset : (unsigned short)indices.size()) - step.indexOffset;
|
||||
|
||||
glDrawRangeElements(step.lines ? GL_LINES : GL_TRIANGLES, step.vertStart, step.vertEnd,
|
||||
count, GL_UNSIGNED_SHORT, (void*)(step.indexOffset * sizeof(unsigned short)));
|
||||
}
|
||||
|
||||
shaderUniforms = 0;
|
||||
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
steps.clear();
|
||||
last = 0;
|
||||
pNextShaderBuffer = shaderBuffer;
|
||||
}
|
||||
|
||||
VertexBufferTCV* VertexBufferTCV::fetch(const RenderState* mat) {
|
||||
auto& vbuff = tcv;
|
||||
if(vbuff.last == 0 || vbuff.last->material != mat) {
|
||||
if(vbuff.steps.size() == vbMaxSteps)
|
||||
vbuff.draw(FC_StepLimit);
|
||||
|
||||
RenderStep step;
|
||||
step.material = mat;
|
||||
step.lines = false;
|
||||
step.indexOffset = (unsigned short)tcv.indices.size();
|
||||
step.vertStart = tcv.steps.empty() ? 0 : tcv.steps.back().vertEnd + 1;
|
||||
step.vertEnd = 0;
|
||||
|
||||
if(mat->shader && !mat->shader->constant) {
|
||||
step.shaderCache = tcv.pNextShaderBuffer;
|
||||
mat->shader->saveDynamicVars(tcv.pNextShaderBuffer);
|
||||
tcv.pNextShaderBuffer += mat->shader->dynamicFloats;
|
||||
}
|
||||
else {
|
||||
step.shaderCache = 0;
|
||||
}
|
||||
|
||||
vbuff.steps.push_back(step);
|
||||
vbuff.last = &vbuff.steps.back();
|
||||
}
|
||||
else if(mat->shader && !mat->shader->constant) {
|
||||
auto* buffer = vbuff.pNextShaderBuffer;
|
||||
unsigned floats = mat->shader->dynamicFloats;
|
||||
|
||||
if(buffer + floats >= vbuff.shaderBuffer + vbuff.shaderBufferSize) {
|
||||
//Flush the buffer and re-fetch the step
|
||||
vbuff.draw(FC_ShaderLimit);
|
||||
return fetch(mat);
|
||||
}
|
||||
else {
|
||||
mat->shader->saveDynamicVars(buffer);
|
||||
auto* lastStep = vbuff.last;
|
||||
|
||||
//When a shader uses a small enough amount of data, check to see if it's duplicated
|
||||
//If it is, we can batch it into one step
|
||||
if(floats > vbFloatLimit || memcmp(lastStep->shaderCache, buffer, floats * sizeof(float)) != 0) {
|
||||
tcv.steps.push_back(*lastStep);
|
||||
lastStep = &tcv.steps.back();
|
||||
tcv.last = lastStep;
|
||||
|
||||
lastStep->shaderCache = buffer;
|
||||
lastStep->indexOffset = (unsigned short)tcv.indices.size();
|
||||
lastStep->vertStart = lastStep->vertEnd + 1;
|
||||
lastStep->vertEnd = 0;
|
||||
vbuff.pNextShaderBuffer += floats;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &vbuff;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "lighting.h"
|
||||
#include "compat/gl.h"
|
||||
#include <set>
|
||||
|
||||
namespace render {
|
||||
namespace light {
|
||||
|
||||
PointLight::PointLight() : att_constant(1), att_linear(0), att_quadratic(0) {
|
||||
}
|
||||
|
||||
float PointLight::distanceFrom(const vec3f& pos) {
|
||||
return (float)pos.distanceToSQ(position);
|
||||
}
|
||||
|
||||
void setPosition(const vec3f& pos);
|
||||
void setPosition(const vec3d& pos);
|
||||
|
||||
void PointLight::enable(unsigned& lightIndex, const vec3f& offset) {
|
||||
GLenum LIGHT = GL_LIGHT0 + lightIndex; ++lightIndex;
|
||||
glEnable(LIGHT);
|
||||
glLightfv(LIGHT,GL_DIFFUSE,(GLfloat*)&diffuse);
|
||||
glLightfv(LIGHT,GL_SPECULAR,(GLfloat*)&specular);
|
||||
|
||||
glLightf(LIGHT,GL_QUADRATIC_ATTENUATION,att_quadratic);
|
||||
|
||||
float pos[4] = {position.x + offset.x, position.y + offset.y, position.z + offset.z, 1.f};
|
||||
glLightfv(LIGHT,GL_POSITION,(GLfloat*)&pos);
|
||||
}
|
||||
|
||||
vec3f PointLight::getPosition() const {
|
||||
return position;
|
||||
}
|
||||
|
||||
float PointLight::getRadius() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
|
||||
NodePointLight::NodePointLight(scene::Node* follow) : followNode(follow) {
|
||||
follow->grab();
|
||||
}
|
||||
|
||||
NodePointLight::~NodePointLight() {
|
||||
if(followNode)
|
||||
followNode->drop();
|
||||
followNode = nullptr;
|
||||
}
|
||||
|
||||
vec3f NodePointLight::getPosition() const {
|
||||
if(!followNode)
|
||||
return position;
|
||||
return vec3f(followNode->abs_position);
|
||||
}
|
||||
|
||||
float NodePointLight::getRadius() const {
|
||||
if(!followNode)
|
||||
return radius;
|
||||
return followNode->abs_scale;
|
||||
}
|
||||
|
||||
//TODO: This is probably out of sync by one frame
|
||||
void NodePointLight::enable(unsigned& lightIndex, const vec3f& offset) {
|
||||
if(!followNode) {
|
||||
lightIndex += 1;
|
||||
return;
|
||||
}
|
||||
if(followNode->queuedDelete || !followNode->parent) {
|
||||
followNode->drop();
|
||||
followNode = nullptr;
|
||||
lightIndex += 1;
|
||||
return;
|
||||
}
|
||||
const auto& v = followNode->abs_position;
|
||||
position.x = (float)v.x;
|
||||
position.y = (float)v.y;
|
||||
position.z = (float)v.z;
|
||||
PointLight::enable(lightIndex, offset);
|
||||
}
|
||||
|
||||
std::set<LightSource*> lights;
|
||||
|
||||
void registerLight(LightSource* light) {
|
||||
lights.insert(light);
|
||||
}
|
||||
|
||||
void unregisterLight(LightSource* light) {
|
||||
lights.erase(light);
|
||||
}
|
||||
|
||||
void destroyLights() {
|
||||
for(auto it = lights.begin(); it != lights.end(); ++it)
|
||||
delete *it;
|
||||
lights.clear();
|
||||
}
|
||||
|
||||
void resetLights() {
|
||||
Colorf diffuse, specular;
|
||||
float pos[4] = {0.f, 0.f, 0.f, 1.f};
|
||||
|
||||
for(unsigned i = 1; i < 8; ++i)
|
||||
glDisable(GL_LIGHT0 + i);
|
||||
|
||||
glEnable(GL_LIGHT0);
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat*)&diffuse);
|
||||
glLightfv(GL_LIGHT0, GL_SPECULAR, (GLfloat*)&specular);
|
||||
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.f);
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat*)&pos);
|
||||
}
|
||||
|
||||
//Finds up to <maximum> nearest lights to <from>
|
||||
unsigned findNearestLights(const vec3f& from, LightSource** pLights, unsigned maximum) {
|
||||
unsigned found = 0;
|
||||
float distances[GL_MAX_LIGHTS];
|
||||
if(maximum > GL_MAX_LIGHTS)
|
||||
maximum = GL_MAX_LIGHTS;
|
||||
|
||||
for(auto i = lights.begin(), end = lights.end(); i != end; ++i) {
|
||||
float dist = (*i)->distanceFrom(from);
|
||||
|
||||
for(unsigned d = 0; d < found; ++d) {
|
||||
if(distances[d] > dist) {
|
||||
for(unsigned x = found-1; x > d; --x) {
|
||||
distances[x] = distances[x-1];
|
||||
pLights[x] = pLights[x-1];
|
||||
}
|
||||
distances[d] = dist;
|
||||
pLights[d] = *i;
|
||||
goto nextLight;
|
||||
}
|
||||
}
|
||||
|
||||
if(found < maximum) {
|
||||
pLights[found] = *i;
|
||||
distances[found] = dist;
|
||||
++found;
|
||||
}
|
||||
|
||||
nextLight:;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "vec3.h"
|
||||
#include "color.h"
|
||||
#include "scene/node.h"
|
||||
|
||||
namespace render {
|
||||
namespace light {
|
||||
|
||||
struct LightSource {
|
||||
//Returns a distance that can be used in comparative distance checks (not actual distance)
|
||||
virtual float distanceFrom(const vec3f& pos) = 0;
|
||||
|
||||
//Enables the light, and advance <lightIndex>
|
||||
virtual void enable(unsigned& lightIndex, const vec3f& offset) = 0;
|
||||
|
||||
//Get data from light
|
||||
virtual vec3f getPosition() const = 0;
|
||||
virtual float getRadius() const = 0;
|
||||
|
||||
virtual ~LightSource() {}
|
||||
};
|
||||
|
||||
struct PointLight : public LightSource {
|
||||
vec3f position;
|
||||
float radius;
|
||||
|
||||
float att_constant, att_linear, att_quadratic;
|
||||
Colorf diffuse, specular;
|
||||
|
||||
float distanceFrom(const vec3f& pos);
|
||||
|
||||
void enable(unsigned& lightIndex, const vec3f& offset);
|
||||
vec3f getPosition() const;
|
||||
float getRadius() const;
|
||||
PointLight();
|
||||
};
|
||||
|
||||
struct NodePointLight : public PointLight {
|
||||
scene::Node* followNode;
|
||||
|
||||
void enable(unsigned& lightIndex, const vec3f& offset);
|
||||
vec3f getPosition() const;
|
||||
float getRadius() const;
|
||||
NodePointLight(scene::Node* follow);
|
||||
~NodePointLight();
|
||||
};
|
||||
|
||||
void registerLight(LightSource* light);
|
||||
void unregisterLight(LightSource* light);
|
||||
//Removes all registered lights
|
||||
void destroyLights();
|
||||
//Returns lighting to its default state (a single white light at 0,0,0)
|
||||
void resetLights();
|
||||
|
||||
//Finds up to <maximum> nearest lights to <from>
|
||||
unsigned findNearestLights(const vec3f& from, LightSource** lights, unsigned maximum);
|
||||
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "render/obj_loader.h"
|
||||
#include "compat/misc.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace render {
|
||||
|
||||
struct VertexIndex {
|
||||
int a, b, c;
|
||||
|
||||
VertexIndex(int A, int B, int C) : a(A), b(B), c(C) {}
|
||||
|
||||
bool operator<(const VertexIndex& other) const {
|
||||
return memcmp(this, &other, sizeof(int) * 3) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
void loadMeshOBJ(const char* filename, Mesh& mesh) {
|
||||
// Read all the appropriate separate data arrays
|
||||
std::vector<vec3f> vertices;
|
||||
std::vector<vec3f> normals;
|
||||
std::vector<float> ucoord;
|
||||
std::vector<float> vcoord;
|
||||
|
||||
float scale = 1.f;
|
||||
|
||||
std::string line;
|
||||
std::ifstream file(filename);
|
||||
vec3f vec;
|
||||
|
||||
std::map<VertexIndex, unsigned> vertex_map;
|
||||
|
||||
if(file.is_open()) {
|
||||
while(file.good()) {
|
||||
std::getline(file, line);
|
||||
|
||||
if(line.size() == 0)
|
||||
continue;
|
||||
|
||||
switch(line[0]) {
|
||||
case 's':
|
||||
if(line[1] == 'c') { //Non-standard extension 'scaling factor'
|
||||
sscanf(line.c_str(), "sc %f", &scale);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
switch(line[1]) {
|
||||
case ' ':
|
||||
// Vertex
|
||||
sscanf(line.c_str(), "v %f %f %f", &vec.x, &vec.y, &vec.z);
|
||||
vec *= scale;
|
||||
vertices.push_back(vec);
|
||||
break;
|
||||
case 'n':
|
||||
// Normal
|
||||
sscanf(line.c_str(), "vn %f %f %f", &vec.x, &vec.y, &vec.z);
|
||||
normals.push_back(vec);
|
||||
break;
|
||||
case 't':
|
||||
// Texture
|
||||
float u, v;
|
||||
sscanf(line.c_str(), "vt %f %f", &u, &v);
|
||||
ucoord.push_back(u);
|
||||
vcoord.push_back(v);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
// Face
|
||||
int vertsThisLine = 3;
|
||||
int vertexIndex[4];
|
||||
int uvIndex[4];
|
||||
int normalIndex[4];
|
||||
|
||||
int elemIndex[4];
|
||||
|
||||
int items = sscanf(line.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d",
|
||||
&vertexIndex[0], &uvIndex[0], &normalIndex[0],
|
||||
&vertexIndex[1], &uvIndex[1], &normalIndex[1],
|
||||
&vertexIndex[2], &uvIndex[2], &normalIndex[2],
|
||||
&vertexIndex[3], &uvIndex[3], &normalIndex[3]);
|
||||
vertsThisLine = items / 3;
|
||||
|
||||
int vertCount = (int)vertices.size();
|
||||
int normCount = (int)normals.size();
|
||||
int uvCount = (int)ucoord.size();
|
||||
|
||||
for(int i = 0; i < vertsThisLine; ++i) {
|
||||
if(vertexIndex[i] > 0)
|
||||
vertexIndex[i] -= 1;
|
||||
if(normalIndex[i] > 0)
|
||||
normalIndex[i] -= 1;
|
||||
if(uvIndex[i] > 0)
|
||||
uvIndex[i] -= 1;
|
||||
|
||||
if(vertexIndex[i] >= vertCount || vertexIndex[i] < -vertCount)
|
||||
goto ignoreFace;
|
||||
if(normalIndex[i] >= normCount || normalIndex[i] < -normCount)
|
||||
goto ignoreFace;
|
||||
if(uvIndex[i] >= uvCount || uvIndex[i] < -uvCount)
|
||||
goto ignoreFace;
|
||||
|
||||
if(vertexIndex[i] < 0)
|
||||
vertexIndex[i] = vertCount + vertexIndex[i];
|
||||
|
||||
if(normalIndex[i] < 0)
|
||||
normalIndex[i] = normCount + normalIndex[i];
|
||||
|
||||
if(uvIndex[i] < 0)
|
||||
uvIndex[i] = uvCount + uvIndex[i];
|
||||
|
||||
VertexIndex ind(vertexIndex[i], normalIndex[i], uvIndex[i]);
|
||||
|
||||
auto it = vertex_map.find(ind);
|
||||
if(it == vertex_map.end()) {
|
||||
Vertex v;
|
||||
v.position = vertices[vertexIndex[i]];
|
||||
v.normal = normals[normalIndex[i]];
|
||||
v.u = ucoord[uvIndex[i]];
|
||||
v.v = 1.f - vcoord[uvIndex[i]];
|
||||
|
||||
elemIndex[i] = mesh.vertices.size();
|
||||
mesh.vertices.push_back(v);
|
||||
vertex_map[ind] = elemIndex[i];
|
||||
}
|
||||
else {
|
||||
elemIndex[i] = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if(vertsThisLine >= 3)
|
||||
mesh.faces.push_back(Mesh::Face(elemIndex[0], elemIndex[1], elemIndex[2]));
|
||||
if(vertsThisLine >= 4)
|
||||
mesh.faces.push_back(Mesh::Face(elemIndex[0], elemIndex[2], elemIndex[3]));
|
||||
ignoreFace:
|
||||
break;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
void loadMeshOBJ(const char* filename, Mesh& mesh);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,362 @@
|
||||
#include "render/ogex_loader.h"
|
||||
#include "str_util.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include "vec2.h"
|
||||
#include "matrix.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
struct GEXGeometry {
|
||||
std::vector<vec3f> positions, normals;
|
||||
std::vector<vec2f> uvs[3];
|
||||
std::vector<Colorf> colors;
|
||||
std::vector<Mesh::Face> faces;
|
||||
};
|
||||
|
||||
struct GEXNode {
|
||||
Matrix transform;
|
||||
std::string meshID;
|
||||
std::vector<GEXNode*> nodes;
|
||||
|
||||
GEXNode& makeChild() {
|
||||
auto* node = new GEXNode();
|
||||
nodes.push_back(node);
|
||||
return *node;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for(auto i = nodes.begin(), end = nodes.end(); i != end; ++i) {
|
||||
(**i).clear();
|
||||
delete *i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GEXSection {
|
||||
std::string id, attrib;
|
||||
GEXNode* node;
|
||||
};
|
||||
|
||||
struct StringView {
|
||||
std::string str;
|
||||
std::string::size_type start;
|
||||
|
||||
StringView() : start(0) {
|
||||
}
|
||||
|
||||
StringView(const std::string& s) : start(0), str(s) {
|
||||
}
|
||||
|
||||
void operator=(const std::string& s) {
|
||||
str = s;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
std::string::size_type find(char c) {
|
||||
auto x = str.find(c, start);
|
||||
if(x != std::string::npos)
|
||||
return x - start;
|
||||
else
|
||||
return x;
|
||||
}
|
||||
|
||||
void substr(std::string& out, std::string::size_type first, std::string::size_type count) {
|
||||
out.assign(str.begin() + (start + first), str.begin() + (start + first + count));
|
||||
}
|
||||
|
||||
void advance(std::string::size_type off) {
|
||||
start += off;
|
||||
}
|
||||
};
|
||||
|
||||
static std::string& extractSegment(StringView& data, std::string& out) {
|
||||
out.clear();
|
||||
auto start = data.find('{');
|
||||
auto pos = data.find('}');
|
||||
if(pos == std::string::npos || start == std::string::npos || start > pos)
|
||||
return out;
|
||||
data.substr(out, start+1,pos-(start+1));
|
||||
data.advance(pos+1);
|
||||
return out;
|
||||
}
|
||||
|
||||
void loadMeshOGEX(const char* filename, Mesh& mesh) {
|
||||
std::string line, segment;
|
||||
StringView data;
|
||||
std::ifstream file(filename);
|
||||
|
||||
if(!file.is_open())
|
||||
return;
|
||||
|
||||
unsigned row = 0;
|
||||
|
||||
GEXSection section;
|
||||
GEXNode root;
|
||||
GEXNode* node = &root;
|
||||
|
||||
std::stack<GEXSection> sections;
|
||||
std::unordered_map<std::string, std::unique_ptr<GEXGeometry>> parts;
|
||||
GEXGeometry* part = nullptr;
|
||||
|
||||
char name[129], buffer[129];
|
||||
|
||||
std::function<bool(std::string&)> innerParse;
|
||||
|
||||
while(file.good()) {
|
||||
std::getline(file, line);
|
||||
line = trim(line);
|
||||
|
||||
if(line.size() == 0)
|
||||
continue;
|
||||
data = line;
|
||||
|
||||
if(line == "{") {
|
||||
sections.push(section);
|
||||
section.id.clear();
|
||||
section.attrib.clear();
|
||||
section.node = nullptr;
|
||||
}
|
||||
else if(line == "}") {
|
||||
if(!sections.empty())
|
||||
sections.pop();
|
||||
if(sections.empty())
|
||||
node = &root;
|
||||
else if(sections.top().node)
|
||||
node = sections.top().node;
|
||||
innerParse = nullptr;
|
||||
}
|
||||
else if(line == "float[16]" || line == "float[3]" || line == "float[2]") {
|
||||
//Simplify parsing
|
||||
}
|
||||
else if(innerParse) {
|
||||
if(innerParse(line))
|
||||
innerParse = nullptr;
|
||||
}
|
||||
else if(sections.size() >= 0) {
|
||||
std::string head, tail;
|
||||
|
||||
if(line[0] != '{' && splitKeyValue(line, head, tail, " ") && !tail.empty()) {
|
||||
StringView value(tail);
|
||||
|
||||
//Parse x {...}, x (...) {...}, and x $...\n{
|
||||
if(tail[0] == '{') {
|
||||
if(head == "ObjectRef") {
|
||||
if(tail.front() == '{' && tail.back() == '}')
|
||||
tail = tail.substr(1, tail.size()-2);
|
||||
value.advance(1);
|
||||
auto id = trim(extractSegment(value, segment), "\"$");
|
||||
if(!id.empty() && node)
|
||||
node->meshID = id;
|
||||
}
|
||||
}
|
||||
else if(tail[0] == '$') {
|
||||
if(sscanf(tail.c_str(), "$%128s", buffer) == 1)
|
||||
section.attrib = buffer;
|
||||
|
||||
section.id = head;
|
||||
if(head == "Node" || head == "GeometryNode") {
|
||||
node = &node->makeChild();
|
||||
section.node = node;
|
||||
}
|
||||
}
|
||||
else if(tail[0] == '(') {
|
||||
if(sscanf(tail.c_str() + 1, "attrib = \"%128s", buffer) == 1)
|
||||
section.attrib = trim(std::string(buffer), "\")");
|
||||
|
||||
if(head == "Mesh") {
|
||||
section.id = "Mesh";
|
||||
part = new GEXGeometry();
|
||||
parts[sections.top().attrib].reset(part);
|
||||
}
|
||||
else if(head == "VertexArray") {
|
||||
section.id = head;
|
||||
if(section.attrib == "position") {
|
||||
innerParse = [&](std::string& line) -> bool {
|
||||
std::vector<std::string> values;
|
||||
extractSegment(data, segment);
|
||||
while(!segment.empty()) {
|
||||
split(segment, values, ",", true);
|
||||
if(values.size() == 3)
|
||||
part->positions.push_back(vec3f(atof(values[0].c_str()), atof(values[1].c_str()), atof(values[2].c_str())));
|
||||
values.clear();
|
||||
|
||||
extractSegment(data, segment);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else if(section.attrib == "normal") {
|
||||
innerParse = [&](std::string& line) -> bool {
|
||||
std::vector<std::string> values;
|
||||
extractSegment(data, segment);
|
||||
while(!segment.empty()) {
|
||||
split(segment, values, ",", true);
|
||||
if(values.size() == 3)
|
||||
part->normals.push_back(vec3f(atof(values[0].c_str()), atof(values[1].c_str()), atof(values[2].c_str())));
|
||||
values.clear();
|
||||
|
||||
extractSegment(data, segment);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else if(section.attrib == "texcoord" || section.attrib == "texcoord[1]" || section.attrib == "texcoord[2]") {
|
||||
int index = 0;
|
||||
if(section.attrib == "texcoord")
|
||||
index = 0;
|
||||
else if(section.attrib == "texcoord[1]")
|
||||
index = 1;
|
||||
else if(section.attrib == "texcoord[2]")
|
||||
index = 2;
|
||||
|
||||
innerParse = [&,index](std::string& line) -> bool {
|
||||
std::vector<std::string> values;
|
||||
extractSegment(data, segment);
|
||||
while(!segment.empty()) {
|
||||
split(segment, values, ",", true);
|
||||
if(values.size() == 2) {
|
||||
vec2f uv = vec2f(atof(values[0].c_str()), atof(values[1].c_str()));
|
||||
part->uvs[index].push_back(uv);
|
||||
}
|
||||
values.clear();
|
||||
|
||||
extractSegment(data, segment);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else if(section.attrib == "color") {
|
||||
innerParse = [&](std::string& line) -> bool {
|
||||
std::vector<std::string> values;
|
||||
extractSegment(data, segment);
|
||||
while(!segment.empty()) {
|
||||
split(segment, values, ",", true);
|
||||
if(values.size() == 3)
|
||||
part->colors.push_back(Colorf(atof(values[0].c_str()), atof(values[1].c_str()), atof(values[2].c_str())));
|
||||
values.clear();
|
||||
|
||||
extractSegment(data, segment);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
else if(head == "IndexArray") {
|
||||
section.id = head;
|
||||
goto readIndexArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(line == "IndexArray") {
|
||||
section.id = "IndexArray";
|
||||
readIndexArray:
|
||||
innerParse = [&](std::string& line) -> bool {
|
||||
std::vector<std::string> values;
|
||||
extractSegment(data, segment);
|
||||
while(!segment.empty()) {
|
||||
split(segment, values, ",", true);
|
||||
if(values.size() == 3)
|
||||
part->faces.push_back(Mesh::Face(atoi(values[0].c_str()), atoi(values[1].c_str()), atoi(values[2].c_str())));
|
||||
values.clear();
|
||||
|
||||
extractSegment(data, segment);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else if(line == "Transform") {
|
||||
row = 0;
|
||||
innerParse = [&](std::string& line) -> bool {
|
||||
if(!node)
|
||||
return true;
|
||||
line = trim(line, "{}");
|
||||
std::vector<std::string> values;
|
||||
split(line, values, ",", true);
|
||||
if(values.size() == 4)
|
||||
for(unsigned i = 0; i < 4; ++i)
|
||||
node->transform[row*4 + i] = toNumber<double>(values[i]);
|
||||
++row;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(GEXNode&,Matrix)> processNode;
|
||||
|
||||
auto process = [&](GEXNode& n, Matrix transform) {
|
||||
Matrix mat = n.transform * transform;
|
||||
|
||||
auto p = parts.find(n.meshID);
|
||||
if(p != parts.end()) {
|
||||
auto& part = *p->second.get();
|
||||
unsigned short indexBase = mesh.vertices.size();
|
||||
|
||||
for(size_t i = 0; i < part.positions.size(); ++i) {
|
||||
Vertex vert;
|
||||
vert.position = mat * part.positions[i];
|
||||
if(i < part.normals.size())
|
||||
vert.normal = mat.rotate(part.normals[i]);
|
||||
else
|
||||
vert.normal = vert.position.normalized();
|
||||
if(i < part.uvs[0].size()) {
|
||||
vert.u = part.uvs[0][i].x;
|
||||
vert.v = part.uvs[0][i].y;
|
||||
}
|
||||
mesh.vertices.push_back(vert);
|
||||
|
||||
//Optional second/thid uv
|
||||
{
|
||||
auto& uvs = part.uvs[1];
|
||||
auto& uvs2 = part.uvs[2];
|
||||
if(i < uvs.size() || i < uvs2.size()) {
|
||||
if(mesh.uvs2.size() < indexBase)
|
||||
mesh.uvs2.resize(indexBase);
|
||||
vec4f uv;
|
||||
if(i < uvs.size()) {
|
||||
uv.x = uvs[i].x;
|
||||
uv.y = uvs[i].y;
|
||||
}
|
||||
if(i < uvs2.size()) {
|
||||
uv.z = uvs2[i].x;
|
||||
uv.w = uvs2[i].y;
|
||||
}
|
||||
mesh.uvs2.push_back(uv);
|
||||
}
|
||||
}
|
||||
|
||||
//Optional vertex color
|
||||
if(i < part.colors.size()) {
|
||||
if(mesh.colors.size() < indexBase)
|
||||
mesh.colors.resize(indexBase);
|
||||
mesh.colors.push_back(part.colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < part.faces.size(); ++i) {
|
||||
Mesh::Face face = part.faces[i];
|
||||
face.a += indexBase;
|
||||
face.b += indexBase;
|
||||
face.c += indexBase;
|
||||
|
||||
if(face.a < mesh.vertices.size() && face.b < mesh.vertices.size() && face.c < mesh.vertices.size())
|
||||
mesh.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto i = n.nodes.begin(), end = n.nodes.end(); i != end; ++i)
|
||||
processNode(**i, mat);
|
||||
};
|
||||
|
||||
processNode = process;
|
||||
process(root, Matrix());
|
||||
root.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
void loadMeshOGEX(const char* filename, Mesh& mesh);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "aabbox.h"
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace render {
|
||||
|
||||
class RenderMesh {
|
||||
public:
|
||||
virtual void resetToMesh(const Mesh& mesh) = 0;
|
||||
virtual const RenderMesh* selectLOD(double distance) const = 0;
|
||||
virtual void setLOD(double distance, const RenderMesh* mesh) = 0;
|
||||
virtual const AABBoxf& getBoundingBox() const = 0;
|
||||
virtual const Mesh& getMesh() const = 0;
|
||||
|
||||
virtual unsigned getMeshBytes() const = 0;
|
||||
|
||||
virtual void render() const = 0;
|
||||
virtual ~RenderMesh() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "render_state.h"
|
||||
#include "memory/AllocOnlyPool.h"
|
||||
#include "threads.h"
|
||||
#include <string.h>
|
||||
|
||||
namespace render {
|
||||
|
||||
memory::AllocOnlyPool<RenderState,threads::Mutex> renderStatePool(256);
|
||||
|
||||
RenderState::RenderState() : culling(FC_Back),
|
||||
depthTest(DT_Less), depthWrite(true), lighting(true),
|
||||
normalizeNormals(false), baseMat(MAT_Solid), drawMode(DM_Fill),
|
||||
wrapHorizontal(TW_Repeat), wrapVertical(TW_Repeat),
|
||||
filterMin(TF_Linear), filterMag(TF_Linear),
|
||||
mipmap(true), cachePixels(false),
|
||||
diffuse(1,1,1,1), specular(1,1,1,1), shininess(3.f), textures(), shader(0), constant(true)
|
||||
{
|
||||
memset(textures, 0, sizeof(textures));
|
||||
}
|
||||
|
||||
void* RenderState::operator new(size_t size) {
|
||||
return renderStatePool.alloc();
|
||||
}
|
||||
|
||||
void RenderState::operator delete(void* p) {
|
||||
renderStatePool.dealloc((RenderState*)p);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include "compat/misc.h"
|
||||
#include "render/shader.h"
|
||||
#include "color.h"
|
||||
#include <string>
|
||||
|
||||
#ifndef RENDER_MAX_TEXTURES
|
||||
#define RENDER_MAX_TEXTURES 8
|
||||
#endif
|
||||
|
||||
namespace render {
|
||||
class Texture;
|
||||
|
||||
unsigned_enum(FaceCulling) {
|
||||
FC_None,
|
||||
FC_Front,
|
||||
FC_Back,
|
||||
FC_Both
|
||||
};
|
||||
|
||||
unsigned_enum(DepthTest) {
|
||||
DT_Never,
|
||||
DT_Less,
|
||||
DT_Equal,
|
||||
DT_LessEqual,
|
||||
DT_Greater,
|
||||
DT_NotEqual,
|
||||
DT_GreaterEqual,
|
||||
DT_Always,
|
||||
|
||||
//A depth test that always passes is equivalent to no test
|
||||
DT_NoDepthTest = DT_Always,
|
||||
};
|
||||
|
||||
unsigned_enum(TextureWrap) {
|
||||
TW_Repeat,
|
||||
TW_Clamp,
|
||||
TW_ClampEdge,
|
||||
TW_Mirror
|
||||
};
|
||||
|
||||
unsigned_enum(TextureFilter) {
|
||||
TF_Nearest,
|
||||
TF_Linear
|
||||
};
|
||||
|
||||
unsigned_enum(BaseMaterial) {
|
||||
MAT_Solid,
|
||||
MAT_Add,
|
||||
MAT_Alpha,
|
||||
MAT_Font,
|
||||
|
||||
MAT_Overlay,
|
||||
};
|
||||
|
||||
unsigned_enum(DrawMode) {
|
||||
DM_Fill,
|
||||
DM_Line,
|
||||
};
|
||||
|
||||
//Holds the target render state
|
||||
//=====
|
||||
//Notes:
|
||||
// Alpha test is always >0
|
||||
// Normalize Normals requires uniform scaling
|
||||
struct RenderState {
|
||||
FaceCulling culling : 2;
|
||||
DepthTest depthTest : 4;
|
||||
BaseMaterial baseMat : 3;
|
||||
DrawMode drawMode : 2;
|
||||
bool depthWrite : 1;
|
||||
bool lighting : 1;
|
||||
bool normalizeNormals : 1;
|
||||
TextureWrap wrapHorizontal : 2;
|
||||
TextureWrap wrapVertical : 2;
|
||||
TextureFilter filterMin : 2;
|
||||
TextureFilter filterMag : 2;
|
||||
bool mipmap : 1;
|
||||
bool cachePixels : 1;
|
||||
bool constant : 1;
|
||||
|
||||
Colorf diffuse, specular;
|
||||
float shininess;
|
||||
|
||||
Texture* textures[RENDER_MAX_TEXTURES];
|
||||
const Shader* shader;
|
||||
|
||||
void* operator new(size_t size);
|
||||
void operator delete(void* p);
|
||||
|
||||
RenderState();
|
||||
};
|
||||
|
||||
struct MaterialGroup {
|
||||
std::string prefix;
|
||||
RenderState base;
|
||||
std::vector<RenderState*> materials;
|
||||
std::vector<std::string> names;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace render {
|
||||
|
||||
class ShaderProgram {
|
||||
public:
|
||||
virtual int compile() = 0;
|
||||
virtual ~ShaderProgram() {}
|
||||
};
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
ShaderProgram* program;
|
||||
|
||||
enum VarType {
|
||||
VT_invalid = 0,
|
||||
VT_int,
|
||||
VT_int2,
|
||||
VT_int3,
|
||||
VT_int4,
|
||||
VT_float,
|
||||
VT_float2,
|
||||
VT_float3,
|
||||
VT_float4,
|
||||
VT_mat3
|
||||
};
|
||||
|
||||
struct Variable {
|
||||
VarType type : 16;
|
||||
unsigned short count : 16;
|
||||
union {
|
||||
mutable int* _ints;
|
||||
mutable float* _floats;
|
||||
};
|
||||
union {
|
||||
mutable void* _args;
|
||||
};
|
||||
union {
|
||||
void (*_intcall)(int*,unsigned short,void*);
|
||||
void (*_floatcall)(float*,unsigned short,void*);
|
||||
};
|
||||
bool constant;
|
||||
|
||||
Variable() : type(VT_invalid), count(0), _ints(0), _args(0), _intcall(0), constant(true) {}
|
||||
Variable(VarType Type, unsigned short Count) : type(Type), count(Count), _args(0), _intcall(0), constant(true) {
|
||||
switch(Type) {
|
||||
case VT_int:
|
||||
_ints = new int[Count*1]; break;
|
||||
case VT_int2:
|
||||
_ints = new int[Count*2]; break;
|
||||
case VT_int3:
|
||||
_ints = new int[Count*3]; break;
|
||||
case VT_int4:
|
||||
_ints = new int[Count*4]; break;
|
||||
case VT_float:
|
||||
_floats = new float[Count*1]; break;
|
||||
case VT_float2:
|
||||
_floats = new float[Count*2]; break;
|
||||
case VT_float3:
|
||||
_floats = new float[Count*3]; break;
|
||||
case VT_float4:
|
||||
_floats = new float[Count*4]; break;
|
||||
case VT_mat3:
|
||||
_floats = new float[Count*9]; break;
|
||||
case VT_invalid:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Variable> vars;
|
||||
unsigned dynamicFloats;
|
||||
|
||||
//Whether the shader's inputs are constant across a frame
|
||||
bool constant;
|
||||
|
||||
virtual int compile() = 0;
|
||||
|
||||
virtual void addVariable(const std::string& name, const Variable& var) {
|
||||
vars.push_back(var);
|
||||
if(!var.constant) {
|
||||
constant = false;
|
||||
switch(var.type) {
|
||||
case VT_int:
|
||||
case VT_float:
|
||||
dynamicFloats += var.count; break;
|
||||
case VT_int2:
|
||||
case VT_float2:
|
||||
dynamicFloats += 2 * var.count; break;
|
||||
case VT_int3:
|
||||
case VT_float3:
|
||||
dynamicFloats += 3 * var.count; break;
|
||||
case VT_int4:
|
||||
case VT_float4:
|
||||
dynamicFloats += 4 * var.count; break;
|
||||
case VT_invalid:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void bind(float* dynamicBuffer) const = 0;
|
||||
virtual void updateDynamicVars() const = 0;
|
||||
virtual void saveDynamicVars(float* buffer) const = 0;
|
||||
virtual void loadDynamicVars(float* buffer) const = 0;
|
||||
|
||||
virtual ~Shader() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "obj/object.h"
|
||||
#include "scene/node.h"
|
||||
#include "main/references.h"
|
||||
#include "main/logging.h"
|
||||
#include <map>
|
||||
|
||||
struct StateVar {
|
||||
ScriptObjectType* type;
|
||||
unsigned offset;
|
||||
};
|
||||
|
||||
std::map<std::string,unsigned> vars;
|
||||
std::vector<StateVar> stateVars;
|
||||
|
||||
unsigned registerVar(const std::string& name) {
|
||||
auto entry = vars.find(name);
|
||||
if(entry == vars.end()) {
|
||||
unsigned index = (unsigned)vars.size();
|
||||
vars[name] = index;
|
||||
return index;
|
||||
}
|
||||
else {
|
||||
return entry->second;
|
||||
}
|
||||
}
|
||||
|
||||
void prepareShaderStateVars() {
|
||||
stateVars.resize(vars.size());
|
||||
for(auto i = vars.begin(), end = vars.end(); i != end; ++i) {
|
||||
StateVar& var = stateVars[i->second];
|
||||
var.type = 0;
|
||||
|
||||
auto pos = i->first.find("::");
|
||||
if(pos == std::string::npos) {
|
||||
error("Shader State Variable: '%s' is not a state (should be ObjectType::MemberName)", i->first.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string objName = i->first.substr(0,pos), memberName = i->first.substr(pos+2);
|
||||
|
||||
ScriptObjectType* type = getScriptObjectType(objName);
|
||||
if(type == 0) {
|
||||
error("Shader State Variable: '%s' is not an object type (from '%s')", objName.c_str(), i->first.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
bool memberFound = false;
|
||||
|
||||
//Locate the member, and make sure it is a double
|
||||
const StateDefinition& states = *type->states;
|
||||
unsigned base = sizeof(Object);
|
||||
states.align(base);
|
||||
for(unsigned j = 0; j < states.types.size(); ++j) {
|
||||
auto& member = states.types[j];
|
||||
if(member.name == memberName) {
|
||||
memberFound = true;
|
||||
|
||||
if(member.def->type == "double") {
|
||||
var.offset = base + member.offset;
|
||||
var.type = type;
|
||||
}
|
||||
else {
|
||||
error("Shader State Variable: Only doubles are supported");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!memberFound) {
|
||||
error("Shader State Variable: %s has no member of type '%s'", objName.c_str(), memberName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clearShaderStateVars() {
|
||||
stateVars.clear();
|
||||
vars.clear();
|
||||
}
|
||||
|
||||
void shader_statevars(float* out, unsigned short n, void* args) {
|
||||
if(!scene::renderingNode)
|
||||
return;
|
||||
|
||||
Object* obj = scene::renderingNode->obj;
|
||||
if(obj) {
|
||||
int* indices = (int*)args;
|
||||
char* base = (char*)obj;
|
||||
|
||||
for(unsigned short i = 0; i < n; ++i) {
|
||||
const StateVar& var = stateVars[indices[i]];
|
||||
if(obj->type == var.type)
|
||||
out[i] = (float)*(double*)(base + var.offset);
|
||||
else
|
||||
out[i] = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(unsigned short i = 0; i < n; ++i)
|
||||
out[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "render/spritesheet.h"
|
||||
|
||||
void shader_sprite_pos(float* output, unsigned short,void*) {
|
||||
if(render::SpriteSheet::current) {
|
||||
vec4f pos = render::SpriteSheet::current->getSourceUV(render::SpriteSheet::currentIndex);
|
||||
output[0] = pos.x;
|
||||
output[1] = pos.y;
|
||||
output[2] = pos.z;
|
||||
output[3] = pos.w;
|
||||
}
|
||||
else {
|
||||
output[0] = 0.f;
|
||||
output[1] = 0.f;
|
||||
output[2] = 1.f;
|
||||
output[3] = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
namespace render {
|
||||
|
||||
const SpriteSheet* SpriteSheet::current = nullptr;
|
||||
unsigned SpriteSheet::currentIndex = 0;
|
||||
|
||||
SpriteSheet::SpriteSheet()
|
||||
: mode(SM_Horizontal), width(32), height(32), spacing(0), perLine(0), dirty(true) {
|
||||
}
|
||||
|
||||
inline vec2i getXY(const SpriteSheet& sheet, unsigned index) {
|
||||
if(sheet.dirty) {
|
||||
auto* texture = sheet.material.textures[0];
|
||||
if(!texture) {
|
||||
sheet.dirty = false;
|
||||
sheet.perLine = 0;
|
||||
return vec2i();
|
||||
}
|
||||
else if(!texture->loaded) {
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
sheet.dirty = false;
|
||||
sheet.perLine = 0;
|
||||
|
||||
if(!texture)
|
||||
return vec2i();
|
||||
|
||||
if(sheet.mode == SM_Horizontal) {
|
||||
unsigned imgWidth = texture->size.width;
|
||||
if(imgWidth != 0)
|
||||
sheet.perLine = (imgWidth - sheet.spacing) / (sheet.width + sheet.spacing);
|
||||
}
|
||||
else { //SM_Vertical:
|
||||
unsigned imgHeight = texture->size.height;
|
||||
if(imgHeight != 0)
|
||||
sheet.perLine = (imgHeight - sheet.spacing) / (sheet.height + sheet.spacing);
|
||||
}
|
||||
}
|
||||
|
||||
if(sheet.mode == SM_Horizontal) {
|
||||
int perRow = sheet.perLine;
|
||||
if(perRow != 0)
|
||||
return vec2i(index % perRow, index / perRow);
|
||||
}
|
||||
else { //SM_Vertical
|
||||
int perCol = sheet.perLine;
|
||||
if(perCol != 0)
|
||||
return vec2i(index / perCol, index % perCol);
|
||||
}
|
||||
|
||||
return vec2i();
|
||||
}
|
||||
|
||||
vec4f SpriteSheet::getSourceUV(unsigned index) const {
|
||||
if(material.textures[0]) {
|
||||
const vec2i imgSize = material.textures[0]->size;
|
||||
if(imgSize.width == 0 || imgSize.height == 0)
|
||||
return vec4f();
|
||||
|
||||
switch(mode) {
|
||||
case SM_Horizontal: {
|
||||
unsigned perRow = (imgSize.width - spacing) / (width + spacing);
|
||||
|
||||
float x = float((index % perRow) * (width + spacing) + spacing) / float(imgSize.width);
|
||||
float y = float((index / perRow) * (height + spacing) + spacing) / float(imgSize.height);
|
||||
|
||||
return vec4f(x,y,x + float(width)/float(imgSize.width), y + float(height)/float(imgSize.height));
|
||||
} break;
|
||||
case SM_Vertical: {
|
||||
unsigned perCol = (imgSize.height - spacing) / (height + spacing);
|
||||
|
||||
float x = float((index / perCol) * (width + spacing) + spacing) / float(imgSize.width);
|
||||
float y = float((index % perCol) * (height + spacing) + spacing) / float(imgSize.height);
|
||||
|
||||
return vec4f(x,y,x + float(width)/float(imgSize.width), y + float(height)/float(imgSize.height));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return vec4f(0,0,1.f,1.f);
|
||||
}
|
||||
|
||||
bool SpriteSheet::isPixelActive(unsigned index, const vec2i& px) const {
|
||||
recti src = getSource(index);
|
||||
if(!src.isWithin(px + src.topLeft))
|
||||
return false;
|
||||
auto* tex = material.textures[0];
|
||||
if(!tex)
|
||||
return false;
|
||||
return tex->isPixelActive(src.topLeft + px);
|
||||
}
|
||||
|
||||
recti SpriteSheet::getSource(unsigned index) const {
|
||||
vec2i pos = getXY(*this, index);
|
||||
|
||||
recti source;
|
||||
source.topLeft.x = pos.x * (width + spacing) + spacing;
|
||||
source.topLeft.y = pos.y * (height + spacing) + spacing;
|
||||
source.botRight.x = source.topLeft.x + width;
|
||||
source.botRight.y = source.topLeft.y + height;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
void SpriteSheet::getSource(unsigned index, vec2f* out) const {
|
||||
vec2i pos = getXY(*this, index);
|
||||
|
||||
out[0].x = (float)(pos.x * (width + spacing) + spacing);
|
||||
out[0].y = (float)(pos.y * (height + spacing) + spacing);
|
||||
|
||||
out[2].x = out[0].x + width;
|
||||
out[2].y = out[0].y + height;
|
||||
|
||||
out[1].x = out[2].x;
|
||||
out[1].y = out[0].y;
|
||||
|
||||
out[3].x = out[0].x;
|
||||
out[3].y = out[2].y;
|
||||
}
|
||||
|
||||
unsigned SpriteSheet::getCount() const {
|
||||
if(material.textures[0]) {
|
||||
vec2i texSize = material.textures[0]->size;
|
||||
if(texSize.width == 0 || texSize.height == 0)
|
||||
return 0;
|
||||
return (texSize.width / width) * (texSize.height / height);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteSheet::render(RenderDriver& driver, unsigned index, const vec3d* vertices, const Color* color) const {
|
||||
current = this;
|
||||
currentIndex = index;
|
||||
|
||||
vec2f texcoords[4];
|
||||
getSource(index, texcoords);
|
||||
|
||||
driver.switchToRenderState(material);
|
||||
driver.drawQuad(vertices, texcoords, color);
|
||||
|
||||
current = nullptr;
|
||||
}
|
||||
|
||||
void SpriteSheet::render(RenderDriver& driver, unsigned index, recti rectangle, const Color* color, const recti* clip, const Shader* shader, double rotation) const {
|
||||
current = this;
|
||||
currentIndex = index;
|
||||
|
||||
recti source = getSource(index);
|
||||
|
||||
if(shader) {
|
||||
static render::RenderState state;
|
||||
state = material;
|
||||
state.shader = shader;
|
||||
state.constant = false;
|
||||
|
||||
driver.drawRectangle(rectangle, &state, &source, color, clip, rotation);
|
||||
}
|
||||
else {
|
||||
driver.drawRectangle(rectangle, &material, &source, color, clip, rotation);
|
||||
}
|
||||
|
||||
current = nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "vec2.h"
|
||||
#include "vec4.h"
|
||||
#include "render/driver.h"
|
||||
#include "render/render_state.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
enum SpriteMode {
|
||||
SM_Horizontal,
|
||||
SM_Vertical,
|
||||
};
|
||||
|
||||
class SpriteSheet {
|
||||
public:
|
||||
static const SpriteSheet* current;
|
||||
static unsigned currentIndex;
|
||||
|
||||
RenderState material;
|
||||
SpriteMode mode;
|
||||
int width, height;
|
||||
int spacing;
|
||||
mutable int perLine;
|
||||
mutable bool dirty;
|
||||
|
||||
unsigned getCount() const;
|
||||
|
||||
SpriteSheet();
|
||||
|
||||
bool isPixelActive(unsigned index, const vec2i& px) const;
|
||||
vec4f getSourceUV(unsigned index) const;
|
||||
recti getSource(unsigned index) const;
|
||||
void getSource(unsigned index, vec2f* out) const;
|
||||
void render(RenderDriver& driver, unsigned index, const vec3d* vertices, const Color* color = 0) const;
|
||||
void render(RenderDriver& driver, unsigned index, recti rectangle, const Color* color = 0, const recti* clip = 0, const render::Shader* shader = 0, double rotation = 0.0) const;
|
||||
};
|
||||
|
||||
class Sprite {
|
||||
public:
|
||||
const SpriteSheet* sheet;
|
||||
const RenderState* mat;
|
||||
unsigned index;
|
||||
Color color;
|
||||
|
||||
Sprite() : sheet(0), mat(0), index(0) {
|
||||
}
|
||||
|
||||
Sprite(const SpriteSheet* Sheet, unsigned Index)
|
||||
: sheet(Sheet), mat(0), index(Index) {
|
||||
}
|
||||
|
||||
Sprite(const RenderState* Material)
|
||||
: sheet(0), mat(Material), index(0) {
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "vec2.h"
|
||||
#include "image.h"
|
||||
#include "rect.h"
|
||||
#include "render_state.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
enum TextureType {
|
||||
TT_2D,
|
||||
TT_Cubemap
|
||||
};
|
||||
|
||||
class Texture {
|
||||
public:
|
||||
TextureType type;
|
||||
vec2i size;
|
||||
bool loaded;
|
||||
bool hasMipMaps;
|
||||
mutable RenderState prevRenderState;
|
||||
|
||||
virtual bool isPixelActive(vec2i px) const = 0;
|
||||
|
||||
virtual void loadStart(Image& image, bool mipmap = true, bool cachePixels = false, unsigned lod = 0) = 0;
|
||||
virtual void loadPartial(Image& image, const recti& pixels, bool cachePixels = false, unsigned lod = 0) = 0;
|
||||
virtual void loadFinish(bool mipmap, unsigned lod = 0) = 0;
|
||||
|
||||
virtual void load(Image& image, bool mipmap = true, bool cachePixels = false) = 0;
|
||||
virtual void save(Image& image) const = 0;
|
||||
virtual void bind() = 0;
|
||||
virtual unsigned getID() const = 0;
|
||||
|
||||
virtual unsigned getTextureBytes() const = 0;
|
||||
|
||||
virtual ~Texture() {}
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
#include "vec3.h"
|
||||
#include "vec2.h"
|
||||
#include "color.h"
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace scene {
|
||||
class Node;
|
||||
};
|
||||
|
||||
namespace render {
|
||||
struct RenderState;
|
||||
|
||||
extern unsigned vbFloatLimit, vbMaxSteps;
|
||||
|
||||
enum FlushCause {
|
||||
FC_Forced,
|
||||
FC_VertexLimit,
|
||||
FC_StepLimit,
|
||||
FC_ShaderLimit,
|
||||
FC_COUNT
|
||||
};
|
||||
|
||||
extern unsigned vbFlushCounts[FC_COUNT];
|
||||
|
||||
struct RenderStep {
|
||||
const RenderState* material;
|
||||
float* shaderCache;
|
||||
unsigned short indexOffset;
|
||||
unsigned short vertStart, vertEnd;
|
||||
bool lines;
|
||||
};
|
||||
|
||||
struct VertexTCV {
|
||||
vec2f uv;
|
||||
Color col;
|
||||
vec3f pos;
|
||||
|
||||
inline void set(const vec3f& Pos) {
|
||||
col = Color();
|
||||
uv = vec2f();
|
||||
pos = Pos;
|
||||
}
|
||||
|
||||
inline void set(const vec2f& UV) {
|
||||
col = Color();
|
||||
uv = UV;
|
||||
pos = vec3f();
|
||||
}
|
||||
|
||||
inline void set(Color Col) {
|
||||
col = Col;
|
||||
uv = vec2f();
|
||||
pos = vec3f();
|
||||
}
|
||||
|
||||
inline void set(const vec3f& Pos, const vec2f& UV) {
|
||||
col = Color();
|
||||
uv = UV;
|
||||
pos = Pos;
|
||||
}
|
||||
|
||||
inline void set(const vec3f& Pos, const vec2f& UV, Color Col) {
|
||||
col = Col;
|
||||
uv = UV;
|
||||
pos = Pos;
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexCV {
|
||||
Color col;
|
||||
vec3f pos;
|
||||
};
|
||||
|
||||
struct VertexTV {
|
||||
vec2f uv;
|
||||
vec3f pos;
|
||||
};
|
||||
|
||||
class VertexBufferTCV {
|
||||
RenderStep* last;
|
||||
std::deque<unsigned> vertBuffer, indBuffer;
|
||||
std::vector<VertexTCV> vertices;
|
||||
std::vector<unsigned short> indices;
|
||||
std::vector<RenderStep> steps;
|
||||
float* shaderBuffer, *pNextShaderBuffer;
|
||||
unsigned shaderBufferSize;
|
||||
|
||||
void flushRetainingLast(FlushCause reason);
|
||||
void duplicateLast();
|
||||
|
||||
public:
|
||||
static VertexBufferTCV* fetch(const RenderState* mat);
|
||||
VertexTCV* request(unsigned polygons, unsigned polyType);
|
||||
void draw(FlushCause reason = FC_Forced);
|
||||
|
||||
VertexBufferTCV();
|
||||
~VertexBufferTCV();
|
||||
};
|
||||
|
||||
//Renders out all pending vertex buffers
|
||||
void renderVertexBuffers();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,239 @@
|
||||
#include "render/x_loader.h"
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include "matrix.h"
|
||||
#include "vec2.h"
|
||||
#include "mesh.h"
|
||||
#include "str_util.h"
|
||||
|
||||
namespace render {
|
||||
|
||||
bool startsWith(const std::string& str, const std::string& start) {
|
||||
return str.compare(0, start.size(), start) == 0;
|
||||
}
|
||||
|
||||
void loadXNormals(std::ifstream& file, std::vector<unsigned>& normIndices, std::vector<vec3f>& normals, Matrix& matrix) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
unsigned norms = 0;
|
||||
sscanf(line.c_str(), " %u", &norms);
|
||||
normals.reserve(norms);
|
||||
|
||||
//Normals
|
||||
for(unsigned i = 0; i < norms; ++i) {
|
||||
std::getline(file, line);
|
||||
|
||||
vec3d norm;
|
||||
sscanf(line.c_str(), " %lf;%lf;%lf;,", &norm.x, &norm.y, &norm.z);
|
||||
normals.push_back(vec3f(matrix.rotate(norm).normalized()));
|
||||
}
|
||||
|
||||
std::getline(file, line);
|
||||
unsigned faces = 0;
|
||||
sscanf(line.c_str(), " %u", &faces);
|
||||
normIndices.reserve(norms * 4);
|
||||
|
||||
//Face normals
|
||||
for(unsigned i = 0; i < faces; ++i) {
|
||||
std::getline(file, line);
|
||||
|
||||
unsigned type, indices[4];
|
||||
auto args = sscanf(line.c_str(), " %u;%u,%u,%u,%u", &type, &indices[0], &indices[1], &indices[2], &indices[3]);
|
||||
if(args >= 4) {
|
||||
normIndices.push_back(indices[0]);
|
||||
normIndices.push_back(indices[1]);
|
||||
normIndices.push_back(indices[2]);
|
||||
|
||||
if(args == 5) {
|
||||
normIndices.push_back(indices[0]);
|
||||
normIndices.push_back(indices[2]);
|
||||
normIndices.push_back(indices[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
std::getline(file, line);
|
||||
} while(file.good() && line.find('}') == std::string::npos);
|
||||
}
|
||||
|
||||
void loadXUVs(std::ifstream& file, std::vector<vec2f>& uvs) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
unsigned uvCount = 0;
|
||||
sscanf(line.c_str(), " %u", &uvCount);
|
||||
uvs.reserve(uvCount);
|
||||
|
||||
//Normals
|
||||
for(unsigned i = 0; i < uvCount; ++i) {
|
||||
std::getline(file, line);
|
||||
|
||||
vec2f uv;
|
||||
sscanf(line.c_str(), " %f;%f;,", &uv.x, &uv.y);
|
||||
uvs.push_back(uv);
|
||||
}
|
||||
|
||||
do {
|
||||
std::getline(file, line);
|
||||
} while(file.good() && line.find('}') == std::string::npos);
|
||||
}
|
||||
|
||||
void loadXMesh(std::ifstream& file, Mesh& mesh, Matrix& matrix) {
|
||||
std::vector<vec2f> uvs;
|
||||
std::vector<vec3f> positions, normals;
|
||||
std::vector<unsigned> posIndices, normIndices;
|
||||
|
||||
std::string line;
|
||||
|
||||
std::getline(file, line);
|
||||
unsigned verts = 0;
|
||||
sscanf(line.c_str(), " %u", &verts);
|
||||
positions.reserve(verts);
|
||||
|
||||
//Vertices
|
||||
for(unsigned i = 0; i < verts; ++i) {
|
||||
std::getline(file, line);
|
||||
|
||||
vec3d vert;
|
||||
sscanf(line.c_str(), " %lf;%lf;%lf;,", &vert.x, &vert.y, &vert.z);
|
||||
positions.push_back(vec3f(matrix * vert));
|
||||
}
|
||||
|
||||
std::getline(file, line);
|
||||
unsigned faces = 0;
|
||||
sscanf(line.c_str(), " %u", &faces);
|
||||
posIndices.reserve(verts * 4);
|
||||
|
||||
//Faces
|
||||
for(unsigned i = 0; i < faces; ++i) {
|
||||
std::getline(file, line);
|
||||
|
||||
unsigned type, indices[4];
|
||||
auto args = sscanf(line.c_str(), " %u;%u,%u,%u,%u", &type, &indices[0], &indices[1], &indices[2], &indices[3]);
|
||||
if(args >= 4) {
|
||||
posIndices.push_back(indices[0]);
|
||||
posIndices.push_back(indices[1]);
|
||||
posIndices.push_back(indices[2]);
|
||||
|
||||
if(args == 5) {
|
||||
posIndices.push_back(indices[0]);
|
||||
posIndices.push_back(indices[2]);
|
||||
posIndices.push_back(indices[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Load normals and texture coords
|
||||
std::getline(file, line);
|
||||
do {
|
||||
line = trim(line, " \t");
|
||||
if(startsWith(line, "MeshNormals")) {
|
||||
loadXNormals(file, normIndices, normals, matrix);
|
||||
}
|
||||
else if(startsWith(line, "MeshTextureCoords")) {
|
||||
loadXUVs(file, uvs);
|
||||
}
|
||||
else if(line.find('{') != std::string::npos) {
|
||||
//Skip unknown { ... } blocks
|
||||
unsigned depth = 1;
|
||||
while(depth > 0 && file.good()) {
|
||||
std::getline(file, line);
|
||||
auto pos = line.find_first_of("{}");
|
||||
if(pos == std::string::npos)
|
||||
continue;
|
||||
if(line[pos] == '{')
|
||||
++depth;
|
||||
else
|
||||
--depth;
|
||||
}
|
||||
}
|
||||
|
||||
std::getline(file, line);
|
||||
} while(file.good() && line.find('}') == std::string::npos);
|
||||
|
||||
for(unsigned int i = 0; i + 2 < posIndices.size() && i + 2 < normIndices.size(); i += 3) {
|
||||
Vertex verts[3];
|
||||
for(unsigned v = 0; v < 3; ++v) {
|
||||
unsigned pInd = posIndices[i+v], nInd = normIndices[i+v];
|
||||
|
||||
if(pInd >= positions.size() || nInd >= normals.size())
|
||||
return; //Out of bounds on our inputs, return what we've gotten so far
|
||||
|
||||
verts[v].position = positions[pInd];
|
||||
verts[v].normal = normals[nInd];
|
||||
|
||||
if(pInd < uvs.size()) {
|
||||
verts[v].u = uvs[pInd].x;
|
||||
verts[v].v = uvs[pInd].y;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short base = (unsigned short)mesh.vertices.size();
|
||||
mesh.vertices.push_back(verts[0]);
|
||||
mesh.vertices.push_back(verts[1]);
|
||||
mesh.vertices.push_back(verts[2]);
|
||||
|
||||
mesh.faces.push_back(Mesh::Face(base, base+1, base+2));
|
||||
}
|
||||
}
|
||||
|
||||
void loadXFrame(std::ifstream& file, Mesh& mesh, Matrix* baseMatrix = 0) {
|
||||
Matrix matrix;
|
||||
if(baseMatrix)
|
||||
matrix = *baseMatrix;
|
||||
|
||||
while(file.good()) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
line = trim(line, " \t");
|
||||
if(line.empty())
|
||||
continue;
|
||||
|
||||
if(startsWith(line, "FrameTransformMatrix")) {
|
||||
std::getline(file, line);
|
||||
Matrix lineMatrix;
|
||||
sscanf(line.c_str(), " %lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf;;",
|
||||
&lineMatrix[0], &lineMatrix[1], &lineMatrix[2], &lineMatrix[3],
|
||||
&lineMatrix[4], &lineMatrix[5], &lineMatrix[6], &lineMatrix[7],
|
||||
&lineMatrix[8], &lineMatrix[9], &lineMatrix[10], &lineMatrix[11],
|
||||
&lineMatrix[12], &lineMatrix[13], &lineMatrix[14], &lineMatrix[15] );
|
||||
|
||||
if(baseMatrix)
|
||||
matrix = *baseMatrix * lineMatrix;
|
||||
else
|
||||
matrix = lineMatrix;
|
||||
std::getline(file, line); //Read the } line off
|
||||
}
|
||||
else if(startsWith(line, "Frame")) {
|
||||
loadXFrame(file, mesh, &matrix);
|
||||
}
|
||||
else if(startsWith(line, "Mesh")) {
|
||||
loadXMesh(file, mesh, matrix);
|
||||
}
|
||||
else if(line.find('}') != std::string::npos) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadMeshX(const char* filename, Mesh& mesh) {
|
||||
std::ifstream file(filename);
|
||||
|
||||
if(file.is_open()) {
|
||||
while(file.good()) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
line = trim(line, " \t");
|
||||
if(line.empty())
|
||||
continue;
|
||||
|
||||
if(startsWith(line, "Frame"))
|
||||
loadXFrame(file, mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace render {
|
||||
|
||||
void loadMeshX(const char* filename, Mesh& mesh);
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user