Skip to content

Commit d35c2a8

Browse files
committed
concave polygon support - fixes #56
1 parent 780efea commit d35c2a8

File tree

2 files changed

+197
-28
lines changed

2 files changed

+197
-28
lines changed

src/ofbx.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4227,5 +4227,197 @@ const char* getError()
42274227
return Error::s_message;
42284228
}
42294229

4230+
static bool isConvex(const GeometryData& geom, const GeometryPartition::Polygon& polygon) {
4231+
if (polygon.vertex_count < 3) return false;
4232+
if (polygon.vertex_count == 3) return true;
4233+
4234+
const Vec3Attributes positions = geom.getPositions();
4235+
if (!positions.values) return false;
4236+
4237+
Vec3 normal = {0, 0, 0};
4238+
// Compute polygon normal using Newell's method for robustness
4239+
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
4240+
for (int i = 0; i < polygon.vertex_count; ++i) {
4241+
int curr_idx = polygon.from_vertex + i;
4242+
int next_idx = polygon.from_vertex + ((i + 1) % polygon.vertex_count);
4243+
4244+
Vec3 v_curr = positions.get(curr_idx);
4245+
Vec3 v_next = positions.get(next_idx);
4246+
4247+
normal.x += (v_curr.y - v_next.y) * (v_curr.z + v_next.z);
4248+
normal.y += (v_curr.z - v_next.z) * (v_curr.x + v_next.x);
4249+
normal.z += (v_curr.x - v_next.x) * (v_curr.y + v_next.y);
4250+
}
4251+
4252+
float normal_lensq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z;
4253+
if (normal_lensq < 1e-6f) return false;
4254+
4255+
bool sign_positive = false;
4256+
bool sign_negative = false;
4257+
4258+
for (int i = 0; i < polygon.vertex_count; ++i) {
4259+
int prev_idx = polygon.from_vertex + ((i - 1 + polygon.vertex_count) % polygon.vertex_count);
4260+
int curr_idx = polygon.from_vertex + i;
4261+
int next_idx = polygon.from_vertex + ((i + 1) % polygon.vertex_count);
4262+
4263+
Vec3 v_prev = positions.get(prev_idx);
4264+
Vec3 v_curr = positions.get(curr_idx);
4265+
Vec3 v_next = positions.get(next_idx);
4266+
4267+
// Vectors from current vertex to adjacent vertices
4268+
Vec3 edge1 = {v_prev.x - v_curr.x, v_prev.y - v_curr.y, v_prev.z - v_curr.z};
4269+
Vec3 edge2 = {v_next.x - v_curr.x, v_next.y - v_curr.y, v_next.z - v_curr.z};
4270+
4271+
// Cross product to get turn direction
4272+
Vec3 cross = {
4273+
edge1.y * edge2.z - edge1.z * edge2.y,
4274+
edge1.z * edge2.x - edge1.x * edge2.z,
4275+
edge1.x * edge2.y - edge1.y * edge2.x
4276+
};
4277+
4278+
float dot = cross.x * normal.x + cross.y * normal.y + cross.z * normal.z;
4279+
4280+
if (dot > 1e-6f) sign_positive = true;
4281+
else if (dot < -1e-6f) sign_negative = true;
4282+
4283+
if (sign_positive && sign_negative) return false;
4284+
}
4285+
4286+
return true;
4287+
}
4288+
4289+
u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& polygon, int* tri_indices, int* tmp) {
4290+
if (polygon.vertex_count < 3) return 0;
4291+
if (polygon.vertex_count == 3) {
4292+
tri_indices[0] = polygon.from_vertex;
4293+
tri_indices[1] = polygon.from_vertex + 1;
4294+
tri_indices[2] = polygon.from_vertex + 2;
4295+
return 3;
4296+
}
4297+
4298+
if (isConvex(geom, polygon)) {
4299+
for (int tri = 0; tri < polygon.vertex_count - 2; ++tri) {
4300+
tri_indices[tri * 3 + 0] = polygon.from_vertex;
4301+
tri_indices[tri * 3 + 1] = polygon.from_vertex + 1 + tri;
4302+
tri_indices[tri * 3 + 2] = polygon.from_vertex + 2 + tri;
4303+
}
4304+
return 3 * (polygon.vertex_count - 2);
4305+
}
4306+
4307+
// ear clipping - the simplest (and not the most efficient) implementation
4308+
int tmp_mem[128]; // temporary memory if use did not pass any in `tmp`
4309+
int* vertices = tmp ? tmp : tmp_mem;
4310+
4311+
if (!tmp && polygon.vertex_count > 128) return 0;
4312+
4313+
for (int i = 0; i < polygon.vertex_count; ++i) {
4314+
vertices[i] = polygon.from_vertex + i;
4315+
}
4316+
4317+
int num_vertices = polygon.vertex_count;
4318+
int tri_count = 0;
4319+
4320+
const Vec3Attributes positions = geom.getPositions();
4321+
if (!positions.values) return 0;
4322+
4323+
Vec3 v0 = positions.get(vertices[0]);
4324+
Vec3 v1 = positions.get(vertices[1]);
4325+
Vec3 v2 = positions.get(vertices[2]);
4326+
4327+
Vec3 edge1 = {v1.x - v0.x, v1.y - v0.y, v1.z - v0.z};
4328+
Vec3 edge2 = {v2.x - v0.x, v2.y - v0.y, v2.z - v0.z};
4329+
4330+
Vec3 normal = {
4331+
edge1.y * edge2.z - edge1.z * edge2.y,
4332+
edge1.z * edge2.x - edge1.x * edge2.z,
4333+
edge1.x * edge2.y - edge1.y * edge2.x
4334+
};
4335+
4336+
while (num_vertices > 3) {
4337+
bool ear_found = false;
4338+
4339+
for (int i = 0; i < num_vertices; ++i) {
4340+
const int prev = (i - 1 + num_vertices) % num_vertices;
4341+
const int curr = i;
4342+
const int next = (i + 1) % num_vertices;
4343+
4344+
const Vec3 vp = positions.get(vertices[prev]);
4345+
const Vec3 vc = positions.get(vertices[curr]);
4346+
const Vec3 vn = positions.get(vertices[next]);
4347+
4348+
const Vec3 e1 = {vc.x - vp.x, vc.y - vp.y, vc.z - vp.z};
4349+
const Vec3 e2 = {vn.x - vc.x, vn.y - vc.y, vn.z - vc.z};
4350+
4351+
const Vec3 cross = {
4352+
e1.y * e2.z - e1.z * e2.y,
4353+
e1.z * e2.x - e1.x * e2.z,
4354+
e1.x * e2.y - e1.y * e2.x
4355+
};
4356+
4357+
const float dot = cross.x * normal.x + cross.y * normal.y + cross.z * normal.z;
4358+
4359+
// 3 vertices with wrong winding order can not make an ear
4360+
if (dot <= 0) continue;
4361+
4362+
// Check if any other vertex is inside this triangle
4363+
bool is_ear = true;
4364+
for (int j = 0; j < num_vertices; ++j) {
4365+
if (j == prev || j == curr || j == next) continue;
4366+
4367+
Vec3 vt = positions.get(vertices[j]);
4368+
4369+
// Barycentric coordinate test
4370+
Vec3 v0v1 = {vc.x - vp.x, vc.y - vp.y, vc.z - vp.z};
4371+
Vec3 v0v2 = {vn.x - vp.x, vn.y - vp.y, vn.z - vp.z};
4372+
Vec3 v0vt = {vt.x - vp.x, vt.y - vp.y, vt.z - vp.z};
4373+
4374+
float dot00 = v0v2.x * v0v2.x + v0v2.y * v0v2.y + v0v2.z * v0v2.z;
4375+
float dot01 = v0v2.x * v0v1.x + v0v2.y * v0v1.y + v0v2.z * v0v1.z;
4376+
float dot02 = v0v2.x * v0vt.x + v0v2.y * v0vt.y + v0v2.z * v0vt.z;
4377+
float dot11 = v0v1.x * v0v1.x + v0v1.y * v0v1.y + v0v1.z * v0v1.z;
4378+
float dot12 = v0v1.x * v0vt.x + v0v1.y * v0vt.y + v0v1.z * v0vt.z;
4379+
4380+
float inv_denom = 1.0f / (dot00 * dot11 - dot01 * dot01);
4381+
float u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
4382+
float v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
4383+
4384+
if (u > 0 && v > 0 && u + v < 1) {
4385+
is_ear = false;
4386+
break;
4387+
}
4388+
}
4389+
4390+
if (is_ear) {
4391+
tri_indices[tri_count * 3 + 0] = vertices[prev];
4392+
tri_indices[tri_count * 3 + 1] = vertices[curr];
4393+
tri_indices[tri_count * 3 + 2] = vertices[next];
4394+
tri_count++;
4395+
4396+
// Remove current vertex from polygon
4397+
for (int k = curr; k < num_vertices - 1; ++k) {
4398+
vertices[k] = vertices[k + 1];
4399+
}
4400+
num_vertices--;
4401+
ear_found = true;
4402+
break;
4403+
}
4404+
}
4405+
4406+
if (!ear_found) {
4407+
// Fallback: couldn't find ear, add remaining triangle
4408+
break;
4409+
}
4410+
}
4411+
4412+
// Add final triangle
4413+
if (num_vertices == 3) {
4414+
tri_indices[tri_count * 3 + 0] = vertices[0];
4415+
tri_indices[tri_count * 3 + 1] = vertices[1];
4416+
tri_indices[tri_count * 3 + 2] = vertices[2];
4417+
tri_count++;
4418+
}
4419+
4420+
return tri_count * 3;
4421+
}
42304422

