| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Compositor sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/internal/mesh.h> | |
| #include <gpac/color.h> | |
| #if !defined(GPAC_DISABLE_3D) && !defined(GPAC_DISABLE_EVG) | |
| /*for GPAC_HAS_GLU*/ | |
| #include "gl_inc.h" | |
| #ifndef CALLBACK | |
| #define CALLBACK | |
| #endif | |
| #ifdef GPAC_HAS_GLU | |
| #ifdef GPAC_CONFIG_IOS | |
| #define GLdouble GLfloat | |
| #endif | |
| typedef struct | |
| { | |
| /*for tesselation*/ | |
| GLUtesselator *tess_obj; | |
| GF_Mesh *mesh; | |
| /*vertex indices: we cannot use a static array because reallocating the array will likely change memory | |
| address of indices, hence break triangulator*/ | |
| GF_List *vertex_index; | |
| } MeshTess; | |
| static void CALLBACK mesh_tess_begin(GLenum which) { | |
| gf_assert(which==GL_TRIANGLES); | |
| } | |
| static void CALLBACK mesh_tess_end(void) { | |
| } | |
| static void CALLBACK mesh_tess_error(GLenum error_code) | |
| { | |
| if (error_code) | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Mesh] Tesselate error %s\n", gluErrorString(error_code))); | |
| } | |
| /*only needed to force GL_TRIANGLES*/ | |
| static void CALLBACK mesh_tess_edgeflag(GLenum flag) { } | |
| static void CALLBACK mesh_tess_vertex(void *vertexData, void *user_data) | |
| { | |
| MeshTess *tess = (MeshTess *) user_data; | |
| mesh_set_index(tess->mesh, *(u32*)vertexData); | |
| } | |
| static void CALLBACK mesh_tess_combine(GLdouble coords[3], void* vertex_data[4], GLfloat weight[4], void** out_data, void *user_data) | |
| { | |
| u32 i, idx; | |
| u32 *new_idx; | |
| SFVec3f n; | |
| SFVec2f tx; | |
| SFColor col; | |
| MeshTess *tess = (MeshTess *) user_data; | |
| col.red = col.green = col.blue = 0; | |
| if (tess->mesh->flags & MESH_HAS_COLOR) { | |
| for (i=0; i<4; i++) { | |
| if (weight[i]) { | |
| SFColorRGBA rgba; | |
| Fixed _weight = FLT2FIX(weight[i]); | |
| idx = * (u32 *) vertex_data[i]; | |
| MESH_GET_COLOR(rgba, tess->mesh->vertices[idx]); | |
| col.red += gf_mulfix(_weight, rgba.red); | |
| col.green += gf_mulfix(_weight, rgba.green); | |
| col.blue += gf_mulfix(_weight, rgba.blue); | |
| } | |
| } | |
| } | |
| n.x = n.y = n.z = 0; | |
| if (tess->mesh->flags & MESH_IS_2D) { | |
| n.z = FIX_ONE; | |
| } else { | |
| for (i=0; i<4; i++) { | |
| if (weight[i]) { | |
| Fixed _weight = FLT2FIX(weight[i]); | |
| SFVec3f _n; | |
| idx = * (u32 *) vertex_data[i]; | |
| MESH_GET_NORMAL(_n, tess->mesh->vertices[idx]); | |
| n.x += gf_mulfix(_weight, _n.x); | |
| n.y += gf_mulfix(_weight, _n.y); | |
| n.z += gf_mulfix(_weight, _n.z); | |
| } | |
| } | |
| } | |
| tx.x = tx.y = 0; | |
| if (!(tess->mesh->flags & MESH_NO_TEXTURE)) { | |
| for (i=0; i<4; i++) { | |
| if (weight[i]) { | |
| Fixed _weight = FLT2FIX(weight[i]); | |
| idx = * (u32 *) vertex_data[i]; | |
| tx.x += gf_mulfix(_weight, tess->mesh->vertices[idx].texcoords.x); | |
| tx.y += gf_mulfix(_weight, tess->mesh->vertices[idx].texcoords.y); | |
| } | |
| } | |
| } | |
| new_idx = (u32 *) gf_malloc(sizeof(u32)); | |
| gf_list_add(tess->vertex_index, new_idx); | |
| *new_idx = tess->mesh->v_count; | |
| mesh_set_vertex(tess->mesh, FLT2FIX( (Float) coords[0]), FLT2FIX( (Float) coords[1]), FLT2FIX( (Float) coords[2]), n.x, n.y, n.z, tx.x, tx.y); | |
| *out_data = new_idx; | |
| } | |
| void gf_mesh_tesselate_path(GF_Mesh *mesh, GF_Path *path, u32 outline_style) | |
| { | |
| u32 i, j, cur, nb_pts; | |
| u32 *idx; | |
| Fixed w, h, min_y; | |
| GF_Rect rc; | |
| GLdouble vertex[3]; | |
| MeshTess *tess; | |
| if (!mesh || !path || !path->n_contours) return; | |
| tess = gf_malloc(sizeof(MeshTess)); | |
| if (!tess) return; | |
| memset(tess, 0, sizeof(MeshTess)); | |
| tess->tess_obj = gluNewTess(); | |
| if (!tess->tess_obj) { | |
| gf_free(tess); | |
| return; | |
| } | |
| tess->vertex_index = gf_list_new(); | |
| mesh_reset(mesh); | |
| mesh->flags |= MESH_IS_2D; | |
| if (outline_style==1) mesh->flags |= MESH_NO_TEXTURE; | |
| tess->mesh = mesh; | |
| gluTessCallback(tess->tess_obj, GLU_TESS_VERTEX_DATA, (void (CALLBACK*)()) &mesh_tess_vertex); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_BEGIN, (void (CALLBACK*)()) &mesh_tess_begin); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_END, (void (CALLBACK*)()) &mesh_tess_end); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_COMBINE_DATA, (void (CALLBACK*)()) &mesh_tess_combine); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_ERROR, (void (CALLBACK*)()) &mesh_tess_error); | |
| gluTessCallback(tess->tess_obj, GLU_EDGE_FLAG,(void (CALLBACK*)()) &mesh_tess_edgeflag); | |
| if (path->flags & GF_PATH_FILL_ZERO_NONZERO) gluTessProperty(tess->tess_obj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); | |
| gluTessBeginPolygon(tess->tess_obj, tess); | |
| gluTessNormal(tess->tess_obj, 0, 0, 1); | |
| gf_path_flatten(path); | |
| gf_path_get_bounds(path, &rc); | |
| w = rc.width; | |
| h = rc.height; | |
| min_y = rc.y - h; | |
| vertex[2] = 0; | |
| /*since we're not sure whether subpaths overlaps or not, tesselate everything*/ | |
| cur = 0; | |
| for (i=0; i<path->n_contours; i++) { | |
| nb_pts = 1+path->contours[i]-cur; | |
| gluTessBeginContour(tess->tess_obj); | |
| for (j=0; j<nb_pts; j++) { | |
| GF_Point2D pt = path->points[cur+j]; | |
| Fixed u = gf_divfix(pt.x - rc.x, w); | |
| Fixed v = gf_divfix(pt.y - min_y, h); | |
| idx = (u32 *) gf_malloc(sizeof(u32)); | |
| *idx = mesh->v_count; | |
| gf_list_add(tess->vertex_index, idx); | |
| mesh_set_vertex(mesh, pt.x, pt.y, 0, 0, 0, FIX_ONE, u, v); | |
| vertex[0] = (Double) FIX2FLT(pt.x); | |
| vertex[1] = (Double) FIX2FLT(pt.y); | |
| gluTessVertex(tess->tess_obj, vertex, idx); | |
| } | |
| gluTessEndContour(tess->tess_obj); | |
| cur+=nb_pts; | |
| } | |
| gluTessEndPolygon(tess->tess_obj); | |
| gluDeleteTess(tess->tess_obj); | |
| while (gf_list_count(tess->vertex_index)) { | |
| u32 *idx = gf_list_get(tess->vertex_index, 0); | |
| gf_list_rem(tess->vertex_index, 0); | |
| gf_free(idx); | |
| } | |
| gf_list_del(tess->vertex_index); | |
| gf_free(tess); | |
| mesh->bounds.min_edge.x = rc.x; | |
| mesh->bounds.min_edge.y = rc.y-rc.height; | |
| mesh->bounds.max_edge.x = rc.x+rc.width; | |
| mesh->bounds.max_edge.y = rc.y; | |
| mesh->bounds.min_edge.z = mesh->bounds.max_edge.z = 0; | |
| gf_bbox_refresh(&mesh->bounds); | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| mesh_tess_error(0); | |
| } | |
| #endif | |
| } | |
| #else | |
| void gf_mesh_tesselate_path(GF_Mesh *mesh, GF_Path *path, u32 outline_style) { } | |
| #endif | |
| #define GetPoint2D(pt, apt) \ | |
| if (!direction) { pt.x = - apt.pos.z; pt.y = apt.pos.y; } \ | |
| else if (direction==1) { pt.x = apt.pos.z; pt.y = apt.pos.x; } \ | |
| else if (direction==2) { pt.x = apt.pos.x; pt.y = apt.pos.y; } \ | |
| #define ConvCompare(delta) \ | |
| ( (delta.x > 0) ? -1 : \ | |
| (delta.x < 0) ? 1 : \ | |
| (delta.y > 0) ? -1 : \ | |
| (delta.y < 0) ? 1 : \ | |
| 0 ) | |
| #define ConvGetPointDelta(delta, pprev, pcur ) \ | |
| /* Given a previous point 'pprev', read a new point into 'pcur' */ \ | |
| /* and return delta in 'delta'. */ \ | |
| GetPoint2D(pcur, pts[iread]); iread++; \ | |
| delta.x = pcur.x - pprev.x; \ | |
| delta.y = pcur.y - pprev.y; \ | |
| #define ConvCross(p, q) gf_mulfix(p.x,q.y) - gf_mulfix(p.y,q.x); | |
| #define ConvCheckTriple \ | |
| if ( (thisDir = ConvCompare(dcur)) == -curDir ) { \ | |
| ++dirChanges; \ | |
| /* if ( dirChanges > 2 ) return NotConvex; */ \ | |
| } \ | |
| curDir = thisDir; \ | |
| cross = ConvCross(dprev, dcur); \ | |
| if ( cross > 0 ) { \ | |
| if ( angleSign == -1 ) return GF_POLYGON_COMPLEX; \ | |
| angleSign = 1; \ | |
| } \ | |
| else if (cross < 0) { \ | |
| if (angleSign == 1) return GF_POLYGON_COMPLEX; \ | |
| angleSign = -1; \ | |
| } \ | |
| pSecond = pThird; \ | |
| dprev.x = dcur.x; \ | |
| dprev.y = dcur.y; \ | |
| u32 polygon_check_convexity(GF_Vertex *pts, u32 len, u32 direction) | |
| { | |
| s32 curDir, thisDir = 0, dirChanges = 0, angleSign = 0; | |
| u32 iread; | |
| Fixed cross; | |
| GF_Point2D pSecond, pThird, pSaveSecond; | |
| GF_Point2D dprev, dcur; | |
| if (len<3) return GF_POLYGON_CONVEX_LINE; | |
| pSecond.x = pSecond.y = 0; | |
| pThird = pSecond; | |
| GetPoint2D(pThird, pts[0]); | |
| iread = 1; | |
| ConvGetPointDelta(dprev, pThird, pSecond); | |
| pSaveSecond = pSecond; | |
| /*initial direction */ | |
| curDir = ConvCompare(dprev); | |
| while ( iread < len) { | |
| /* Get different point, break if no more points */ | |
| ConvGetPointDelta(dcur, pSecond, pThird ); | |
| if ( (dcur.x == 0) && (dcur.y == 0) ) continue; | |
| /* Check current three points */ | |
| ConvCheckTriple; | |
| } | |
| /* Must check for direction changes from last vertex back to first */ | |
| /* Prepare for 'ConvexCheckTriple' */ | |
| GetPoint2D(pThird, pts[0]); | |
| dcur.x = pThird.x - pSecond.x; | |
| dcur.y = pThird.y - pSecond.y; | |
| if ( ConvCompare(dcur) ) { | |
| ConvCheckTriple; | |
| } | |
| /* and check for direction changes back to second vertex */ | |
| dcur.x = pSaveSecond.x - pSecond.x; | |
| dcur.y = pSaveSecond.y - pSecond.y; | |
| /* Don't care about 'pThird' now */ | |
| ConvCheckTriple; | |
| /* Decide on polygon type given accumulated status */ | |
| if ( dirChanges > 2 ) return GF_POLYGON_COMPLEX; | |
| if ( angleSign > 0 ) return GF_POLYGON_CONVEX_CCW; | |
| if ( angleSign < 0 ) return GF_POLYGON_CONVEX_CW; | |
| return GF_POLYGON_CONVEX_LINE; | |
| } | |
| void TesselateFaceMesh(GF_Mesh *dest, GF_Mesh *orig) | |
| { | |
| u32 poly_type, i, nb_pts, init_idx, direction; | |
| Fixed max_nor_coord, c; | |
| SFVec3f nor; | |
| #ifdef GPAC_HAS_GLU | |
| u32 *idx; | |
| GLdouble vertex[3]; | |
| MeshTess *tess; | |
| #endif | |
| /*get normal*/ | |
| if (orig->flags & MESH_IS_2D) { | |
| nor.x = nor.y = 0; | |
| nor.z = FIX_ONE; | |
| } else { | |
| MESH_GET_NORMAL(nor, orig->vertices[0]); | |
| } | |
| /*select projection direction*/ | |
| direction = 0; | |
| max_nor_coord = ABS(nor.x); | |
| c = ABS(nor.y); | |
| if (c>max_nor_coord) { | |
| direction = 1; | |
| max_nor_coord = c; | |
| } | |
| c = ABS(nor.z); | |
| if (c>max_nor_coord) direction = 2; | |
| /*if this is a convex polygone don't triangulate*/ | |
| poly_type = polygon_check_convexity(orig->vertices, orig->v_count, direction); | |
| switch (poly_type) { | |
| case GF_POLYGON_CONVEX_LINE: | |
| /*do NOT try to make face CCW otherwise we loose front/back faces...*/ | |
| case GF_POLYGON_CONVEX_CW: | |
| case GF_POLYGON_CONVEX_CCW: | |
| init_idx = dest->v_count; | |
| nb_pts = orig->v_count; | |
| for (i=0; i<nb_pts; i++) { | |
| mesh_set_vertex_vx(dest, &orig->vertices[i]); | |
| } | |
| nb_pts -= 1; | |
| for (i=1; i<nb_pts; i++) { | |
| mesh_set_triangle(dest, init_idx, init_idx + i, init_idx + i+1); | |
| } | |
| return; | |
| default: | |
| break; | |
| } | |
| #ifdef GPAC_HAS_GLU | |
| /*tesselate it*/ | |
| tess = gf_malloc(sizeof(MeshTess)); | |
| if (!tess) return; | |
| memset(tess, 0, sizeof(MeshTess)); | |
| tess->tess_obj = gluNewTess(); | |
| if (!tess->tess_obj) { | |
| gf_free(tess); | |
| return; | |
| } | |
| tess->vertex_index = gf_list_new(); | |
| tess->mesh = dest; | |
| gluTessCallback(tess->tess_obj, GLU_TESS_VERTEX_DATA, (void (CALLBACK*)()) &mesh_tess_vertex); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_BEGIN, (void (CALLBACK*)()) &mesh_tess_begin); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_END, (void (CALLBACK*)()) &mesh_tess_end); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_COMBINE_DATA, (void (CALLBACK*)()) &mesh_tess_combine); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_ERROR, (void (CALLBACK*)()) &mesh_tess_error); | |
| gluTessCallback(tess->tess_obj, GLU_EDGE_FLAG,(void (CALLBACK*)()) &mesh_tess_edgeflag); | |
| gluTessBeginPolygon(tess->tess_obj, tess); | |
| gluTessBeginContour(tess->tess_obj); | |
| for (i=0; i<orig->v_count; i++) { | |
| idx = (u32 *) gf_malloc(sizeof(u32)); | |
| *idx = dest->v_count; | |
| gf_list_add(tess->vertex_index, idx); | |
| mesh_set_vertex_vx(dest, &orig->vertices[i]); | |
| vertex[0] = (Double) FIX2FLT(orig->vertices[i].pos.x); | |
| vertex[1] = (Double) FIX2FLT(orig->vertices[i].pos.y); | |
| vertex[2] = (Double) FIX2FLT(orig->vertices[i].pos.z); | |
| gluTessVertex(tess->tess_obj, vertex, idx); | |
| } | |
| gluTessEndContour(tess->tess_obj); | |
| gluTessEndPolygon(tess->tess_obj); | |
| gluDeleteTess(tess->tess_obj); | |
| while (gf_list_count(tess->vertex_index)) { | |
| u32 *idx = gf_list_get(tess->vertex_index, 0); | |
| gf_list_rem(tess->vertex_index, 0); | |
| gf_free(idx); | |
| } | |
| gf_list_del(tess->vertex_index); | |
| gf_free(tess); | |
| #endif | |
| } | |
| #ifdef GPAC_HAS_GLU | |
| void TesselateFaceMeshComplex(GF_Mesh *dest, GF_Mesh *orig, u32 nbFaces, u32 *ptsPerFaces) | |
| { | |
| u32 i, cur_pt_faces, cur_face; | |
| u32 *idx; | |
| GLdouble vertex[3]; | |
| MeshTess *tess; | |
| /*tesselate it*/ | |
| tess = gf_malloc(sizeof(MeshTess)); | |
| if (!tess) return; | |
| memset(tess, 0, sizeof(MeshTess)); | |
| tess->tess_obj = gluNewTess(); | |
| if (!tess->tess_obj) { | |
| gf_free(tess); | |
| return; | |
| } | |
| tess->vertex_index = gf_list_new(); | |
| tess->mesh = dest; | |
| gluTessCallback(tess->tess_obj, GLU_TESS_VERTEX_DATA, (void (CALLBACK*)()) &mesh_tess_vertex); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_BEGIN, (void (CALLBACK*)()) &mesh_tess_begin); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_END, (void (CALLBACK*)()) &mesh_tess_end); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_COMBINE_DATA, (void (CALLBACK*)()) &mesh_tess_combine); | |
| gluTessCallback(tess->tess_obj, GLU_TESS_ERROR, (void (CALLBACK*)()) &mesh_tess_error); | |
| gluTessCallback(tess->tess_obj, GLU_EDGE_FLAG,(void (CALLBACK*)()) &mesh_tess_edgeflag); | |
| gluTessBeginPolygon(tess->tess_obj, tess); | |
| gluTessBeginContour(tess->tess_obj); | |
| cur_pt_faces = 0; | |
| cur_face = 0; | |
| for (i=0; i<orig->v_count; i++) { | |
| if (i>= cur_pt_faces + ptsPerFaces[cur_face]) { | |
| cur_pt_faces += ptsPerFaces[cur_face]; | |
| cur_face++; | |
| if (cur_face>=nbFaces) break; | |
| gluTessEndContour(tess->tess_obj); | |
| gluTessBeginContour(tess->tess_obj); | |
| } | |
| idx = (u32 *) gf_malloc(sizeof(u32)); | |
| *idx = dest->v_count; | |
| gf_list_add(tess->vertex_index, idx); | |
| mesh_set_vertex_vx(dest, &orig->vertices[i]); | |
| vertex[0] = (Double) FIX2FLT(orig->vertices[i].pos.x); | |
| vertex[1] = (Double) FIX2FLT(orig->vertices[i].pos.y); | |
| vertex[2] = (Double) FIX2FLT(orig->vertices[i].pos.z); | |
| gluTessVertex(tess->tess_obj, vertex, idx); | |
| } | |
| gluTessEndContour(tess->tess_obj); | |
| gluTessEndPolygon(tess->tess_obj); | |
| gluDeleteTess(tess->tess_obj); | |
| while (gf_list_count(tess->vertex_index)) { | |
| u32 *idx = gf_list_get(tess->vertex_index, 0); | |
| gf_list_rem(tess->vertex_index, 0); | |
| gf_free(idx); | |
| } | |
| gf_list_del(tess->vertex_index); | |
| gf_free(tess); | |
| } | |
| #endif | |
| #endif // !defined(GPAC_DISABLE_3D) && !defined(GPAC_DISABLE_EVG) |