2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* W3Dmain.c --
|
|
|
|
|
*
|
|
|
|
|
* Procedures to interface the 3D rendering window with the window package
|
|
|
|
|
* for the purposes of window creation, deletion, and modification.
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2003 Open Circuit Design, Inc., for MultiGiG Ltd.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
/* This file is only applicable for OpenGL-enabled graphics; THREE_D is */
|
|
|
|
|
/* defined by the "make config" process and includes all preconditions. */
|
|
|
|
|
#ifdef THREE_D
|
|
|
|
|
|
|
|
|
|
#include <X11/Xlib.h>
|
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
|
#include <X11/keysym.h>
|
|
|
|
|
|
|
|
|
|
#include <GL/gl.h>
|
|
|
|
|
#include <GL/glx.h>
|
|
|
|
|
|
|
|
|
|
#include "tcltk/tclmagic.h"
|
|
|
|
|
#include "utils/magic.h"
|
|
|
|
|
#include "utils/geometry.h"
|
|
|
|
|
#include "windows/windows.h"
|
|
|
|
|
#include "tiles/tile.h"
|
|
|
|
|
#include "utils/hash.h"
|
|
|
|
|
#include "utils/undo.h"
|
|
|
|
|
#include "database/database.h"
|
|
|
|
|
#include "utils/main.h"
|
|
|
|
|
#include "commands/commands.h"
|
|
|
|
|
#include "graphics/wind3d.h"
|
|
|
|
|
#include "graphics/graphicsInt.h"
|
|
|
|
|
#include "graphics/graphics.h"
|
|
|
|
|
#include "grTOGLInt.h"
|
|
|
|
|
#include "textio/textio.h"
|
|
|
|
|
#include "textio/txcommands.h"
|
|
|
|
|
#include "utils/utils.h"
|
|
|
|
|
#include "utils/styles.h"
|
|
|
|
|
#include "dbwind/dbwtech.h"
|
|
|
|
|
#include "dbwind/dbwind.h"
|
|
|
|
|
#include "extract/extract.h"
|
|
|
|
|
#include "graphics/glyphs.h"
|
|
|
|
|
#include "utils/malloc.h"
|
|
|
|
|
#include "windows/windInt.h" /* for access to redisplay pointer */
|
|
|
|
|
#include "cif/cif.h"
|
|
|
|
|
#include "cif/CIFint.h" /* access to CIFPlanes, CIFCurStyle, etc. */
|
|
|
|
|
|
2022-10-10 11:50:15 +02:00
|
|
|
/* C99 compat */
|
|
|
|
|
#include "utils/signals.h"
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
extern Display *grXdpy; /* X11 display */
|
|
|
|
|
extern GLXContext grXcontext; /* OpenGL/X11 interface def. */
|
|
|
|
|
extern XVisualInfo *grVisualInfo; /* OpenGL preferred visual */
|
|
|
|
|
extern char *MainDisplayType; /* make sure we're using OpenGL */
|
|
|
|
|
extern HashTable grTOGLWindowTable;
|
|
|
|
|
extern int grXscrn;
|
|
|
|
|
extern int grCurFill;
|
|
|
|
|
|
|
|
|
|
static bool w3dNeedStyle;
|
|
|
|
|
static bool w3dIsLocked;
|
|
|
|
|
static int w3dStyle;
|
|
|
|
|
static MagWindow *w3dWindow;
|
|
|
|
|
extern bool grDriverInformed;
|
|
|
|
|
|
|
|
|
|
extern void grInformDriver();
|
|
|
|
|
|
|
|
|
|
global WindClient W3DclientID;
|
|
|
|
|
|
|
|
|
|
#define glTransYs(n) (DisplayHeight(grXdpy, grXscrn)-(n))
|
|
|
|
|
|
|
|
|
|
/* forward declarations */
|
|
|
|
|
|
|
|
|
|
void W3DCIFredisplay();
|
|
|
|
|
void W3Dredisplay();
|
|
|
|
|
void Set3DDefaults();
|
|
|
|
|
void w3drefreshFunc();
|
|
|
|
|
bool W3Ddelete();
|
|
|
|
|
|
|
|
|
|
/* ------------------------Low-Level Routines--------------------------------- */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dLock(w)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
{
|
|
|
|
|
grSimpleLock(w, TRUE);
|
|
|
|
|
w3dSetProjection(w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dUnlock(w)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
{
|
|
|
|
|
glFlush();
|
|
|
|
|
glFinish();
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
|
|
|
|
|
|
glDisable(GL_COLOR_MATERIAL);
|
|
|
|
|
glDisable(GL_LIGHTING);
|
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
|
glDisable(GL_POLYGON_SMOOTH);
|
|
|
|
|
|
|
|
|
|
grSimpleUnlock(w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------Low-Level Drawing Routines------------------------------ */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillEdge(bbox, r, ztop, zbot)
|
|
|
|
|
Rect *bbox; /* tile bounding box */
|
|
|
|
|
Rect *r;
|
|
|
|
|
float ztop;
|
|
|
|
|
float zbot;
|
|
|
|
|
{
|
|
|
|
|
float ztmp;
|
|
|
|
|
float xbot = (float)r->r_xbot;
|
|
|
|
|
float ybot = (float)r->r_ybot;
|
|
|
|
|
float xtop = (float)r->r_xtop;
|
|
|
|
|
float ytop = (float)r->r_ytop;
|
|
|
|
|
|
|
|
|
|
if (ytop == bbox->r_ybot || xbot == bbox->r_xtop)
|
|
|
|
|
{
|
|
|
|
|
/* reverse z top and bottom so rectangle is drawn */
|
|
|
|
|
/* counterclockwise as seen from the outside. */
|
|
|
|
|
|
|
|
|
|
ztmp = zbot;
|
|
|
|
|
zbot = ztop;
|
|
|
|
|
ztop = ztmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
glVertex3f(xbot, ybot, zbot);
|
|
|
|
|
glVertex3f(xbot, ybot, ztop);
|
|
|
|
|
glVertex3f(xtop, ytop, ztop);
|
|
|
|
|
glVertex3f(xtop, ytop, zbot);
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillPolygon(p, np, zval, istop)
|
|
|
|
|
Point *p;
|
|
|
|
|
int np;
|
|
|
|
|
float zval;
|
|
|
|
|
bool istop;
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
if (istop) /* counterclockwise */
|
|
|
|
|
for (i = 0; i < np; i++)
|
|
|
|
|
glVertex3f((float)(p[i].p_x), (float)(p[i].p_y), zval);
|
|
|
|
|
else /* clockwise */
|
|
|
|
|
for (i = np - 1; i >= 0; i--)
|
|
|
|
|
glVertex3f((float)(p[i].p_x), (float)(p[i].p_y), zval);
|
|
|
|
|
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillTile(r, zval, istop)
|
|
|
|
|
Rect *r;
|
|
|
|
|
float zval;
|
|
|
|
|
bool istop;
|
|
|
|
|
{
|
|
|
|
|
float xbot, ybot, xtop, ytop;
|
|
|
|
|
|
|
|
|
|
ybot = (float)r->r_ybot;
|
|
|
|
|
ytop = (float)r->r_ytop;
|
|
|
|
|
|
|
|
|
|
if (istop)
|
|
|
|
|
{
|
|
|
|
|
xbot = (float)r->r_xbot;
|
|
|
|
|
xtop = (float)r->r_xtop;
|
|
|
|
|
}
|
|
|
|
|
else /* makes path counterclockwise as seen from the bottom */
|
|
|
|
|
{
|
|
|
|
|
xbot = (float)r->r_xtop;
|
|
|
|
|
xtop = (float)r->r_xbot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
glVertex3f(xbot, ybot, zval);
|
|
|
|
|
glVertex3f(xtop, ybot, zval);
|
|
|
|
|
glVertex3f(xtop, ytop, zval);
|
|
|
|
|
glVertex3f(xbot, ytop, zval);
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillXSide(xstart, xend, yval, ztop, zbot)
|
|
|
|
|
float xstart, xend, yval, ztop, zbot;
|
|
|
|
|
{
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
glVertex3f(xstart, yval, zbot);
|
|
|
|
|
glVertex3f(xstart, yval, ztop);
|
|
|
|
|
glVertex3f(xend, yval, ztop);
|
|
|
|
|
glVertex3f(xend, yval, zbot);
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillYSide(xval, ystart, yend, ztop, zbot)
|
|
|
|
|
float xval, ystart, yend, ztop, zbot;
|
|
|
|
|
{
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
glVertex3f(xval, ystart, zbot);
|
|
|
|
|
glVertex3f(xval, ystart, ztop);
|
|
|
|
|
glVertex3f(xval, yend, ztop);
|
|
|
|
|
glVertex3f(xval, yend, zbot);
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This routine assumes that vector (x1,y1)->(x2,y2) is in the */
|
|
|
|
|
/* counterclockwise direction with respect to the tile interior. */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillDiagonal(x1, y1, x2, y2, ztop, zbot)
|
|
|
|
|
float x1, y1, x2, y2, ztop, zbot;
|
|
|
|
|
{
|
|
|
|
|
glBegin(GL_POLYGON);
|
|
|
|
|
glVertex3f(x1, y1, zbot);
|
|
|
|
|
glVertex3f(x2, y2, zbot);
|
|
|
|
|
glVertex3f(x2, y2, ztop);
|
|
|
|
|
glVertex3f(x1, y1, ztop);
|
|
|
|
|
glEnd();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dFillOps(trans, tile, cliprect, ztop, zbot)
|
|
|
|
|
Transform *trans;
|
|
|
|
|
Tile *tile;
|
|
|
|
|
Rect *cliprect;
|
|
|
|
|
float ztop;
|
|
|
|
|
float zbot;
|
|
|
|
|
{
|
|
|
|
|
LinkedRect *tilesegs, *segptr;
|
|
|
|
|
Rect r, r2;
|
|
|
|
|
float xbot, ybot, xtop, ytop;
|
|
|
|
|
Point p[5];
|
|
|
|
|
int np;
|
|
|
|
|
|
|
|
|
|
r2.r_xbot = LEFT(tile);
|
|
|
|
|
r2.r_ybot = BOTTOM(tile);
|
|
|
|
|
r2.r_xtop = RIGHT(tile);
|
|
|
|
|
r2.r_ytop = TOP(tile);
|
|
|
|
|
|
|
|
|
|
GeoTransRect(trans, &r2, &r);
|
|
|
|
|
|
|
|
|
|
if (IsSplit(tile))
|
|
|
|
|
{
|
|
|
|
|
Rect fullr;
|
|
|
|
|
TileType dinfo;
|
|
|
|
|
|
|
|
|
|
dinfo = DBTransformDiagonal(TiGetTypeExact(tile), trans);
|
|
|
|
|
|
|
|
|
|
fullr = r;
|
|
|
|
|
if (cliprect != NULL)
|
|
|
|
|
GeoClip(&r, cliprect);
|
|
|
|
|
|
|
|
|
|
GrClipTriangle(&fullr, &r, cliprect != NULL, dinfo, p, &np);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (np > 0)
|
|
|
|
|
{
|
|
|
|
|
w3dFillPolygon(p, np, ztop, TRUE);
|
|
|
|
|
w3dFillPolygon(p, np, zbot, FALSE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Clip the tile area to the clipping area */
|
|
|
|
|
if (cliprect != NULL)
|
|
|
|
|
GeoClip(&r, cliprect);
|
|
|
|
|
|
|
|
|
|
/* draw tile top and bottom */
|
|
|
|
|
if (!GEO_RECTNULL(&r))
|
|
|
|
|
{
|
|
|
|
|
w3dFillTile(&r, ztop, TRUE);
|
|
|
|
|
w3dFillTile(&r, zbot, FALSE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If height is zero, don't bother to draw sides */
|
|
|
|
|
if (ztop == zbot) return;
|
|
|
|
|
|
|
|
|
|
/* Find tile outline and render sides */
|
|
|
|
|
|
|
|
|
|
if (GrBoxOutline(tile, &tilesegs))
|
|
|
|
|
{
|
|
|
|
|
xbot = (float)r.r_xbot;
|
|
|
|
|
ybot = (float)r.r_ybot;
|
|
|
|
|
xtop = (float)r.r_xtop;
|
|
|
|
|
ytop = (float)r.r_ytop;
|
|
|
|
|
|
|
|
|
|
if (r.r_xtop != r.r_xbot)
|
|
|
|
|
{
|
|
|
|
|
w3dFillXSide(xtop, xbot, ybot, ztop, zbot);
|
|
|
|
|
w3dFillXSide(xbot, xtop, ytop, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
if (r.r_ytop != r.r_ybot)
|
|
|
|
|
{
|
|
|
|
|
w3dFillYSide(xbot, ybot, ytop, ztop, zbot);
|
|
|
|
|
w3dFillYSide(xtop, ytop, ybot, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (segptr = tilesegs; segptr != NULL; segptr = segptr->r_next)
|
|
|
|
|
{
|
|
|
|
|
GeoTransRect(trans, &segptr->r_r, &r2);
|
|
|
|
|
if (cliprect != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (GEO_OVERLAP(cliprect, &r2))
|
|
|
|
|
{
|
|
|
|
|
GeoClip(&r2, cliprect);
|
|
|
|
|
w3dFillEdge(&r, &r2, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
w3dFillEdge(&r, &r2, ztop, zbot);
|
|
|
|
|
freeMagic(segptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* For non-manhattan tiles, GrBoxOutline only returns */
|
|
|
|
|
/* the manhattan edges. This leaves the (possibly */
|
|
|
|
|
/* clipped) diagonal edge to render. */
|
|
|
|
|
|
|
|
|
|
if (IsSplit(tile))
|
|
|
|
|
{
|
|
|
|
|
int cp;
|
|
|
|
|
for (cp = 0; cp < np - 1; cp++)
|
|
|
|
|
{
|
|
|
|
|
if ((p[cp].p_x != p[cp + 1].p_x) && (p[cp].p_y != p[cp + 1].p_y))
|
|
|
|
|
{
|
|
|
|
|
w3dFillDiagonal((float)p[cp].p_x, (float)p[cp].p_y,
|
|
|
|
|
(float)p[cp + 1].p_x, (float)p[cp + 1].p_y,
|
|
|
|
|
ztop, zbot);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (cp == (np - 1))
|
|
|
|
|
if ((p[cp].p_x != p[0].p_x) && (p[cp].p_y != p[0].p_y))
|
|
|
|
|
w3dFillDiagonal((float)p[cp].p_x, (float)p[cp].p_y,
|
|
|
|
|
(float)p[0].p_x, (float)p[0].p_y,
|
|
|
|
|
ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Render edges cut by the clipping area, if they're */
|
|
|
|
|
/* inside the tile, so the tile doesn't look "hollow". */
|
|
|
|
|
|
|
|
|
|
if (cliprect != NULL)
|
|
|
|
|
{
|
|
|
|
|
xbot = (float)r.r_xbot;
|
|
|
|
|
ybot = (float)r.r_ybot;
|
|
|
|
|
xtop = (float)r.r_xtop;
|
|
|
|
|
ytop = (float)r.r_ytop;
|
|
|
|
|
|
|
|
|
|
if (r.r_ytop > r.r_ybot)
|
|
|
|
|
{
|
|
|
|
|
if (r.r_xtop == cliprect->r_xtop)
|
|
|
|
|
w3dFillYSide(xtop, ytop, ybot, ztop, zbot);
|
|
|
|
|
if (r.r_xbot == cliprect->r_xbot)
|
|
|
|
|
w3dFillYSide(xbot, ybot, ytop, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
if (r.r_xtop > r.r_xbot)
|
|
|
|
|
{
|
|
|
|
|
if (r.r_ytop == cliprect->r_ytop)
|
|
|
|
|
w3dFillXSide(xbot, xtop, ytop, ztop, zbot);
|
|
|
|
|
if (r.r_ybot == cliprect->r_ybot)
|
|
|
|
|
w3dFillXSide(xtop, xbot, ybot, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dRenderVolume(tile, trans, cliprect)
|
|
|
|
|
Tile *tile;
|
|
|
|
|
Transform *trans;
|
|
|
|
|
Rect *cliprect;
|
|
|
|
|
{
|
|
|
|
|
float zbot, ztop;
|
|
|
|
|
float ftop = 0.0, fthk = 0.0;
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
|
|
|
|
|
crec = (W3DclientRec *)(w3dWindow->w_clientData);
|
|
|
|
|
|
|
|
|
|
ExtGetZAxis(tile, &ftop, &fthk);
|
|
|
|
|
|
|
|
|
|
/* Negate z-axis values for OpenGL display */
|
|
|
|
|
ztop = -ftop * crec->scale_z;
|
|
|
|
|
zbot = ztop - (fthk * crec->scale_z);
|
|
|
|
|
|
|
|
|
|
GR_CHECK_LOCK();
|
2020-05-23 23:13:14 +02:00
|
|
|
if (!grDriverInformed)
|
2017-04-25 14:41:48 +02:00
|
|
|
grInformDriver();
|
|
|
|
|
|
|
|
|
|
if ((grCurFill == GR_STSOLID) || (grCurFill == GR_STSTIPPLE))
|
|
|
|
|
{
|
|
|
|
|
w3dFillOps(trans, tile, cliprect, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* To do: Outlines and contact crosses */
|
|
|
|
|
/* To do: Labels */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dRenderCIF(tile, layer, trans)
|
|
|
|
|
Tile *tile;
|
|
|
|
|
CIFLayer *layer;
|
|
|
|
|
Transform *trans;
|
|
|
|
|
{
|
|
|
|
|
float zbot, ztop;
|
|
|
|
|
float ftop, fthk;
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
|
|
|
|
|
crec = (W3DclientRec *)(w3dWindow->w_clientData);
|
|
|
|
|
|
|
|
|
|
ftop = layer->cl_height;
|
|
|
|
|
fthk = layer->cl_thick;
|
|
|
|
|
|
|
|
|
|
/* Negate z-axis values for OpenGL display */
|
|
|
|
|
ztop = -ftop * crec->scale_z;
|
|
|
|
|
zbot = ztop - (fthk * crec->scale_z);
|
|
|
|
|
|
|
|
|
|
GR_CHECK_LOCK();
|
2020-05-23 23:13:14 +02:00
|
|
|
if (!grDriverInformed)
|
2017-04-25 14:41:48 +02:00
|
|
|
grInformDriver();
|
|
|
|
|
|
|
|
|
|
if ((grCurFill == GR_STSOLID) || (grCurFill == GR_STSTIPPLE))
|
|
|
|
|
{
|
|
|
|
|
w3dFillOps(trans, tile, NULL, ztop, zbot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* To do: Outlines */
|
|
|
|
|
/* To do: Labels */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dClear()
|
|
|
|
|
{
|
|
|
|
|
float fr, fg, fb;
|
|
|
|
|
int cidx, lr, lb, lg;
|
|
|
|
|
|
|
|
|
|
/* STYLE_TRANSPARENT uses the background color definition, */
|
|
|
|
|
/* so we get RGB values from there. */
|
|
|
|
|
|
|
|
|
|
cidx = GrStyleTable[STYLE_TRANSPARENT].color;
|
|
|
|
|
GrGetColor(cidx, &lr, &lg, &lb);
|
|
|
|
|
|
|
|
|
|
fr = ((GLfloat)lr / 255);
|
|
|
|
|
fg = ((GLfloat)lg / 255);
|
|
|
|
|
fb = ((GLfloat)lb / 255);
|
|
|
|
|
|
|
|
|
|
glClearColor(fr, fg, fb, 0.0);
|
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -------------------High-Level Drawing Routines------------------------------ */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dSetProjection(w)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
Window wind;
|
|
|
|
|
GLfloat light0_pos[] = { 0.0, 0.0, 0.0, 0.0 }; /* light #0 position */
|
|
|
|
|
GLfloat light0_amb[] = { 0.4, 0.4, 0.4, 1.0 }; /* light #0 ambient int. */
|
|
|
|
|
GLfloat light0_dif[] = { 0.0, 0.0, 0.0, 1.0 }; /* light #0 diffuse int. */
|
|
|
|
|
|
|
|
|
|
GLfloat light1_pos[] = { 50.0, 50.0, 50.0, 1.0 }; /* light #1 position */
|
|
|
|
|
GLfloat light1_amb[] = { 0.0, 0.0, 0.0, 1.0 }; /* light #1 ambient int. */
|
|
|
|
|
GLfloat light1_dif[] = { 1.0, 1.0, 1.0, 1.0 }; /* light #1 diffuse int. */
|
|
|
|
|
|
|
|
|
|
crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
wind = Tk_WindowId((Tk_Window)w->w_grdata);
|
|
|
|
|
if (wind == 0) return; /* window was closed by window manager? */
|
|
|
|
|
|
|
|
|
|
/* Should not mess with surfaceArea---we want the area to be displayed */
|
|
|
|
|
/* in crec, in magic internal coordinates, set by the edit box (else- */
|
|
|
|
|
/* where). Set the viewport to maintain 1:1 x:y aspect ratio on the */
|
|
|
|
|
/* rendered volume. */
|
|
|
|
|
|
|
|
|
|
glXMakeCurrent(grXdpy, (GLXDrawable)wind, grXcontext);
|
|
|
|
|
|
|
|
|
|
if (crec->level > 0)
|
|
|
|
|
{
|
|
|
|
|
glEnable(GL_LINE_SMOOTH);
|
|
|
|
|
glEnable(GL_POLYGON_SMOOTH);
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Need to look into dealing properly with double-buffered graphics */
|
|
|
|
|
glDrawBuffer(GL_FRONT);
|
|
|
|
|
/* glDrawBuffer(GL_BACK); */
|
|
|
|
|
|
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
|
glLoadIdentity();
|
|
|
|
|
|
|
|
|
|
/* Lighting */
|
|
|
|
|
|
|
|
|
|
glEnable(GL_COLOR_MATERIAL);
|
|
|
|
|
glEnable(GL_LIGHTING);
|
|
|
|
|
glEnable(GL_LIGHT0);
|
|
|
|
|
glEnable(GL_LIGHT1);
|
|
|
|
|
|
|
|
|
|
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
|
|
|
|
|
|
|
|
|
|
glLightfv(GL_LIGHT0, GL_POSITION, light0_pos); /* positional */
|
|
|
|
|
glLightfv(GL_LIGHT0, GL_AMBIENT, light0_amb); /* ambient */
|
|
|
|
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_dif); /* diffuse */
|
|
|
|
|
|
|
|
|
|
glLightfv(GL_LIGHT1, GL_POSITION, light1_pos); /* directional */
|
|
|
|
|
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_amb); /* ambient */
|
|
|
|
|
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_dif); /* diffuse */
|
|
|
|
|
|
|
|
|
|
/* Fill front-facing polygons only */
|
|
|
|
|
|
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
|
glCullFace(GL_BACK);
|
|
|
|
|
|
|
|
|
|
/* Preserve aspect ratio of 1:1 for internal units on the display */
|
|
|
|
|
|
|
|
|
|
glScalef((GLfloat)crec->height / (GLfloat)crec->width, 1.0, 1.0);
|
|
|
|
|
glViewport((GLsizei)0, (GLsizei)0, (GLsizei)crec->width, (GLsizei)crec->height);
|
|
|
|
|
|
|
|
|
|
/* Projection matrix manipulations */
|
|
|
|
|
|
|
|
|
|
glScalef(crec->scale_xy, crec->scale_xy, crec->prescale_z);
|
|
|
|
|
|
|
|
|
|
glRotatef(crec->view_x, 1.0, 0.0, 0.0);
|
|
|
|
|
glRotatef(crec->view_y, 0.0, 1.0, 0.0);
|
|
|
|
|
glRotatef(crec->view_z, 0.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
|
|
glTranslatef(crec->trans_x, crec->trans_y, crec->trans_z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Magic layer tile painting function */
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
w3dPaintFunc(tile, cxp)
|
|
|
|
|
Tile *tile; /* Tile to be displayed */
|
|
|
|
|
TreeContext *cxp; /* From DBTreeSrTiles */
|
|
|
|
|
{
|
|
|
|
|
SearchContext *scx = cxp->tc_scx;
|
|
|
|
|
|
|
|
|
|
/* Allow display interrupt a la dbwPaintFunc(). */
|
|
|
|
|
/* HOWEVER, note that GrEventPendingPtr looks at */
|
|
|
|
|
/* events in the window defined by toglCurrent, */
|
|
|
|
|
/* which is not the 3D window. So rendering in the */
|
|
|
|
|
/* 3D window can only be interrupted by events in */
|
|
|
|
|
/* the layout window. */
|
|
|
|
|
|
|
|
|
|
/* Honor display suspend status */
|
|
|
|
|
if (GrDisplayStatus == DISPLAY_SUSPEND) return 0;
|
|
|
|
|
|
|
|
|
|
if (GrDisplayStatus == DISPLAY_BREAK_PENDING)
|
|
|
|
|
{
|
|
|
|
|
GrDisplayStatus = DISPLAY_IN_PROGRESS;
|
|
|
|
|
if (GrEventPendingPtr)
|
|
|
|
|
{
|
|
|
|
|
if ((*GrEventPendingPtr)())
|
|
|
|
|
sigOnInterrupt(0);
|
|
|
|
|
else
|
|
|
|
|
SigSetTimer(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!w3dIsLocked)
|
|
|
|
|
{
|
|
|
|
|
w3dLock(w3dWindow);
|
|
|
|
|
w3dIsLocked = TRUE;
|
|
|
|
|
}
|
|
|
|
|
if (w3dNeedStyle)
|
|
|
|
|
{
|
|
|
|
|
GrSetStuff(w3dStyle);
|
|
|
|
|
w3dNeedStyle = FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w3dRenderVolume(tile, &scx->scx_trans, &scx->scx_area);
|
|
|
|
|
return 0; /* keep the search going! */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* CIF layer tile painting function */
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
w3dCIFPaintFunc(tile, arg)
|
|
|
|
|
Tile *tile; /* Tile to be displayed */
|
|
|
|
|
ClientData *arg; /* is NULL */
|
|
|
|
|
{
|
|
|
|
|
CIFLayer *layer = (CIFLayer *)arg;
|
|
|
|
|
|
|
|
|
|
/* Honor display suspend status */
|
|
|
|
|
if (GrDisplayStatus == DISPLAY_SUSPEND) return 0;
|
|
|
|
|
|
|
|
|
|
/* Allow display interrupt a la dbwPaintFunc() */
|
|
|
|
|
if (GrDisplayStatus == DISPLAY_BREAK_PENDING)
|
|
|
|
|
{
|
|
|
|
|
GrDisplayStatus = DISPLAY_IN_PROGRESS;
|
|
|
|
|
if (GrEventPendingPtr)
|
|
|
|
|
{
|
|
|
|
|
if ((*GrEventPendingPtr)())
|
|
|
|
|
sigOnInterrupt(0);
|
|
|
|
|
else
|
|
|
|
|
SigSetTimer(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!w3dIsLocked)
|
|
|
|
|
{
|
|
|
|
|
w3dLock(w3dWindow);
|
|
|
|
|
w3dIsLocked = TRUE;
|
|
|
|
|
}
|
|
|
|
|
if (w3dNeedStyle)
|
|
|
|
|
{
|
|
|
|
|
/* TxError("CIF layer 0x%x (%s) render style %d\n",
|
|
|
|
|
layer,
|
|
|
|
|
layer->cl_name,
|
|
|
|
|
layer->cl_renderStyle); */
|
|
|
|
|
GrSetStuff(layer->cl_renderStyle + TECHBEGINSTYLES);
|
|
|
|
|
w3dNeedStyle = FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w3dRenderCIF(tile, layer, &GeoIdentityTransform);
|
|
|
|
|
return 0; /* keep the search going! */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -----------------------Command Procedures------------------------------ */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dHelp --
|
|
|
|
|
*
|
|
|
|
|
* Print the list of commands available in this window.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dHelp(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
2024-10-10 21:16:36 +02:00
|
|
|
const char * const *msg;
|
2017-04-25 14:41:48 +02:00
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf("\nWind3D command summary:\n");
|
|
|
|
|
for (msg = WindGetCommandTable(W3DclientID); *msg != NULL; msg++)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf(" %s\n", *msg);
|
|
|
|
|
}
|
|
|
|
|
TxPrintf("\nType '?' in the window to get a key macro summary.\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: help\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dCutBox --
|
|
|
|
|
*
|
|
|
|
|
* Set a clipping box for the 3D view
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dCutBox(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
bool hide = FALSE;
|
|
|
|
|
int lidx = 1, num_layers;
|
|
|
|
|
TileTypeBitMask mask;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1 || cmd->tx_argc == 2 || cmd->tx_argc == 5)
|
|
|
|
|
{
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
if (crec->clipped)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *rlist = Tcl_NewListObj(0, NULL);
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, rlist,
|
|
|
|
|
Tcl_NewIntObj((int)(crec->cutbox.r_xbot)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, rlist,
|
|
|
|
|
Tcl_NewIntObj((int)(crec->cutbox.r_ybot)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, rlist,
|
|
|
|
|
Tcl_NewIntObj((int)(crec->cutbox.r_xtop)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, rlist,
|
|
|
|
|
Tcl_NewIntObj((int)(crec->cutbox.r_ytop)));
|
|
|
|
|
|
|
|
|
|
Tcl_SetObjResult(magicinterp, rlist);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
Tcl_SetResult(magicinterp, "none", NULL);
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(cmd->tx_argv[1], "none"))
|
|
|
|
|
crec->clipped = FALSE;
|
|
|
|
|
if (!strcmp(cmd->tx_argv[1], "box"))
|
|
|
|
|
{
|
|
|
|
|
Rect rootBox;
|
|
|
|
|
CellDef *rootBoxDef;
|
|
|
|
|
CellDef *cellDef = ((CellUse *)w->w_surfaceID)->cu_def;
|
|
|
|
|
|
|
|
|
|
if (ToolGetBox(&rootBoxDef, &rootBox))
|
|
|
|
|
{
|
|
|
|
|
if (rootBoxDef == cellDef)
|
|
|
|
|
{
|
|
|
|
|
crec->clipped = TRUE;
|
|
|
|
|
crec->cutbox = rootBox;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
if (StrIsInt(cmd->tx_argv[1]) &&
|
|
|
|
|
StrIsInt(cmd->tx_argv[2]) &&
|
|
|
|
|
StrIsInt(cmd->tx_argv[3]) &&
|
2017-04-25 14:41:48 +02:00
|
|
|
StrIsInt(cmd->tx_argv[4]))
|
|
|
|
|
{
|
|
|
|
|
crec->clipped = TRUE;
|
|
|
|
|
crec->cutbox.r_xbot = atoi(cmd->tx_argv[1]);
|
|
|
|
|
crec->cutbox.r_ybot = atoi(cmd->tx_argv[2]);
|
|
|
|
|
crec->cutbox.r_xtop = atoi(cmd->tx_argv[3]);
|
|
|
|
|
crec->cutbox.r_ytop = atoi(cmd->tx_argv[4]);
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: cutbox [none|box|llx lly urx ur]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dSeeLayers --
|
|
|
|
|
*
|
|
|
|
|
* See or hide layers from the 3D view
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dSeeLayers(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
bool hide = FALSE;
|
|
|
|
|
int lidx = 1, num_layers;
|
|
|
|
|
TileTypeBitMask mask;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 2 || cmd->tx_argc == 3)
|
|
|
|
|
{
|
|
|
|
|
if (cmd->tx_argc == 3)
|
|
|
|
|
{
|
|
|
|
|
lidx = 2;
|
|
|
|
|
if (!strcmp(cmd->tx_argv[1], "no")) hide = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (crec->cif)
|
|
|
|
|
{
|
|
|
|
|
/* If CIF layers, match the layer name (1 only) */
|
2024-10-04 12:32:55 +02:00
|
|
|
if (!CIFNameToMask(cmd->tx_argv[lidx], &mask, NULL))
|
2017-04-25 14:41:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* If normal layers, pick up the name with TechType */
|
|
|
|
|
if (!CmdParseLayers(cmd->tx_argv[lidx], &mask))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (hide)
|
|
|
|
|
TTMaskClearMask(&crec->visible, &mask);
|
|
|
|
|
else
|
|
|
|
|
TTMaskSetMask(&crec->visible, &mask);
|
|
|
|
|
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: see [no] layer\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dClose --
|
|
|
|
|
*
|
|
|
|
|
* Close the 3D display. This corresponds to the command-line command
|
|
|
|
|
* "closewindow" and overrides the default window client command of the
|
|
|
|
|
* same name.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dClose(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
(void) WindDelete(w);
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: closewindow\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dRescale --
|
|
|
|
|
*
|
|
|
|
|
* Change scale and translation by indicated scalefactor.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dRescale(crec, scalefactor)
|
|
|
|
|
W3DclientRec *crec;
|
2020-05-23 23:13:14 +02:00
|
|
|
float scalefactor;
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
crec->scale_xy /= scalefactor;
|
|
|
|
|
crec->prescale_z /= scalefactor;
|
|
|
|
|
|
|
|
|
|
crec->scale_z *= scalefactor;
|
|
|
|
|
crec->trans_y *= scalefactor;
|
|
|
|
|
crec->trans_x *= scalefactor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dToggleCIF --
|
|
|
|
|
*
|
|
|
|
|
* Change between CIF and magic layer views
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dToggleCIF(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if ((crec->cif == FALSE) && (CIFCurStyle != NULL))
|
|
|
|
|
{
|
|
|
|
|
((clientRec *)(W3DclientID))->w_redisplay = W3DCIFredisplay;
|
|
|
|
|
crec->cif = TRUE;
|
|
|
|
|
w3dRescale(crec, (float)CIFCurStyle->cs_scaleFactor);
|
|
|
|
|
}
|
|
|
|
|
else if (crec->cif == TRUE)
|
|
|
|
|
{
|
|
|
|
|
((clientRec *)(W3DclientID))->w_redisplay = W3Dredisplay;
|
|
|
|
|
crec->cif = FALSE;
|
|
|
|
|
w3dRescale(crec, 1.0 / (float)CIFCurStyle->cs_scaleFactor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: cif\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dLevel --
|
|
|
|
|
*
|
|
|
|
|
* Change rendering level
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dLevel(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
if (StrIsInt(cmd->tx_argv[1]))
|
|
|
|
|
crec->level = atoi(cmd->tx_argv[1]);
|
|
|
|
|
else if (!strcmp(cmd->tx_argv[1], "up"))
|
|
|
|
|
crec->level++;
|
|
|
|
|
else if (!strcmp(cmd->tx_argv[1], "down"))
|
|
|
|
|
crec->level--;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TxError("Usage: level [<n>|up|down]\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (crec->level < 0) crec->level = 0;
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
Tcl_SetObjResult(magicinterp, Tcl_NewIntObj((int)crec->level));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: level [n]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* Function to perform refresh on the entire 3D window
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3drefreshFunc(mw)
|
|
|
|
|
MagWindow *mw;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) mw->w_clientData;
|
|
|
|
|
Rect screenRect;
|
|
|
|
|
|
|
|
|
|
screenRect.r_xbot = 0;
|
|
|
|
|
screenRect.r_ybot = 0;
|
|
|
|
|
screenRect.r_xtop = crec->width;
|
|
|
|
|
screenRect.r_ytop = crec->height;
|
|
|
|
|
|
|
|
|
|
WindAreaChanged(mw, &screenRect);
|
|
|
|
|
WindUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dDefaults --
|
|
|
|
|
*
|
|
|
|
|
* Revert to display defaults
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dDefaults(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
Set3DDefaults(w, crec);
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: defaults\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dRefresh --
|
|
|
|
|
*
|
|
|
|
|
* Refresh the display
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dRefresh(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 1)
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: refresh\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dView --
|
|
|
|
|
*
|
|
|
|
|
* Set the viewing angle into the 3D display. Overrides the window
|
|
|
|
|
* client command of the same name.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dView(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
bool relative = FALSE;
|
|
|
|
|
int argc = cmd->tx_argc;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (argc == 5)
|
|
|
|
|
{
|
|
|
|
|
argc--;
|
|
|
|
|
if (!strncmp(cmd->tx_argv[argc], "rel", 3))
|
|
|
|
|
relative = TRUE;
|
|
|
|
|
else if (strncmp(cmd->tx_argv[argc], "abs", 3))
|
|
|
|
|
{
|
|
|
|
|
TxError("Usage: view angle_x angle_y angle_z absolute|relative\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc == 4)
|
|
|
|
|
{
|
|
|
|
|
/* Pick up x, y, z angles */
|
|
|
|
|
|
|
|
|
|
if (StrIsNumeric(cmd->tx_argv[1]) &&
|
|
|
|
|
StrIsNumeric(cmd->tx_argv[2]) &&
|
|
|
|
|
StrIsNumeric(cmd->tx_argv[3]))
|
|
|
|
|
{
|
|
|
|
|
if (relative)
|
|
|
|
|
{
|
|
|
|
|
crec->view_x += (float)atof(cmd->tx_argv[1]);
|
|
|
|
|
crec->view_y += (float)atof(cmd->tx_argv[2]);
|
|
|
|
|
crec->view_z += (float)atof(cmd->tx_argv[3]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
crec->view_x = (float)atof(cmd->tx_argv[1]);
|
|
|
|
|
crec->view_y = (float)atof(cmd->tx_argv[2]);
|
|
|
|
|
crec->view_z = (float)atof(cmd->tx_argv[3]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Call redisplay function */
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (argc == 1)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *vlist = Tcl_NewListObj(0, NULL);
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->view_x)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->view_y)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->view_z)));
|
|
|
|
|
|
|
|
|
|
Tcl_SetObjResult(magicinterp, vlist);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: view [angle_x angle_y angle_z [relative|absolute]]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dScroll --
|
|
|
|
|
*
|
|
|
|
|
* Set the viewing position into the 3D display.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dScroll(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
bool relative = FALSE;
|
|
|
|
|
int argc = cmd->tx_argc;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (argc == 5)
|
|
|
|
|
{
|
|
|
|
|
argc--;
|
|
|
|
|
if (!strncmp(cmd->tx_argv[argc], "rel", 3))
|
|
|
|
|
relative = TRUE;
|
|
|
|
|
else if (strncmp(cmd->tx_argv[argc], "abs", 3))
|
|
|
|
|
{
|
|
|
|
|
TxError("Usage: scroll pos_x pos_y pos_z absolute|relative\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc == 4)
|
|
|
|
|
{
|
|
|
|
|
/* Pick up x, y, z translations */
|
|
|
|
|
|
|
|
|
|
if (StrIsNumeric(cmd->tx_argv[1]) &&
|
|
|
|
|
StrIsNumeric(cmd->tx_argv[2]) &&
|
|
|
|
|
StrIsNumeric(cmd->tx_argv[3]))
|
|
|
|
|
{
|
|
|
|
|
if (relative)
|
|
|
|
|
{
|
|
|
|
|
crec->trans_x += (float)atof(cmd->tx_argv[1]) / crec->scale_xy;
|
|
|
|
|
crec->trans_y += (float)atof(cmd->tx_argv[2]) / crec->scale_xy;
|
|
|
|
|
crec->trans_z += (float)atof(cmd->tx_argv[3]) / crec->scale_xy;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
crec->trans_x = (float)atof(cmd->tx_argv[1]);
|
|
|
|
|
crec->trans_y = (float)atof(cmd->tx_argv[2]);
|
|
|
|
|
crec->trans_z = (float)atof(cmd->tx_argv[3]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Call redisplay function */
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (argc == 1)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *vlist = Tcl_NewListObj(0, NULL);
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->trans_x)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->trans_y)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->trans_z)));
|
|
|
|
|
|
|
|
|
|
Tcl_SetObjResult(magicinterp, vlist);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: scroll [pos_x pos_y pos_z [absolute|relative]]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dZoom --
|
|
|
|
|
*
|
|
|
|
|
* Set the viewing scale of the 3D display. Overrides the window
|
|
|
|
|
* client command of the same name.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dZoom(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
bool relative = FALSE;
|
|
|
|
|
int argc = cmd->tx_argc;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (argc == 4)
|
|
|
|
|
{
|
|
|
|
|
argc--;
|
|
|
|
|
if (!strncmp(cmd->tx_argv[argc], "rel", 3))
|
|
|
|
|
relative = TRUE;
|
|
|
|
|
else if (strncmp(cmd->tx_argv[argc], "abs", 3))
|
|
|
|
|
{
|
|
|
|
|
TxError("Usage: zoom scale_xy scale_z relative|absolute\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (argc == 3)
|
|
|
|
|
{
|
|
|
|
|
/* Pick up xy and z scales */
|
|
|
|
|
|
|
|
|
|
if (StrIsNumeric(cmd->tx_argv[1]) &&
|
|
|
|
|
StrIsNumeric(cmd->tx_argv[2]))
|
|
|
|
|
{
|
|
|
|
|
float xy, z;
|
|
|
|
|
xy = (float)atof(cmd->tx_argv[1]);
|
|
|
|
|
z = (float)atof(cmd->tx_argv[2]);
|
|
|
|
|
if ((xy <= 0) || (z <= 0))
|
|
|
|
|
{
|
|
|
|
|
TxError("Error: zoom values/factors must be positive and nonzero\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (relative)
|
|
|
|
|
{
|
|
|
|
|
crec->scale_xy *= xy;
|
|
|
|
|
crec->scale_z *= z;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
crec->scale_xy = xy;
|
|
|
|
|
crec->scale_z = z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Call redisplay function */
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc == 1)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *vlist = Tcl_NewListObj(0, NULL);
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->scale_xy)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, vlist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(crec->scale_z)));
|
|
|
|
|
|
|
|
|
|
Tcl_SetObjResult(magicinterp, vlist);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
TxError("Usage: zoom [scale_xy scale_z [relative|absolute]]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* w3dRenderValues --
|
|
|
|
|
*
|
|
|
|
|
* Set values for 3D rendering. These are independent of any
|
|
|
|
|
* extraction or other physical meaning, so we allow them to
|
|
|
|
|
* be independently controlled here.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
w3dRenderValues(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
int lidx;
|
|
|
|
|
CIFLayer *layer;
|
|
|
|
|
W3DclientRec *crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc > 1)
|
|
|
|
|
{
|
|
|
|
|
for (lidx = 0; lidx < CIFCurStyle->cs_nLayers; lidx++)
|
|
|
|
|
{
|
|
|
|
|
layer = CIFCurStyle->cs_layers[lidx];
|
|
|
|
|
if (!strcmp(layer->cl_name, cmd->tx_argv[1]))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lidx == CIFCurStyle->cs_nLayers)
|
|
|
|
|
{
|
|
|
|
|
TxError("Unknown CIF layer \"%s\"\n", cmd->tx_argv[1]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cmd->tx_argc == 2)
|
|
|
|
|
{
|
|
|
|
|
Tcl_Obj *llist;
|
|
|
|
|
llist = Tcl_NewListObj(0, NULL);
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, llist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(layer->cl_height)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, llist,
|
|
|
|
|
Tcl_NewDoubleObj((double)(layer->cl_thick)));
|
|
|
|
|
Tcl_ListObjAppendElement(magicinterp, llist,
|
|
|
|
|
Tcl_NewIntObj((int)(layer->cl_renderStyle)));
|
|
|
|
|
Tcl_SetObjResult(magicinterp, llist);
|
|
|
|
|
}
|
|
|
|
|
else if (cmd->tx_argc == 4 || cmd->tx_argc == 5)
|
|
|
|
|
{
|
|
|
|
|
int style;
|
|
|
|
|
float height, thick;
|
|
|
|
|
|
|
|
|
|
style = -1;
|
|
|
|
|
if (cmd->tx_argc == 5 && StrIsInt(cmd->tx_argv[4]))
|
|
|
|
|
style = atoi(cmd->tx_argv[4]);
|
|
|
|
|
if (!StrIsNumeric(cmd->tx_argv[3]) || !StrIsNumeric(cmd->tx_argv[2]))
|
|
|
|
|
goto badusage;
|
|
|
|
|
height = (float)atof(cmd->tx_argv[2]);
|
|
|
|
|
thick = (float)atof(cmd->tx_argv[3]);
|
|
|
|
|
|
|
|
|
|
/* It is necessary to update ALL layers of the same name */
|
|
|
|
|
for (lidx = 0; lidx < CIFCurStyle->cs_nLayers; lidx++)
|
|
|
|
|
{
|
|
|
|
|
layer = CIFCurStyle->cs_layers[lidx];
|
|
|
|
|
if (!strcmp(layer->cl_name, cmd->tx_argv[1]))
|
|
|
|
|
{
|
|
|
|
|
if (style >= 0) layer->cl_renderStyle = style;
|
|
|
|
|
layer->cl_height = height;
|
|
|
|
|
layer->cl_thick = thick;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Call redisplay function */
|
|
|
|
|
w3drefreshFunc(w);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
badusage:
|
|
|
|
|
TxError("Usage: render name [height thick [style]]\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* Set3DDefaults --
|
|
|
|
|
*
|
|
|
|
|
* Set default values for the viewing parameters (scale, angle,
|
|
|
|
|
* and translation in 3 dimensions)
|
|
|
|
|
*
|
|
|
|
|
* Default view is top-down (layout) view scaled to fit the celldef's
|
|
|
|
|
* bounding box in the 3D window.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
Set3DDefaults(mw, crec)
|
|
|
|
|
MagWindow *mw;
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
{
|
|
|
|
|
int height, width;
|
|
|
|
|
int centerx, centery;
|
|
|
|
|
float scalex, scaley;
|
|
|
|
|
|
|
|
|
|
/* Get translation and scale from the cell bounding box */
|
|
|
|
|
/* (= window's bounding box, set in loadWindow proc) */
|
|
|
|
|
|
|
|
|
|
height = mw->w_bbox->r_ytop - mw->w_bbox->r_ybot;
|
|
|
|
|
width = mw->w_bbox->r_xtop - mw->w_bbox->r_xbot;
|
|
|
|
|
centerx = -(mw->w_bbox->r_xbot + (width >> 1));
|
|
|
|
|
centery = -(mw->w_bbox->r_ybot + (height >> 1));
|
|
|
|
|
|
|
|
|
|
scalex = 2.0 / ((float)width * 1.1); /* Add 10% margins in x and y */
|
|
|
|
|
scaley = 2.0 / ((float)height * 1.1);
|
|
|
|
|
|
|
|
|
|
crec->trans_x = (float)centerx;
|
|
|
|
|
crec->trans_y = (float)centery;
|
|
|
|
|
crec->trans_z = 0.0;
|
|
|
|
|
|
|
|
|
|
crec->scale_xy = (scalex > scaley) ? scaley : scalex;
|
|
|
|
|
crec->scale_z = 25.0; /* Height exaggerated by 25x */
|
|
|
|
|
|
|
|
|
|
/* The small z-scale value is necessary to keep large layout distances */
|
|
|
|
|
/* from exceeding the OpenGL (-1,1) limits of the z-axis. Distances in Z, */
|
|
|
|
|
/* like layer thickness, simply scale up to compensate. */
|
|
|
|
|
|
|
|
|
|
crec->prescale_z = 0.0001;
|
|
|
|
|
|
|
|
|
|
/* layout view (top down) */
|
|
|
|
|
|
|
|
|
|
crec->view_x = 0.0;
|
|
|
|
|
crec->view_y = 0.0;
|
|
|
|
|
crec->view_z = 0.0;
|
|
|
|
|
|
|
|
|
|
TTMaskZero(&crec->visible);
|
|
|
|
|
TTMaskSetMask(&crec->visible, &DBAllTypeBits);
|
|
|
|
|
|
|
|
|
|
/* Scale all factors appropriately for CIF display */
|
|
|
|
|
|
|
|
|
|
if (crec->cif == TRUE)
|
|
|
|
|
w3dRescale(crec, (float)CIFCurStyle->cs_scaleFactor);
|
|
|
|
|
|
|
|
|
|
/* Default is no clipping */
|
|
|
|
|
crec->clipped = FALSE; /* no clipping by default */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* -----------------------Client Procedures------------------------------ */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3DloadWindow --
|
|
|
|
|
*
|
|
|
|
|
* Replace the root cell of a window by the specified cell.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* TRUE if successful, FALSE if not. The 3D rendering window is
|
|
|
|
|
* not allowed to create new cells, only to render existing ones.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
W3DloadWindow(window, name)
|
|
|
|
|
MagWindow *window;
|
|
|
|
|
char *name;
|
|
|
|
|
{
|
|
|
|
|
CellDef *newEditDef;
|
|
|
|
|
CellUse *newEditUse;
|
|
|
|
|
Rect loadBox;
|
|
|
|
|
|
|
|
|
|
newEditDef = DBCellLookDef(name);
|
|
|
|
|
if (newEditDef == (CellDef *)NULL)
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
2023-04-18 17:01:58 +02:00
|
|
|
if (!DBCellRead(newEditDef, TRUE, TRUE, NULL))
|
2017-04-25 14:41:48 +02:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
DBReComputeBbox(newEditDef);
|
|
|
|
|
loadBox = newEditDef->cd_bbox;
|
|
|
|
|
|
|
|
|
|
/* Attach cell to window */
|
|
|
|
|
|
|
|
|
|
newEditUse = DBCellNewUse(newEditDef, (char *) NULL);
|
|
|
|
|
(void) StrDup(&(newEditUse->cu_id), "3D rendered cell");
|
|
|
|
|
|
|
|
|
|
window->w_bbox = &(newEditUse->cu_def->cd_bbox);
|
|
|
|
|
return WindLoad(window, W3DclientID, (ClientData)newEditUse, &loadBox);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3Dcreate --
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* A new window has been created. Create and initialize the needed
|
2017-04-25 14:41:48 +02:00
|
|
|
* structures.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* FALSE if we have too many windows, TRUE otherwise.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Initialize the window to be editing the background color.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
W3Dcreate(window, argc, argv)
|
|
|
|
|
MagWindow *window;
|
|
|
|
|
int argc;
|
|
|
|
|
char *argv[];
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
Tk_Window tkwind, tktop;
|
2020-05-23 23:13:14 +02:00
|
|
|
Window wind;
|
2017-04-25 14:41:48 +02:00
|
|
|
Colormap colormap;
|
|
|
|
|
HashEntry *entry;
|
|
|
|
|
CellDef *boxDef;
|
|
|
|
|
Rect box;
|
|
|
|
|
bool result;
|
|
|
|
|
char *name = NULL;
|
|
|
|
|
|
|
|
|
|
/* At least for now, there's only one 3D window allowed. */
|
|
|
|
|
|
|
|
|
|
if (w3dWindow != NULL)
|
|
|
|
|
{
|
|
|
|
|
TxError("Only one 3D window allowed.\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The 3D rendering, of course, *only* works with OpenGL, so in an */
|
|
|
|
|
/* executable compiled for multiple graphics interfaces, we need */
|
|
|
|
|
/* to make sure that our display type is OpenGL. */
|
|
|
|
|
|
|
|
|
|
if (!GrIsDisplay(MainDisplayType, "OGL"))
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
TxError("Display type is \"%s\". OpenGL is required for the 3D display.\n",
|
2017-04-25 14:41:48 +02:00
|
|
|
MainDisplayType);
|
|
|
|
|
TxError("Please restart magic with option \"-d OGL\".\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
crec = (W3DclientRec *) mallocMagic(sizeof(W3DclientRec));
|
|
|
|
|
|
|
|
|
|
/* The MagWindow structure and frameArea indicates the cross-sectional */
|
|
|
|
|
/* area of the layout to be rendered in the 3D display window. */
|
|
|
|
|
|
|
|
|
|
/* Need to parse the argument list here. . . At least one argument */
|
|
|
|
|
/* should allow the Tk path name to be passed to the routine, as */
|
2020-05-23 23:13:14 +02:00
|
|
|
/* it is for the standard layout window in the Tk interface. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/* Set surface area, etc. of the MagWindow. . . ? */
|
|
|
|
|
|
|
|
|
|
/* Initial window height and width (should be passed as optional */
|
|
|
|
|
/* arguments to the specialopen command) */
|
|
|
|
|
|
|
|
|
|
crec->width = 500;
|
|
|
|
|
crec->height = 500;
|
|
|
|
|
|
|
|
|
|
/* Rendering level (0 = coarse & fast, 1 = finer but slower, etc. */
|
|
|
|
|
/* Render cif by default. */
|
|
|
|
|
|
|
|
|
|
crec->level = 1;
|
|
|
|
|
crec->cif = TRUE;
|
|
|
|
|
|
|
|
|
|
window->w_clientData = (ClientData) crec;
|
|
|
|
|
window->w_flags &= ~(WIND_SCROLLABLE | WIND_SCROLLBARS | WIND_CAPTION |
|
|
|
|
|
WIND_BORDER | WIND_COMMANDS);
|
|
|
|
|
|
|
|
|
|
/* Load the current cellDef into the window */
|
|
|
|
|
|
|
|
|
|
if ((argc > 0) && (strlen(argv[0]) > 0))
|
|
|
|
|
result = W3DloadWindow(window, argv[0]);
|
|
|
|
|
else if (ToolGetBox(&boxDef, &box))
|
|
|
|
|
result = W3DloadWindow(window, boxDef->cd_name);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MagWindow *mw = NULL;
|
|
|
|
|
|
|
|
|
|
windCheckOnlyWindow(&mw, DBWclientID);
|
|
|
|
|
if (mw != NULL)
|
|
|
|
|
{
|
|
|
|
|
boxDef = ((CellUse *)mw->w_surfaceID)->cu_def;
|
|
|
|
|
result = W3DloadWindow(window, boxDef->cd_name);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
TxError("Ambiguous directive: Put cursor box in one of the windows.\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result == FALSE)
|
|
|
|
|
{
|
|
|
|
|
TxError("Cells cannot be created in the 3D window.\n");
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Generate the window. */
|
|
|
|
|
|
|
|
|
|
colormap = XCreateColormap(grXdpy, RootWindow(grXdpy, DefaultScreen(grXdpy)),
|
|
|
|
|
grVisualInfo->visual, AllocNone);
|
|
|
|
|
|
|
|
|
|
if (!(tktop = Tk_MainWindow(magicinterp))) return FALSE;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Check for a Tk pathname for the window; allows window to be */
|
|
|
|
|
/* by a Tk GUI script. */
|
|
|
|
|
if (argc > 1) name = argv[1];
|
|
|
|
|
|
|
|
|
|
if (name == NULL)
|
|
|
|
|
tkwind = Tk_CreateWindowFromPath(magicinterp, tktop, ".magic3d", "");
|
|
|
|
|
else
|
|
|
|
|
tkwind = Tk_CreateWindowFromPath(magicinterp, tktop, name, NULL);
|
|
|
|
|
|
|
|
|
|
if (tkwind != 0)
|
|
|
|
|
{
|
|
|
|
|
window->w_grdata = (ClientData) tkwind;
|
|
|
|
|
entry = HashFind(&grTOGLWindowTable, (char *)tkwind);
|
|
|
|
|
HashSetValue(entry, window);
|
|
|
|
|
|
|
|
|
|
if (name != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* Visual type must match what we chose in the graphics init proc */
|
|
|
|
|
Tk_SetWindowVisual(tkwind, grVisualInfo->visual, toglCurrent.depth,
|
|
|
|
|
colormap);
|
|
|
|
|
Tk_MapWindow(tkwind);
|
|
|
|
|
Tk_GeometryRequest(tkwind, crec->width, crec->height);
|
|
|
|
|
|
|
|
|
|
wind = Tk_WindowId(tkwind);
|
|
|
|
|
if (wind == 0)
|
|
|
|
|
glXMakeCurrent(grXdpy, (GLXDrawable)wind, grXcontext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* execute any pending Tk events */
|
|
|
|
|
|
|
|
|
|
while (Tcl_DoOneEvent(TCL_DONT_WAIT) != 0);
|
|
|
|
|
|
|
|
|
|
/* use the OpenGL Tk event handler (see grTOGL1.c) for the 3d window */
|
|
|
|
|
|
|
|
|
|
Tk_CreateEventHandler(tkwind, ExposureMask | StructureNotifyMask |
|
|
|
|
|
ButtonPressMask | KeyPressMask,
|
|
|
|
|
(Tk_EventProc *)TOGLEventProc, (ClientData)tkwind);
|
|
|
|
|
|
|
|
|
|
w3dWindow = window;
|
|
|
|
|
|
|
|
|
|
/* Use Tcl to pass commands to the window */
|
|
|
|
|
MakeWindowCommand((name == NULL) ? ".magic3d" : name, window);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Now that a cell is loaded, set default values for the */
|
|
|
|
|
/* client record based on the cell bounding box. */
|
|
|
|
|
|
|
|
|
|
Set3DDefaults(window, crec);
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TxError("Could not create a new Tk window\n");
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3Ddelete --
|
|
|
|
|
*
|
|
|
|
|
* Clean up the data structures before deleting a window.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* TRUE if we really want to delete the window, FALSE otherwise.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* A W3DclientRec is freed.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
W3Ddelete(window)
|
|
|
|
|
MagWindow *window;
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *cr;
|
|
|
|
|
Tk_Window xw;
|
|
|
|
|
|
|
|
|
|
cr = (W3DclientRec *) window->w_clientData;
|
|
|
|
|
xw = (Tk_Window)window->w_grdata;
|
|
|
|
|
w3dWindow = NULL;
|
|
|
|
|
|
|
|
|
|
freeMagic((char *)cr);
|
|
|
|
|
window->w_clientData = (ClientData)NULL;
|
|
|
|
|
xw = (Tk_Window)window->w_grdata;
|
|
|
|
|
/* Tk_DestroyWindow(xw); */
|
|
|
|
|
return (TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3Dredisplay --
|
|
|
|
|
*
|
|
|
|
|
* Redisplay a portion of the 3D rendered layout view
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Redisplay is done.
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
W3Dredisplay(w, rootArea, clipArea)
|
|
|
|
|
MagWindow *w; /* The window containing the area. */
|
|
|
|
|
Rect *rootArea; /* Ignore this---area defined by window with box */
|
|
|
|
|
Rect *clipArea; /* Ignore this, too */
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
CellDef *cellDef;
|
|
|
|
|
SearchContext scontext;
|
|
|
|
|
Rect largerArea, *clipRect = &largerArea;
|
|
|
|
|
int i;
|
|
|
|
|
TileTypeBitMask *mask, layers;
|
|
|
|
|
|
|
|
|
|
w3dLock(w);
|
|
|
|
|
|
|
|
|
|
crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
cellDef = ((CellUse *)w->w_surfaceID)->cu_def;
|
|
|
|
|
|
|
|
|
|
if (crec->clipped)
|
|
|
|
|
clipRect = &crec->cutbox;
|
|
|
|
|
|
|
|
|
|
if (rootArea != NULL)
|
|
|
|
|
largerArea = *rootArea;
|
|
|
|
|
else
|
|
|
|
|
largerArea = w->w_surfaceArea;
|
|
|
|
|
|
|
|
|
|
largerArea.r_xbot--;
|
|
|
|
|
largerArea.r_ybot--;
|
|
|
|
|
largerArea.r_xtop++;
|
|
|
|
|
largerArea.r_ytop++;
|
|
|
|
|
|
|
|
|
|
scontext.scx_area = *clipRect;
|
|
|
|
|
scontext.scx_use = ((CellUse *)w->w_surfaceID);
|
|
|
|
|
scontext.scx_x = scontext.scx_y = -1;
|
|
|
|
|
scontext.scx_trans = GeoIdentityTransform;
|
|
|
|
|
|
|
|
|
|
w3dClear();
|
|
|
|
|
|
|
|
|
|
/* follow the same locking procedure as DBWredisplay() */
|
|
|
|
|
w3dUnlock(w);
|
|
|
|
|
w3dIsLocked = FALSE;
|
|
|
|
|
for (i = 0; i < DBWNumStyles; i++)
|
|
|
|
|
{
|
|
|
|
|
/* This should probably be redesigned. . . */
|
|
|
|
|
mask = DBWStyleToTypes(i);
|
|
|
|
|
TTMaskAndMask3(&layers, mask, &crec->visible);
|
|
|
|
|
if (!TTMaskIsZero(&layers))
|
|
|
|
|
{
|
|
|
|
|
w3dStyle = i + TECHBEGINSTYLES;
|
|
|
|
|
w3dNeedStyle = TRUE;
|
|
|
|
|
(void) DBTreeSrTiles(&scontext, &layers, 0,
|
|
|
|
|
w3dPaintFunc, (ClientData) NULL);
|
|
|
|
|
if (w3dIsLocked)
|
|
|
|
|
{
|
|
|
|
|
w3dUnlock(w);
|
|
|
|
|
w3dIsLocked = FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3DCIFredisplay --
|
|
|
|
|
*
|
|
|
|
|
* Redisplay a portion of the 3D rendered layout, in CIF layers.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Redisplay is done.
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
W3DCIFredisplay(w, rootArea, clipArea)
|
|
|
|
|
MagWindow *w; /* The window containing the area. */
|
|
|
|
|
Rect *rootArea; /* Ignore this---area defined by window with box */
|
|
|
|
|
Rect *clipArea; /* Ignore this, too */
|
|
|
|
|
{
|
|
|
|
|
W3DclientRec *crec;
|
|
|
|
|
SearchContext scx;
|
|
|
|
|
CellDef *cellDef;
|
|
|
|
|
Rect clipRect;
|
|
|
|
|
int i;
|
|
|
|
|
TileTypeBitMask *mask;
|
|
|
|
|
|
|
|
|
|
w3dLock(w);
|
|
|
|
|
|
|
|
|
|
crec = (W3DclientRec *) w->w_clientData;
|
|
|
|
|
cellDef = ((CellUse *)w->w_surfaceID)->cu_def;
|
|
|
|
|
|
|
|
|
|
clipRect = (crec->clipped) ? crec->cutbox : cellDef->cd_bbox;
|
|
|
|
|
GEO_EXPAND(&clipRect, CIFCurStyle->cs_radius, &scx.scx_area);
|
|
|
|
|
|
|
|
|
|
/* The following is basically a copy of CIFSeeLayer() */
|
|
|
|
|
|
|
|
|
|
CIFErrorDef = cellDef;
|
|
|
|
|
CIFInitCells();
|
|
|
|
|
UndoDisable();
|
|
|
|
|
CIFDummyUse->cu_def = cellDef;
|
|
|
|
|
scx.scx_use = CIFDummyUse;
|
|
|
|
|
scx.scx_trans = GeoIdentityTransform;
|
|
|
|
|
(void) DBTreeSrTiles(&scx, &DBAllButSpaceAndDRCBits, 0,
|
|
|
|
|
cifHierCopyFunc, (ClientData) CIFComponentDef);
|
2020-11-12 16:34:27 +01:00
|
|
|
CIFGen(CIFComponentDef, cellDef, &clipRect, CIFPlanes, &DBAllTypeBits, TRUE,
|
|
|
|
|
TRUE, FALSE, (ClientData)NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
DBCellClearDef(CIFComponentDef);
|
|
|
|
|
|
|
|
|
|
w3dClear();
|
|
|
|
|
|
|
|
|
|
/* follow the same locking procedure as DBWredisplay() */
|
|
|
|
|
w3dUnlock(w);
|
|
|
|
|
w3dIsLocked = FALSE;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < CIFCurStyle->cs_nLayers; i++)
|
|
|
|
|
{
|
|
|
|
|
if (TTMaskHasType(&crec->visible, i))
|
|
|
|
|
{
|
|
|
|
|
w3dNeedStyle = TRUE;
|
|
|
|
|
DBSrPaintArea((Tile *) NULL, CIFPlanes[i], &TiPlaneRect,
|
|
|
|
|
&CIFSolidBits, w3dCIFPaintFunc,
|
|
|
|
|
(ClientData)(CIFCurStyle->cs_layers[i]));
|
|
|
|
|
|
|
|
|
|
if (w3dIsLocked)
|
|
|
|
|
{
|
|
|
|
|
w3dUnlock(w);
|
|
|
|
|
w3dIsLocked = FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
UndoEnable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3Dcommand --
|
|
|
|
|
*
|
|
|
|
|
* This procedure is invoked by the window package whenever a
|
|
|
|
|
* command is typed while the cursor is over a 3D window.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* TBD
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
W3Dcommand(w, cmd)
|
|
|
|
|
MagWindow *w;
|
|
|
|
|
TxCommand *cmd;
|
|
|
|
|
{
|
|
|
|
|
int cmdNum;
|
|
|
|
|
|
|
|
|
|
switch (cmd->tx_button)
|
|
|
|
|
{
|
|
|
|
|
case TX_NO_BUTTON:
|
|
|
|
|
WindExecute(w, W3DclientID, cmd);
|
|
|
|
|
break;
|
|
|
|
|
case TX_LEFT_BUTTON:
|
|
|
|
|
case TX_RIGHT_BUTTON:
|
|
|
|
|
case TX_MIDDLE_BUTTON:
|
|
|
|
|
/* No action---we shouldn't be here anyway. */
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ASSERT(FALSE, "W3Dcommand");
|
|
|
|
|
}
|
|
|
|
|
UndoNext();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* W3Dinit --
|
|
|
|
|
*
|
|
|
|
|
* Add the 3D rendering window client to the window module.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* Add ourselves as a client to the window package.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
W3Dinit()
|
|
|
|
|
{
|
|
|
|
|
W3DclientID = WindAddClient("wind3d", W3Dcreate, W3Ddelete,
|
|
|
|
|
W3DCIFredisplay, W3Dcommand,
|
|
|
|
|
(void(*)())NULL, (bool(*)())NULL,
|
|
|
|
|
(void(*)())NULL, (GrGlyph *)NULL);
|
|
|
|
|
|
|
|
|
|
/* Register commands with the client */
|
|
|
|
|
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"view [x y z] specify viewpoint angle",
|
|
|
|
|
w3dView, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"scroll [x y z] specify viewpoint position",
|
|
|
|
|
w3dScroll, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"zoom [xy z] specify render volume scale",
|
|
|
|
|
w3dZoom, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"refresh refresh 3D display",
|
|
|
|
|
w3dRefresh, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"cif switch to/from CIF layers display",
|
|
|
|
|
w3dToggleCIF, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"level [<n>|up|down] set rendering level",
|
|
|
|
|
w3dLevel, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"defaults revert to defaults",
|
|
|
|
|
w3dDefaults, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
2020-05-23 23:13:14 +02:00
|
|
|
"closewindow close the 3D display",
|
2017-04-25 14:41:48 +02:00
|
|
|
w3dClose, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"render name [height thick [style]]\n"
|
|
|
|
|
" properties of CIF layer rendering",
|
|
|
|
|
w3dRenderValues, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"see [no] layer view or hide layers from the 3D view",
|
|
|
|
|
w3dSeeLayers, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"cutbox [none|box|llx lly urx ury]\n"
|
|
|
|
|
" set clipping rectangle for 3D view",
|
|
|
|
|
w3dCutBox, FALSE);
|
|
|
|
|
WindAddCommand(W3DclientID,
|
|
|
|
|
"help print this command list",
|
|
|
|
|
w3dHelp, FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* THREE_D */
|