/******************************************************************************* ** File: e3d.cpp ** Author: Derek J. Evans ** Modified: 12 December 1999 ** ** Contents: Basic template 3D engine. All code to-be added to Room101. ** ** Copyright (C) 1999-2000 by Derek J. Evans ALL RIGHTS RESERVED ** ** Permission to use, copy, modify, and distribute this software for ** any purpose and without fee is hereby granted, provided that the above ** copyright notice appear in all copies and that both the copyright notice ** and this permission notice appear in supporting documentation. ** ** THIS CODE AND INFORMATION IS PROVIDED "AS-IS" WITHOUT WARRANTY OF ANY KIND, ** EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED ** WARRANTIES OF MERCHANTBILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. *******************************************************************************/ #include "../main.hpp" #include "../file.hpp" #include "../effects.hpp" #include "../world.hpp" #include "../convex.hpp" #include "../entity.hpp" #include "../point.hpp" #include "../particle.hpp" #include "../vgs.hpp" #include "../spanner.hpp" #include "../sound.hpp" #include "../texture.hpp" /******************************************************************************* ** Global Variables *******************************************************************************/ World world; int bulletid; int explodeid; Entity entity_camera; Entity candles[200]; Entity ent_me; Entity bullets[16]; Entity explode[16]; Entity ball2; int texturedepth[50]; Picture* sprites; Picture* textures; Picture fb; Picture zb; Picture pic_fires[3]; Picture pic_flame; Picture pic_flame2; Picture pic_water; Picture pic_robot; Picture* font1; Picture* font2; Picture glow; Point skypos; ParticleEngine partengine; Camera camera; Game* game; Spanner spanner; Entity** entities; Sound sound_fire; Sound sound_explode; Sound sound_where; /******************************************************************************* ** EntityAdd *******************************************************************************/ void EntityAdd(Entity* entity) { entities = (Entity**) MemoryRealloc(entities, NumberOf(entities) + 1, sizeof(Entity*)); entities[NumberOf(entities) - 1] = entity; } /******************************************************************************* ** FaceRender *******************************************************************************/ int nafterfaces; Face* afterfaces[1000]; Vector render_pos; World* render_world; Camera* render_camera; Matrix render_matrix; void FaceRender(Face* face, Spanner* spanner, Matrix matrix) { Vertex t1[POLYGON_MAX]; Vertex t2[POLYGON_MAX]; int n = NumberOf(face->vertices); for (int i = 0; i < n; i++) { VectorTransform(t1[i][0], face->vertices[i], matrix); } n = VertexArrayClipToFrustum1(t2, t1, n); if (n >= 3) { VertexArrayProject(t2, n, render_camera->origin); VertexArrayScanXYZ(t2, n); assert(face->surfaceid > -1); TextureMap( &render_world->textures[face->surfaceid], spanner, render_camera, &render_world->surfaces[face->surfaceid], matrix); } } /******************************************************************************* ** _BSPRender *******************************************************************************/ void _BSPRender(BSP* bsp, Matrix matrix) { if (bsp && spanner.ngaps && bsp->sides[0]) { int side = PlaneDistanceTo(bsp->plane, render_pos) > 0.0; _BSPRender(bsp->sides[side ? 0 : 1], matrix); for (int i = 0; i < NumberOf(bsp->faces); i++) { Face* face = &bsp->faces[i]; if (PlaneDistanceTo(face->plane, render_pos) > 0.0) { switch (face->colour) { case 0: break; case 1: afterfaces[nafterfaces++] = face; break; default: FaceRender(face, &spanner, matrix); break; } } } _BSPRender(bsp->sides[side ? 1 : 0], matrix); } } void BSPRender(World* world, Camera* camera, Matrix matrix, Vector pos) { v3dcpy(render_pos, pos); render_world = world; render_camera = camera; nafterfaces = 0; _BSPRender(world->bsp, matrix); } void WorldRender(World* world, Camera* camera) { Vector zero = {0.0f, 0.0f, 0.0f}; Matrix scale, matrix; Vector pos; int i; MatrixMultiply(matrix, world->matrix, camera->matrix); VectorInverseTransform(pos, zero, matrix); MatrixScale(scale, 1.0, camera->aspect, 1.0); MatrixMultiply(matrix, matrix, scale); SpannerInvalidate(&spanner); BSPRender(world, camera, matrix, pos); for (i = 0; i < NumberOf(entities); i++) { EntityRender(entities[i], camera); } for (i = 0; i < nafterfaces; i++) { FaceRender(afterfaces[i], NULL, matrix); } } void BehaviourExplode(Entity* entity) { entity->issolid = 0; if (!entity->texture) entity->texture = &pic_fires[explodeid % 3]; entity->scale[0] += 0.15f; entity->scale[1] += 0.15f; entity->scale[2] += 0.15f; if (entity->life-- == 0) { entity->behaviour = NULL; entity->texture = NULL; } } void EntitySpawn(Entity* dst, Entity* src, void (*behaviour)(Entity*)) { EntityCreate(dst, src->pos[0], src->pos[1], src->pos[2], behaviour); } void BehaviourCandle(Entity* entity) { entity->texture = &pic_flame; VectorCreate(entity->scale, 2.0f, 2.0f, 2.0f); } void BehaviourBullet(Entity* entity) { entity->radius = 32; entity->issolid = 0; entity->texture = &glow; if (entity->collision) { SoundEffect(&sound_explode); entity->behaviour = NULL; entity->texture = NULL; EntitySpawn(&explode[explodeid], entity, BehaviourExplode); VectorCreate(explode[explodeid].scale, 0.0f, 0.0f, 0.0f); explode[explodeid].life = 700; explodeid = (explodeid + 1) % 16; } } void BehaviourFollow(Entity* entity) { Vector vector; entity->radius = 120; entity->issolid = 1; entity->texture = &pic_robot; entity->vel[1] -= 0.08f; VectorSubtract(vector, ent_me.pos, entity->pos); vector[1] = 0.0; VectorSetLength(vector, vector, 0.1f); VectorAddition(entity->vel, entity->vel, vector); if (VectorMagnitude(entity->vel) > 1.0f) VectorSetLength(entity->vel, entity->vel, 1.0f); } void BehaviourCamera(Entity* entity) { Matrix xrot, yrot, zrot, matrix, trans; entity->radius = 32.0f; entity->issolid = 0; MatrixTranslate(trans, -entity->pos[0], -entity->pos[1], -entity->pos[2]); MatrixRotateX(xrot, entity->rot[0]); MatrixRotateY(yrot, entity->rot[1]); MatrixRotateZ(zrot, entity->rot[2]); MatrixMultiply(matrix, trans, yrot, xrot); CameraLocate(&camera, matrix); } void BehaviourMe(Entity* entity) { static Vector force = {0.0f, 0.0f, 1.0f}; static int fire; static int jump; static float bounce; Matrix matrix, xrot, yrot, zrot, xyzrot; Vector vector; entity->radius += game->keyboard[KB_C] ? -2 : 2; entity->radius = Clamp(entity->radius, 92.0f, 128.0f); entity->issolid = 1; entity->vel[1] -= 0.1f; VectorCreate(entity_camera.vel, 0.0f, 0.0f, 0.0f); v3dcpy(entity_camera.pos, entity->pos); v3dcpy(entity_camera.rot, entity->rot); bounce += VectorMagnitude(entity->vel); float ttt = sin(bounce * DTOR) * (entity->radius / 4.0f) + (entity->radius * 1.4f); entity_camera.pos[1] += ttt; MatrixRotateX(xrot, entity->rot[0]); MatrixRotateY(yrot, entity->rot[1]); MatrixRotateZ(zrot, entity->rot[2]); MatrixMultiply(xyzrot, zrot, yrot, xrot); if (entity->isonground) { VectorScale(entity->vel, entity->vel, entity->radius / 134.0f); MatrixTranspose(matrix, yrot); VectorRotate(vector, force, matrix); if (game->keyboard[KB_UPARROW] || game->rightbutton) VectorAddition(entity->vel, entity->vel, vector); if (game->keyboard[KB_DNARROW] || game->leftbutton ) VectorSubtract(entity->vel, entity->vel, vector); if (VectorMagnitude(entity->vel) > 4.0f) VectorSetLength(entity->vel, entity->vel, 4.0f); } if (game->keyboard[KB_CTRL]) { if (fire == 0) { fire = 1; SoundEffect(&sound_fire); VectorCreate(vector, 0.0f, 0.0f, 10.0f); MatrixTranspose(matrix, xyzrot); VectorRotate(vector, vector, matrix); VectorCreate(bullets[bulletid].pos, entity->pos[0], entity->pos[1] + 50, entity->pos[2]); v3dcpy(bullets[bulletid].vel, vector); bullets[bulletid].behaviour = BehaviourBullet; bulletid = (bulletid + 1) % 16; } } else { fire = 0; } if (game->keyboard[KB_SPACE]) { if (jump == 0) { jump = 1; entity->vel[1] += 5.0; } } else { if (entity->isonground) { jump = 0; } } entity->rot[1] += (game->mousevel.x / 4.0f); entity->rot[0] -= (game->mousevel.y / 4.0f); if (game->keyboard[KB_LTARROW]) entity->rot[1] -= 1.0f; if (game->keyboard[KB_RTARROW]) entity->rot[1] += 1.0f; if (game->keyboard[0x1E]) entity->rot[0] += 1.0f; if (game->keyboard[0x2C]) entity->rot[0] -= 1.0f; if (entity->rot[0] > 70.0f) entity->rot[0] = 70.0f; if (entity->rot[0] < -70.0f) entity->rot[0] = -70.0f; while (entity->rot[1] > 360.0f) entity->rot[1] -= 360.0f; while (entity->rot[1] < 0.0f) entity->rot[1] += 360.0f; skypos.x = (int)(entity->rot[1] * (640.0 / 360.0)) % 640; skypos.y = 0; //entity->rot[0] * -2.0 + 141.0; } float tp = 400; void GameMotion(Game* game) { Vector vector; char s[100]; int i, j; tp -= 0.3f; if (tp < -500) tp = 400; for (i = 0; i < NumberOf(entities); i++) { if (entities[i]->behaviour) entities[i]->behaviour(entities[i]); } for (i = 0; i < NumberOf(entities); i++) { for (j = i + 1; j < NumberOf(entities); j++) { EntityToEntityCollision(entities[i], entities[j]); } } for (i = 0; i < NumberOf(entities); i++) { EntityCollisionProcess(entities[i], &world); } //MotionTick(&box.rot); ParticleEngineUpdate(&partengine); int x, y; for (y = 0; y < pic_flame.height; y++) { x = Round(sin(((y<<3) + (game->timer_count << 2)) * DTOR) * ((pic_flame.height - y) >> 3)); PictureCopy(PICTURE_COPY, &pic_flame, x, y, pic_flame.width + x, y + 1, &pic_flame2, 0, y, pic_flame2.width, y + 1, 0, 0, 0); } EffectRipple(&textures[1], &pic_water, (360.0f / pic_water.width), 8.0f, game->timer_count); for (i = 0; i < NumberOf(world.surfaces); i++) { Surface* surface = &world.surfaces[i]; if (surface->colour == 2) { VectorScale(vector, surface->PMN[1], 0.0005f); VectorAddition(surface->PMN[0], surface->PMN[0], vector); } } } void PictureLineFeed(Picture* picture) { picture->y += picture->lineheight; if (picture->y > (picture->height - picture->lineheight)) { picture->y -= picture->lineheight; PictureCopy(PICTURE_COPY, picture, 0, 0, picture->width, picture->height - picture->lineheight, picture, 0, picture->lineheight, picture->width, picture->height, 0, 0, 0); PictureCopy(PICTURE_FILL, picture, 0, picture->height - picture->lineheight, picture->width, picture->height, 0, 0, 0, 0, 0, 0, 0, 0); } } void PictureNewLine(Picture* picture) { picture->x = 0; PictureLineFeed(picture); } Picture* FontDownSample(Picture* src) { Picture* dst = PictureArrayCreate(NumberOf(src)); for (int i = 0; i < NumberOf(dst); i++) { EffectDownSample(&dst[i], &src[i]); } return dst; } Picture* FontCharacter(Picture* src, int c) { c -= ' '; if (c < 0 || c >= NumberOf(src)) c = 0; return &src[c]; } int FontTextWidth(Picture* src, char* s) { int width = 0; while (*s) width += FontCharacter(src, *s++)->width; return width; } void DrawChar(Picture* dst, int c, Picture* src) { src = FontCharacter(src, c); dst->lineheight = Max(src->height, dst->lineheight); switch (c) { case '\n': { PictureNewLine(dst); break; } default: { PictureCopy(PICTURE_COPY, dst, dst->x, dst->y, dst->x + src->width, dst->y + src->height, src, 0, 0, src->width, src->height, 0, 0, 0); dst->x += src->width; break; } } } void DrawString(Picture* dst, char* s, Picture* src) { while (*s) DrawChar(dst, *s++, src); } void PicturePutString(Picture* dst, char* s, Picture* src) { int width = FontTextWidth(src, s); if ((dst->x + width) >= dst->width) PictureNewLine(dst); DrawString(dst, s, src); } void PictureDrawMemo(Picture* dst, char* s, Picture* src) { char* w; char c; for (w = s; *s;) { c = *s++; if (isspace(c)) { c = *s; *s = '\0'; PicturePutString(dst, w, src); *s = c; w = s; } } } void GameRender(Game* game) { String s; static int frame; WorldRender(&world, &camera); //ParticleEngineRender(&partengine, &camera); //DrawString(&textures[20], tp, 50, // "...You have entered Room101. The power of darkness is upon you... ", font2); //ModelRender(&box, &camera, &fb, &zb); frame++; sprintf(s, "%d", game->timer_count / frame); game->fb.x = 0; game->fb.y = 0; //DrawString(&game->fb, s, font2); } void GameCreate(Game* _game) { FILE* file; char s[100]; int i; game = _game; printf("Loading fonts\n"); file = FileOpen("../../media/data/MS_SANS_.36", "rb"); font1 = PictureArrayRead(file); font2 = FontDownSample(font1); PictureArrayDelete(font1); FileClose(file); printf("Creating video window\n"); int h = game->fb.height * 1.0f; int y = (game->fb.height - h) >> 1; PictureWindow(&fb, &game->fb, 0, y, game->fb.width, h); PictureWindow(&zb, &game->zb, 0, y, game->zb.width, h); PictureLoad(&pic_fires[0], "../../media/data/fire1.hic"); PictureLoad(&pic_fires[1], "../../media/data/fire2.hic"); PictureLoad(&pic_fires[2], "../../media/data/fire3.hic"); PictureLoad(&pic_flame , "../../media/sprites/flame.hic"); PictureLoad(&pic_flame2 , "../../media/sprites/flame.hic"); PictureLoad(&pic_robot , "../../media/sprites/robot.hic"); PictureLoad(&pic_water , "../../media/textures/1.hic"); PictureLoad(&glow , "../../media/data/glow.hic" ); CameraCreate(&camera, &fb, &zb, ((float)fb.width / (float)fb.height) / GamePixelWidth()); SpannerCreate(&spanner, fb.width, fb.height); printf("Loading textures\n"); textures = (Picture*) MemoryAlloc(21, sizeof(Picture)); for (i = 0; i < NumberOf(textures); i++) { sprintf(s, "../../media/textures/%d.hic", i); PictureLoad(&textures[i], s); } file = FileOpen("../../media/textures/depth.txt", "r"); for (i = 0; i < NumberOf(textures); i++) { fscanf(file, "%d", &texturedepth[i]); } FileClose(file); printf("Loading sprites\n"); sprites = (Picture*) MemoryAlloc(2, sizeof(Picture)); for (i = 0; i < NumberOf(sprites); i++) { sprintf(s, "../../media/sprites/%d.hic", i); PictureLoad(&sprites[i], s); } printf("Reading world\n"); file = FileOpen("../../media/data/bsp.dat", "rb"); WorldRead(&world, textures, texturedepth, file); FileClose(file); ParticleEngineCreate(&partengine, 800, 0, 200, 0); for (i = 0; i < 16; i++) { EntityCreate(&bullets[i], 0, 0, 0, NULL); EntityAdd(&bullets[i]); EntityCreate(&explode[i], 0, 0, 0, NULL); EntityAdd(&explode[i]); } /* for (i = 0; i < sphere.XYZ.n; i++) { EntityAdd(EntityCreate( sphere.keyframes.items[0].items[i][0] * 150, sphere.keyframes.items[0].items[i][1] * 150 + 100, sphere.keyframes.items[0].items[i][2] * 150, 0, 0, 0, &sprites.items[0], 0, NULL)); } */ EntityCreate(&entity_camera, 0, 0, 0, BehaviourCamera); EntityAdd(&entity_camera); EntityCreate(&ent_me, 0, 400, 0, BehaviourMe); EntityAdd(&ent_me); //EntityCreate(&ball2, 300, 400, 0, BehaviourFollow); EntityAdd(&ball2); for (i = 0; i < NumberOf(world.lights); i++) { Light* light = &world.lights[i]; if (!memcmp(light->name, "candle", 6)) { EntityCreate(&candles[i], light->pos[0], light->pos[1], light->pos[2], BehaviourCandle); EntityAdd(&candles[i]); } } /* EntityCreate(&entity, 0, 300, 0, 0, 0, 0, &sprites[1], 0, BehaviourFollow); EntityAdd(&entity); EntityCreate(&entity, 0, 300, 0, 0, 0, 0, &sprites[1], 0, BehaviourFollow); EntityAdd(&entity); EntityCreate(&entity, 0, 300, 0, 0, 0, 0, &sprites[1], 0, BehaviourFollow); EntityAdd(&entity); EntityCreate(&entity, 0, 300, 0, 0, 0, 0, &sprites[1], 0, BehaviourFollow); EntityAdd(&entity); */ printf("Loading sound files\n"); SoundLoadFromFile(&sound_fire , "../../media/sound/mfxlaser.wav"); SoundLoadFromFile(&sound_explode, "../../media/sound/v2tmp0.wav"); //SoundLoadFromFile(&sound_where, "sound/where.wav"); //SoundPlay(&sound_where, 1, 1); PictureBlitRect(&game->fb, game->fb.rect, PixelCreate(0, 0, 0)); printf("Engine ready\n"); }