home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
misc
/
volume21
/
sipp
/
part04
< prev
next >
Wrap
Text File
|
1991-07-23
|
51KB
|
1,681 lines
Newsgroups: comp.sources.misc
From: Jonas Yngvesson <jonas-y@isy.liu.se>
Subject: v21i029: sipp - A 3D rendering library v2.1, Part04/08
Message-ID: <1991Jul23.181700.27805@sparky.IMD.Sterling.COM>
X-Md4-Signature: b09f7aedec58793386724d8ce2f19af1
Date: Tue, 23 Jul 1991 18:17:00 GMT
Approved: kent@sparky.imd.sterling.com
Submitted-by: Jonas Yngvesson <jonas-y@isy.liu.se>
Posting-number: Volume 21, Issue 29
Archive-name: sipp/part04
Supersedes: sipp2.0: Volume 16, Issue 5-10
Environment: UNIX
#!/bin/sh
# This is part 04 of sipp-2.1
# ============= libsipp/rendering.c ==============
if test ! -d 'libsipp'; then
echo 'x - creating directory libsipp'
mkdir 'libsipp'
fi
if test -f 'libsipp/rendering.c' -a X"$1" != X"-c"; then
echo 'x - skipping libsipp/rendering.c (File already exists)'
else
echo 'x - extracting libsipp/rendering.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'libsipp/rendering.c' &&
/**
X ** sipp - SImple Polygon Processor
X **
X ** A general 3d graphic package
X **
X ** Copyright Jonas Yngvesson (jonas-y@isy.liu.se) 1988/89/90/91
X ** Inge Wallin (ingwa@isy.liu.se) 1990/91
X **
X ** This program is free software; you can redistribute it and/or modify
X ** it under the terms of the GNU General Public License as published by
X ** the Free Software Foundation; either version 1, or any later version.
X ** This program is distributed in the hope that it will be useful,
X ** but WITHOUT ANY WARRANTY; without even the implied warranty of
X ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
X ** GNU General Public License for more details.
X ** You can receive a copy of the GNU General Public License from the
X ** Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
X **/
X
/**
X ** rendering.c - Functions that handles rendering of the scene.
X **/
X
#include <stdio.h>
#ifndef NOMEMCPY
#include <memory.h>
#endif
X
#include <xalloca.h>
#include <smalloc.h>
X
#include <lightsource.h>
#include <geometric.h>
#include <rendering.h>
#include <objects.h>
#include <sipp.h>
#include <sipp_bitmap.h>
#include <viewpoint.h>
#include <patchlevel.h>
X
char *SIPP_VERSION = "2.1";
X
/*
X * Static global variables.
X */
static bool show_backfaces; /* Don't do backface culling */
static Edge **y_bucket; /* Y-bucket for edge lists. */
static FILE *image_file; /* File to store image in */
X /* when rendering into a file. */
static void *image_pm; /* Pixmap to store image in when */
X /* rendering into a pix/bitmap. */
static void (*pixmap_set)(); /* Pointer to function for setting */
X /* a pixel or draw a line in image_pm */
X
X
/*
X * Stack of transformation matrices used
X * when traversing an object hierarchy.
X */
static struct tm_stack_t {
X Transf_mat mat;
X struct tm_stack_t *next;
} *tm_stack;
X
static Transf_mat curr_mat; /* Current transformation matrix */
X
X
X
/*
X * Calculate the normal vector for all polygons in the polygon list PSTART.
X *
X * Check if the polygon is backfacing with respect to the current
X * viewpoint.
X *
X * The normalized normal is added to a normal kept at each vertex
X * in the polygon. This will produce, at each vertex, an average of the
X * normals of the adjectent plygons.
X */
static void
calc_normals(pstart, eyepoint)
X Polygon *pstart; /* Head of polygon list */
X Vector eyepoint; /* Viewpoint transformed to local coordinate system */
{
X Vector normal;
X Vertex_ref *vref1, *vref2;
X Polygon *polyref;
X double plane_const;
X
X for (polyref = pstart; polyref != NULL; polyref = polyref->next) {
X vref1 = polyref->vertices;
X vref2 = vref1->next;
X
X normal.x = normal.y = normal.z = 0.0;
X do {
X normal.x += ((vref1->vertex->y - vref2->vertex->y)
X * (vref1->vertex->z + vref2->vertex->z));
X normal.y += ((vref1->vertex->z - vref2->vertex->z)
X * (vref1->vertex->x + vref2->vertex->x));
X normal.z += ((vref1->vertex->x - vref2->vertex->x)
X * (vref1->vertex->y + vref2->vertex->y));
X vref1 = vref1->next;
X vref2 = ((vref2->next == NULL)?polyref->vertices:vref2->next);
X } while (vref1 != NULL);
X vecnorm(&normal);
X
X /*
X * Take care of backfacing polygons.
X */
X plane_const = -(normal.x * vref2->vertex->x
X + normal.y * vref2->vertex->y
X + normal.z * vref2->vertex->z);
X if (VecDot(eyepoint, normal) + plane_const <= 0.0) {
X if (show_backfaces) {
X polyref->backface = FALSE;
X VecNegate(normal);
X } else {
X polyref->backface = TRUE;
X }
X } else {
X polyref->backface = FALSE;
X }
X
X /*
X * Add the calculated normal to all vertices
X * in the poygon. This will result in an avaraged normal
X * at each vertex after all polygons have been pprocessed.
X */
X for (vref1 = polyref->vertices; vref1 != NULL; vref1 = vref1->next) {
X vref1->vertex->a += normal.x;
X vref1->vertex->b += normal.y;
X vref1->vertex->c += normal.z;
X }
X }
}
X
X
X
/*
X * Walk around a polygon, create the surrounding
X * edges and sort them into the y-bucket.
X */
static void
create_edges(view_vert, polygon, surface, render_mode)
X View_coord *view_vert;
X int polygon;
X Surface *surface;
X int render_mode;
{
X Edge *edge;
X View_coord *view_ref, *last;
X int nderiv, y1, y2;
X double deltay;
X double x1, x2, xstep;
X double z1, z2, zstep;
X double nx1, nx2, nxstep;
X double ny1, ny2, nystep;
X double nz1, nz2, nzstep;
X double u1, u2, ustep;
X double v1, v2, vstep;
X double w1, w2, wstep;
X
X
X view_ref = last = view_vert;
X
X do {
X view_ref = view_ref->next;
X
X /*
X * If we are drawing a line image we dont need
X * to build a complete edgelist. We draw the
X * lines directly instead.
X *
X * Since many lines are drawn twice (edges shared between
X * two polygons) and many line drawing algorithms are unsymmetrical
X * we need to make sure lines are always drawn in the same
X * direction
X */
X if (render_mode == LINE) {
X if (view_ref->y < view_ref->next->y) {
X (*pixmap_set)(image_pm,
X (int)(view_ref->x + 0.5),
X (int)(view_ref->y + 0.5),
X (int)(view_ref->next->x + 0.5),
X (int)(view_ref->next->y + 0.5));
X } else {
X (*pixmap_set)(image_pm,
X (int)(view_ref->next->x + 0.5),
X (int)(view_ref->next->y + 0.5),
X (int)(view_ref->x + 0.5),
X (int)(view_ref->y + 0.5));
X }
X continue;
X }
X
X /*
X * Check if the slope of the edge is positive or negative
X * or zero.
X */
X y1 = (int)(view_ref->y + 0.5);
X y2 = (int)(view_ref->next->y + 0.5);
X deltay = (double)(y2 - y1);
X
X if (deltay > 0.0)
X nderiv = 1;
X else if (deltay < 0.0)
X nderiv = -1;
X else
X nderiv = 0;
X
X /*
X * Check if the edge is horizontal. In that case we
X * just skip it.
X */
X if (nderiv != 0) {
X
X edge = (Edge *)smalloc(sizeof(Edge));
X
X x1 = view_ref->x;
X x2 = view_ref->next->x;
X z1 = view_ref->z;
X z2 = view_ref->next->z;
X nx1 = view_ref->nx;
X nx2 = view_ref->next->nx;
X ny1 = view_ref->ny;
X ny2 = view_ref->next->ny;
X nz1 = view_ref->nz;
X nz2 = view_ref->next->nz;
X u1 = view_ref->u;
X u2 = view_ref->next->u;
X v1 = view_ref->v;
X v2 = view_ref->next->v;
X w1 = view_ref->w;
X w2 = view_ref->next->w;
X
X deltay = fabs(deltay);
X xstep = (x2 - x1) / deltay;
X zstep = (z2 - z1) / deltay;
X if (render_mode != FLAT) {
X nxstep = (nx2 - nx1) / deltay;
X nystep = (ny2 - ny1) / deltay;
X nzstep = (nz2 - nz1) / deltay;
X if (render_mode == PHONG) {
X ustep = (u2 - u1) / deltay;
X vstep = (v2 - v1) / deltay;
X wstep = (w2 - w1) / deltay;
X }
X }
X
X if (nderiv > 0) {
X
X /*
X * The edge has positive slope
X */
X edge->y = y2;
X edge->y_stop = y1;
X edge->x = x2;
X edge->z = z2;
X edge->nx = nx2;
X edge->ny = ny2;
X edge->nz = nz2;
X edge->u = u2;
X edge->v = v2;
X edge->w = w2;
X edge->xstep = -xstep;
X edge->zstep = -zstep;
X if (render_mode != FLAT) {
X edge->nxstep = -nxstep;
X edge->nystep = -nystep;
X edge->nzstep = -nzstep;
X if (render_mode == PHONG) {
X edge->ustep = -ustep;
X edge->vstep = -vstep;
X edge->wstep = -wstep;
X }
X }
X
X } else {
X
X /*
X * The edge has negative slope.
X */
X edge->y = y1;
X edge->y_stop = y2;
X edge->x = x1;
X edge->z = z1;
X edge->nx = nx1;
X edge->ny = ny1;
X edge->nz = nz1;
X edge->u = u1;
X edge->v = v1;
X edge->w = w1;
X edge->xstep = xstep;
X edge->zstep = zstep;
X if (render_mode != FLAT) {
X edge->nxstep = nxstep;
X edge->nystep = nystep;
X edge->nzstep = nzstep;
X if (render_mode == PHONG) {
X edge->ustep = ustep;
X edge->vstep = vstep;
X edge->wstep = wstep;
X }
X }
X }
X edge->polygon = polygon;
X edge->surface = surface;
X edge->next = y_bucket[edge->y];
X y_bucket[edge->y] = edge;
X }
X } while (view_ref != last);
}
X
X
X
/*
X * Calculate a new vertex by interpolation between
X * V1 and V2.
X */
static View_coord *
interpolate(v1, v2, ratio)
X View_coord *v1, *v2;
X double ratio;
{
X View_coord *tmp;
X
X tmp = (View_coord *)smalloc(sizeof(View_coord));
X
X tmp->x = v1->x + ratio * (v2->x - v1->x);
X tmp->y = v1->y + ratio * (v2->y - v1->y);
X tmp->z = v1->z + ratio * (v2->z - v1->z);
X tmp->nx = v1->nx + ratio * (v2->nx - v1->nx);
X tmp->ny = v1->ny + ratio * (v2->ny - v1->ny);
X tmp->nz = v1->nz + ratio * (v2->nz - v1->nz);
X tmp->u = v1->u + ratio * (v2->u - v1->u);
X tmp->v = v1->v + ratio * (v2->v - v1->v);
X tmp->w = v1->w + ratio * (v2->w - v1->w);
X tmp->next = NULL;
X
X return tmp;
}
X
X
X
/*
X * Clip a polygon using the Sutherland-Hodgeman algorithm for
X * reentrant clipping;
X */
#define XMIN 0
#define XMAX 1
#define YMIN 2
#define YMAX 3
#define ZMIN 4
#define ZMAX 5
X
static View_coord *
polygon_clip(vlist, plane, first_vert)
X View_coord *vlist;
X int plane;
X bool first_vert;
{
X static View_coord *first;
X static View_coord *curr;
X View_coord *out1;
X View_coord *out2;
X double curr_limit;
X double first_limit;
X double vlist_limit;
X double ratio;
X bool visible;
X
X out1 = out2 = NULL;
X
X if (vlist == NULL) {
X
X /*
X * Did we get an empty list from the start?
X */
X if (first_vert) {
X return NULL;
X }
X
X /*
X * Last vertex, close the polygon.
X */
X ratio = 0.0;
X curr_limit = curr->z * camera.focal_ratio;
X first_limit = first->z * camera.focal_ratio;
X
X switch (plane) {
X
X case XMIN:
X if ((curr->x < -curr_limit && first->x >= -first_limit)
X || (curr->x >= -curr_limit && first->x < -first_limit)) {
X ratio = fabs(curr->x + curr_limit);
X ratio /= (ratio + fabs(first->x + first_limit));
X }
X break;
X
X case XMAX:
X if ((curr->x <= curr_limit && first->x > first_limit)
X || (curr->x > curr_limit && first->x <= first_limit)) {
X ratio = fabs(curr->x - curr_limit);
X ratio /= (ratio + fabs(first->x - first_limit));
X }
X break;
X
X case YMIN:
X if ((curr->y < -curr_limit && first->y >= -first_limit)
X || (curr->y >= -curr_limit && first->y < -first_limit)) {
X ratio = fabs(curr->y + curr_limit);
X ratio /= (ratio + fabs(first->y + first_limit));
X }
X break;
X
X case YMAX:
X if ((curr->y <= curr_limit && first->y > first_limit)
X || (curr->y > curr_limit && first->y <= first_limit)) {
X ratio = fabs(curr->y - curr_limit);
X ratio /= (ratio + fabs(first->y - first_limit));
X }
X break;
X
X case ZMIN:
X if ((curr->z < hither && first->z >= hither)
X || (curr->z >= hither && first->z < hither)) {
X ratio = fabs(curr->z - hither);
X ratio = ratio / (ratio + fabs(first->z - hither));
X }
X break;
X
X case ZMAX:
X if ((curr->z <= yon && first->z > yon)
X || (curr->z > yon && first->z <= yon)) {
X ratio = fabs(curr->z - yon);
X ratio = ratio / (ratio + fabs(first->z - yon));
X }
X break;
X }
X
X if (ratio != 0.0) {
X out1 = interpolate(curr, first, ratio);
X return out1;
X } else {
X return NULL;
X }
X }
X
X vlist_limit = vlist->z * camera.focal_ratio;
X
X if (first_vert) {
X first = vlist;
X } else {
X ratio = 0.0;
X curr_limit = curr->z * camera.focal_ratio;
X
X switch (plane) {
X
X case XMIN:
X if ((curr->x < -curr_limit && vlist->x >= -vlist_limit)
X || (curr->x >= -curr_limit && vlist->x < -vlist_limit)) {
X ratio = fabs(curr->x + curr_limit);
X ratio /= (ratio + fabs(vlist->x + vlist_limit));
X }
X break;
X
X case XMAX:
X if ((curr->x <= curr_limit && vlist->x > vlist_limit)
X || (curr->x > curr_limit && vlist->x <= vlist_limit)) {
X ratio = fabs(curr->x - curr_limit);
X ratio /= (ratio + fabs(vlist->x - vlist_limit));
X }
X break;
X
X case YMIN:
X if ((curr->y < -curr_limit && vlist->y >= -vlist_limit)
X || (curr->y >= -curr_limit && vlist->y < -vlist_limit)) {
X ratio = fabs(curr->y + curr_limit);
X ratio /= (ratio + fabs(vlist->y + vlist_limit));
X }
X break;
X
X case YMAX:
X if ((curr->y <= curr_limit && vlist->y > vlist_limit)
X || (curr->y > curr_limit && vlist->y <= vlist_limit)) {
X ratio = fabs(curr->y - curr_limit);
X ratio /= (ratio + fabs(vlist->y - vlist_limit));
X }
X break;
X
X case ZMIN:
X if ((curr->z < hither && vlist->z >= hither)
X || (curr->z >= hither && vlist->z < hither)) {
X ratio = fabs(curr->z - hither);
X ratio = ratio / (ratio + fabs(vlist->z - hither));
X }
X break;
X
X case ZMAX:
X if ((curr->z <= yon && vlist->z > yon)
X || (curr->z > yon && vlist->z <= yon)) {
X ratio = fabs(curr->z - yon);
X ratio = ratio / (ratio + fabs(vlist->z - yon));
X }
X break;
X }
X
X if (ratio != 0.0) {
X out1 = interpolate(curr, vlist, ratio);
X out1->next = vlist;
X }
X }
X
X curr = vlist;
X visible = FALSE;
X switch (plane) {
X
X case XMIN:
X visible = (curr->x >= -vlist_limit);
X break;
X
X case XMAX:
X visible = (curr->x <= vlist_limit);
X break;
X
X case YMIN:
X visible = (curr->y >= -vlist_limit);
X break;
X
X case YMAX:
X visible = (curr->y <= vlist_limit);
X break;
X
X case ZMIN:
X visible = (curr->z >= hither);
X break;
X
X case ZMAX:
X visible = (curr->z <= yon);
X break;
X }
X
X if (visible) {
X out2 = curr;
X out2->next = polygon_clip(curr->next, plane, FALSE);
X return ((out1) ? (out1) : (out2));
X
X } else {
X if (out1) {
X out1->next = polygon_clip(curr->next, plane, FALSE);
X } else {
X out1 = polygon_clip(curr->next, plane, FALSE);
X }
X free(vlist);
X return out1;
X }
}
X
X
X
/*
X * Transform vertices into view coordinates. The transform is
X * defined in MATRIX. Store the transformed vertices in a
X * temporary list, create edges in the y_bucket.
X */
static void
transf_vertices(vertex_list, surface, view_mat, tr_mat,
X xsiz, ysiz, render_mode)
X Vertex_ref *vertex_list;
X Surface *surface;
X Transf_mat *view_mat;
X Transf_mat *tr_mat;
X double xsiz, ysiz;
X int render_mode;
{
X static int polygon = 0; /* incremented for each call to provide */
X /* unique polygon id numbers */
X Vertex_ref *vref;
X View_coord *nhead;
X View_coord *view_ref;
X View_coord *mark;
X Color color;
X double minsize;
X double tmp;
X
X vref = vertex_list;
X nhead = NULL;
X
X minsize = ((xsiz > ysiz) ? ysiz : xsiz);
X
X while (vref != NULL) {
X
X view_ref = (View_coord *)smalloc(sizeof(View_coord));
X
X
X /*
X * Transform the normal (world coordinates).
X */
X view_ref->nx = (vref->vertex->a * tr_mat->mat[0][0]
X + vref->vertex->b * tr_mat->mat[1][0]
X + vref->vertex->c * tr_mat->mat[2][0]);
X view_ref->ny = (vref->vertex->a * tr_mat->mat[0][1]
X + vref->vertex->b * tr_mat->mat[1][1]
X + vref->vertex->c * tr_mat->mat[2][1]);
X view_ref->nz = (vref->vertex->a * tr_mat->mat[0][2]
X + vref->vertex->b * tr_mat->mat[1][2]
X + vref->vertex->c * tr_mat->mat[2][2]);
X
X /*
X * Transform the vertex into view coordinates.
X */
X view_ref->x = (vref->vertex->x * view_mat->mat[0][0]
X + vref->vertex->y * view_mat->mat[1][0]
X + vref->vertex->z * view_mat->mat[2][0]
X + view_mat->mat[3][0]);
X view_ref->y = (vref->vertex->x * view_mat->mat[0][1]
X + vref->vertex->y * view_mat->mat[1][1]
X + vref->vertex->z * view_mat->mat[2][1]
X + view_mat->mat[3][1]);
X view_ref->z = (vref->vertex->x * view_mat->mat[0][2]
X + vref->vertex->y * view_mat->mat[1][2]
X + vref->vertex->z * view_mat->mat[2][2]
X + view_mat->mat[3][2]);
X
X /*
X * Texture coordinates is not affected by transformations.
X */
X view_ref->u = vref->vertex->u;
X view_ref->v = vref->vertex->v;
X view_ref->w = vref->vertex->w;
X
X view_ref->next = nhead;
X nhead = view_ref;
X
X vref = vref->next;
X }
X
X /*
X * Clip the resulting polygon. We need to do this
X * before the perpective transformation to keep texture
X * coordinates correct.
X */
X nhead = polygon_clip(nhead, ZMIN, TRUE);
X nhead = polygon_clip(nhead, ZMAX, TRUE);
X if (xsiz > ysiz) {
X tmp = camera.focal_ratio;
X camera.focal_ratio *= xsiz / ysiz;
X nhead = polygon_clip(nhead, XMIN, TRUE);
X nhead = polygon_clip(nhead, XMAX, TRUE);
X camera.focal_ratio = tmp;
X nhead = polygon_clip(nhead, YMIN, TRUE);
X nhead = polygon_clip(nhead, YMAX, TRUE);
X } else {
X tmp = camera.focal_ratio;
X camera.focal_ratio *= ysiz / xsiz;
X nhead = polygon_clip(nhead, YMIN, TRUE);
X nhead = polygon_clip(nhead, YMAX, TRUE);
X camera.focal_ratio = tmp;
X nhead = polygon_clip(nhead, XMIN, TRUE);
X nhead = polygon_clip(nhead, XMAX, TRUE);
X }
X
X if (nhead == NULL) { /* Nothing left? */
X return;
X }
X
X
X
X /*
X * If we are flat shading, we need a color for the polygon.
X * We call the shader at the first vertex to get this.
X * (This is not quite correct since the normal here is
X * an averaged normal of the surrounding polygons)
X */
X if (render_mode == FLAT) {
X (*surface->shader)
X (nhead->nx, nhead->ny, nhead->nz,
X nhead->u, nhead->v, nhead->w,
X camera.vec, lightsrc_stack,
X surface->surface, &color);
X }
X
X
X /*
X * Walk around the new (clipped and transformed) polygon and
X * transform it into perspective screen coordinates.
X * We have to transform the texture coordinates too in order
X * to interpolate them linearly in "screen space". This
X * transformation is inverted in render_line().
X * If we are doing gouraud shading we call the shader at each
X * vertex.
X * Last we tie the head and tail together forming a cirkular
X * list, this simplifies edge creation.
X */
X for (view_ref = nhead;; view_ref = view_ref->next) {
X view_ref->z *= camera.focal_ratio;
X view_ref->x = view_ref->x * minsize / view_ref->z + xsiz;
X view_ref->y = view_ref->y * minsize / view_ref->z + ysiz;
X view_ref->u /= view_ref->z;
X view_ref->v /= view_ref->z;
X view_ref->w /= view_ref->z;
X
X if (render_mode == GOURAUD) {
X (*surface->shader)
X (view_ref->nx, view_ref->ny, view_ref->nz,
X view_ref->u, view_ref->v, view_ref->w,
X camera.vec, lightsrc_stack,
X surface->surface, &color);
X }
X
X if (render_mode == GOURAUD || render_mode == FLAT) {
X view_ref->nx = color.red;
X view_ref->ny = color.grn;
X view_ref->nz = color.blu;
X }
X
X if (view_ref->next == NULL) {
X view_ref->next = nhead;
X break;
X }
X }
X
X create_edges(nhead, polygon++, surface, render_mode);
X
X /*
X * Free the memory used by the transformed polygon.
X */
X mark = nhead;
X do {
X view_ref = nhead;
X nhead = nhead->next;
X free(view_ref);
X } while (nhead != mark);
}
X
X
X
/*
X * Initialize the scanline z-buffer and the actual picture
X * scanline buffer.
X */
static void
init_buffers(res, z_buffer, scanline)
X int res;
X double *z_buffer;
X unsigned char *scanline;
{
X int i;
X
#ifdef NOMEMCPY
X bzero(scanline, res * 3);
#else
X memset(scanline, 0, res * 3);
#endif
X for (i = 0; i < res; i++)
X z_buffer[i] = 1.0e100;
}
X
X
/*
X * Read edge pairs from the edge list EDGE_LIST. Walk along the scanline
X * and interpolate z value, texture coordinates and normal vector as
X * we go. Call the shader and write into scanline buffer according to
X * result on z-buffer test.
X */
static void
render_line(res, z_buffer, scanline, edge_list, render_mode)
X int res;
X double *z_buffer;
X unsigned char *scanline;
X Edge *edge_list;
X int render_mode;
{
X double z, zstep;
X double nx, nxstep;
X double ny, nystep;
X double nz, nzstep;
X double u, ustep;
X double v, vstep;
X double w, wstep;
X double ur, vr, wr;
X double ratio;
X Color color;
X int i, j, x, xstop;
X Edge *startedge, *stopedge;
X
X startedge = edge_list;
X stopedge = NULL;
X while (startedge != NULL) {
X stopedge = startedge->next;
X x = (int)(startedge->x + 0.5);
X xstop = (int)(stopedge->x + 0.5);
X z = startedge->z;
X nx = startedge->nx;
X ny = startedge->ny;
X nz = startedge->nz;
X u = startedge->u;
X v = startedge->v;
X w = startedge->w;
X if (x < xstop) {
X ratio = (double)(xstop - x);
X zstep = (stopedge->z - z) / ratio;
X if (render_mode != FLAT) {
X nxstep = (stopedge->nx - nx) / ratio;
X nystep = (stopedge->ny - ny) / ratio;
X nzstep = (stopedge->nz - nz) / ratio;
X if (render_mode == PHONG) {
X ustep = (stopedge->u - u) / ratio;
X vstep = (stopedge->v - v) / ratio;
X wstep = (stopedge->w - w) / ratio;
X }
X }
X } else {
X zstep = 0.0;
X nxstep = nystep = nzstep = 0.0;
X ustep = vstep = wstep = 0.0;
X }
X
X for (i = x, j = i * 3; i <= xstop; i++) {
X
X if (z < z_buffer[i]) {
X
X if (render_mode == PHONG) {
X ur = u * z; /* Transform texture coordinates back */
X vr = v * z; /* from their homogenouos form */
X wr = w * z;
X (*startedge->surface->shader)
X (nx, ny, nz, ur, vr, wr,
X camera.vec, lightsrc_stack,
X startedge->surface->surface, &color);
X scanline[j++] = (unsigned char)(color.red * 255.0 + 0.5);
X scanline[j++] = (unsigned char)(color.grn * 255.0 + 0.5);
X scanline[j++] = (unsigned char)(color.blu * 255.0 + 0.5);
X
X } else if (render_mode == FLAT || render_mode == GOURAUD) {
X scanline[j++] = (unsigned char)(nx * 255.0 + 0.5);
X scanline[j++] = (unsigned char)(ny * 255.0 + 0.5);
X scanline[j++] = (unsigned char)(nz * 255.0 + 0.5);
X }
X
X z_buffer[i] = z;
X
X } else if (i >= res) {
X break;
X
X } else {
X j += 3;
X }
X
X z += zstep;
X if (render_mode != FLAT) {
X nx += nxstep;
X ny += nystep;
X nz += nzstep;
X if (render_mode == PHONG) {
X u += ustep;
X v += vstep;
X w += wstep;
X }
X }
X }
X startedge = stopedge->next;
X }
}
X
X
X
X
/*
X * Insert an edge into an edge list. Edges belonging to the same
X * polygon must be inserted sorted in x, so that edge pairs are
X * created.
X */
static Edge *
insert_edge(edge_list, edge, poly_found)
X Edge *edge_list, *edge;
X bool poly_found;
{
X if (edge_list == NULL) {
X edge_list = edge;
X edge->next = NULL;
X } else if (edge_list->polygon == edge->polygon) {
X if (edge_list->x > edge->x) {
X edge->next = edge_list;
X edge_list = edge;
X } else if ((((int)(edge_list->x + 0.5)) == ((int)(edge->x + 0.5)))
X && (edge_list->xstep > edge->xstep)) {
X edge->next = edge_list;
X edge_list = edge;
X } else {
X edge_list->next = insert_edge(edge_list->next, edge, TRUE);
X }
X } else if (poly_found) {
X edge->next = edge_list;
X edge_list = edge;
X } else {
X edge_list->next = insert_edge(edge_list->next, edge, FALSE);
X }
X
X return edge_list;
}
X
X
X
/*
X * Merge two edge lists.
X */
static Edge *
merge_edge_lists(list1, list2)
X Edge *list1, *list2;
{
X Edge *eref1, *eref2, *next;
X
X if (list2 == NULL)
X return list1;
X
X eref1 = list1;
X eref2 = list2;
X do {
X next = eref2->next;
X eref1 = insert_edge(eref1, eref2, FALSE);
X eref2 = next;
X } while (eref2 != NULL);
X
X return eref1;
}
X
X
X
/*
X * Store a rendered line on the place indicated by STORAGE_MODE.
X */
static void
store_line(buf, npixels, line, storage_mode)
X unsigned char *buf;
X int npixels;
X int line;
X int storage_mode;
{
X int i, j;
X
X switch (storage_mode) {
X case PBM_FILE:
X fwrite(buf, sizeof(unsigned char), npixels, image_file);
X fflush(image_file);
X break;
X
X case PPM_FILE:
X fwrite(buf, sizeof(unsigned char), npixels * 3, image_file);
X fflush(image_file);
X break;
X
X case PIXMAP:
X for (i = 0, j = 0; j < npixels; j++, i += 3) {
X (*pixmap_set)(image_pm, j, line, buf[i], buf[i + 1], buf[i + 2]);
X }
X break;
X
X default:
X break;
X }
}
X
X
/*
X * Allocate the needed buffers. Create a list of active edges and
X * move down the y-bucket, inserting and deleting edges from this
X * active list as we go. Call render_line for each scanline and
X * do an average filtering before storing the scanline.
X */
static void
scan_and_render(xres, yres, storage_mode, render_mode, oversampl)
X int xres, yres;
X int storage_mode;
X int render_mode;
X int oversampl;
{
X Edge *active_list, *edgep, *edgep2;
X double *z_buffer;
X unsigned char **linebuf;
X int curr_line;
X int y, next_edge;
X int sum;
X int i, j, k, l;
X
X z_buffer = (double *)scalloc(xres, sizeof(double));
X linebuf = (unsigned char **)alloca(oversampl * sizeof(unsigned char *));
X for (i = 0; i < oversampl; i++) {
X linebuf[i] = (unsigned char *)scalloc(xres * 3, sizeof(unsigned char));
X }
X
X if (storage_mode == PPM_FILE) {
X fprintf(image_file, "P6\n");
X fprintf(image_file, "#Image rendered with SIPP %s%s\n",
X SIPP_VERSION, PATCHLEVEL);
X fprintf(image_file, "%d\n%d\n255\n", xres / oversampl,
X yres / oversampl);
X }
X
X y = yres - 1;
X active_list = NULL;
X curr_line = 0;
X
X while (y >= 0) {
X
X active_list = merge_edge_lists(active_list, y_bucket[y]);
X next_edge = y - 1;
X
X while (next_edge >=0 && y_bucket[next_edge] == NULL)
X next_edge--;
X
X while (y > next_edge) {
X
X init_buffers(xres, z_buffer, linebuf[curr_line]);
X render_line(xres, z_buffer, linebuf[curr_line], active_list,
X render_mode);
X
X if (++curr_line == oversampl) {
X
X /*
X * Average the pixel.
X */
X for (i = 0; i < ((xres / oversampl)); i++) {
X for (l = 0; l < 3; l++) {
X sum = 0;
X for (j = i * 3 * oversampl + l;
X j < (i * 3 * oversampl + l + 3 * oversampl);
X j += 3) {
X for (k = 0; k < oversampl; k++) {
X sum += *(linebuf[k] + j);
X }
X }
X *(linebuf[0] + i * 3 + l)
X = sum / (oversampl * oversampl);
X }
X }
X store_line(linebuf[0], xres / oversampl,
X (yres - y - 1) / oversampl, storage_mode);
X curr_line = 0;
X }
X
X if (active_list != NULL) {
X
X edgep2 = active_list;
X edgep = active_list->next;
X while (edgep != NULL) {
X if (edgep->y <= (edgep->y_stop + 1)) {
X edgep2->next = edgep->next;
X free(edgep);
X edgep = edgep2->next;
X } else {
X edgep2 = edgep;
X edgep = edgep->next;
X }
X }
X
X if (active_list->y <= (active_list->y_stop + 1)) {
X edgep = active_list;
X active_list = active_list->next;
X free(edgep);
X }
X
X edgep = active_list;
X while (edgep != NULL) {
X edgep->y--;
X edgep->x += edgep->xstep;
X edgep->z += edgep->zstep;
X if (render_mode != FLAT) {
X edgep->nx += edgep->nxstep;
X edgep->ny += edgep->nystep;
X edgep->nz += edgep->nzstep;
X if (render_mode == PHONG) {
X edgep->u += edgep->ustep;
X edgep->v += edgep->vstep;
X edgep->w += edgep->wstep;
X }
X }
X edgep = edgep->next;
X }
X }
X y--;
X }
X }
X free(z_buffer);
X for (i = 0; i < oversampl; i++) {
X free(linebuf[i]);
X }
}
X
X
X
/*
X * Reset the averaged normals in the vertex tree P.
X */
static void
reset_normals(vref)
X Vertex *vref;
{
X if (vref != NULL) {
X vref->a = 0;
X vref->b = 0;
X vref->c = 0;
X reset_normals(vref->big);
X reset_normals(vref->sml);
X }
}
X
X
X
/*
X * Push the current transformation matrix on the matrix stack.
X */
static void
matrix_push()
{
X struct tm_stack_t *new_tm;
X
X new_tm = (struct tm_stack_t *)smalloc(sizeof(struct tm_stack_t));
X MatCopy(&new_tm->mat, &curr_mat);
X new_tm->next = tm_stack;
X tm_stack = new_tm;
}
X
X
/*
X * Pop the top of the matrix stack and make
X * it the new current transformation matrix.
X */
static void
matrix_pop()
{
X struct tm_stack_t *tmp;
X
X MatCopy(&curr_mat, &tm_stack->mat);
X tmp = tm_stack;
X tm_stack = tm_stack->next;
X free(tmp);
}
X
X
X
/*
X * Traverse an object hierarchy, transform each object
X * according to its transformation matrix.
X * Transform all polygons in the object to view coordinates.
X * Build the edge lists in y_bucket.
X */
static void
traverse_object_tree(object, view_mat, xres, yres, render_mode)
X Object *object;
X Transf_mat *view_mat;
X int xres, yres;
X int render_mode;
{
X Object *objref;
X Surface *surfref;
X Polygon *polyref;
X Vector eyepoint, tmp;
X Transf_mat loc_view_mat;
X double m[3][4], dtmp;
X int i, j;
X
X
X if (object == NULL) {
X return;
X }
X
X for (objref = object; objref != NULL; objref = objref->next) {
X
X matrix_push();
X mat_mul(&curr_mat, &objref->transf, &curr_mat);
X mat_mul(&loc_view_mat, &curr_mat, view_mat);
X
X tmp.x = camera.x0;
X tmp.y = camera.y0;
X tmp.z = camera.z0;
X
X /*
X * Do an inverse transformation of the viewpoint to use
X * when doing backface culling (in calc_normals()).
X */
X tmp.x -= curr_mat.mat[3][0];
X tmp.y -= curr_mat.mat[3][1];
X tmp.z -= curr_mat.mat[3][2];
X m[0][0] = curr_mat.mat[0][0] ; m[0][1] = curr_mat.mat[1][0];
X m[0][2] = curr_mat.mat[2][0] ; m[0][3] = tmp.x;
X m[1][0] = curr_mat.mat[0][1] ; m[1][1] = curr_mat.mat[1][1];
X m[1][2] = curr_mat.mat[2][1] ; m[1][3] = tmp.y;
X m[2][0] = curr_mat.mat[0][2] ; m[2][1] = curr_mat.mat[1][2];
X m[2][2] = curr_mat.mat[2][2] ; m[2][3] = tmp.z;
X
X if (m[0][0] == 0.0) {
X if (m[1][0] != 0.0)
X j = 1;
X else
X j = 2;
X for (i = 0; i < 4; i++) {
X dtmp = m[0][i];
X m[0][i] = m[j][i];
X m[j][i] = dtmp;
X }
X }
X
X for (j = 1; j < 3; j++) {
X m[j][0] /= (-m[0][0]);
X for (i = 1; i < 4; i++)
X m[j][i] += m[0][i] * m[j][0];
X }
X
X if (m[1][1] == 0.0)
X for (i = 1; i < 4; i++) {
X dtmp = m[1][i];
X m[1][i] = m[2][i];
X m[2][i] = dtmp;
X }
X
X if (m[1][1] != 0.0) {
X m[2][1] /= (-m[1][1]);
X m[2][2] += m[1][2] * m[2][1];
X m[2][3] += m[1][3] * m[2][1];
X }
X
X eyepoint.z = m[2][3] / m[2][2];
X eyepoint.y = (m[1][3] - eyepoint.z * m[1][2]) / m[1][1];
X eyepoint.x = (m[0][3] - eyepoint.z * m[0][2]
X - eyepoint.y * m[0][1]) / m[0][0];
X
X for (surfref = objref->surfaces; surfref != NULL;
X surfref = surfref->next) {
X
X calc_normals(surfref->polygons, eyepoint);
X
X for (polyref = surfref->polygons; polyref != NULL;
X polyref = polyref->next) {
X
X if (!polyref->backface) {
X transf_vertices(polyref->vertices, surfref,
X &loc_view_mat,
X &curr_mat, (double)xres / 2.0,
X (double)yres / 2.0, render_mode);
X }
X
X }
X reset_normals(surfref->vertices);
X
X }
X traverse_object_tree(objref->sub_obj, view_mat, xres, yres,
X render_mode);
X matrix_pop();
X }
}
X
X
X
/*
X * Recursively traverse the rendering database (preorder).
X * Call traverse_object_tree for each object.
X */
static void
traverse_object_db(obj_root, view_mat, xres, yres, render_mode)
X Inst_object *obj_root;
X Transf_mat *view_mat;
X int xres, yres;
X int render_mode;
{
X if (obj_root != NULL) {
X traverse_object_tree(obj_root->object, view_mat,
X xres, yres, render_mode);
X traverse_object_db(obj_root->sml, view_mat,
X xres, yres, render_mode);
X traverse_object_db(obj_root->big, view_mat,
X xres, yres, render_mode);
X }
}
X
X
X
X
/*
X * "Main" functions in rendering. Allocate y-bucket, transform vertices
X * into viewing coordinates, make edges and sort them into the y-bucket.
X * Call scan_and_render to do the real work.
X */
static void
render_main(xres, yres, storage_mode, render_mode, oversampling)
X int xres, yres;
X int storage_mode;
X int render_mode;
X int oversampling;
{
X Transf_mat view_mat;
X
X if (render_mode != LINE) {
X xres *= oversampling;
X yres *= oversampling;
X y_bucket = (Edge **)scalloc(yres, sizeof(Edge *));
X
X } else if (storage_mode == PBM_FILE) {
X image_pm = sipp_bitmap_create(xres, yres);
X pixmap_set = sipp_bitmap_line;
X }
X
X get_view_transf(&view_mat);
X MatCopy(&curr_mat, &ident_matrix);
X
X vecnorm(&camera.vec);
X
X traverse_object_db(object_db, &view_mat,
X xres - 1, yres - 1, render_mode);
X
X if (render_mode != LINE) {
X scan_and_render(xres, yres, storage_mode, render_mode, oversampling);
X free(y_bucket);
X
X } else if (storage_mode == PBM_FILE) {
X sipp_bitmap_write(image_file, image_pm);
X sipp_bitmap_destruct(image_pm);
X }
X
X view_vec_eval();
X
}
X
X
void
render_image_file(xres, yres, im_file, render_mode, oversampling)
X int xres, yres;
X FILE *im_file;
X int render_mode;
X int oversampling;
{
X image_file = im_file;
X
X if (render_mode == LINE) {
X render_main(xres, yres, PBM_FILE, render_mode, oversampling);
X } else {
X render_main(xres, yres, PPM_FILE, render_mode, oversampling);
X }
}
X
X
void
render_image_pixmap(xres, yres, pixmap, pixmap_func, render_mode, oversampling)
X int xres, yres;
X void *pixmap;
X void (*pixmap_func)();
X int render_mode;
X int oversampling;
{
X image_pm = pixmap;
X pixmap_set = pixmap_func;
X render_main(xres, yres, PIXMAP, render_mode, oversampling);
}
X
X
X
/*============= Functions that handles global initializations==============*/
X
/*
X * If called with TRUE as argument, no backface culling will
X * be performed. If a polygon is backfacing it will be rendered
X * as facing in the opposit direction.
X */
void
sipp_show_backfaces(flag)
X bool flag;
{
X show_backfaces = flag;
}
X
X
X
/*
X * Necessary initializations.
X */
void
sipp_init()
{
X objects_init();
X lightsource_init();
X sipp_show_backfaces(FALSE);
X viewpoint(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.25);
}
SHAR_EOF
chmod 0644 libsipp/rendering.c ||
echo 'restore of libsipp/rendering.c failed'
Wc_c="`wc -c < 'libsipp/rendering.c'`"
test 39990 -eq "$Wc_c" ||
echo 'libsipp/rendering.c: original size 39990, current size' "$Wc_c"
fi
# ============= libsipp/rendering.h ==============
if test -f 'libsipp/rendering.h' -a X"$1" != X"-c"; then
echo 'x - skipping libsipp/rendering.h (File already exists)'
else
echo 'x - extracting libsipp/rendering.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'libsipp/rendering.h' &&
/**
X ** sipp - SImple Polygon Processor
X **
X ** A general 3d graphic package
X **
X ** Copyright Jonas Yngvesson (jonas-y@isy.liu.se) 1988/89/90/91
X ** Inge Wallin (ingwa@isy.liu.se) 1990/91
X **
X ** This program is free software; you can redistribute it and/or modify
X ** it under the terms of the GNU General Public License as published by
X ** the Free Software Foundation; either version 1, or any later version.
X ** This program is distributed in the hope that it will be useful,
X ** but WITHOUT ANY WARRANTY; without even the implied warranty of
X ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
X ** GNU General Public License for more details.
X ** You can receive a copy of the GNU General Public License from the
X ** Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
X **/
X
/**
X ** rendering.h - Types and interface to the rendering.c
X **/
X
#ifndef RENDERING_H
#define RENDERING_H
X
#include <sipp.h>
X
X
/*
X * Modes for storing the image.
X */
#define PBM_FILE 0
#define PPM_FILE 1
#define PIXMAP 2
X
X
/*
X * Temporary storage of transformed vertices.
X */
typedef struct view_coord_3d {
X double x, y, z; /* Transformed vertex coordinates */
X double nx, ny, nz; /* average normal */
X double u, v, w; /* texture parameters */
X struct view_coord_3d *next; /* next vertex in the list */
} View_coord;
X
X
/*
X * Entry in the edge list used in rendering.
X */
typedef struct edges_3d {
X int y, y_stop; /* Current point and interpolation steps */
X double x, xstep;
X double z, zstep;
X double nx, nxstep; /* Current normal and interp. steps */
X double ny, nystep;
X double nz, nzstep;
X double u, ustep; /* Current texture coordinates and */
X double v, vstep; /* interp. steps */
X double w, wstep;
X int polygon; /* Uniqe polygon id of the polygon to */
X /* which the edge belongs */
X Surface *surface; /* Surface that the edge belongs to */
X struct edges_3d *next; /* Next edge on this scanline */
} Edge;
X
X
#endif /* RENDERING_H */
SHAR_EOF
chmod 0644 libsipp/rendering.h ||
echo 'restore of libsipp/rendering.h failed'
Wc_c="`wc -c < 'libsipp/rendering.h'`"
test 2300 -eq "$Wc_c" ||
echo 'libsipp/rendering.h: original size 2300, current size' "$Wc_c"
fi
# ============= libsipp/shaders.h ==============
if test -f 'libsipp/shaders.h' -a X"$1" != X"-c"; then
echo 'x - skipping libsipp/shaders.h (File already exists)'
else
echo 'x - extracting libsipp/shaders.h (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'libsipp/shaders.h' &&
/**
X ** sipp - SImple Polygon Processor
X **
X ** A general 3d graphic package
X **
X ** Copyright Jonas Yngvesson (jonas-y@isy.liu.se) 1988/89/90/91
X ** Inge Wallin (ingwa@isy.liu.se) 1990/91
X **
X ** This program is free software; you can redistribute it and/or modify
X ** it under the terms of the GNU General Public License as published by
X ** the Free Software Foundation; either version 1, or any later version.
X ** This program is distributed in the hope that it will be useful,
X ** but WITHOUT ANY WARRANTY; without even the implied warranty of
X ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
X ** GNU General Public License for more details.
X ** You can receive a copy of the GNU General Public License from the
X ** Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
X **/
X
/**
X ** shaders.h - This include file defines the different shaders availiable
X ** in sipp. Each shader is defined by a structure containing
X ** the necessary parameters to describe how a surface should
X ** be shaded with that particular shader, and the extern
X ** declaration of the shader function itself.
X **/
X
X
#ifndef _SHADERS_H
#define _SHADERS_H
X
X
#include <sipp.h>
X
X
X
/*
X * Surface description used in strauss_shader().
X */
typedef struct {
X double ambient; /* Fraction of color visible in ambient light */
X double smoothness; /* Smoothness of the surface [0, 1] */
X double metalness; /* Metalness of the surface [0, 1] */
X Color color; /* Base color of the surface */
} Strauss_desc;
X
X
X
/*
X * Surface description for the bozo shader.
X */
typedef struct {
X Color *colors;
X int no_of_cols;
X double ambient;
X double specular;
X double c3;
X double scale; /* Scale the texture by this value */
} Bozo_desc;
X
X
X
/*
X * Surface description used by the wood shader. This shader
X * creates a solid texture (using noise & turbulence) that
X * simulates wood.
X */
typedef struct {
X double ambient;
X double specular;
X double c3;
X double scale; /* Scale the wood texture by this value */
X Color base; /* "Base" color of the surface */
X Color ring; /* Color of the darker rings */
} Wood_desc;
X
X
X
/*
X * Surface description used by the marble shader. marble_shader
X * creates a solid texture (using noise & turbulence) that
X * simulates marble.
X */
typedef struct {
X double ambient;
X double specular;
X double c3;
X double scale; /* Scale the marble texture by this value */
X Color base; /* "Base" color of the surface */
X Color strip; /* Color of the "stripes" in the marble */
} Marble_desc;
X
X
X
/*
X * Surface description used by the granite shader. granite_shader
X * creates a solid texture (using noise) that mixes two colors
X * to simulate granite.
X */
typedef struct {
X double ambient;
X double specular;
X double c3;
X double scale; /* Scale the texture by this value */
X Color col1; /* The two color components */
X Color col2; /* */
} Granite_desc;
X
X
X
/*
X * Mask shader. It uses mask image (ususally a bitmap) to
X * choose between two other shaders. It projects the mask
X * image on the u-v plane (in texture coordinates). When a
X * surface is shaded it calls pixel_test() to check the
X * u, v coordinate in the mask and calls one of two other
X * shaders depending of the outcome of that test.
X */
typedef struct {
X Shader *fg_shader; /* Shader to call if mask(x, y) != 0 */
X void *fg_surface; /* Surface description for fg_shader */
X Shader *bg_shader; /* Shader to call if mask(x, y) == 0 */
X void *bg_surface; /* Surface description for bg_shader */
X void *mask; /* Pointer to mask image */
X bool (*pixel_test)(); /* Function that tests a pixel value */
X int x0, y0; /* Where to put origo in the mask image */
X int xsize, ysize; /* Size of the mask image */
X double xscale, yscale; /* Scale the mask image with these values */
} Mask_desc;
X
X
/*
X * Surface description for the bumpy_shader(). This shader
X * fiddles with the surface normals of a surface so the surface
X * looks bumpy.
X */
X
typedef struct {
X Shader *shader;
X void *surface;
X double scale;
X bool bumpflag;
X bool holeflag;
} Bumpy_desc;
X
X
/*
X * Declarations of the actual shading functions.
X */
extern void strauss_shader();
extern void wood_shader();
extern void marble_shader();
extern void granite_shader();
extern void bozo_shader();
extern void mask_shader();
extern void bumpy_shader();
extern void planet_shader();
X
#endif /* _SHADERS_H */
SHAR_EOF
chmod 0664 libsipp/shaders.h ||
echo 'restore of libsipp/shaders.h failed'
Wc_c="`wc -c < 'libsipp/shaders.h'`"
test 4808 -eq "$Wc_c" ||
echo 'libsipp/shaders.h: original size 4808, current size' "$Wc_c"
fi
true || echo 'restore of libsipp/sipp.h failed'
echo End of part 4, continue with part 5
exit 0
--
------------------------------------------------------------------------------
J o n a s Y n g v e s s o n
Dept. of Electrical Engineering jonas-y@isy.liu.se
University of Linkoping, Sweden ...!uunet!isy.liu.se!jonas-y
exit 0 # Just in case...