42314423
} // namespace ofbx

src/ofbx.h

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -803,34 +803,11 @@ const char* getError();
803803
double fbxTimeToSeconds(i64 value);
804804
i64 secondsToFbxTime(double value);
805805

806-
// TODO nonconvex
807-
inline u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& polygon, int* tri_indices) {
808-
if (polygon.vertex_count < 3) return 0;
809-
if (polygon.vertex_count == 3) {
810-
tri_indices[0] = polygon.from_vertex;
811-
tri_indices[1] = polygon.from_vertex + 1;
812-
tri_indices[2] = polygon.from_vertex + 2;
813-
return 3;
814-
}
815-
else if (polygon.vertex_count == 4) {
816-
tri_indices[0] = polygon.from_vertex + 0;
817-
tri_indices[1] = polygon.from_vertex + 1;
818-
tri_indices[2] = polygon.from_vertex + 2;
819-
820-
tri_indices[3] = polygon.from_vertex + 0;
821-
tri_indices[4] = polygon.from_vertex + 2;
822-
tri_indices[5] = polygon.from_vertex + 3;
823-
return 6;
824-
}
825-
826-
for (int tri = 0; tri < polygon.vertex_count - 2; ++tri) {
827-
tri_indices[tri * 3 + 0] = polygon.from_vertex;
828-
tri_indices[tri * 3 + 1] = polygon.from_vertex + 1 + tri;
829-
tri_indices[tri * 3 + 2] = polygon.from_vertex + 2 + tri;
830-
}
831-
return 3 * (polygon.vertex_count - 2);
832-
}
833-
806+
// You can pass temporary memory in `tmp` (must be at least as large as polygon.vertex_count)
807+
// If `tmp` is null, only concave polygons with polygon.vertex_count <= 128 are supported
808+
// `tri_indices` contains indices of triangles
809+
// Returns number of resulting indices
810+
u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& polygon, int* tri_indices, int* tmp = nullptr);
834811

835812
} // namespace ofbx
836813

0 commit comments

Comments
 (0)