/* Copyright (C) 2003 by Sean David Fleming sean@ivec.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The GNU GPL can also be found at http://www.gnu.org */ #include #include #include #include #include "gdis.h" #include "coords.h" #include "geometry.h" #include "matrix.h" #include "morph.h" #include "numeric.h" #include "spatial.h" #include "select.h" #include "interface.h" #include "shortcuts.h" #ifdef __APPLE__ #include #else #include "opengl.h" #include #endif /* main structure */ extern struct sysenv_pak sysenv; /* handle all the vector/plane creation, intersections etc. */ /* add all the vector normalization/crossprod etc. stuff as well */ /**************************/ /* spatial free primitive */ /**************************/ void spatial_free(gpointer data) { GSList *list; struct spatial_pak *spatial=data; g_assert(spatial != NULL); for (list=spatial->list ; list ; list=g_slist_next(list)) { struct vec_pak *v = list->data; g_free(v->data); g_free(v); } g_slist_free(spatial->list); g_free(spatial->data); g_free(spatial); } /*****************************/ /* destroy spatial primitive */ /*****************************/ void spatial_destroy(gpointer data, struct model_pak *model) { struct spatial_pak *spatial=data; if (spatial && model) { model->spatial = g_slist_remove(model->spatial, spatial); spatial_free(spatial); } } /*********************************/ /* destroy all spatial primitive */ /*********************************/ void spatial_destroy_all(struct model_pak *model) { GSList *list; struct spatial_pak *spatial; g_assert(model != NULL); list = model->spatial; while (list) { spatial = list->data; list = g_slist_next(list); spatial_free(spatial); } g_slist_free(model->spatial); model->spatial = NULL; } /**************************/ /* destroy by type number */ /**************************/ void spatial_destroy_by_type(gint type, struct model_pak *model) { GSList *list; struct spatial_pak *spatial; list = model->spatial; while (list) { spatial = list->data; list = g_slist_next(list); if (type == spatial->type) spatial_destroy(spatial, model); } } /***********************************/ /* destroy spatial by id primitive */ /***********************************/ void spatial_destroy_by_label(const gchar *label, struct model_pak *model) { gint a, b; GSList *list; struct spatial_pak *spatial; g_assert(label != NULL); g_assert(model != NULL); a = strlen(label); list = model->spatial; while (list) { spatial = list->data; list = g_slist_next(list); b = strlen(spatial->label); if (a == b) if (g_strncasecmp(label, spatial->label, a) == 0) spatial_destroy(spatial, model); } } /************************************/ /* primitives for creating spatials */ /************************************/ gpointer spatial_new(const gchar *label, gint type, gint size, gint periodic, struct model_pak *model) { static gint n=0; struct spatial_pak *spatial; g_assert(model != NULL); spatial = g_malloc(sizeof(struct spatial_pak)); model->spatial = g_slist_append(model->spatial, spatial); if (label) spatial->label = g_strdup(label); else { /* FIXME - better facility for creating a unique name */ spatial->label = g_strdup_printf("unknown_%d", n++); } spatial->type = type; spatial->size = size; spatial->periodic = periodic; spatial->show_label = FALSE; spatial->data = NULL; spatial->list = NULL; ARR3SET(spatial->c, sysenv.render.fg_colour); switch (size) { case 1: spatial->method = GL_POINTS; spatial->material = SPATIAL_LINE; break; case 2: spatial->method = GL_LINES; spatial->material = SPATIAL_LINE; break; case 3: spatial->method = GL_TRIANGLES; spatial->material = SPATIAL_SURFACE; break; case 4: spatial->method = GL_QUADS; spatial->material = SPATIAL_SURFACE; break; default: spatial->method = GL_POLYGON; spatial->material = SPATIAL_SURFACE; } /* special case - composite objects */ switch (type) { case SPATIAL_VECTOR: spatial->method = -1; spatial->material = SPATIAL_SOLID; } return(spatial); } /************************************************************/ /* associates a general purpose data pointer with a spatial */ /************************************************************/ void spatial_data_set(gpointer data, gpointer ptr) { struct spatial_pak *spatial=ptr; spatial->data = data; } /***********************************************************/ /* retrieves a general purpose data pointer from a spatial */ /***********************************************************/ gpointer spatial_data_get(gpointer ptr) { struct spatial_pak *spatial=ptr; return(spatial->data); } /*************************************/ /* structure manipulation primitives */ /*************************************/ void spatial_label_show(gpointer data) { struct spatial_pak *spatial=data; spatial->show_label = TRUE; } void spatial_label_hide(gpointer data) { struct spatial_pak *spatial=data; spatial->show_label = FALSE; } /*****************************/ /* vertex creation primitive */ /*****************************/ gpointer vertex_new(gdouble *x, gdouble *n, gdouble *c) { struct vec_pak *v; v = g_malloc(sizeof(struct vec_pak)); if (x) { ARR3SET(v->x, x); } else { VEC3SET(v->x, 0.0, 0.0, 0.0); } if (n) { ARR3SET(v->n, n); } else { VEC3SET(v->n, 0.0, 0.0, 1.0); } if (c) { ARR3SET(v->colour, c); } else { ARR3SET(v->colour, sysenv.render.fg_colour); } v->data = NULL; return(v); } /**************************/ /* add vertex with colour */ /**************************/ void spatial_vertex_add(gdouble *x, gdouble *colour, gpointer ptr) { struct spatial_pak *spatial = ptr; struct vec_pak *vec; vec = vertex_new(x, NULL, colour); spatial->list = g_slist_prepend(spatial->list, vec); } /*************************************/ /* add vertex with normal and colour */ /*************************************/ void spatial_vnorm_add(gdouble *x, gdouble *n, gdouble *colour, gpointer ptr) { struct spatial_pak *spatial = ptr; struct vec_pak *vec; vec = vertex_new(x, n, colour); spatial->list = g_slist_prepend(spatial->list, vec); } /********************************************/ /* add vertex with additional property data */ /********************************************/ void spatial_vdata_add(gdouble *x, gdouble *n, gdouble *colour, gpointer data, gpointer ptr) { struct spatial_pak *spatial = ptr; struct vec_pak *vec; vec = vertex_new(x, n, colour); vec->data = data; spatial->list = g_slist_prepend(spatial->list, vec); } /****************************************************/ /* compute normal for the plane containing 3 points */ /****************************************************/ void compute_normal(gdouble *n, gdouble *c1, gdouble *c2, gdouble *c3) { gdouble v0[3], v1[3], v2[3]; ARR3SET(v0, c1); ARR3SET(v1, c2); ARR3SET(v2, c3); /* make vectors 0->1 & 0->2 */ ARR3SUB(v1, v0); ARR3SUB(v2, v0); /* get normal */ crossprod(n, v1, v2); normalize(n, 3); } /************************************************************/ /* compute normal for a facet defined by a list of vertices */ /************************************************************/ void compute_facet_normal(gdouble *n, GSList *vlist) { gint i, j; struct vertex_pak *v1, *v2, *v3; g_assert(g_slist_length(vlist) > 2); j = g_slist_length(vlist); printf("[size = %d] -------------------------------\n", j); for (i=0 ; ix, v2->x, v3->x); P3VEC("n: ", n); } } /***********************************************/ /* compute best fit plane for a list of atoms */ /**********************************************/ #define DEBUG_COMPUTE_PLANE 0 void compute_plane(gdouble *res, GList *cores) { gdouble tmp[3], vec1[3], vec2[3]; struct core_pak *core1, *core2, *core3; /* FIXME - this only uses the 1st 3 points, in future - an average for n pts */ /* http://www.geocities.com/CollegePark/Campus/1278/JBook/LSQ_Plane.html */ /* shouldn't matter when this routine is properly implemented, as we'll go */ /* through the atom_list in the usual fashoin & terminate when it is NULL, */ /* not when it's data ptr is NULL */ g_assert(cores != NULL); g_assert(g_list_length(cores) > 2); core1 = g_list_nth_data(cores, 0); core2 = g_list_nth_data(cores, 1); core3 = g_list_nth_data(cores, 2); #if DEBUG_COMPUTE_PLANE printf("computing normal from atoms: %p, %p, %p\n", core1, core2, core3); #endif ARR3SET(tmp, core1->x); ARR3SET(vec1, core2->x); ARR3SET(vec2, core3->x); /* make vectors 0->1 & 0->2 */ ARR3SUB(vec1, tmp); ARR3SUB(vec2, tmp); /* get normal */ crossprod(res, vec1, vec2); } /******************************************/ /* get vertex from list with common facet */ /******************************************/ gpointer vertex_common_facet(struct vertex_pak *v, GSList *vlist) { GSList *list1, *list2; struct vertex_pak *v1; for (list1=vlist ; list1 ; list1=g_slist_next(list1)) { v1 = list1->data; for (list2=v->adj ; list2 ; list2=g_slist_next(list2)) { if (g_slist_find(v1->adj, list2->data)) return(v1); } } return(NULL); } /*****************************************/ /* compute the angle between two vectors */ /*****************************************/ /* TODO - replace all via() calls with this */ gdouble vector_angle(gdouble *vec1, gdouble *vec2, gint dim) { gint i; gdouble lenprod, dot=0.0; gdouble *ptr1=vec1, *ptr2=vec2; gdouble cosa=0.0; for (i=0 ; i FRACTION_TOLERANCE) cosa = dot / lenprod; /* enforce range */ cosa = CLAMP(cosa, -1.0, 1.0); return(acos(cosa)); } /**********************************************************/ /* order a set of input coplanar vertices to be clockwise */ /**********************************************************/ /* n is the surface normal */ #define DEBUG_SPATIAL_ORDER_FACET 0 GSList *spatial_order_facet(gdouble *norm, GSList *source, struct model_pak *model) { gint new, old; gdouble total, angle, min; gdouble a[3], b[3], c[3], m[3], n[3]; GSList *list, *pool, *sorted; struct vertex_pak *v1, *v2, *v3; g_assert (model != NULL); /* convert input miller to cartesian space normal */ ARR3SET(n, norm); vecmat(model->latmat, n); normalize(n, 3); /* compute midpoint (acts as orientation vector) */ total = 0.0; VEC3SET(m, 0.0, 0.0, 0.0); for (list=source ; list ; list=g_slist_next(list)) { v1 = list->data; ARR3ADD(m, v1->x); total++; } VEC3MUL(m, 1.0/total); /* vertex pool */ pool = g_slist_copy(source); old = g_slist_length(source); v1 = pool->data; pool = g_slist_remove(pool, v1); /* build a sorted list */ sorted = NULL; sorted = g_slist_append(sorted, v1); new = 1; /* loop until we assign all source vertices */ while (new < old) { /* search the current pool for correct normal orientations */ min = 2.0*G_PI; v3 = NULL; /* vector from midpoint to current reference vertex */ ARR3SET(a, v1->x); ARR3SUB(a, m); for (list=pool ; list ; list=g_slist_next(list)) { v2 = list->data; ARR3SET(b, v2->x); ARR3SUB(b, m); crossprod(c, a, b); /* consider vertex only if it satisfies clockwise condition */ /* if (vector_angle(c, n, 3) < 0.1) */ if (vector_angle(c, n, 3) < 0.5*G_PI) { /* search for minimum angle separation */ angle = vector_angle(a, b, 3); if (angle < min) { min = angle; v3 = v2; } } } /* best vertex is now the reference */ if (v3) { sorted = g_slist_prepend(sorted, v3); pool = g_slist_remove(pool, v3); v1 = v3; } else gui_text_show(ERROR, "Failed to find next vertex.\n"); new++; } return(sorted); } /***************************************************/ /* build a polygon spatial from a list of vertices */ /***************************************************/ #define DEBUG_SPATIAL_BUILD_FACET 0 gpointer spatial_build_facet(gdouble *n, GSList *source, struct model_pak *model) { GSList *list, *vlist; gpointer spatial; struct vertex_pak *v; g_assert(model != NULL); /* CURRENT */ vlist = spatial_order_facet(n, source, model); spatial = spatial_new(NULL, SPATIAL_MORPHOLOGY, 0, FALSE, model); spatial_label_show(spatial); for (list=vlist ; list ; list=g_slist_next(list)) { v = list->data; spatial_vnorm_add(v->x, v->n, sysenv.render.morph_colour, spatial); } g_slist_free(vlist); return(spatial); } /************************************/ /* create a unit cell bounded plane */ /************************************/ #define DEBUG_SPATIAL_CELL_PLANE 0 void spatial_cell_plane(GSList *cores, struct model_pak *model) { gint i, j, k, flag; gdouble a1[3], a2[3], a3[3]; gdouble x1[3], x2[3], x3[3]; gdouble n1[4], n2[4], n3[4]; gdouble walls[24]; gpointer spatial; #if DEBUG_SPATIAL_CELL_PLANE GSList *list; #endif GSList *vlist; struct core_pak *core1, *core2, *core3; struct vertex_pak *v; g_assert(cores != NULL); g_assert(model != NULL); /* get cell vectors */ a1[0] = model->latmat[0]; a1[1] = model->latmat[3]; a1[2] = model->latmat[6]; a2[0] = model->latmat[1]; a2[1] = model->latmat[4]; a2[2] = model->latmat[7]; a3[0] = model->latmat[2]; a3[1] = model->latmat[5]; a3[2] = model->latmat[8]; /* setup bc walls */ crossprod(&walls[0], a2, a3); walls[3] = model->pbc[0]; normalize(&walls[0], 3); ARR3SET(&walls[4], &walls[0]); VEC3MUL(&walls[4], -1.0); walls[7] = 0.0; /* get separation between (origin containing) plane to end of a vector */ vector_point2plane(x1, a1, &walls[4]); walls[3] = VEC3MAG(x1); /* setup ca walls */ crossprod(&walls[8], a3, a1); walls[11] = model->pbc[1]; normalize(&walls[8], 3); ARR3SET(&walls[12], &walls[8]); VEC3MUL(&walls[12], -1.0); walls[15] = 0.0; /* get separation between (origin containing) plane to end of a vector */ vector_point2plane(x1, a2, &walls[12]); walls[11] = VEC3MAG(x1); /* setup ab walls */ crossprod(&walls[16], a1, a2); walls[19] = model->pbc[2]; normalize(&walls[16], 3); ARR3SET(&walls[20], &walls[16]); VEC3MUL(&walls[20], -1.0); walls[23] = 0.0; /* get separation between (origin containing) plane to end of a vector */ vector_point2plane(x1, a3, &walls[20]); walls[19] = VEC3MAG(x1); #if DEBUG_SPATIAL_CELL_PLANE P3MAT("latmat: ", model->latmat); for (i=0 ; i<6 ; i++) { P4VEC("wall: ", &walls[4*i]); } #endif core1 = g_slist_nth_data(cores, 0); core2 = g_slist_nth_data(cores, 1); core3 = g_slist_nth_data(cores, 2); g_assert(core1 != NULL); g_assert(core2 != NULL); g_assert(core3 != NULL); /* get cartesian normal */ ARR3SET(x1, core1->x); ARR3SET(x2, core2->x); ARR3SET(x3, core3->x); vecmat(model->latmat, x1); vecmat(model->latmat, x2); vecmat(model->latmat, x3); calc_norm(n1, x1, x2, x3); normalize(n1, 3); /* compute distance (from origin) to plane */ /* NB: sign is important */ ARR3SET(x1, core1->x); vecmat(model->latmat, x1); ARR3SET(x2, x1); ARR3MUL(x2, n1); n1[3] = x2[0] + x2[1] + x2[2]; #if DEBUG_SPATIAL_CELL_PLANE P4VEC("n1: ", n1); #endif /* compute intersections with cell walls */ vlist = NULL; for (i=0 ; i<5 ; i++) { for (j=i+1 ; j<6 ; j++) { ARR4SET(n2, &walls[4*i]); ARR4SET(n3, &walls[4*j]); if (plane_intersect(x1, n1, n2, n3)) { flag = 0; /* eliminate intersections outside the unit cell */ ARR3SET(x2, x1); vecmat(model->ilatmat, x2); for (k=3 ; k-- ; ) { /* NB: precision errors may be a factor here */ if (x2[k] > 1.0+FRACTION_TOLERANCE || x2[k] < -FRACTION_TOLERANCE) flag++; } if (!flag) { /* printf("====================================\n"); P4VEC("n2: ", n2); P4VEC("n3: ", n3); P4VEC("x1: ", x1); */ /* store the intersection point */ v = g_malloc(sizeof(struct vertex_pak)); vlist = g_slist_prepend(vlist, v); ARR3SET(v->x, x2); v->adj = NULL; /* compute facet adjacencies */ for (k=0 ; k<6 ; k++) { /* get distance of point to facets */ vector_point2plane(x2, x1, &walls[4*k]); if (VEC3MAGSQ(x2) < FRACTION_TOLERANCE) { /* printf(" + (%d)\n", k); */ v->adj = g_slist_prepend(v->adj, GINT_TO_POINTER(k)); } } } } } } k = g_slist_length(vlist); /* order the vertices (via common facets) */ if (k > 2) { spatial = spatial_new("plane", SPATIAL_GENERIC, 0, TRUE, model); v = vlist->data; spatial_vertex_add(v->x, NULL, spatial); #if DEBUG_SPATIAL_CELL_PLANE P3VEC("v: ", v->x); for (list=v->adj ; list ; list=g_slist_next(list)) { printf(" + (%d)\n", GPOINTER_TO_INT(list->data)); } #endif vlist = g_slist_remove(vlist, v); v = vertex_common_facet(v, vlist); while (v) { spatial_vertex_add(v->x, NULL, spatial); #if DEBUG_SPATIAL_CELL_PLANE P3VEC("v: ", v->x); for (list=v->adj ; list ; list=g_slist_next(list)) { printf(" + (%d)\n", GPOINTER_TO_INT(list->data)); } #endif vlist = g_slist_remove(vlist, v); v = vertex_common_facet(v, vlist); } } g_slist_free(vlist); } /**************************************/ /* spatial point definition primitive */ /**************************************/ void spatial_point_add(struct core_pak *core, struct model_pak *data) { gint reset=0; gchar *label; gdouble plane[4], x1[3], x2[3], ortho1[3], ortho2[3]; GSList *list; struct core_pak *core1, *core2; struct spatial_pak *spatial; static guint count=0; static GSList *cores=NULL; g_assert(core != NULL); g_assert(data != NULL); /* add the new atom */ cores = g_slist_append(cores, core); select_add_core(core, data); data->state++; switch( data->mode) { case DEFINE_VECTOR: if (data->state==2) { /* new vector */ label = g_strdup_printf("vector_%d", count++); spatial = spatial_new(label, SPATIAL_VECTOR, 2, FALSE, data); g_free(label); /* get the (lattice space) separation vector */ core1 = g_slist_nth_data(cores, 0); g_assert(core1 != NULL); ARR3SET(x1, core1->x); ARR3SET(x2, core->x); ARR3SUB(x2, core1->x); /* convert to real space & scale */ vecmat(data->latmat, x2); normalize(x2, 3); VEC3MUL(x2, 0.7); /* convert back to lattice space */ vecmat(data->ilatmat, x2); ARR3ADD(x2, x1); /* add the vertices */ spatial_vertex_add(x2, NULL, spatial); spatial_vertex_add(x1, NULL, spatial); /* drawing update */ coords_compute(data); sysenv.refresh_dialog=TRUE; redraw_canvas(SINGLE); reset++; } break; case DEFINE_PLANE: if (data->state == 3) { /* CURRENT */ if (data->periodic == 3) { spatial_cell_plane(cores, data); } else { /* new plane */ label = g_strdup_printf("plane_%d", count++); spatial = spatial_new(label, SPATIAL_GENERIC, 4, FALSE, data); g_free(label); core1 = g_slist_nth_data(cores, 0); core2 = g_slist_nth_data(cores, 1); g_assert(core1 != NULL); g_assert(core2 != NULL); /* compute plane normal */ calc_norm(plane, core1->rx, core2->rx, core->rx); /* vector from first to second point (NB: normalized in cartesian space) */ ARR3SET(ortho1, core2->x); ARR3SUB(ortho1, core1->x); vecmat(data->latmat, ortho1); normalize(ortho1, 3); vecmat(data->ilatmat, ortho1); /* get orthogonal vector */ crossprod(ortho2, plane, ortho1); vecmat(data->latmat, ortho2); normalize(ortho2, 3); vecmat(data->ilatmat, ortho2); /* define 4 points for the plane */ ARR3SET(x1, core1->x); ARR3ADD(x1, ortho1); ARR3ADD(x1, ortho2); spatial_vertex_add(x1, NULL, spatial); ARR3SET(x1, core1->x); ARR3ADD(x1, ortho1); ARR3SUB(x1, ortho2); spatial_vertex_add(x1, NULL, spatial); ARR3SET(x1, core1->x); ARR3SUB(x1, ortho1); ARR3SUB(x1, ortho2); spatial_vertex_add(x1, NULL, spatial); ARR3SET(x1, core1->x); ARR3SUB(x1, ortho1); ARR3ADD(x1, ortho2); spatial_vertex_add(x1, NULL, spatial); } /* drawing update */ coords_init(REDO_COORDS, data); sysenv.refresh_dialog=TRUE; redraw_canvas(SINGLE); reset++; } break; } /* completed an object */ if (reset) { for (list=cores ; list ; list=g_slist_next(list)) { core1 = list->data; select_del_core(core1, data); } g_slist_free(cores); cores=NULL; data->state = 0; } }