2 * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 * - Remove/free unused memory
30 * - Optimize for memory consumption
31 * - Profile for computation speed
32 * - Implement good error algorithm
33 * - Target polygon count/detail
36 /* For GPQueue comparators */
37 static gint tri_cmp(RoamTriangle *a, RoamTriangle *b, gpointer data)
39 if (a->error < b->error) return 1;
40 else if (a->error > b->error) return -1;
43 static gint dia_cmp(RoamDiamond *a, RoamDiamond *b, gpointer data)
45 if (a->error < b->error) return -1;
46 else if (a->error > b->error) return 1;
53 RoamPoint *roam_point_new(double x, double y, double z)
55 RoamPoint *self = g_new0(RoamPoint, 1);
61 void roam_point_add_triangle(RoamPoint *self, RoamTriangle *triangle)
63 for (int i = 0; i < 3; i++) {
64 self->norm[i] *= self->refs;
65 self->norm[i] += triangle->norm[i];
68 for (int i = 0; i < 3; i++)
69 self->norm[i] /= self->refs;
71 void roam_point_remove_triangle(RoamPoint *self, RoamTriangle *triangle)
73 for (int i = 0; i < 3; i++) {
74 self->norm[i] *= self->refs;
75 self->norm[i] -= triangle->norm[i];
78 for (int i = 0; i < 3; i++)
79 self->norm[i] /= self->refs;
85 RoamTriangle *roam_triangle_new(RoamPoint *l, RoamPoint *m, RoamPoint *r)
87 RoamTriangle *self = g_new0(RoamTriangle, 1);
98 pa[0] = self->p.l->x - self->p.m->x;
99 pa[1] = self->p.l->y - self->p.m->y;
100 pa[2] = self->p.l->z - self->p.m->z;
102 pb[0] = self->p.r->x - self->p.m->x;
103 pb[1] = self->p.r->y - self->p.m->y;
104 pb[2] = self->p.r->z - self->p.m->z;
106 self->norm[0] = pa[1] * pb[2] - pa[2] * pb[1];
107 self->norm[1] = pa[2] * pb[0] - pa[0] * pb[2];
108 self->norm[2] = pa[0] * pb[1] - pa[1] * pb[0];
110 double total = sqrt(self->norm[0] * self->norm[0] +
111 self->norm[1] * self->norm[1] +
112 self->norm[2] * self->norm[2]);
114 self->norm[0] /= total;
115 self->norm[1] /= total;
116 self->norm[2] /= total;
118 //g_message("roam_triangle_new: %p", self);
122 void roam_triangle_add(RoamTriangle *self,
123 RoamTriangle *left, RoamTriangle *base, RoamTriangle *right,
130 roam_point_add_triangle(self->p.l, self);
131 roam_point_add_triangle(self->p.m, self);
132 roam_point_add_triangle(self->p.r, self);
134 roam_triangle_update_error(self, sphere, NULL);
136 self->handle = g_pqueue_push(sphere->triangles, self);
139 void roam_triangle_remove(RoamTriangle *self, RoamSphere *sphere)
141 /* Update vertex normals */
142 roam_point_remove_triangle(self->p.l, self);
143 roam_point_remove_triangle(self->p.m, self);
144 roam_point_remove_triangle(self->p.r, self);
146 g_pqueue_remove(sphere->triangles, self->handle);
149 void roam_triangle_sync_neighbors(RoamTriangle *new, RoamTriangle *old, RoamTriangle *neigh)
151 if (neigh->t.l == old) neigh->t.l = new;
152 else if (neigh->t.b == old) neigh->t.b = new;
153 else if (neigh->t.r == old) neigh->t.r = new;
154 else g_assert_not_reached();
157 gboolean roam_triangle_update_error_visible(RoamPoint *point, double *model, double *proj)
160 int view[4] = {0,0,1,1};
161 if (!gluProject(point->x, point->y, point->z, model, proj, view, &x, &y, &z)) {
162 g_warning("RoamTriangle: update_error_visible - gluProject failed");
165 return !(x < 0 || x > 1 ||
170 void roam_triangle_update_error(RoamTriangle *self, RoamSphere *sphere, GPQueue *triangles)
173 (self->p.l->x + self->p.r->x)/2,
174 (self->p.l->y + self->p.r->y)/2,
175 (self->p.l->z + self->p.r->z)/2,
177 RoamPoint good = cur;
178 roam_sphere_update_point(sphere, &good);
180 //g_message("cur = (%+5.2f %+5.2f %+5.2f) good = (%+5.2f %+5.2f %+5.2f)",
181 // cur[0], cur[1], cur[2], good[0], good[1], good[2]);
183 double model[16], proj[16];
186 glGetDoublev(GL_MODELVIEW_MATRIX, model);
187 glGetDoublev(GL_PROJECTION_MATRIX, proj);
188 glGetIntegerv(GL_VIEWPORT, view);
189 if (glGetError() != GL_NO_ERROR) {
190 g_warning("RoamTriangle: update_error - glGet failed");
194 double scur[3], sgood[3];
195 gluProject( cur.x, cur.y, cur.z, model,proj,view, &scur[0], &scur[1], &scur[2]);
196 gluProject(good.x,good.y,good.z, model,proj,view, &sgood[0],&sgood[1],&sgood[2]);
198 /* Not exactly correct, could be out on both sides (middle in) */
199 if (!roam_triangle_update_error_visible(self->p.l, model, proj) &&
200 !roam_triangle_update_error_visible(self->p.m, model, proj) &&
201 !roam_triangle_update_error_visible(self->p.r, model, proj) &&
202 !roam_triangle_update_error_visible(&good, model, proj)) {
205 self->error = sqrt((scur[0]-sgood[0])*(scur[0]-sgood[0]) +
206 (scur[1]-sgood[1])*(scur[1]-sgood[1]));
208 /* Multiply by size of triangle (todo, do this in screen space) */
209 double sa[3], sb[3], sc[3];
210 double *a = (double*)self->p.l;
211 double *b = (double*)self->p.m;
212 double *c = (double*)self->p.r;
213 gluProject(a[0],a[1],a[2], model,proj,view, &sa[0],&sa[1],&sa[2]);
214 gluProject(b[0],b[1],b[2], model,proj,view, &sb[0],&sb[1],&sb[2]);
215 gluProject(c[0],c[1],c[2], model,proj,view, &sc[0],&sc[1],&sc[2]);
216 double size = -( sa[0]*(sb[1]-sc[1]) + sb[0]*(sc[1]-sa[1]) + sc[0]*(sa[1]-sb[1]) ) / 2.0;
217 //g_message("%f * %f = %f", self->error, size, self->error * size);
218 /* Size < 0 == backface */
223 g_pqueue_priority_changed(triangles, self->handle);
226 void roam_triangle_split(RoamTriangle *self, RoamSphere *sphere)
228 //g_message("roam_triangle_split: %p, e=%f\n", self, self->error);
232 if (self != self->t.b->t.b)
233 roam_triangle_split(self->t.b, sphere);
235 RoamTriangle *base = self->t.b;
237 /* Calculate midpoint */
238 RoamPoint *mid = roam_point_new(
239 (self->p.l->x + self->p.r->x)/2,
240 (self->p.l->y + self->p.r->y)/2,
241 (self->p.l->z + self->p.r->z)/2
243 roam_sphere_update_point(sphere, mid);
245 /* Add new triangles */
246 RoamTriangle *sl = roam_triangle_new(self->p.m, mid, self->p.l); // Self Left
247 RoamTriangle *sr = roam_triangle_new(self->p.r, mid, self->p.m); // Self Right
248 RoamTriangle *bl = roam_triangle_new(base->p.m, mid, base->p.l); // Base Left
249 RoamTriangle *br = roam_triangle_new(base->p.r, mid, base->p.m); // Base Right
251 /* tri,l, base, r, sphere */
252 roam_triangle_add(sl, sr, self->t.l, br, sphere);
253 roam_triangle_add(sr, bl, self->t.r, sl, sphere);
254 roam_triangle_add(bl, br, base->t.l, sr, sphere);
255 roam_triangle_add(br, sl, base->t.r, bl, sphere);
257 roam_triangle_sync_neighbors(sl, self, self->t.l);
258 roam_triangle_sync_neighbors(sr, self, self->t.r);
259 roam_triangle_sync_neighbors(bl, base, base->t.l);
260 roam_triangle_sync_neighbors(br, base, base->t.r);
262 /* Remove old triangles */
263 roam_triangle_remove(self, sphere);
264 roam_triangle_remove(base, sphere);
266 /* Add/Remove diamonds */
267 RoamDiamond *diamond = roam_diamond_new(self, base, sl, sr, bl, br);
268 roam_diamond_add(diamond, sphere);
269 roam_diamond_remove(self->diamond, sphere);
270 roam_diamond_remove(base->diamond, sphere);
273 void roam_triangle_draw_normal(RoamTriangle *self)
276 (self->p.l->x + self->p.m->x + self->p.r->x)/3.0,
277 (self->p.l->y + self->p.m->y + self->p.r->y)/3.0,
278 (self->p.l->z + self->p.m->z + self->p.r->z)/3.0,
281 center[0]+self->norm[0]/2,
282 center[1]+self->norm[1]/2,
283 center[2]+self->norm[2]/2,
294 RoamDiamond *roam_diamond_new(
295 RoamTriangle *parent0, RoamTriangle *parent1,
296 RoamTriangle *kid0, RoamTriangle *kid1,
297 RoamTriangle *kid2, RoamTriangle *kid3)
299 RoamDiamond *self = g_new0(RoamDiamond, 1);
306 kid0->diamond = self;
307 kid1->diamond = self;
308 kid2->diamond = self;
309 kid3->diamond = self;
311 self->parent[0] = parent0;
312 self->parent[1] = parent1;
316 void roam_diamond_add(RoamDiamond *self, RoamSphere *sphere)
319 roam_diamond_update_error(self, sphere, NULL);
320 self->handle = g_pqueue_push(sphere->diamonds, self);
322 void roam_diamond_remove(RoamDiamond *self, RoamSphere *sphere)
324 if (self && self->active) {
325 self->active = FALSE;
326 g_pqueue_remove(sphere->diamonds, self->handle);
329 void roam_diamond_merge(RoamDiamond *self, RoamSphere *sphere)
331 //g_message("roam_diamond_merge: %p, e=%f\n", self, self->error);
335 /* TODO: pick the best split */
336 RoamTriangle **kid = self->kid;
338 /* Create triangles */
339 RoamTriangle *tri = self->parent[0];
340 RoamTriangle *base = self->parent[1];
342 roam_triangle_add(tri, kid[0]->t.b, base, kid[1]->t.b, sphere);
343 roam_triangle_add(base, kid[2]->t.b, tri, kid[3]->t.b, sphere);
345 roam_triangle_sync_neighbors(tri, kid[0], kid[0]->t.b);
346 roam_triangle_sync_neighbors(tri, kid[1], kid[1]->t.b);
347 roam_triangle_sync_neighbors(base, kid[2], kid[2]->t.b);
348 roam_triangle_sync_neighbors(base, kid[3], kid[3]->t.b);
350 /* Remove triangles */
351 roam_triangle_remove(kid[0], sphere);
352 roam_triangle_remove(kid[1], sphere);
353 roam_triangle_remove(kid[2], sphere);
354 roam_triangle_remove(kid[3], sphere);
356 /* Add/Remove triangles */
357 if (tri->t.l->t.l == tri->t.r->t.r &&
358 tri->t.l->t.l != tri && tri->diamond)
359 roam_diamond_add(tri->diamond, sphere);
361 if (base->t.l->t.l == base->t.r->t.r &&
362 base->t.l->t.l != base && base->diamond)
363 roam_diamond_add(base->diamond, sphere);
365 /* Remove and free diamond and child triangles */
366 roam_diamond_remove(self, sphere);
367 g_assert(self->kid[0]->p.m == self->kid[1]->p.m &&
368 self->kid[1]->p.m == self->kid[2]->p.m &&
369 self->kid[2]->p.m == self->kid[3]->p.m);
370 g_assert(self->kid[0]->p.m->refs == 0);
371 g_free(self->kid[0]->p.m);
372 g_free(self->kid[0]);
373 g_free(self->kid[1]);
374 g_free(self->kid[2]);
375 g_free(self->kid[3]);
378 void roam_diamond_update_error(RoamDiamond *self, RoamSphere *sphere, GPQueue *diamonds)
380 roam_triangle_update_error(self->parent[0], sphere, NULL);
381 roam_triangle_update_error(self->parent[1], sphere, NULL);
382 self->error = MAX(self->parent[0]->error, self->parent[1]->error);
384 g_pqueue_priority_changed(diamonds, self->handle);
390 RoamSphere *roam_sphere_new(RoamTriFunc tri_func, RoamHeightFunc height_func, gpointer user_data)
392 RoamSphere *self = g_new0(RoamSphere, 1);
393 self->tri_func = tri_func;
394 self->height_func = height_func;
395 self->user_data = user_data;
397 self->triangles = g_pqueue_new((GCompareDataFunc)tri_cmp, NULL);
398 self->diamonds = g_pqueue_new((GCompareDataFunc)dia_cmp, NULL);
400 RoamPoint *vertexes[] = {
401 roam_point_new( 1, 1, 1), // 0
402 roam_point_new( 1, 1,-1), // 1
403 roam_point_new( 1,-1, 1), // 2
404 roam_point_new( 1,-1,-1), // 3
405 roam_point_new(-1, 1, 1), // 4
406 roam_point_new(-1, 1,-1), // 5
407 roam_point_new(-1,-1, 1), // 6
408 roam_point_new(-1,-1,-1), // 7
410 int _triangles[][2][3] = {
411 /*lv mv rv ln, bn, rn */
412 {{3,2,0}, {10, 1, 7}}, // 0
413 {{0,1,3}, { 9, 0, 2}}, // 1
414 {{7,3,1}, {11, 3, 1}}, // 2
415 {{1,5,7}, { 8, 2, 4}}, // 3
416 {{6,7,5}, {11, 5, 3}}, // 4
417 {{5,4,6}, { 8, 4, 6}}, // 5
418 {{2,6,4}, {10, 7, 5}}, // 6
419 {{4,0,2}, { 9, 6, 0}}, // 7
420 {{4,5,1}, { 5, 9, 3}}, // 8
421 {{1,0,4}, { 1, 8, 7}}, // 9
422 {{6,2,3}, { 6,11, 0}}, // 10
423 {{3,7,6}, { 2,10, 4}}, // 11
425 RoamTriangle *triangles[12];
427 for (int i = 0; i < 8; i++)
428 roam_sphere_update_point(self, vertexes[i]);
429 for (int i = 0; i < 12; i++)
430 triangles[i] = roam_triangle_new(
431 vertexes[_triangles[i][0][0]],
432 vertexes[_triangles[i][0][1]],
433 vertexes[_triangles[i][0][2]]);
434 for (int i = 0; i < 12; i++)
435 roam_triangle_add(triangles[i],
436 triangles[_triangles[i][1][0]],
437 triangles[_triangles[i][1][1]],
438 triangles[_triangles[i][1][2]],
443 static void roam_sphere_update_errors_cb(gpointer obj, GPtrArray *ptrs)
445 g_ptr_array_add(ptrs, obj);
447 void roam_sphere_update_errors(RoamSphere *self)
449 g_debug("RoamSphere: update_errors - polys=%d", self->polys);
450 GPtrArray *ptrs = g_ptr_array_new();
451 g_pqueue_foreach(self->triangles, (GFunc)roam_sphere_update_errors_cb, ptrs);
452 for (int i = 0; i < ptrs->len; i++)
453 roam_triangle_update_error(ptrs->pdata[i], self, self->triangles);
454 g_ptr_array_free(ptrs, TRUE);
456 ptrs = g_ptr_array_new();
457 g_pqueue_foreach(self->diamonds, (GFunc)roam_sphere_update_errors_cb, ptrs);
458 for (int i = 0; i < ptrs->len; i++)
459 roam_diamond_update_error(ptrs->pdata[i], self, self->diamonds);
460 g_ptr_array_free(ptrs, TRUE);
462 void roam_sphere_update_point(RoamSphere *self, RoamPoint *point)
464 self->height_func(point, self->user_data);
466 void roam_sphere_split_one(RoamSphere *self)
468 RoamTriangle *to_split = g_pqueue_peek(self->triangles);
469 if (!to_split) return;
470 roam_triangle_split(to_split, self);
472 void roam_sphere_merge_one(RoamSphere *self)
474 RoamDiamond *to_merge = g_pqueue_peek(self->diamonds);
475 if (!to_merge) return;
476 roam_diamond_merge(to_merge, self);
478 gint roam_sphere_split_merge(RoamSphere *self)
480 gint iters = 0, max_iters = 500;
482 while (self->polys < target && iters++ < max_iters)
483 roam_sphere_split_one(self);
484 while (self->polys > target && iters++ < max_iters)
485 roam_sphere_merge_one(self);
486 while (((RoamTriangle*)g_pqueue_peek(self->triangles))->error >
487 ((RoamDiamond *)g_pqueue_peek(self->diamonds ))->error &&
488 iters++ < max_iters) {
489 roam_sphere_split_one(self);
490 if (((RoamTriangle*)g_pqueue_peek(self->triangles))->error >
491 ((RoamDiamond *)g_pqueue_peek(self->diamonds ))->error)
492 roam_sphere_merge_one(self);
496 void roam_sphere_draw(RoamSphere *self)
498 g_pqueue_foreach(self->triangles, (GFunc)self->tri_func, self->user_data);
500 void roam_sphere_draw_normals(RoamSphere *self)
502 g_pqueue_foreach(self->triangles, (GFunc)roam_triangle_draw_normal, NULL);
504 static void roam_sphere_free_tri(RoamTriangle *tri)
506 if (--tri->p.l->refs == 0) g_free(tri->p.l);
507 if (--tri->p.m->refs == 0) g_free(tri->p.m);
508 if (--tri->p.r->refs == 0) g_free(tri->p.r);
511 void roam_sphere_free(RoamSphere *self)
513 /* Slow method, but it should work */
514 while (self->polys > 12)
515 roam_sphere_merge_one(self);
516 /* TODO: free points */
517 g_pqueue_foreach(self->triangles, (GFunc)roam_sphere_free_tri, NULL);
518 g_pqueue_free(self->triangles);
519 g_pqueue_free(self->diamonds);