obj loader refactor
This commit is contained in:
@@ -9,17 +9,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "states/game/debug_object.hpp"
|
#include "states/game/debug_object.hpp"
|
||||||
#include <loaders/3d/tyrobj/tyrobj_loader.hpp>
|
#include <loaders/3d/obj_loader/obj_loader.hpp>
|
||||||
#include <file/file_utils.hpp>
|
#include <file/file_utils.hpp>
|
||||||
|
|
||||||
using Tyra::Color;
|
using Tyra::Color;
|
||||||
using Tyra::FileUtils;
|
using Tyra::FileUtils;
|
||||||
using Tyra::TyrobjLoader;
|
using Tyra::ObjLoader;
|
||||||
|
|
||||||
namespace Demo {
|
namespace Demo {
|
||||||
|
|
||||||
DebugObject::DebugObject(TextureRepository* repo) {
|
DebugObject::DebugObject(TextureRepository* repo) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data =
|
auto* data =
|
||||||
loader.load(FileUtils::fromCwd("game/models/debug.obj"), 1, .5F, true);
|
loader.load(FileUtils::fromCwd("game/models/debug.obj"), 1, .5F, true);
|
||||||
data->normalsEnabled = false;
|
data->normalsEnabled = false;
|
||||||
|
|||||||
@@ -9,56 +9,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "states/game/enemy/enemy.hpp"
|
#include "states/game/enemy/enemy.hpp"
|
||||||
#include <loaders/3d/tyrobj/tyrobj_loader.hpp>
|
#include "loaders/3d/obj_loader/obj_loader.hpp"
|
||||||
#include <file/file_utils.hpp>
|
#include <file/file_utils.hpp>
|
||||||
|
|
||||||
using Tyra::FileUtils;
|
using Tyra::FileUtils;
|
||||||
using Tyra::TyrobjLoader;
|
using Tyra::ObjLoader;
|
||||||
|
|
||||||
namespace Demo {
|
namespace Demo {
|
||||||
|
|
||||||
Enemy::Enemy(TextureRepository* repo) {
|
Enemy::Enemy(TextureRepository* repo) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
|
|
||||||
auto* bodyData = loader.load(
|
auto* bodyData = loader.load(
|
||||||
FileUtils::fromCwd("game/models/soldier/soldier.obj"), 4, 30.0F, true);
|
FileUtils::fromCwd("game/models/soldier/soldier.obj"), 4, 20.0F, true);
|
||||||
bodyData->normalsEnabled = false;
|
bodyData->normalsEnabled = false;
|
||||||
bodyMesh = new DynamicMesh(*bodyData);
|
bodyMesh = new DynamicMesh(*bodyData);
|
||||||
|
|
||||||
TYRA_LOG("First vertex: ", bodyData->frames[0]->vertices[0].getPrint());
|
|
||||||
TYRA_LOG("Last vertex: ",
|
|
||||||
bodyData->frames[0]
|
|
||||||
->vertices[bodyData->frames[0]->verticesCount - 1]
|
|
||||||
.getPrint());
|
|
||||||
|
|
||||||
TYRA_LOG("First index: ", bodyData->materials[0]->vertexFaces[0]);
|
|
||||||
TYRA_LOG(
|
|
||||||
"Last index: ",
|
|
||||||
bodyData->materials[0]->vertexFaces[bodyData->materials[0]->count - 1]);
|
|
||||||
|
|
||||||
delete bodyData;
|
delete bodyData;
|
||||||
|
|
||||||
bodyMesh->getMaterial(0)->color.r = 16.0F;
|
|
||||||
bodyMesh->getMaterial(0)->color.g = 16.0F;
|
|
||||||
bodyMesh->getMaterial(0)->color.b = 16.0F;
|
|
||||||
|
|
||||||
bodyMesh->playAnimation(0, bodyMesh->getFramesCount() - 1);
|
bodyMesh->playAnimation(0, bodyMesh->getFramesCount() - 1);
|
||||||
bodyMesh->setAnimSpeed(0.15F);
|
bodyMesh->setAnimSpeed(0.1F);
|
||||||
|
|
||||||
repo->addByMesh(bodyMesh, FileUtils::fromCwd("game/models/soldier/"), "png");
|
repo->addByMesh(bodyMesh, FileUtils::fromCwd("game/models/soldier/"), "png");
|
||||||
|
|
||||||
// auto* headData = loader.load(
|
bodyMesh->translation.translateY(-5.0F);
|
||||||
// FileUtils::fromCwd("game/models/soldier/head.md2"), scale, true);
|
|
||||||
// headData->normalsEnabled = false;
|
|
||||||
// headMesh = new DynamicMesh(*headData);
|
|
||||||
// delete headData;
|
|
||||||
|
|
||||||
// headMesh->getMaterial(0)->color.r = 16.0F;
|
|
||||||
// headMesh->getMaterial(0)->color.g = 16.0F;
|
|
||||||
// headMesh->getMaterial(0)->color.b = 16.0F;
|
|
||||||
|
|
||||||
// repo->addByMesh(headMesh, FileUtils::fromCwd("game/models/soldier/"),
|
|
||||||
// "png");
|
|
||||||
|
|
||||||
allocateOptions();
|
allocateOptions();
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "states/game/player/weapon.hpp"
|
#include "states/game/player/weapon.hpp"
|
||||||
#include <loaders/3d/tyrobj/tyrobj_loader.hpp>
|
#include <loaders/3d/obj_loader/obj_loader.hpp>
|
||||||
#include <file/file_utils.hpp>
|
#include <file/file_utils.hpp>
|
||||||
|
|
||||||
using Tyra::FileUtils;
|
using Tyra::FileUtils;
|
||||||
using Tyra::TyrobjLoader;
|
using Tyra::ObjLoader;
|
||||||
|
|
||||||
namespace Demo {
|
namespace Demo {
|
||||||
|
|
||||||
Weapon::Weapon(TextureRepository* repo) {
|
Weapon::Weapon(TextureRepository* repo) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data = loader.load(FileUtils::fromCwd("game/models/ak47/ak47.obj"), 1,
|
auto* data = loader.load(FileUtils::fromCwd("game/models/ak47/ak47.obj"), 1,
|
||||||
.5F, true);
|
.5F, true);
|
||||||
data->normalsEnabled = false;
|
data->normalsEnabled = false;
|
||||||
|
|||||||
@@ -9,16 +9,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "states/game/skybox/skybox.hpp"
|
#include "states/game/skybox/skybox.hpp"
|
||||||
#include <loaders/3d/tyrobj/tyrobj_loader.hpp>
|
#include <loaders/3d/obj_loader/obj_loader.hpp>
|
||||||
#include <file/file_utils.hpp>
|
#include <file/file_utils.hpp>
|
||||||
|
|
||||||
using Tyra::FileUtils;
|
using Tyra::FileUtils;
|
||||||
using Tyra::TyrobjLoader;
|
using Tyra::ObjLoader;
|
||||||
|
|
||||||
namespace Demo {
|
namespace Demo {
|
||||||
|
|
||||||
Skybox::Skybox(TextureRepository* repo) {
|
Skybox::Skybox(TextureRepository* repo) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data = loader.load(FileUtils::fromCwd("game/models/skybox/skybox.obj"),
|
auto* data = loader.load(FileUtils::fromCwd("game/models/skybox/skybox.obj"),
|
||||||
1, 100.0F, true);
|
1, 100.0F, true);
|
||||||
data->normalsEnabled = false;
|
data->normalsEnabled = false;
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "states/game/terrain/terrain.hpp"
|
#include "states/game/terrain/terrain.hpp"
|
||||||
#include <loaders/3d/tyrobj/tyrobj_loader.hpp>
|
#include <loaders/3d/obj_loader/obj_loader.hpp>
|
||||||
#include <file/file_utils.hpp>
|
#include <file/file_utils.hpp>
|
||||||
|
|
||||||
using Tyra::FileUtils;
|
using Tyra::FileUtils;
|
||||||
using Tyra::TyrobjLoader;
|
using Tyra::ObjLoader;
|
||||||
|
|
||||||
namespace Demo {
|
namespace Demo {
|
||||||
Terrain::Terrain(TextureRepository* repo)
|
Terrain::Terrain(TextureRepository* repo)
|
||||||
@@ -22,7 +22,7 @@ Terrain::Terrain(TextureRepository* repo)
|
|||||||
Vec4(-196.6F, 0.0F, -412.0F, 1.0F), // Left up
|
Vec4(-196.6F, 0.0F, -412.0F, 1.0F), // Left up
|
||||||
Vec4(286.0F, 0.0F, 443.3F, 1.0F) // Right down
|
Vec4(286.0F, 0.0F, 443.3F, 1.0F) // Right down
|
||||||
) {
|
) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data = loader.load(
|
auto* data = loader.load(
|
||||||
FileUtils::fromCwd("game/models/terrain/terrain.obj"), 1, 25.0F, true);
|
FileUtils::fromCwd("game/models/terrain/terrain.obj"), 1, 25.0F, true);
|
||||||
data->normalsEnabled = false;
|
data->normalsEnabled = false;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Tyra {
|
|||||||
/**
|
/**
|
||||||
* Data needed for constructing mesh class.
|
* Data needed for constructing mesh class.
|
||||||
* All dynamic data ownership is moved to mesh class, so
|
* All dynamic data ownership is moved to mesh class, so
|
||||||
* there is no any deallocation!
|
* verts, coords.. are not deallocated!
|
||||||
*/
|
*/
|
||||||
class MeshBuilder2Data {
|
class MeshBuilder2Data {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "./mesh_builder2_material_frame_data.hpp"
|
#include "./mesh_builder2_material_frame_data.hpp"
|
||||||
|
#include "renderer/models/color.hpp"
|
||||||
|
|
||||||
namespace Tyra {
|
namespace Tyra {
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ class MeshBuilder2MaterialData {
|
|||||||
|
|
||||||
std::vector<MeshBuilder2MaterialFrameData*> frames;
|
std::vector<MeshBuilder2MaterialFrameData*> frames;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
Color ambient;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tyra
|
} // namespace Tyra
|
||||||
|
|||||||
@@ -0,0 +1,853 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace mapbox {
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
template <std::size_t I, typename T>
|
||||||
|
struct nth {
|
||||||
|
inline static typename std::tuple_element<I, T>::type get(const T& t) {
|
||||||
|
return std::get<I>(t);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename N = uint32_t>
|
||||||
|
class Earcut {
|
||||||
|
public:
|
||||||
|
std::vector<N> indices;
|
||||||
|
std::size_t vertices = 0;
|
||||||
|
|
||||||
|
template <typename Polygon>
|
||||||
|
void operator()(const Polygon& points);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Node {
|
||||||
|
Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {}
|
||||||
|
Node(const Node&) = delete;
|
||||||
|
Node& operator=(const Node&) = delete;
|
||||||
|
Node(Node&&) = delete;
|
||||||
|
Node& operator=(Node&&) = delete;
|
||||||
|
|
||||||
|
const N i;
|
||||||
|
const double x;
|
||||||
|
const double y;
|
||||||
|
|
||||||
|
// previous and next vertice nodes in a polygon ring
|
||||||
|
Node* prev = nullptr;
|
||||||
|
Node* next = nullptr;
|
||||||
|
|
||||||
|
// z-order curve value
|
||||||
|
int32_t z = 0;
|
||||||
|
|
||||||
|
// previous and next nodes in z-order
|
||||||
|
Node* prevZ = nullptr;
|
||||||
|
Node* nextZ = nullptr;
|
||||||
|
|
||||||
|
// indicates whether this is a steiner point
|
||||||
|
bool steiner = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Ring>
|
||||||
|
Node* linkedList(const Ring& points, const bool clockwise);
|
||||||
|
Node* filterPoints(Node* start, Node* end = nullptr);
|
||||||
|
void earcutLinked(Node* ear, int pass = 0);
|
||||||
|
bool isEar(Node* ear);
|
||||||
|
bool isEarHashed(Node* ear);
|
||||||
|
Node* cureLocalIntersections(Node* start);
|
||||||
|
void splitEarcut(Node* start);
|
||||||
|
template <typename Polygon>
|
||||||
|
Node* eliminateHoles(const Polygon& points, Node* outerNode);
|
||||||
|
Node* eliminateHole(Node* hole, Node* outerNode);
|
||||||
|
Node* findHoleBridge(Node* hole, Node* outerNode);
|
||||||
|
bool sectorContainsSector(const Node* m, const Node* p);
|
||||||
|
void indexCurve(Node* start);
|
||||||
|
Node* sortLinked(Node* list);
|
||||||
|
int32_t zOrder(const double x_, const double y_);
|
||||||
|
Node* getLeftmost(Node* start);
|
||||||
|
bool pointInTriangle(double ax, double ay, double bx, double by, double cx,
|
||||||
|
double cy, double px, double py) const;
|
||||||
|
bool isValidDiagonal(Node* a, Node* b);
|
||||||
|
double area(const Node* p, const Node* q, const Node* r) const;
|
||||||
|
bool equals(const Node* p1, const Node* p2);
|
||||||
|
bool intersects(const Node* p1, const Node* q1, const Node* p2,
|
||||||
|
const Node* q2);
|
||||||
|
bool onSegment(const Node* p, const Node* q, const Node* r);
|
||||||
|
int sign(double val);
|
||||||
|
bool intersectsPolygon(const Node* a, const Node* b);
|
||||||
|
bool locallyInside(const Node* a, const Node* b);
|
||||||
|
bool middleInside(const Node* a, const Node* b);
|
||||||
|
Node* splitPolygon(Node* a, Node* b);
|
||||||
|
template <typename Point>
|
||||||
|
Node* insertNode(std::size_t i, const Point& p, Node* last);
|
||||||
|
void removeNode(Node* p);
|
||||||
|
|
||||||
|
bool hashing;
|
||||||
|
double minX, maxX;
|
||||||
|
double minY, maxY;
|
||||||
|
double inv_size = 0;
|
||||||
|
|
||||||
|
template <typename T, typename Alloc = std::allocator<T>>
|
||||||
|
class ObjectPool {
|
||||||
|
public:
|
||||||
|
ObjectPool() {}
|
||||||
|
ObjectPool(std::size_t blockSize_) { reset(blockSize_); }
|
||||||
|
~ObjectPool() { clear(); }
|
||||||
|
template <typename... Args>
|
||||||
|
T* construct(Args&&... args) {
|
||||||
|
if (currentIndex >= blockSize) {
|
||||||
|
currentBlock = alloc_traits::allocate(alloc, blockSize);
|
||||||
|
allocations.emplace_back(currentBlock);
|
||||||
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
T* object = ¤tBlock[currentIndex++];
|
||||||
|
alloc_traits::construct(alloc, object, std::forward<Args>(args)...);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
void reset(std::size_t newBlockSize) {
|
||||||
|
for (auto allocation : allocations) {
|
||||||
|
alloc_traits::deallocate(alloc, allocation, blockSize);
|
||||||
|
}
|
||||||
|
allocations.clear();
|
||||||
|
blockSize = std::max<std::size_t>(1, newBlockSize);
|
||||||
|
currentBlock = nullptr;
|
||||||
|
currentIndex = blockSize;
|
||||||
|
}
|
||||||
|
void clear() { reset(blockSize); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* currentBlock = nullptr;
|
||||||
|
std::size_t currentIndex = 1;
|
||||||
|
std::size_t blockSize = 1;
|
||||||
|
std::vector<T*> allocations;
|
||||||
|
Alloc alloc;
|
||||||
|
typedef typename std::allocator_traits<Alloc> alloc_traits;
|
||||||
|
};
|
||||||
|
ObjectPool<Node> nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename N>
|
||||||
|
template <typename Polygon>
|
||||||
|
void Earcut<N>::operator()(const Polygon& points) {
|
||||||
|
// reset
|
||||||
|
indices.clear();
|
||||||
|
vertices = 0;
|
||||||
|
|
||||||
|
if (points.empty()) return;
|
||||||
|
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
int threshold = 80;
|
||||||
|
std::size_t len = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; threshold >= 0 && i < points.size(); i++) {
|
||||||
|
threshold -= static_cast<int>(points[i].size());
|
||||||
|
len += points[i].size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// estimate size of nodes and indices
|
||||||
|
nodes.reset(len * 3 / 2);
|
||||||
|
indices.reserve(len + points[0].size());
|
||||||
|
|
||||||
|
Node* outerNode = linkedList(points[0], true);
|
||||||
|
if (!outerNode || outerNode->prev == outerNode->next) return;
|
||||||
|
|
||||||
|
if (points.size() > 1) outerNode = eliminateHoles(points, outerNode);
|
||||||
|
|
||||||
|
// if the shape is not too simple, we'll use z-order curve hash later;
|
||||||
|
// calculate polygon bbox
|
||||||
|
hashing = threshold < 0;
|
||||||
|
if (hashing) {
|
||||||
|
Node* p = outerNode->next;
|
||||||
|
minX = maxX = outerNode->x;
|
||||||
|
minY = maxY = outerNode->y;
|
||||||
|
do {
|
||||||
|
x = p->x;
|
||||||
|
y = p->y;
|
||||||
|
minX = std::min<double>(minX, x);
|
||||||
|
minY = std::min<double>(minY, y);
|
||||||
|
maxX = std::max<double>(maxX, x);
|
||||||
|
maxY = std::max<double>(maxY, y);
|
||||||
|
p = p->next;
|
||||||
|
} while (p != outerNode);
|
||||||
|
|
||||||
|
// minX, minY and size are later used to transform coords into integers for
|
||||||
|
// z-order calculation
|
||||||
|
inv_size = std::max<double>(maxX - minX, maxY - minY);
|
||||||
|
inv_size = inv_size != .0 ? (1. / inv_size) : .0;
|
||||||
|
}
|
||||||
|
|
||||||
|
earcutLinked(outerNode);
|
||||||
|
|
||||||
|
nodes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a circular doubly linked list from polygon points in the specified
|
||||||
|
// winding order
|
||||||
|
template <typename N>
|
||||||
|
template <typename Ring>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::linkedList(const Ring& points,
|
||||||
|
const bool clockwise) {
|
||||||
|
using Point = typename Ring::value_type;
|
||||||
|
double sum = 0;
|
||||||
|
const std::size_t len = points.size();
|
||||||
|
std::size_t i, j;
|
||||||
|
Node* last = nullptr;
|
||||||
|
|
||||||
|
// calculate original winding order of a polygon ring
|
||||||
|
for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) {
|
||||||
|
const auto& p1 = points[i];
|
||||||
|
const auto& p2 = points[j];
|
||||||
|
const double p20 = util::nth<0, Point>::get(p2);
|
||||||
|
const double p10 = util::nth<0, Point>::get(p1);
|
||||||
|
const double p11 = util::nth<1, Point>::get(p1);
|
||||||
|
const double p21 = util::nth<1, Point>::get(p2);
|
||||||
|
sum += (p20 - p10) * (p11 + p21);
|
||||||
|
}
|
||||||
|
|
||||||
|
// link points into circular doubly-linked list in the specified winding order
|
||||||
|
if (clockwise == (sum > 0)) {
|
||||||
|
for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last);
|
||||||
|
} else {
|
||||||
|
for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last && equals(last, last->next)) {
|
||||||
|
removeNode(last);
|
||||||
|
last = last->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices += len;
|
||||||
|
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eliminate colinear or duplicate points
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::filterPoints(Node* start, Node* end) {
|
||||||
|
if (!end) end = start;
|
||||||
|
|
||||||
|
Node* p = start;
|
||||||
|
bool again;
|
||||||
|
do {
|
||||||
|
again = false;
|
||||||
|
|
||||||
|
if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) {
|
||||||
|
removeNode(p);
|
||||||
|
p = end = p->prev;
|
||||||
|
|
||||||
|
if (p == p->next) break;
|
||||||
|
again = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
} while (again || p != end);
|
||||||
|
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// main ear slicing loop which triangulates a polygon (given as a linked list)
|
||||||
|
template <typename N>
|
||||||
|
void Earcut<N>::earcutLinked(Node* ear, int pass) {
|
||||||
|
if (!ear) return;
|
||||||
|
|
||||||
|
// interlink polygon nodes in z-order
|
||||||
|
if (!pass && hashing) indexCurve(ear);
|
||||||
|
|
||||||
|
Node* stop = ear;
|
||||||
|
Node* prev;
|
||||||
|
Node* next;
|
||||||
|
|
||||||
|
int iterations = 0;
|
||||||
|
|
||||||
|
// iterate through ears, slicing them one by one
|
||||||
|
while (ear->prev != ear->next) {
|
||||||
|
iterations++;
|
||||||
|
prev = ear->prev;
|
||||||
|
next = ear->next;
|
||||||
|
|
||||||
|
if (hashing ? isEarHashed(ear) : isEar(ear)) {
|
||||||
|
// cut off the triangle
|
||||||
|
indices.emplace_back(prev->i);
|
||||||
|
indices.emplace_back(ear->i);
|
||||||
|
indices.emplace_back(next->i);
|
||||||
|
|
||||||
|
removeNode(ear);
|
||||||
|
|
||||||
|
// skipping the next vertice leads to less sliver triangles
|
||||||
|
ear = next->next;
|
||||||
|
stop = next->next;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ear = next;
|
||||||
|
|
||||||
|
// if we looped through the whole remaining polygon and can't find any more
|
||||||
|
// ears
|
||||||
|
if (ear == stop) {
|
||||||
|
// try filtering points and slicing again
|
||||||
|
if (!pass) earcutLinked(filterPoints(ear), 1);
|
||||||
|
|
||||||
|
// if this didn't work, try curing all small self-intersections locally
|
||||||
|
else if (pass == 1) {
|
||||||
|
ear = cureLocalIntersections(filterPoints(ear));
|
||||||
|
earcutLinked(ear, 2);
|
||||||
|
|
||||||
|
// as a last resort, try splitting the remaining polygon into two
|
||||||
|
} else if (pass == 2)
|
||||||
|
splitEarcut(ear);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::isEar(Node* ear) {
|
||||||
|
const Node* a = ear->prev;
|
||||||
|
const Node* b = ear;
|
||||||
|
const Node* c = ear->next;
|
||||||
|
|
||||||
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
||||||
|
|
||||||
|
// now make sure we don't have other points inside the potential ear
|
||||||
|
Node* p = ear->next->next;
|
||||||
|
|
||||||
|
while (p != ear->prev) {
|
||||||
|
if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
|
||||||
|
area(p->prev, p, p->next) >= 0)
|
||||||
|
return false;
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::isEarHashed(Node* ear) {
|
||||||
|
const Node* a = ear->prev;
|
||||||
|
const Node* b = ear;
|
||||||
|
const Node* c = ear->next;
|
||||||
|
|
||||||
|
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
|
||||||
|
|
||||||
|
// triangle bbox; min & max are calculated like this for speed
|
||||||
|
const double minTX = std::min<double>(a->x, std::min<double>(b->x, c->x));
|
||||||
|
const double minTY = std::min<double>(a->y, std::min<double>(b->y, c->y));
|
||||||
|
const double maxTX = std::max<double>(a->x, std::max<double>(b->x, c->x));
|
||||||
|
const double maxTY = std::max<double>(a->y, std::max<double>(b->y, c->y));
|
||||||
|
|
||||||
|
// z-order range for the current triangle bbox;
|
||||||
|
const int32_t minZ = zOrder(minTX, minTY);
|
||||||
|
const int32_t maxZ = zOrder(maxTX, maxTY);
|
||||||
|
|
||||||
|
// first look for points inside the triangle in increasing z-order
|
||||||
|
Node* p = ear->nextZ;
|
||||||
|
|
||||||
|
while (p && p->z <= maxZ) {
|
||||||
|
if (p != ear->prev && p != ear->next &&
|
||||||
|
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
|
||||||
|
area(p->prev, p, p->next) >= 0)
|
||||||
|
return false;
|
||||||
|
p = p->nextZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// then look for points in decreasing z-order
|
||||||
|
p = ear->prevZ;
|
||||||
|
|
||||||
|
while (p && p->z >= minZ) {
|
||||||
|
if (p != ear->prev && p != ear->next &&
|
||||||
|
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
|
||||||
|
area(p->prev, p, p->next) >= 0)
|
||||||
|
return false;
|
||||||
|
p = p->prevZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go through all polygon nodes and cure small local self-intersections
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::cureLocalIntersections(Node* start) {
|
||||||
|
Node* p = start;
|
||||||
|
do {
|
||||||
|
Node* a = p->prev;
|
||||||
|
Node* b = p->next->next;
|
||||||
|
|
||||||
|
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
|
||||||
|
if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) &&
|
||||||
|
locallyInside(b, a)) {
|
||||||
|
indices.emplace_back(a->i);
|
||||||
|
indices.emplace_back(p->i);
|
||||||
|
indices.emplace_back(b->i);
|
||||||
|
|
||||||
|
// remove two nodes involved
|
||||||
|
removeNode(p);
|
||||||
|
removeNode(p->next);
|
||||||
|
|
||||||
|
p = start = b;
|
||||||
|
}
|
||||||
|
p = p->next;
|
||||||
|
} while (p != start);
|
||||||
|
|
||||||
|
return filterPoints(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try splitting polygon into two and triangulate them independently
|
||||||
|
template <typename N>
|
||||||
|
void Earcut<N>::splitEarcut(Node* start) {
|
||||||
|
// look for a valid diagonal that divides the polygon into two
|
||||||
|
Node* a = start;
|
||||||
|
do {
|
||||||
|
Node* b = a->next->next;
|
||||||
|
while (b != a->prev) {
|
||||||
|
if (a->i != b->i && isValidDiagonal(a, b)) {
|
||||||
|
// split the polygon in two by the diagonal
|
||||||
|
Node* c = splitPolygon(a, b);
|
||||||
|
|
||||||
|
// filter colinear points around the cuts
|
||||||
|
a = filterPoints(a, a->next);
|
||||||
|
c = filterPoints(c, c->next);
|
||||||
|
|
||||||
|
// run earcut on each half
|
||||||
|
earcutLinked(a);
|
||||||
|
earcutLinked(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
b = b->next;
|
||||||
|
}
|
||||||
|
a = a->next;
|
||||||
|
} while (a != start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// link every hole into the outer loop, producing a single-ring polygon without
|
||||||
|
// holes
|
||||||
|
template <typename N>
|
||||||
|
template <typename Polygon>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::eliminateHoles(const Polygon& points,
|
||||||
|
Node* outerNode) {
|
||||||
|
const size_t len = points.size();
|
||||||
|
|
||||||
|
std::vector<Node*> queue;
|
||||||
|
for (size_t i = 1; i < len; i++) {
|
||||||
|
Node* list = linkedList(points[i], false);
|
||||||
|
if (list) {
|
||||||
|
if (list == list->next) list->steiner = true;
|
||||||
|
queue.push_back(getLeftmost(list));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(queue.begin(), queue.end(),
|
||||||
|
[](const Node* a, const Node* b) { return a->x < b->x; });
|
||||||
|
|
||||||
|
// process holes from left to right
|
||||||
|
for (size_t i = 0; i < queue.size(); i++) {
|
||||||
|
outerNode = eliminateHole(queue[i], outerNode);
|
||||||
|
outerNode = filterPoints(outerNode, outerNode->next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find a bridge between vertices that connects hole with an outer ring and and
|
||||||
|
// link it
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::eliminateHole(Node* hole,
|
||||||
|
Node* outerNode) {
|
||||||
|
Node* bridge = findHoleBridge(hole, outerNode);
|
||||||
|
if (!bridge) {
|
||||||
|
return outerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* bridgeReverse = splitPolygon(bridge, hole);
|
||||||
|
|
||||||
|
// filter collinear points around the cuts
|
||||||
|
Node* filteredBridge = filterPoints(bridge, bridge->next);
|
||||||
|
filterPoints(bridgeReverse, bridgeReverse->next);
|
||||||
|
|
||||||
|
// Check if input node was removed by the filtering
|
||||||
|
return outerNode == bridge ? filteredBridge : outerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::findHoleBridge(Node* hole,
|
||||||
|
Node* outerNode) {
|
||||||
|
Node* p = outerNode;
|
||||||
|
double hx = hole->x;
|
||||||
|
double hy = hole->y;
|
||||||
|
double qx = -std::numeric_limits<double>::infinity();
|
||||||
|
Node* m = nullptr;
|
||||||
|
|
||||||
|
// find a segment intersected by a ray from the hole's leftmost Vertex to the
|
||||||
|
// left; segment's endpoint with lesser x will be potential connection Vertex
|
||||||
|
do {
|
||||||
|
if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) {
|
||||||
|
double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y);
|
||||||
|
if (x <= hx && x > qx) {
|
||||||
|
qx = x;
|
||||||
|
if (x == hx) {
|
||||||
|
if (hy == p->y) return p;
|
||||||
|
if (hy == p->next->y) return p->next;
|
||||||
|
}
|
||||||
|
m = p->x < p->next->x ? p : p->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = p->next;
|
||||||
|
} while (p != outerNode);
|
||||||
|
|
||||||
|
if (!m) return 0;
|
||||||
|
|
||||||
|
if (hx == qx) return m; // hole touches outer segment; pick leftmost endpoint
|
||||||
|
|
||||||
|
// look for points inside the triangle of hole Vertex, segment intersection
|
||||||
|
// and endpoint; if there are no points found, we have a valid connection;
|
||||||
|
// otherwise choose the Vertex of the minimum angle with the ray as connection
|
||||||
|
// Vertex
|
||||||
|
|
||||||
|
const Node* stop = m;
|
||||||
|
double tanMin = std::numeric_limits<double>::infinity();
|
||||||
|
double tanCur = 0;
|
||||||
|
|
||||||
|
p = m;
|
||||||
|
double mx = m->x;
|
||||||
|
double my = m->y;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (hx >= p->x && p->x >= mx && hx != p->x &&
|
||||||
|
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy,
|
||||||
|
p->x, p->y)) {
|
||||||
|
tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential
|
||||||
|
|
||||||
|
if (locallyInside(p, hole) &&
|
||||||
|
(tanCur < tanMin ||
|
||||||
|
(tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
|
||||||
|
m = p;
|
||||||
|
tanMin = tanCur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p->next;
|
||||||
|
} while (p != stop);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether sector in vertex m contains sector in vertex p in the same
|
||||||
|
// coordinates
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::sectorContainsSector(const Node* m, const Node* p) {
|
||||||
|
return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interlink polygon nodes in z-order
|
||||||
|
template <typename N>
|
||||||
|
void Earcut<N>::indexCurve(Node* start) {
|
||||||
|
assert(start);
|
||||||
|
Node* p = start;
|
||||||
|
|
||||||
|
do {
|
||||||
|
p->z = p->z ? p->z : zOrder(p->x, p->y);
|
||||||
|
p->prevZ = p->prev;
|
||||||
|
p->nextZ = p->next;
|
||||||
|
p = p->next;
|
||||||
|
} while (p != start);
|
||||||
|
|
||||||
|
p->prevZ->nextZ = nullptr;
|
||||||
|
p->prevZ = nullptr;
|
||||||
|
|
||||||
|
sortLinked(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simon Tatham's linked list merge sort algorithm
|
||||||
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::sortLinked(Node* list) {
|
||||||
|
assert(list);
|
||||||
|
Node* p;
|
||||||
|
Node* q;
|
||||||
|
Node* e;
|
||||||
|
Node* tail;
|
||||||
|
int i, numMerges, pSize, qSize;
|
||||||
|
int inSize = 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
p = list;
|
||||||
|
list = nullptr;
|
||||||
|
tail = nullptr;
|
||||||
|
numMerges = 0;
|
||||||
|
|
||||||
|
while (p) {
|
||||||
|
numMerges++;
|
||||||
|
q = p;
|
||||||
|
pSize = 0;
|
||||||
|
for (i = 0; i < inSize; i++) {
|
||||||
|
pSize++;
|
||||||
|
q = q->nextZ;
|
||||||
|
if (!q) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
qSize = inSize;
|
||||||
|
|
||||||
|
while (pSize > 0 || (qSize > 0 && q)) {
|
||||||
|
if (pSize == 0) {
|
||||||
|
e = q;
|
||||||
|
q = q->nextZ;
|
||||||
|
qSize--;
|
||||||
|
} else if (qSize == 0 || !q) {
|
||||||
|
e = p;
|
||||||
|
p = p->nextZ;
|
||||||
|
pSize--;
|
||||||
|
} else if (p->z <= q->z) {
|
||||||
|
e = p;
|
||||||
|
p = p->nextZ;
|
||||||
|
pSize--;
|
||||||
|
} else {
|
||||||
|
e = q;
|
||||||
|
q = q->nextZ;
|
||||||
|
qSize--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tail)
|
||||||
|
tail->nextZ = e;
|
||||||
|
else
|
||||||
|
list = e;
|
||||||
|
|
||||||
|
e->prevZ = tail;
|
||||||
|
tail = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = q;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail->nextZ = nullptr;
|
||||||
|
|
||||||
|
if (numMerges <= 1) return list;
|
||||||
|
|
||||||
|
inSize *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// z-order of a Vertex given coords and size of the data bounding box
|
||||||
|
template <typename N>
|
||||||
|
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
|
||||||
|
// coords are transformed into non-negative 15-bit integer range
|
||||||
|
int32_t x = static_cast<int32_t>(32767.0 * (x_ - minX) * inv_size);
|
||||||
|
int32_t y = static_cast<int32_t>(32767.0 * (y_ - minY) * inv_size);
|
||||||
|
|
||||||
|
x = (x | (x << 8)) & 0x00FF00FF;
|
||||||
|
x = (x | (x << 4)) & 0x0F0F0F0F;
|
||||||
|
x = (x | (x << 2)) & 0x33333333;
|
||||||
|
x = (x | (x << 1)) & 0x55555555;
|
||||||
|
|
||||||
|
y = (y | (y << 8)) & 0x00FF00FF;
|
||||||
|
y = (y | (y << 4)) & 0x0F0F0F0F;
|
||||||
|
y = (y | (y << 2)) & 0x33333333;
|
||||||
|
y = (y | (y << 1)) & 0x55555555;
|
||||||
|
|
||||||
|
return x | (y << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the leftmost node of a polygon ring
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::getLeftmost(Node* start) {
|
||||||
|
Node* p = start;
|
||||||
|
Node* leftmost = start;
|
||||||
|
do {
|
||||||
|
if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y))
|
||||||
|
leftmost = p;
|
||||||
|
p = p->next;
|
||||||
|
} while (p != start);
|
||||||
|
|
||||||
|
return leftmost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a point lies within a convex triangle
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by,
|
||||||
|
double cx, double cy, double px,
|
||||||
|
double py) const {
|
||||||
|
return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
|
||||||
|
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
|
||||||
|
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a diagonal between two polygon nodes is valid (lies in polygon
|
||||||
|
// interior)
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::isValidDiagonal(Node* a, Node* b) {
|
||||||
|
return a->next->i != b->i && a->prev->i != b->i &&
|
||||||
|
!intersectsPolygon(a, b) && // dones't intersect other edges
|
||||||
|
((locallyInside(a, b) && locallyInside(b, a) &&
|
||||||
|
middleInside(a, b) && // locally visible
|
||||||
|
(area(a->prev, a, b->prev) != 0.0 ||
|
||||||
|
area(a, b->prev, b) !=
|
||||||
|
0.0)) || // does not create opposite-facing sectors
|
||||||
|
(equals(a, b) && area(a->prev, a, a->next) > 0 &&
|
||||||
|
area(b->prev, b, b->next) > 0)); // special zero-length case
|
||||||
|
}
|
||||||
|
|
||||||
|
// signed area of a triangle
|
||||||
|
template <typename N>
|
||||||
|
double Earcut<N>::area(const Node* p, const Node* q, const Node* r) const {
|
||||||
|
return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if two points are equal
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::equals(const Node* p1, const Node* p2) {
|
||||||
|
return p1->x == p2->x && p1->y == p2->y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if two segments intersect
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2,
|
||||||
|
const Node* q2) {
|
||||||
|
int o1 = sign(area(p1, q1, p2));
|
||||||
|
int o2 = sign(area(p1, q1, q2));
|
||||||
|
int o3 = sign(area(p2, q2, p1));
|
||||||
|
int o4 = sign(area(p2, q2, q1));
|
||||||
|
|
||||||
|
if (o1 != o2 && o3 != o4) return true; // general case
|
||||||
|
|
||||||
|
if (o1 == 0 && onSegment(p1, p2, q1))
|
||||||
|
return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
|
||||||
|
if (o2 == 0 && onSegment(p1, q2, q1))
|
||||||
|
return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
|
||||||
|
if (o3 == 0 && onSegment(p2, p1, q2))
|
||||||
|
return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
|
||||||
|
if (o4 == 0 && onSegment(p2, q1, q2))
|
||||||
|
return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for collinear points p, q, r, check if point q lies on segment pr
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
|
||||||
|
return q->x <= std::max<double>(p->x, r->x) &&
|
||||||
|
q->x >= std::min<double>(p->x, r->x) &&
|
||||||
|
q->y <= std::max<double>(p->y, r->y) &&
|
||||||
|
q->y >= std::min<double>(p->y, r->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename N>
|
||||||
|
int Earcut<N>::sign(double val) {
|
||||||
|
return (0.0 < val) - (val < 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a polygon diagonal intersects any polygon segments
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::intersectsPolygon(const Node* a, const Node* b) {
|
||||||
|
const Node* p = a;
|
||||||
|
do {
|
||||||
|
if (p->i != a->i && p->next->i != a->i && p->i != b->i &&
|
||||||
|
p->next->i != b->i && intersects(p, p->next, a, b))
|
||||||
|
return true;
|
||||||
|
p = p->next;
|
||||||
|
} while (p != a);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a polygon diagonal is locally inside the polygon
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::locallyInside(const Node* a, const Node* b) {
|
||||||
|
return area(a->prev, a, a->next) < 0
|
||||||
|
? area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0
|
||||||
|
: area(a, b, a->prev) < 0 || area(a, a->next, b) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the middle Vertex of a polygon diagonal is inside the polygon
|
||||||
|
template <typename N>
|
||||||
|
bool Earcut<N>::middleInside(const Node* a, const Node* b) {
|
||||||
|
const Node* p = a;
|
||||||
|
bool inside = false;
|
||||||
|
double px = (a->x + b->x) / 2;
|
||||||
|
double py = (a->y + b->y) / 2;
|
||||||
|
do {
|
||||||
|
if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y &&
|
||||||
|
(px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x))
|
||||||
|
inside = !inside;
|
||||||
|
p = p->next;
|
||||||
|
} while (p != a);
|
||||||
|
|
||||||
|
return inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
// link two polygon vertices with a bridge; if the vertices belong to the same
|
||||||
|
// ring, it splits polygon into two; if one belongs to the outer ring and
|
||||||
|
// another to a hole, it merges it into a single ring
|
||||||
|
template <typename N>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::splitPolygon(Node* a, Node* b) {
|
||||||
|
Node* a2 = nodes.construct(a->i, a->x, a->y);
|
||||||
|
Node* b2 = nodes.construct(b->i, b->x, b->y);
|
||||||
|
Node* an = a->next;
|
||||||
|
Node* bp = b->prev;
|
||||||
|
|
||||||
|
a->next = b;
|
||||||
|
b->prev = a;
|
||||||
|
|
||||||
|
a2->next = an;
|
||||||
|
an->prev = a2;
|
||||||
|
|
||||||
|
b2->next = a2;
|
||||||
|
a2->prev = b2;
|
||||||
|
|
||||||
|
bp->next = b2;
|
||||||
|
b2->prev = bp;
|
||||||
|
|
||||||
|
return b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a node and util::optionally link it with previous one (in a circular
|
||||||
|
// doubly linked list)
|
||||||
|
template <typename N>
|
||||||
|
template <typename Point>
|
||||||
|
typename Earcut<N>::Node* Earcut<N>::insertNode(std::size_t i, const Point& pt,
|
||||||
|
Node* last) {
|
||||||
|
Node* p = nodes.construct(static_cast<N>(i), util::nth<0, Point>::get(pt),
|
||||||
|
util::nth<1, Point>::get(pt));
|
||||||
|
|
||||||
|
if (!last) {
|
||||||
|
p->prev = p;
|
||||||
|
p->next = p;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
assert(last);
|
||||||
|
p->next = last->next;
|
||||||
|
p->prev = last;
|
||||||
|
last->next->prev = p;
|
||||||
|
last->next = p;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename N>
|
||||||
|
void Earcut<N>::removeNode(Node* p) {
|
||||||
|
p->next->prev = p->prev;
|
||||||
|
p->prev->next = p->next;
|
||||||
|
|
||||||
|
if (p->prevZ) p->prevZ->nextZ = p->nextZ;
|
||||||
|
if (p->nextZ) p->nextZ->prevZ = p->prevZ;
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename N = uint32_t, typename Polygon>
|
||||||
|
std::vector<N> earcut(const Polygon& poly) {
|
||||||
|
mapbox::detail::Earcut<N> earcut;
|
||||||
|
earcut(poly);
|
||||||
|
return std::move(earcut.indices);
|
||||||
|
}
|
||||||
|
} // namespace mapbox
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
# ______ ____ ___
|
||||||
|
# | \/ ____| |___|
|
||||||
|
# | | | \ | |
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
||||||
|
# Licenced under Apache License 2.0
|
||||||
|
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../loader.hpp"
|
||||||
|
#include "../builder2/mesh_builder2_data.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include "renderer/models/color.hpp"
|
||||||
|
#include "loaders/3d/obj_loader/tiny_obj_loader.hpp"
|
||||||
|
|
||||||
|
namespace Tyra {
|
||||||
|
|
||||||
|
/** Class responsible for loading & parsing custom Tyra obj files */
|
||||||
|
class ObjLoader : public Loader {
|
||||||
|
public:
|
||||||
|
ObjLoader();
|
||||||
|
~ObjLoader();
|
||||||
|
|
||||||
|
/** Load multiple obj files (dynamic) */
|
||||||
|
MeshBuilder2Data* load(const char* fullpath, const u16& count,
|
||||||
|
const float& scale, const bool& invertT);
|
||||||
|
|
||||||
|
inline MeshBuilder2Data* load(const std::string& fullpath, const u16& count,
|
||||||
|
const float& scale, const bool& invertT) {
|
||||||
|
return load(fullpath.c_str(), count, scale, invertT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setInitialData(MeshBuilder2Data* output, const tinyobj::attrib_t& attrib,
|
||||||
|
const std::vector<tinyobj::shape_t>& shapes,
|
||||||
|
const std::vector<tinyobj::material_t>& materials,
|
||||||
|
const u16& framesCount);
|
||||||
|
|
||||||
|
void importFrame(MeshBuilder2Data* output, const tinyobj::attrib_t& attrib,
|
||||||
|
const std::vector<tinyobj::shape_t>& shapes,
|
||||||
|
const std::vector<tinyobj::material_t>& materials,
|
||||||
|
const u16& frameIndex, const float& scale,
|
||||||
|
const bool& invertY, const u16& count);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Tyra
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
# ______ ____ ___
|
|
||||||
# | \/ ____| |___|
|
|
||||||
# | | | \ | |
|
|
||||||
#-----------------------------------------------------------------------
|
|
||||||
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
|
||||||
# Licenced under Apache License 2.0
|
|
||||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../../loader.hpp"
|
|
||||||
#include "../builder/mesh_builder_data.hpp"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace Tyra {
|
|
||||||
|
|
||||||
struct TyraobjData {
|
|
||||||
u32 vertexCount, stsCount, normalsCount, colorsCount, materialsCount,
|
|
||||||
framesCount;
|
|
||||||
u32* materialsFaces;
|
|
||||||
float scale;
|
|
||||||
bool invertT;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TyraobjReadInfo {
|
|
||||||
u32 verticesI, coordsI, normalsI, colorsI, faceI;
|
|
||||||
s16 materialsI;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Class responsible for loading & parsing custom Tyra obj files */
|
|
||||||
class TyrobjLoader : public Loader {
|
|
||||||
public:
|
|
||||||
TyrobjLoader();
|
|
||||||
~TyrobjLoader();
|
|
||||||
|
|
||||||
/** Load multiple tyrobj files (dynamic) */
|
|
||||||
MeshBuilderData* load(const char* fullpath, const u16& count,
|
|
||||||
const float& scale, const bool& invertT);
|
|
||||||
|
|
||||||
inline MeshBuilderData* load(const std::string& fullpath, const u16& count,
|
|
||||||
const float& scale, const bool& invertT) {
|
|
||||||
return load(fullpath.c_str(), count, scale, invertT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
TyraobjData scan(FILE* file, const u32& framesCount);
|
|
||||||
void loadFile(FILE* file, const std::string& path, const u16& frameIndex,
|
|
||||||
const TyraobjData& inputData, MeshBuilderData* outputData);
|
|
||||||
|
|
||||||
void initReadInfo(TyraobjReadInfo* info);
|
|
||||||
void readVertices(TyraobjReadInfo* info, FILE* file, const u16& frameIndex,
|
|
||||||
const TyraobjData& inputData, MeshBuilderData* outputData);
|
|
||||||
void readTextureCoords(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const u16& frameIndex, const TyraobjData& inputData,
|
|
||||||
MeshBuilderData* outputData);
|
|
||||||
void readNormals(TyraobjReadInfo* info, FILE* file, const u16& frameIndex,
|
|
||||||
MeshBuilderData* outputData);
|
|
||||||
void readColors(TyraobjReadInfo* info, FILE* file, const u16& frameIndex,
|
|
||||||
MeshBuilderData* outputData);
|
|
||||||
void readMaterials(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const TyraobjData& inputData, MeshBuilderData* outputData);
|
|
||||||
void readFaces(TyraobjReadInfo* info, FILE* file,
|
|
||||||
MeshBuilderData* outputData);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Tyra
|
|
||||||
@@ -17,6 +17,7 @@ namespace Tyra {
|
|||||||
class Loader {
|
class Loader {
|
||||||
protected:
|
protected:
|
||||||
std::string getFilenameFromPath(const std::string& path);
|
std::string getFilenameFromPath(const std::string& path);
|
||||||
|
std::string getPathFromFilename(const std::string& path);
|
||||||
std::string getFilenameWithoutExtension(const std::string& filename);
|
std::string getFilenameWithoutExtension(const std::string& filename);
|
||||||
std::string getExtensionOfFilename(const std::string& filename);
|
std::string getExtensionOfFilename(const std::string& filename);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ MeshBuilder2Data::MeshBuilder2Data() {
|
|||||||
lightMapEnabled = false;
|
lightMapEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshBuilder2Data::~MeshBuilder2Data() {}
|
MeshBuilder2Data::~MeshBuilder2Data() {
|
||||||
|
for (auto& material : materials) {
|
||||||
|
delete material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Tyra
|
} // namespace Tyra
|
||||||
|
|||||||
@@ -13,7 +13,13 @@
|
|||||||
|
|
||||||
namespace Tyra {
|
namespace Tyra {
|
||||||
|
|
||||||
MeshBuilder2MaterialData::MeshBuilder2MaterialData() {}
|
MeshBuilder2MaterialData::MeshBuilder2MaterialData() {
|
||||||
MeshBuilder2MaterialData::~MeshBuilder2MaterialData() {}
|
ambient.set(128.0F, 128.0F, 128.0F, 128.0F);
|
||||||
|
}
|
||||||
|
MeshBuilder2MaterialData::~MeshBuilder2MaterialData() {
|
||||||
|
for (auto& frame : frames) {
|
||||||
|
delete frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Tyra
|
} // namespace Tyra
|
||||||
|
|||||||
+8
-9
@@ -12,9 +12,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include "debug/debug.hpp"
|
#include "debug/debug.hpp"
|
||||||
#include "loaders/3d/builder/mesh_builder_data.hpp"
|
#include "loaders/3d/builder/mesh_builder_data.hpp"
|
||||||
#include "loaders/3d/md2/anorms.hpp"
|
#include "loaders/3d/md2_loader/anorms.hpp"
|
||||||
#include "loaders/loader.hpp"
|
#include "loaders/loader.hpp"
|
||||||
#include "loaders/3d/md2/md2_loader.hpp"
|
#include "loaders/3d/md2_loader/md2_loader.hpp"
|
||||||
|
|
||||||
namespace Tyra {
|
namespace Tyra {
|
||||||
|
|
||||||
@@ -96,9 +96,10 @@ MeshBuilderData* MD2Loader::load(const char* fullpath, const float& scale,
|
|||||||
u32 stsCount = header.num_st;
|
u32 stsCount = header.num_st;
|
||||||
u32 trianglesCount = header.num_tris;
|
u32 trianglesCount = header.num_tris;
|
||||||
|
|
||||||
auto framesBuffer = new char[framesCount * header.framesize];
|
auto framesBufferSize = framesCount * header.framesize;
|
||||||
|
auto framesBuffer = new char[framesBufferSize];
|
||||||
fseek(file, header.ofs_frames, SEEK_SET);
|
fseek(file, header.ofs_frames, SEEK_SET);
|
||||||
fread(framesBuffer, framesCount * header.framesize, 1, file);
|
fread(framesBuffer, framesBufferSize, 1, file);
|
||||||
|
|
||||||
auto stsBuffer = new char[stsCount * sizeof(texCoord_t)];
|
auto stsBuffer = new char[stsCount * sizeof(texCoord_t)];
|
||||||
fseek(file, header.ofs_st, SEEK_SET);
|
fseek(file, header.ofs_st, SEEK_SET);
|
||||||
@@ -116,6 +117,9 @@ MeshBuilderData* MD2Loader::load(const char* fullpath, const float& scale,
|
|||||||
result->textureCoordsEnabled = true;
|
result->textureCoordsEnabled = true;
|
||||||
result->manyColorsEnabled = false;
|
result->manyColorsEnabled = false;
|
||||||
|
|
||||||
|
result->materials[0]->allocateFaces(trianglesCount * 3);
|
||||||
|
result->materials[0]->name = getFilenameWithoutExtension(filename);
|
||||||
|
|
||||||
frame_t* frame;
|
frame_t* frame;
|
||||||
Vec4 temp(0.0F, 0.0F, 0.0F, 1.0F);
|
Vec4 temp(0.0F, 0.0F, 0.0F, 1.0F);
|
||||||
for (u32 j = 0; j < framesCount; j++) {
|
for (u32 j = 0; j < framesCount; j++) {
|
||||||
@@ -123,9 +127,6 @@ MeshBuilderData* MD2Loader::load(const char* fullpath, const float& scale,
|
|||||||
result->frames[j]->allocateNormals(vertexCount);
|
result->frames[j]->allocateNormals(vertexCount);
|
||||||
result->frames[j]->allocateTextureCoords(stsCount);
|
result->frames[j]->allocateTextureCoords(stsCount);
|
||||||
|
|
||||||
result->materials[0]->allocateFaces(trianglesCount * 3);
|
|
||||||
result->materials[0]->name = getFilenameWithoutExtension(filename);
|
|
||||||
|
|
||||||
frame = reinterpret_cast<frame_t*>(&framesBuffer[header.framesize * j]);
|
frame = reinterpret_cast<frame_t*>(&framesBuffer[header.framesize * j]);
|
||||||
|
|
||||||
for (u32 i = 0; i < vertexCount; i++) {
|
for (u32 i = 0; i < vertexCount; i++) {
|
||||||
@@ -179,8 +180,6 @@ MeshBuilderData* MD2Loader::load(const char* fullpath, const float& scale,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TYRA_LOG("MD2 file \"", filename, "\" loaded!");
|
|
||||||
|
|
||||||
delete[] framesBuffer;
|
delete[] framesBuffer;
|
||||||
delete[] stsBuffer;
|
delete[] stsBuffer;
|
||||||
delete[] trianglesBuffer;
|
delete[] trianglesBuffer;
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
# ______ ____ ___
|
||||||
|
# | \/ ____| |___|
|
||||||
|
# | | | \ | |
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
||||||
|
# Licenced under Apache License 2.0
|
||||||
|
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "math/vec2.hpp"
|
||||||
|
#include "math/vec4.hpp"
|
||||||
|
#include "renderer/models/color.hpp"
|
||||||
|
#include "debug/debug.hpp"
|
||||||
|
#include "loaders/3d/obj_loader/obj_loader.hpp"
|
||||||
|
|
||||||
|
#define TINYOBJLOADER_USE_MAPBOX_EARCUT
|
||||||
|
#define TINYOBJLOADER_IMPLEMENTATION
|
||||||
|
#include "loaders/3d/obj_loader/tiny_obj_loader.hpp"
|
||||||
|
|
||||||
|
namespace Tyra {
|
||||||
|
|
||||||
|
ObjLoader::ObjLoader() {}
|
||||||
|
|
||||||
|
ObjLoader::~ObjLoader() {}
|
||||||
|
|
||||||
|
MeshBuilder2Data* ObjLoader::load(const char* fullpath, const u16& count,
|
||||||
|
const float& scale, const bool& invertY) {
|
||||||
|
std::string path = fullpath;
|
||||||
|
std::string basePath = getPathFromFilename(path);
|
||||||
|
|
||||||
|
TYRA_ASSERT(!path.empty(), "Provided path is empty!");
|
||||||
|
|
||||||
|
auto rawFilename = getFilenameWithoutExtension(path);
|
||||||
|
auto extension = getExtensionOfFilename(path);
|
||||||
|
|
||||||
|
auto* result = new MeshBuilder2Data();
|
||||||
|
|
||||||
|
tinyobj::ObjReaderConfig readerConfig;
|
||||||
|
readerConfig.triangulate = count == 1;
|
||||||
|
readerConfig.triangulation_method = count == 1 ? "earcut" : "simple";
|
||||||
|
readerConfig.mtl_search_path = basePath;
|
||||||
|
|
||||||
|
for (u16 i = 1; i <= count; i++) {
|
||||||
|
std::string filePath = rawFilename + std::to_string(i) + "." + extension;
|
||||||
|
if (count == 1) filePath = path;
|
||||||
|
|
||||||
|
tinyobj::ObjReader reader;
|
||||||
|
|
||||||
|
if (!reader.ParseFromFile(filePath, readerConfig)) {
|
||||||
|
if (!reader.Error().empty()) {
|
||||||
|
TYRA_TRAP("TinyObjLoader: ", reader.Error());
|
||||||
|
}
|
||||||
|
TYRA_TRAP("Unknown TinyObjLoader error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reader.Warning().empty()) {
|
||||||
|
TYRA_WARN("TinyObjReader: ", reader.Warning());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& attrib = reader.GetAttrib();
|
||||||
|
auto& shapes = reader.GetShapes();
|
||||||
|
auto& materials = reader.GetMaterials();
|
||||||
|
|
||||||
|
TYRA_ASSERT(
|
||||||
|
materials.size() > 0,
|
||||||
|
"No material data found! Please add .mtl file(s) and assign them via "
|
||||||
|
"mtlib in obj file");
|
||||||
|
|
||||||
|
if (i == 1) {
|
||||||
|
setInitialData(result, attrib, shapes, materials, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
importFrame(result, attrib, shapes, materials, i - 1, scale, invertY,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjLoader::setInitialData(
|
||||||
|
MeshBuilder2Data* output, const tinyobj::attrib_t& attrib,
|
||||||
|
const std::vector<tinyobj::shape_t>& shapes,
|
||||||
|
const std::vector<tinyobj::material_t>& materials, const u16& framesCount) {
|
||||||
|
if (attrib.texcoords.size()) output->textureCoordsEnabled = true;
|
||||||
|
|
||||||
|
if (attrib.normals.size()) output->normalsEnabled = true;
|
||||||
|
|
||||||
|
TYRA_ASSERT(materials.size() > 0,
|
||||||
|
"No material data found! Please add .mtl "
|
||||||
|
"file(s) and assign them via mtlib in obj "
|
||||||
|
"file");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < materials.size(); i++) {
|
||||||
|
auto* material = new MeshBuilder2MaterialData();
|
||||||
|
|
||||||
|
material->name = materials[i].name;
|
||||||
|
|
||||||
|
material->ambient.set(materials[i].ambient[0] * 255.0F,
|
||||||
|
materials[i].ambient[1] * 255.0F,
|
||||||
|
materials[i].ambient[2] * 255.0F, 128.0F);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < framesCount; j++) {
|
||||||
|
auto* frame = new MeshBuilder2MaterialFrameData();
|
||||||
|
material->frames.push_back(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
output->materials.push_back(material);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObjLoader::importFrame(MeshBuilder2Data* output,
|
||||||
|
const tinyobj::attrib_t& attrib,
|
||||||
|
const std::vector<tinyobj::shape_t>& shapes,
|
||||||
|
const std::vector<tinyobj::material_t>& materials,
|
||||||
|
const u16& frameIndex, const float& scale,
|
||||||
|
const bool& invertY, const u16& count) {
|
||||||
|
for (size_t i = 0; i < shapes.size(); i++) {
|
||||||
|
const auto& mesh = shapes[i].mesh;
|
||||||
|
|
||||||
|
// Loop over shapes
|
||||||
|
for (size_t s = 0; s < shapes.size(); s++) {
|
||||||
|
size_t index_offset = 0;
|
||||||
|
// Loop over faces
|
||||||
|
for (size_t f = 0; f < mesh.num_face_vertices.size(); f++) {
|
||||||
|
auto materialId = mesh.material_ids[f];
|
||||||
|
auto* outFrame = output->materials[materialId]->frames[frameIndex];
|
||||||
|
|
||||||
|
auto isAllocated = outFrame->vertices != nullptr;
|
||||||
|
|
||||||
|
if (!isAllocated) {
|
||||||
|
auto vertCount = mesh.num_face_vertices.size() * 3;
|
||||||
|
|
||||||
|
outFrame->count = vertCount;
|
||||||
|
outFrame->vertices = new Vec4[vertCount];
|
||||||
|
|
||||||
|
if (output->textureCoordsEnabled)
|
||||||
|
outFrame->textureCoords = new Vec4[vertCount];
|
||||||
|
|
||||||
|
if (output->normalsEnabled) outFrame->normals = new Vec4[vertCount];
|
||||||
|
} else {
|
||||||
|
TYRA_ASSERT(outFrame->count == mesh.num_face_vertices.size() * 3,
|
||||||
|
"Multiple usage of the same \"usemtl\" is not supported! "
|
||||||
|
"Please merge them!");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fv = size_t(mesh.num_face_vertices[f]);
|
||||||
|
|
||||||
|
TYRA_ASSERT(count == 1 || fv == 3,
|
||||||
|
"Please triangulate obj files if you are animating!",
|
||||||
|
"Recommended Blender options: ",
|
||||||
|
"- Obj exporting: triangulate off, keep vertex order",
|
||||||
|
"- Object modifiers: triangulate (as first!), then other "
|
||||||
|
"modifiers");
|
||||||
|
|
||||||
|
// Loop over vertices in the face.
|
||||||
|
for (size_t v = 0; v < fv; v++) {
|
||||||
|
// access to vertex
|
||||||
|
tinyobj::index_t idx = mesh.indices[index_offset + v];
|
||||||
|
tinyobj::real_t vx =
|
||||||
|
attrib.vertices[3 * size_t(idx.vertex_index) + 0];
|
||||||
|
tinyobj::real_t vy =
|
||||||
|
attrib.vertices[3 * size_t(idx.vertex_index) + 1];
|
||||||
|
tinyobj::real_t vz =
|
||||||
|
attrib.vertices[3 * size_t(idx.vertex_index) + 2];
|
||||||
|
|
||||||
|
outFrame->vertices[index_offset + v].set(vx, vy, vz, 1.0F);
|
||||||
|
outFrame->vertices[index_offset + v] *= scale;
|
||||||
|
|
||||||
|
// Check if `normal_index` is zero or positive. negative = no normal
|
||||||
|
// data
|
||||||
|
if (idx.normal_index >= 0) {
|
||||||
|
tinyobj::real_t nx =
|
||||||
|
attrib.normals[3 * size_t(idx.normal_index) + 0];
|
||||||
|
tinyobj::real_t ny =
|
||||||
|
attrib.normals[3 * size_t(idx.normal_index) + 1];
|
||||||
|
tinyobj::real_t nz =
|
||||||
|
attrib.normals[3 * size_t(idx.normal_index) + 2];
|
||||||
|
|
||||||
|
outFrame->normals[index_offset + v].set(nx, ny, nz, 1.0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if `texcoord_index` is zero or positive. negative =
|
||||||
|
// notexcoord data
|
||||||
|
if (idx.texcoord_index >= 0) {
|
||||||
|
tinyobj::real_t tx =
|
||||||
|
attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
|
||||||
|
tinyobj::real_t ty =
|
||||||
|
attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
|
||||||
|
|
||||||
|
auto finalY = invertY ? 1.0F - ty : ty;
|
||||||
|
outFrame->textureCoords[index_offset + v].set(tx, finalY, 1.0F,
|
||||||
|
0.0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index_offset += fv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tyra
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
/*
|
|
||||||
# ______ ____ ___
|
|
||||||
# | \/ ____| |___|
|
|
||||||
# | | | \ | |
|
|
||||||
#-----------------------------------------------------------------------
|
|
||||||
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
|
||||||
# Licenced under Apache License 2.0
|
|
||||||
# Michał Mostowik <mostek3pl@gmail.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string>
|
|
||||||
#include "math/vec2.hpp"
|
|
||||||
#include "math/vec4.hpp"
|
|
||||||
#include "renderer/models/color.hpp"
|
|
||||||
#include "debug/debug.hpp"
|
|
||||||
#include "loaders/3d/tyrobj/tyrobj_loader.hpp"
|
|
||||||
|
|
||||||
namespace Tyra {
|
|
||||||
|
|
||||||
TyrobjLoader::TyrobjLoader() {}
|
|
||||||
|
|
||||||
TyrobjLoader::~TyrobjLoader() {}
|
|
||||||
|
|
||||||
MeshBuilderData* TyrobjLoader::load(const char* fullpath, const u16& count,
|
|
||||||
const float& scale, const bool& invertT) {
|
|
||||||
std::string path = fullpath;
|
|
||||||
TYRA_ASSERT(!path.empty(), "Provided path is empty!");
|
|
||||||
|
|
||||||
auto rawFilename = getFilenameWithoutExtension(path);
|
|
||||||
auto extension = getExtensionOfFilename(path);
|
|
||||||
|
|
||||||
std::string firstFile = path;
|
|
||||||
if (count > 1) firstFile = rawFilename + "1." + extension;
|
|
||||||
|
|
||||||
FILE* firstFileHandler = fopen(firstFile.c_str(), "rb");
|
|
||||||
TYRA_ASSERT(firstFileHandler != nullptr, "Failed to load: ", firstFile);
|
|
||||||
auto data = scan(firstFileHandler, count);
|
|
||||||
data.scale = scale;
|
|
||||||
data.invertT = invertT;
|
|
||||||
fclose(firstFileHandler);
|
|
||||||
|
|
||||||
auto result = new MeshBuilderData();
|
|
||||||
result->allocate(data.framesCount, data.materialsCount);
|
|
||||||
result->normalsEnabled = data.normalsCount > 0;
|
|
||||||
result->textureCoordsEnabled = data.stsCount > 0;
|
|
||||||
result->manyColorsEnabled = data.colorsCount > 0;
|
|
||||||
|
|
||||||
for (u16 i = 1; i <= count; i++) {
|
|
||||||
std::string filePath = rawFilename + std::to_string(i) + "." + extension;
|
|
||||||
if (count == 1) filePath = path;
|
|
||||||
|
|
||||||
FILE* fileHandler = fopen(filePath.c_str(), "rb");
|
|
||||||
TYRA_ASSERT(fileHandler != nullptr, "Failed to load: ", filePath);
|
|
||||||
loadFile(fileHandler, filePath, i - 1, data, result);
|
|
||||||
fclose(fileHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data.materialsFaces;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TyraobjData TyrobjLoader::scan(FILE* file, const u32& framesCount) {
|
|
||||||
TyraobjData result;
|
|
||||||
result.vertexCount = 0;
|
|
||||||
result.stsCount = 0;
|
|
||||||
result.normalsCount = 0;
|
|
||||||
result.colorsCount = 0;
|
|
||||||
result.materialsCount = 0;
|
|
||||||
result.framesCount = framesCount;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
char lineHeader[128];
|
|
||||||
int res = fscanf(file, "%s", lineHeader);
|
|
||||||
if (res != EOF) {
|
|
||||||
if (strcmp(lineHeader, "v") == 0)
|
|
||||||
result.vertexCount += 1;
|
|
||||||
else if (strcmp(lineHeader, "vt") == 0)
|
|
||||||
result.stsCount += 1;
|
|
||||||
else if (strcmp(lineHeader, "vn") == 0)
|
|
||||||
result.normalsCount += 1;
|
|
||||||
else if (strcmp(lineHeader, "vc") == 0)
|
|
||||||
result.colorsCount += 1;
|
|
||||||
else if (strcmp(lineHeader, "usemtl") == 0)
|
|
||||||
result.materialsCount += 1;
|
|
||||||
} else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.materialsFaces = new u32[result.materialsCount];
|
|
||||||
s16 currentMatI = -1;
|
|
||||||
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
u32 facesCounter = 0;
|
|
||||||
while (1) {
|
|
||||||
char lineHeader[128];
|
|
||||||
int res = fscanf(file, "%s", lineHeader);
|
|
||||||
if (res != EOF) {
|
|
||||||
if (strcmp(lineHeader, "usemtl") == 0) {
|
|
||||||
if (currentMatI >= 0) // Skip -1
|
|
||||||
{
|
|
||||||
result.materialsFaces[currentMatI] = facesCounter;
|
|
||||||
facesCounter = 0;
|
|
||||||
}
|
|
||||||
currentMatI++;
|
|
||||||
} else if (strcmp(lineHeader, "f") == 0)
|
|
||||||
facesCounter += 3;
|
|
||||||
} else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.materialsFaces[currentMatI] = facesCounter; // Allocate last one
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::loadFile(FILE* file, const std::string& path,
|
|
||||||
const u16& frameIndex, const TyraobjData& inputData,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
outputData->frames[frameIndex]->allocateVertices(inputData.vertexCount);
|
|
||||||
outputData->frames[frameIndex]->allocateNormals(inputData.normalsCount);
|
|
||||||
outputData->frames[frameIndex]->allocateTextureCoords(inputData.stsCount);
|
|
||||||
outputData->frames[frameIndex]->allocateColors(inputData.colorsCount);
|
|
||||||
|
|
||||||
TyraobjReadInfo readInfo;
|
|
||||||
initReadInfo(&readInfo);
|
|
||||||
|
|
||||||
int res = 0;
|
|
||||||
char* lineHeader = new char[128];
|
|
||||||
|
|
||||||
while (res != EOF) {
|
|
||||||
if (strcmp(lineHeader, "v") == 0) {
|
|
||||||
readVertices(&readInfo, file, frameIndex, inputData, outputData);
|
|
||||||
} else if (strcmp(lineHeader, "vt") == 0) {
|
|
||||||
readTextureCoords(&readInfo, file, frameIndex, inputData, outputData);
|
|
||||||
} else if (strcmp(lineHeader, "vn") == 0) {
|
|
||||||
readNormals(&readInfo, file, frameIndex, outputData);
|
|
||||||
} else if (strcmp(lineHeader, "vc") == 0) {
|
|
||||||
readColors(&readInfo, file, frameIndex, outputData);
|
|
||||||
} else if (strcmp(lineHeader, "usemtl") == 0) {
|
|
||||||
readInfo.materialsI++;
|
|
||||||
readInfo.faceI = 0;
|
|
||||||
if (frameIndex == 0) {
|
|
||||||
readMaterials(&readInfo, file, inputData, outputData);
|
|
||||||
}
|
|
||||||
} else if (strcmp(lineHeader, "f") == 0) {
|
|
||||||
if (frameIndex == 0) {
|
|
||||||
readFaces(&readInfo, file, outputData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = fscanf(file, "%s", lineHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] lineHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::initReadInfo(TyraobjReadInfo* info) {
|
|
||||||
info->verticesI = 0;
|
|
||||||
info->coordsI = 0;
|
|
||||||
info->normalsI = 0;
|
|
||||||
info->colorsI = 0;
|
|
||||||
info->faceI = 0;
|
|
||||||
info->materialsI = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::readVertices(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const u16& frameIndex,
|
|
||||||
const TyraobjData& inputData,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
Vec4 vector(0.0F, 0.0F, 0.0F, 1.0F);
|
|
||||||
fscanf(file, "%f %f %f\n", &vector.x, &vector.y, &vector.z);
|
|
||||||
outputData->frames[frameIndex]->vertices[info->verticesI++].set(
|
|
||||||
vector * inputData.scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::readTextureCoords(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const u16& frameIndex,
|
|
||||||
const TyraobjData& inputData,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
Vec2 point(0.0F, 0.0F);
|
|
||||||
fscanf(file, "%f %f\n", &point.x, &point.y);
|
|
||||||
if (inputData.invertT) point.y = 1.0F - point.y;
|
|
||||||
outputData->frames[frameIndex]->textureCoords[info->coordsI++].set(
|
|
||||||
point.x, point.y, 1.0F, 0.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::readNormals(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const u16& frameIndex,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
Vec4 vector(0.0F, 0.0F, 0.0F, 1.0F);
|
|
||||||
fscanf(file, "%f %f %f\n", &vector.x, &vector.y, &vector.z);
|
|
||||||
outputData->frames[frameIndex]->normals[info->normalsI++].set(vector);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::readColors(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const u16& frameIndex,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
Color color(128.0F, 128.0F, 128.0F, 128.0F);
|
|
||||||
fscanf(file, "%f %f %f %f\n", &color.r, &color.g, &color.b, &color.a);
|
|
||||||
outputData->frames[frameIndex]->colors[info->colorsI++].set(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TyrobjLoader::readMaterials(TyraobjReadInfo* info, FILE* file,
|
|
||||||
const TyraobjData& inputData,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
char temp[30];
|
|
||||||
fscanf(file, "%s\n", temp);
|
|
||||||
outputData->materials[info->materialsI]->allocateFaces(
|
|
||||||
inputData.materialsFaces[info->materialsI]);
|
|
||||||
outputData->materials[info->materialsI]->name = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
int test123 = 0;
|
|
||||||
|
|
||||||
void TyrobjLoader::readFaces(TyraobjReadInfo* info, FILE* file,
|
|
||||||
MeshBuilderData* outputData) {
|
|
||||||
int* x = new int[9];
|
|
||||||
fpos_t start;
|
|
||||||
fgetpos(file, &start);
|
|
||||||
int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &x[0], &x[1],
|
|
||||||
&x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &x[8]);
|
|
||||||
fsetpos(file, &start);
|
|
||||||
|
|
||||||
// TODO: No support of vc!
|
|
||||||
u32 vertexIndex[3], coordIndex[3], normalIndex[3]; // ,colorIndex[3];
|
|
||||||
for (u16 i = 0; i < 3; i++) {
|
|
||||||
vertexIndex[i] = 0;
|
|
||||||
coordIndex[i] = 0;
|
|
||||||
normalIndex[i] = 0;
|
|
||||||
// colorIndex[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int newerMatches = 0;
|
|
||||||
|
|
||||||
switch (matches) {
|
|
||||||
/** Vs, VTs and VNs all set */
|
|
||||||
case 9: {
|
|
||||||
fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0],
|
|
||||||
&coordIndex[0], &normalIndex[0], &vertexIndex[1], &coordIndex[1],
|
|
||||||
&normalIndex[1], &vertexIndex[2], &coordIndex[2], &normalIndex[2]);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
/** Loaded only two digits (V, VT) succesfuly. Not setting VN. */
|
|
||||||
case 3: {
|
|
||||||
fscanf(file, "%d/%d/ %d/%d/ %d/%d/\n", &vertexIndex[0], &coordIndex[0],
|
|
||||||
&vertexIndex[1], &coordIndex[1], &vertexIndex[2], &coordIndex[2]);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
/** Only V set. Checking for existance of VT or VN. */
|
|
||||||
case 1: {
|
|
||||||
/** Check for existance of V/// configuration.. */
|
|
||||||
newerMatches = fscanf(file, "%d// %d// %d//\n", &x[0], &x[1], &x[2]);
|
|
||||||
fsetpos(file, &start);
|
|
||||||
if (newerMatches == 3) {
|
|
||||||
/** Configuration confirmed. */
|
|
||||||
fscanf(file, "%d// %d// %d//\n", &vertexIndex[0], &vertexIndex[1],
|
|
||||||
&vertexIndex[2]);
|
|
||||||
} else {
|
|
||||||
/** Failed, checking configuration V//VN */
|
|
||||||
newerMatches = fscanf(file, "%d//%d %d//%d %d//%d", &x[0], &x[1], &x[2],
|
|
||||||
&x[3], &x[4], &x[5]);
|
|
||||||
fsetpos(file, &start);
|
|
||||||
TYRA_ASSERT(newerMatches == 6, "Unknown .obj face for .obj file!");
|
|
||||||
/** Configuration confirmed. */
|
|
||||||
newerMatches = fscanf(file, "%d//%d %d//%d %d//%d", &vertexIndex[0],
|
|
||||||
&normalIndex[0], &vertexIndex[1], &normalIndex[1],
|
|
||||||
&vertexIndex[2], &normalIndex[2]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} break;
|
|
||||||
default: {
|
|
||||||
TYRA_TRAP("Unknown faces format in .obj file!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set vertex indices
|
|
||||||
if (matches == 9 || matches == 2 || matches == 1) {
|
|
||||||
outputData->materials[info->materialsI]->vertexFaces[info->faceI] =
|
|
||||||
vertexIndex[0] - 1;
|
|
||||||
outputData->materials[info->materialsI]->vertexFaces[info->faceI + 1] =
|
|
||||||
vertexIndex[1] - 1;
|
|
||||||
outputData->materials[info->materialsI]->vertexFaces[info->faceI + 2] =
|
|
||||||
vertexIndex[2] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set texture coord indices
|
|
||||||
if (matches == 9 || matches == 2) {
|
|
||||||
outputData->materials[info->materialsI]->textureCoordFaces[info->faceI] =
|
|
||||||
coordIndex[0] - 1;
|
|
||||||
outputData->materials[info->materialsI]
|
|
||||||
->textureCoordFaces[info->faceI + 1] = coordIndex[1] - 1;
|
|
||||||
outputData->materials[info->materialsI]
|
|
||||||
->textureCoordFaces[info->faceI + 2] = coordIndex[2] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set normal indices
|
|
||||||
if (matches == 9) {
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI] =
|
|
||||||
normalIndex[0] - 1;
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI + 1] =
|
|
||||||
normalIndex[1] - 1;
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI + 2] =
|
|
||||||
normalIndex[2] - 1;
|
|
||||||
} else if (matches == 1 && newerMatches == 6) {
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI] =
|
|
||||||
normalIndex[0] - 1;
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI + 1] =
|
|
||||||
normalIndex[1] - 1;
|
|
||||||
outputData->materials[info->materialsI]->normalFaces[info->faceI + 2] =
|
|
||||||
normalIndex[2] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push index
|
|
||||||
info->faceI += 3;
|
|
||||||
|
|
||||||
delete[] x;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Tyra
|
|
||||||
@@ -21,6 +21,14 @@ std::string Loader::getFilenameFromPath(const std::string& path) {
|
|||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Loader::getPathFromFilename(const std::string& path) {
|
||||||
|
std::string basepath = path.substr(0, path.find_last_of("/\\"));
|
||||||
|
if (basepath.size() == path.size()) {
|
||||||
|
basepath = path.substr(0, path.find_last_of(":\\"));
|
||||||
|
}
|
||||||
|
return basepath;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Loader::getFilenameWithoutExtension(const std::string& filename) {
|
std::string Loader::getFilenameWithoutExtension(const std::string& filename) {
|
||||||
auto lastindex = filename.find_last_of(".");
|
auto lastindex = filename.find_last_of(".");
|
||||||
return filename.substr(0, lastindex);
|
return filename.substr(0, lastindex);
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ namespace Tyra {
|
|||||||
DynamicMesh::DynamicMesh(const MeshBuilderData& data) : Mesh(data) {
|
DynamicMesh::DynamicMesh(const MeshBuilderData& data) : Mesh(data) {
|
||||||
framesCount = data.framesCount;
|
framesCount = data.framesCount;
|
||||||
|
|
||||||
TYRA_ERROR(framesCount > 1,
|
TYRA_WARN(
|
||||||
"Frames count should be greater than 1 for DynamicMesh! Maybe you "
|
"Frames count should be greater than 1 for DynamicMesh! Maybe you "
|
||||||
"should use StaticMesh?");
|
"should use StaticMesh?");
|
||||||
|
|
||||||
frames = new MeshFrame*[framesCount];
|
frames = new MeshFrame*[framesCount];
|
||||||
for (u32 i = 0; i < framesCount; i++) {
|
for (u32 i = 0; i < framesCount; i++) {
|
||||||
@@ -33,9 +33,9 @@ DynamicMesh::DynamicMesh(const MeshBuilderData& data) : Mesh(data) {
|
|||||||
DynamicMesh::DynamicMesh(const MeshBuilder2Data& data) : Mesh(data) {
|
DynamicMesh::DynamicMesh(const MeshBuilder2Data& data) : Mesh(data) {
|
||||||
framesCount = data.materials[0]->frames.size();
|
framesCount = data.materials[0]->frames.size();
|
||||||
|
|
||||||
TYRA_ERROR(framesCount > 1,
|
TYRA_WARN(
|
||||||
"Frames count should be greater than 1 for DynamicMesh! Maybe you "
|
"Frames count should be greater than 1 for DynamicMesh! Maybe you "
|
||||||
"should use StaticMesh?");
|
"should use StaticMesh?");
|
||||||
|
|
||||||
frames = new MeshFrame*[framesCount];
|
frames = new MeshFrame*[framesCount];
|
||||||
for (u32 i = 0; i < framesCount; i++) {
|
for (u32 i = 0; i < framesCount; i++) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ MeshMaterial::MeshMaterial(const MeshBuilder2Data& data,
|
|||||||
singleColorFlag = true;
|
singleColorFlag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
color.set(128.0F, 128.0F, 128.0F, 128.0F);
|
color.set(material->ambient);
|
||||||
|
|
||||||
_name = material->name;
|
_name = material->name;
|
||||||
TYRA_ASSERT(_name.length() > 0, "MeshMaterial name cannot be empty");
|
TYRA_ASSERT(_name.length() > 0, "MeshMaterial name cannot be empty");
|
||||||
|
|||||||
@@ -56,10 +56,12 @@ MeshMaterialFrame::MeshMaterialFrame(const MeshBuilder2Data& data,
|
|||||||
|
|
||||||
TYRA_ASSERT(frame->count > 0, "Vertex count must be greater than 0");
|
TYRA_ASSERT(frame->count > 0, "Vertex count must be greater than 0");
|
||||||
TYRA_ASSERT(frame->vertices != nullptr, "Vertex array can't be null");
|
TYRA_ASSERT(frame->vertices != nullptr, "Vertex array can't be null");
|
||||||
TYRA_ASSERT(frame->normals != nullptr, "Normal array can't be null");
|
TYRA_ASSERT(!data.normalsEnabled || frame->normals != nullptr,
|
||||||
TYRA_ASSERT(frame->textureCoords != nullptr,
|
"Normal array can't be null");
|
||||||
|
TYRA_ASSERT(!data.textureCoordsEnabled || frame->textureCoords != nullptr,
|
||||||
"Texture coordinate array can't be null");
|
"Texture coordinate array can't be null");
|
||||||
TYRA_ASSERT(frame->colors != nullptr, "Color array can't be null");
|
TYRA_ASSERT(!data.lightMapEnabled || frame->colors != nullptr,
|
||||||
|
"Color array can't be null");
|
||||||
|
|
||||||
bbox = new BBox(frame->vertices, frame->count);
|
bbox = new BBox(frame->vertices, frame->count);
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
#include "tutorial_01.hpp"
|
#include "tutorial_01.hpp"
|
||||||
#include "file/file_utils.hpp"
|
#include "file/file_utils.hpp"
|
||||||
#include "loaders/3d/md2/md2_loader.hpp"
|
#include "loaders/3d/md2_loader/md2_loader.hpp"
|
||||||
#include "loaders/3d/tyrobj/tyrobj_loader.hpp"
|
#include "loaders/3d/obj_loader/obj_loader.hpp"
|
||||||
#include "thread/threading.hpp"
|
#include "thread/threading.hpp"
|
||||||
|
|
||||||
namespace Tyra {
|
namespace Tyra {
|
||||||
@@ -201,9 +201,9 @@ StaticMesh* getStaticMesh(Renderer* renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StaticMesh* getSkybox(Renderer* renderer) {
|
StaticMesh* getSkybox(Renderer* renderer) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data =
|
auto* data =
|
||||||
loader.load(FileUtils::fromCwd("skybox/skybox.tyrobj"), 1, 200.0F, true);
|
loader.load(FileUtils::fromCwd("skybox/skybox.obj"), 1, 200.0F, true);
|
||||||
auto* result = new StaticMesh(*data);
|
auto* result = new StaticMesh(*data);
|
||||||
// result->translation.translateZ(-30.0F);
|
// result->translation.translateZ(-30.0F);
|
||||||
delete data;
|
delete data;
|
||||||
@@ -231,9 +231,8 @@ DynamicMesh* getWarrior(Renderer* renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DynamicMesh* getCube(Renderer* renderer) {
|
DynamicMesh* getCube(Renderer* renderer) {
|
||||||
TyrobjLoader loader;
|
ObjLoader loader;
|
||||||
auto* data =
|
auto* data = loader.load(FileUtils::fromCwd("untitled.obj"), 2, 3.0F, false);
|
||||||
loader.load(FileUtils::fromCwd("untitled.tyrobj"), 2, 3.0F, false);
|
|
||||||
|
|
||||||
auto* result = new DynamicMesh(*data);
|
auto* result = new DynamicMesh(*data);
|
||||||
result->translation.translateZ(-30.0F);
|
result->translation.translateZ(-30.0F);
|
||||||
|
|||||||
Reference in New Issue
Block a user