summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/GameInitDispose.cpp318
-rw-r--r--src/Models.cpp79
-rw-r--r--src/Models.h65
-rw-r--r--src/misc.h15
-rw-r--r--src/misc.zig91
5 files changed, 237 insertions, 331 deletions
diff --git a/src/GameInitDispose.cpp b/src/GameInitDispose.cpp
index 0022a07..8c9ea72 100644
--- a/src/GameInitDispose.cpp
+++ b/src/GameInitDispose.cpp
@@ -114,6 +114,8 @@ void LoadSounds(bool musictoggle)
 	gSampleSet[footstepsound + 2] = loadSound("footstep/2.ogg");
 	gSampleSet[footstepsound + 3] = loadSound("footstep/3.ogg");
 	gSampleSet[footstepsound + 4] = loadSound("footstep/4.ogg");
+	gSampleSet[pinpullsound] = loadSound("grenade/pin-pull.flac");
+	gSampleSet[pinreplacesound] = loadSound("grenade/pin-replace.flac");
 	gSampleSet[clicksound] = loadSound("gun/empty-clip.wav");
 	gSampleSet[shotgunsound] = loadSound("gun/fire/20-gauge-shotgun.wav");
 	gSampleSet[pistol2sound] = loadSound("gun/fire/22-magnum-pistol.wav");
@@ -122,8 +124,6 @@ void LoadSounds(bool musictoggle)
 	gSampleSet[riflesound] = loadSound("gun/fire/ar-15-rifle.wav");
 	gSampleSet[nearbulletsound] = loadSound("gun/near-bullet.wav");
 	gSampleSet[reloadsound] = loadSound("gun/reload.wav");
-	gSampleSet[pinpullsound] = loadSound("grenade/pin-pull.flac");
-	gSampleSet[pinreplacesound] = loadSound("grenade/pin-replace.flac");
 	gSampleSet[bodylandsound] = loadSound("impact/body-fall.wav");
 	gSampleSet[bodyhitsound] = loadSound("impact/body-hit.wav");
 	gSampleSet[knifeslashsound] = loadSound("impact/knife-stab.wav");
@@ -250,7 +250,7 @@ void initGame(Game* game)
 		game->timeremaining = 50;
 		game->difficulty= 0.8f;
 
-		ifstream ipstream {"Data/customlevels.txt"};
+		ifstream ipstream {"data/customlevels.txt"};
 		if (ipstream) {
 			ipstream.ignore(256,'\n');//ignore descriptive text
 			ipstream >> game->nummissions;
@@ -511,96 +511,32 @@ void initGame(Game* game)
 
 	// Setup block models
 	if (!game->initialized) {
-		game->blocks[0].load((char*) ":Data:Models:Block1.solid");
-		game->blocks[1].load((char*) ":Data:Models:Block2.solid");
-		game->blocks[2].load((char*) ":Data:Models:Block3.solid");
-		game->blocks[3].load((char*) ":Data:Models:Block4.solid");
-		for (auto&& block : game->blocks) {
-			block.Rotate(90, 0, 0);
-			block.Scale(0.8f, 0.8f, 0.8f);
-			block.CalculateNormals();
-
-			// Fix block radius
-			auto& center = block.boundingspherecenter;
-			center.x = center.y = center.z = 0;
-
-			float radiusqr = 0.0;
-			for (int x = 0; x < block.vertexNum; x++) {
-				auto distance = findDistancefast(center,
-					block.vertex[x]);
-				if (distance > radiusqr)
-					radiusqr = distance;
-			}
-			block.boundingsphereradius = sqrt(radiusqr);
-		}
-
-		game->sidewalkcollide.load((char*) ":Data:Models:Lowheightcollide.solid");
-		game->sidewalkcollide.Rotate(90, 0, 0);
-		game->sidewalkcollide.Scale(0.8f, 0.8f, 0.8f);
-		game->sidewalkcollide.CalculateNormals();
-
-		game->blockwalls[0].load((char*) ":Data:Models:Block1collide.solid");
-		game->blockwalls[1].load((char*) ":Data:Models:Block2collide.solid");
-		game->blockwalls[2].load((char*) ":Data:Models:Block3collide.solid");
-		game->blockwalls[3].load((char*) ":Data:Models:Block4collide.solid");
-		for (auto&& blockwall : game->blockwalls) {
-			blockwall.Rotate(90, 0, 0);
-			blockwall.Scale(0.8f, 0.75f, 0.8f);
-			blockwall.CalculateNormals();
-		}
-
-		game->blockroofs[0].load((char*) ":Data:Models:Highblock1collide.solid");
-		game->blockroofs[1].load((char*) ":Data:Models:Highblock2collide.solid");
-		game->blockroofs[2].load((char*) ":Data:Models:Highblock3collide.solid");
-		game->blockroofs[3].load((char*) ":Data:Models:Highblock4collide.solid");
-		for (auto&& blockroof : game->blockroofs) {
-			blockroof.Rotate(90, 0, 0);
-			blockroof.Scale(0.8f, 0.8f, 0.8f);
-			blockroof.CalculateNormals();
-		}
-
-		game->blockcollide[0].load((char*) ":Data:Models:block1complete.solid");
-		game->blockcollide[1].load((char*) ":Data:Models:block2complete.solid");
-		game->blockcollide[2].load((char*) ":Data:Models:block3complete.solid");
-		game->blockcollide[3].load((char*) ":Data:Models:block4complete.solid");
-		for (auto&& blockcollide : game->blockcollide) {
-			blockcollide.Rotate(90, 0, 0);
-			blockcollide.Scale(0.8f, 0.8f, 0.8f);
-			blockcollide.CalculateNormals();
-		}
-
-		game->blocksimplecollide[0].load((char*) ":Data:Models:lowsimplecollide1.solid");
-		game->blocksimplecollide[1].load((char*) ":Data:Models:lowsimplecollide2.solid");
-		game->blocksimplecollide[2].load((char*) ":Data:Models:lowsimplecollide3.solid");
-		game->blocksimplecollide[3].load((char*) ":Data:Models:lowsimplecollide4.solid");
-		for (auto&& blocksimplecollide : game->blocksimplecollide) {
-			blocksimplecollide.Rotate(90, 0, 0);
-			blocksimplecollide.Scale(0.8f, 0.8f, 0.8f);
-			blocksimplecollide.CalculateNormals();
-		}
-
-		game->blockocclude.load((char*) ":Data:Models:blockocclude.solid");
-		game->blockocclude.Rotate(90, 0, 0);
-		game->blockocclude.Scale(0.8f, 0.8f, 0.8f);
-		game->blockocclude.CalculateNormals();
-
-		game->blocksimple.load((char*) ":Data:Models:blocksimple.solid");
-		game->blocksimple.Rotate(90, 0, 0);
-		game->blocksimple.Scale(0.8f, 2.0f, 0.8f);
-		game->blocksimple.CalculateNormals();
-
-		game->street.load((char*) ":Data:Models:streetsubdivided2.solid");
-		game->street.Rotate(90,0,0);
-		game->street.Scale(0.01f, 0.01f, 0.01f);
-		game->street.CalculateNormals();
-
-		game->Bigstreet = game->street;
-		game->Bigstreet.Scale(10000.0f, 10000.0f, 10000.0f);
-
-		game->path.load((char*) ":Data:Models:path.solid");
-		game->path.Rotate(90,0,0);
-		game->path.Scale(0.8f, 0.8f, 0.8f);
-		game->path.CalculateNormals();
+		game->blocks[0].load("blocks/0.off");
+		game->blocks[1].load("blocks/1.off");
+		game->blocks[2].load("blocks/2.off");
+		game->blocks[3].load("blocks/3.off");
+		game->sidewalkcollide.load("collide/sidewalk.off");
+		game->blockwalls[0].load("collide/blocks/walls/0.off");
+		game->blockwalls[1].load("collide/blocks/walls/1.off");
+		game->blockwalls[2].load("collide/blocks/walls/2.off");
+		game->blockwalls[3].load("collide/blocks/walls/3.off");
+		game->blockroofs[0].load("collide/blocks/roofs/0.off");
+		game->blockroofs[1].load("collide/blocks/roofs/1.off");
+		game->blockroofs[2].load("collide/blocks/roofs/2.off");
+		game->blockroofs[3].load("collide/blocks/roofs/3.off");
+		game->blockcollide[0].load("collide/blocks/0.off");
+		game->blockcollide[1].load("collide/blocks/1.off");
+		game->blockcollide[2].load("collide/blocks/2.off");
+		game->blockcollide[3].load("collide/blocks/3.off");
+		game->blocksimplecollide[0].load("collide/blocks/simple/0.off");
+		game->blocksimplecollide[1].load("collide/blocks/simple/1.off");
+		game->blocksimplecollide[2].load("collide/blocks/simple/2.off");
+		game->blocksimplecollide[3].load("collide/blocks/simple/3.off");
+		game->blockocclude.load("blocks/occlude.off");
+		game->blocksimple.load("blocks/simple.off");
+		game->street.load("streets/small.off");
+		game->Bigstreet.load("streets/big.off");
+		game->path.load("streets/path.off");
 	}
 
 	auto& vip = game->person[game->numpeople = 1];
@@ -662,163 +598,30 @@ void initGame(Game* game)
 		}
 
 	if (!game->initialized) {
-		//Load player model
-		skeletonmodels[0].load((char*) ":Data:Models:Head.solid");
-		skeletonmodels[0].Rotate(90,0,0);
-		skeletonmodels[0].Scale(.02,.02,.02);
-		skeletonmodels[0].CalculateNormals();
-		skeletonmodels[1].load((char*) ":Data:Models:Chest.solid");
-		skeletonmodels[1].Rotate(90,0,0);
-		skeletonmodels[1].Scale(.02,.02,.02);
-		skeletonmodels[1].CalculateNormals();
-		skeletonmodels[2].load((char*) ":Data:Models:Abdomen.solid");
-		skeletonmodels[2].Rotate(90,0,0);
-		skeletonmodels[2].Scale(.02,.02,.02);
-		skeletonmodels[2].CalculateNormals();
-		skeletonmodels[3].load((char*) ":Data:Models:Upper arm.solid");
-		skeletonmodels[3].Rotate(90,0,0);
-		skeletonmodels[3].Scale(.02,.02,.02);
-		skeletonmodels[3].CalculateNormals();
-		skeletonmodels[4].load((char*) ":Data:Models:Lower arm.solid");
-		skeletonmodels[4].Rotate(90,0,0);
-		skeletonmodels[4].Scale(.02,.02,.02);
-		skeletonmodels[4].CalculateNormals();
-		skeletonmodels[5].load((char*) ":Data:Models:Hand.solid");
-		skeletonmodels[5].Rotate(90,0,0);
-		skeletonmodels[5].Scale(.02,.02,.02);
-		skeletonmodels[5].CalculateNormals();
-		skeletonmodels[6].load((char*) ":Data:Models:Upper leg.solid");
-		skeletonmodels[6].Rotate(90,0,0);
-		skeletonmodels[6].Scale(.02,.02,.02);
-		skeletonmodels[6].CalculateNormals();
-		skeletonmodels[7].load((char*) ":Data:Models:Lower leg.solid");
-		skeletonmodels[7].Rotate(90,0,0);
-		skeletonmodels[7].Scale(.02,.02,.02);
-		skeletonmodels[7].CalculateNormals();
-		skeletonmodels[8].load((char*) ":Data:Models:Foot.solid");
-		skeletonmodels[8].Rotate(90,0,0);
-		skeletonmodels[8].Scale(.02,.02,.02);
-		skeletonmodels[8].CalculateNormals();
-		skeletonmodels[9].load((char*) ":Data:Models:Shades.solid");
-		skeletonmodels[9].Rotate(90,0,0);
-		skeletonmodels[9].Scale(.02,.02,.02);
-		skeletonmodels[9].CalculateNormals();
-
-		//Load gun models
-		gunmodels[sniperriflemodel].load((char*) ":Data:Models:sniperrifle.solid");
-
-		gunmodels[sniperriflemodel].Rotate(0,0,90);
-
-		gunmodels[sniperriflemodel].Scale(.001,.001,.001);
-
-		gunmodels[sniperriflemodel].CalculateNormals();
-
-		gunmodels[assaultriflemodel].load((char*) ":Data:Models:assaultrifle.solid");
-
-		gunmodels[assaultriflemodel].Rotate(0,0,90);
-
-		gunmodels[assaultriflemodel].Scale(.01,.01,.01);
-
-		gunmodels[assaultriflemodel].CalculateNormals();
-
-		gunmodels[handgunbasemodel].load((char*) ":Data:Models:Handgunbase.solid");
-
-		gunmodels[handgunbasemodel].Rotate(0,0,90);
-
-		gunmodels[handgunbasemodel].Rotate(180,0,0);
-
-		gunmodels[handgunbasemodel].Scale(.014,.014,.014);
-
-		gunmodels[handgunbasemodel].CalculateNormals();
-
-		gunmodels[handgunbasemodel].MultColor(.6);
-
-		gunmodels[handgunslidemodel].load((char*) ":Data:Models:Handgunslide.solid");
-
-		gunmodels[handgunslidemodel].Rotate(0,0,90);
-
-		gunmodels[handgunslidemodel].Rotate(180,0,0);
-
-		gunmodels[handgunslidemodel].Scale(.014,.014,.014);
-
-		gunmodels[handgunslidemodel].CalculateNormals();
-
-		gunmodels[handgunslidemodel].MultColor(.6);
-
-		gunmodels[handgun2basemodel].load((char*) ":Data:Models:glockbase.solid");
-
-		gunmodels[handgun2basemodel].Rotate(0,0,90);
-
-		gunmodels[handgun2basemodel].Rotate(180,0,0);
-
-		gunmodels[handgun2basemodel].Scale(.014,.014,.014);
-
-		gunmodels[handgun2basemodel].CalculateNormals();
-
-		gunmodels[handgun2basemodel].MultColor(.6);
-
-		gunmodels[handgun2slidemodel].load((char*) ":Data:Models:glockslide.solid");
-
-		gunmodels[handgun2slidemodel].Rotate(0,0,90);
-
-		gunmodels[handgun2slidemodel].Rotate(180,0,0);
-
-		gunmodels[handgun2slidemodel].Scale(.014,.014,.014);
-
-		gunmodels[handgun2slidemodel].CalculateNormals();
-
-		gunmodels[handgun2slidemodel].MultColor(.6);
-
-		gunmodels[grenadebasemodel].load((char*) ":Data:Models:grenadebase.solid");
-
-		gunmodels[grenadebasemodel].Rotate(0,0,90);
-
-		gunmodels[grenadebasemodel].Rotate(180,0,0);
-
-		gunmodels[grenadebasemodel].Scale(.014,.014,.014);
-
-		gunmodels[grenadebasemodel].CalculateNormals();
-
-		gunmodels[grenadepinmodel].load((char*) ":Data:Models:grenadepin.solid");
-
-		gunmodels[grenadepinmodel].Rotate(0,0,90);
-
-		gunmodels[grenadepinmodel].Rotate(180,0,0);
-
-		gunmodels[grenadepinmodel].Scale(.014,.014,.014);
-
-		gunmodels[grenadepinmodel].CalculateNormals();
-
-		gunmodels[grenadespoonmodel].load((char*) ":Data:Models:grenadespoon.solid");
-
-		gunmodels[grenadespoonmodel].Rotate(0,0,90);
-
-		gunmodels[grenadespoonmodel].Rotate(180,0,0);
-
-		gunmodels[grenadespoonmodel].Scale(.014,.014,.014);
-
-		gunmodels[grenadespoonmodel].CalculateNormals();
-
-		gunmodels[knifemodel].load((char*) ":Data:Models:Knife.solid");
-
-		gunmodels[knifemodel].Rotate(0,0,90);
-
-		gunmodels[knifemodel].Rotate(180,0,0);
-
-		gunmodels[knifemodel].Scale(.014,.014,.014);
-
-		gunmodels[knifemodel].CalculateNormals();
-
-		gunmodels[shotgunmodel].load((char*) ":Data:Models:shotgun.solid");
-
-		gunmodels[shotgunmodel].Rotate(0,0,90);
-
-		gunmodels[shotgunmodel].Scale(.001,.001,.001);
-
-		gunmodels[shotgunmodel].CalculateNormals();
-
-		gunmodels[shotgunmodel].MultColor(.6);
-
+		// Load person model
+		skeletonmodels[0].load("skeleton/head.off");
+		skeletonmodels[1].load("skeleton/chest.off");
+		skeletonmodels[2].load("skeleton/abdomen.off");
+		skeletonmodels[3].load("skeleton/arm.off");
+		skeletonmodels[4].load("skeleton/forearm.off");
+		skeletonmodels[5].load("skeleton/hand.off");
+		skeletonmodels[6].load("skeleton/thigh.off");
+		skeletonmodels[7].load("skeleton/leg.off");
+		skeletonmodels[8].load("skeleton/foot.off");
+		skeletonmodels[9].load("skeleton/shades.off");
+
+		// Load weapon models
+		gunmodels[sniperriflemodel].load("guns/sniper-rifle.off");
+		gunmodels[assaultriflemodel].load("guns/assault-rifle.off");
+		gunmodels[handgunbasemodel].load("guns/handgun-big-base.off");
+		gunmodels[handgunslidemodel].load("guns/handgun-big-slide.off");
+		gunmodels[handgun2basemodel].load("guns/handgun-small-base.off");
+		gunmodels[handgun2slidemodel].load("guns/handgun-small-slide.off");
+		gunmodels[grenadebasemodel].load("grenade/base.off");
+		gunmodels[grenadepinmodel].load("grenade/pin.off");
+		gunmodels[grenadespoonmodel].load("grenade/spoon.off");
+		gunmodels[knifemodel].load("knife.off");
+		gunmodels[shotgunmodel].load("guns/shotgun.off");
 	}
 
 	//Setup costumes
@@ -829,35 +632,20 @@ void initGame(Game* game)
 	float bottomcolor[3];
 
 	//Police
-
 	headcolor[0]=(float)240/255;
-
 	headcolor[1]=(float)183/255;
-
 	headcolor[2]=(float)132/255;
-
 	footcolor[0]=(float)119/255;
-
 	footcolor[1]=(float)68/255;
-
 	footcolor[2]=(float)18/255;
-
 	handcolor[0]=(float)240/255;
-
 	handcolor[1]=(float)183/255;
-
 	handcolor[2]=(float)132/255;
-
 	topcolor[0]=(float)14/255;
-
 	topcolor[1]=(float)18/255;
-
 	topcolor[2]=(float)195/255;
-
 	bottomcolor[0]=(float)14/255;
-
 	bottomcolor[1]=(float)18/255;
-
 	bottomcolor[2]=(float)195/255;
 
 	// Greenish skin if zombies
diff --git a/src/Models.cpp b/src/Models.cpp
index cc69c44..6d8eecf 100644
--- a/src/Models.cpp
+++ b/src/Models.cpp
@@ -1,6 +1,5 @@
 #include "Models.h"
-
-#include "Serialize.h"
+#include "misc.h"
 
 //Functions
 void Model::UpdateVertexArray(){
@@ -62,43 +61,55 @@ void Model::UpdateVertexArray(){
 	boundingsphereradius=sqrt(boundingsphereradius);
 }
 
-bool Model::load(Str255 Name)
+void Model::load(const char* path)
 {
-	short				tfile;
-	Files file;
-
-	tfile=file.OpenFile(Name);
-	SetFPos(tfile,fsFromStart,0);
-
-	// read model settings
-	ReadShort(tfile,1,&vertexNum);
-	ReadShort(tfile,1,&TriangleNum);
-
-	// read the model data
-	ReadXYZ(tfile,vertexNum,vertex);
-	ReadTexturedTriangle(tfile,TriangleNum,Triangles);
+	auto model = loadModel(path);
+	vertexNum = model.vertices.len;
+	for (short i = 0; i < vertexNum; ++i) {
+		vertex[i].x = model.vertices.ptr[i].x;
+		vertex[i].y = model.vertices.ptr[i].y;
+		vertex[i].z = model.vertices.ptr[i].z;
+	}
+	free(model.vertices.ptr);
 
-	FSClose(tfile);
+	TriangleNum = model.faces.len;
+	for (short i = 0; i < TriangleNum; ++i) {
+		Triangles[i].vertex[0] = model.faces.ptr[i].v[0];
+		Triangles[i].vertex[1] = model.faces.ptr[i].v[1];
+		Triangles[i].vertex[2] = model.faces.ptr[i].v[2];
+		Triangles[i].r = model.faces.ptr[i].r;
+		Triangles[i].g = model.faces.ptr[i].g;
+		Triangles[i].b = model.faces.ptr[i].b;
+	}
+	free(model.faces.ptr);
 
-	UpdateVertexArray();
+	XYZ average {};
+	for (auto&& v : vertex)
+		boundingspherecenter += v;
+	boundingspherecenter /= vertexNum;
 
-	XYZ average;
-	int howmany;
-	average=0;
-	howmany=0;
-	for(int i=0;i<vertexNum;i++){
-		howmany++;
-		average=average+vertex[i];
-	}
-	average=average/howmany;
-	boundingspherecenter=average;
-	boundingsphereradius=0;
-	for(int i=0;i<vertexNum;i++){
-		if(findDistancefast(average,vertex[i])>boundingsphereradius)boundingsphereradius=findDistancefast(average,vertex[i]);
-	}
-	boundingsphereradius=sqrt(boundingsphereradius);
+	boundingsphereradius = 0;
+	for (auto&& v : vertex)
+		boundingsphereradius = max(boundingsphereradius,
+			findDistancefast(average, v));
+	boundingsphereradius = sqrt(boundingsphereradius);
+	CalculateNormals();
+}
 
-	return 1;
+void Model::save(const char* path)
+{
+	auto f = fopen(path, "w");
+	fprintf(f, "%d %d 0\n", vertexNum, TriangleNum);
+	for (int i = 0; i < vertexNum; ++i)
+		fprintf(f, "%.3f %.3f %.3f\n",
+			vertex[i].x, vertex[i].y, vertex[i].z);
+	for (int i = 0; i < TriangleNum; ++i)
+		fprintf(f, "3 %d %d %d %.3f %.3f %.3f\n",
+			Triangles[i].vertex[0],
+			Triangles[i].vertex[1],
+			Triangles[i].vertex[2],
+			Triangles[i].r, Triangles[i].g, Triangles[i].b);
+	fclose(f);
 }
 
 void Model::Scale(float xscale,float yscale,float zscale)
diff --git a/src/Models.h b/src/Models.h
index 9c48841..2e37a9b 100644
--- a/src/Models.h
+++ b/src/Models.h
@@ -20,41 +20,42 @@
 //
 
 class TexturedTriangle{
-	public:
-				short			vertex[3];
-				float r,g,b;
+public:
+	short vertex[3];
+	float r,g,b;
 };
 
 class Model{
-	public:
-				short	vertexNum,TriangleNum;
-
-				XYZ					vertex[max_model_vertex];
-				XYZ					normals[max_textured_triangle];
-				TexturedTriangle	Triangles[max_textured_triangle];
-				GLfloat 			vArray[max_textured_triangle*27];
-
-				XYZ boundingspherecenter;
-				float boundingsphereradius;
-				int LineCheck(XYZ p1,XYZ p2, XYZ *p);
-				int LineCheck2(XYZ p1,XYZ p2, XYZ *p,XYZ move,float rotate);
-				int LineCheck2(XYZ *p1,XYZ *p2, XYZ *p,XYZ *move,float *rotate);
-				int LineCheck3(XYZ p1,XYZ p2, XYZ *p,XYZ move,float rotate,float *d);
-
-				void UpdateVertexArray();
-				bool load(Str255 Name);
-				void Scale(float xscale,float yscale,float zscale);
-				void ScaleNormals(float xscale,float yscale,float zscale);
-				void Translate(float xtrans,float ytrans,float ztrans);
-				void CalculateNormals();
-				void draw();
-				void draw(float r,float g,float b);
-				void draw(float r,float g,float b, float o);
-				void draw(float r,float g,float b, float x, float y, float z);
-				void Rotate(float xang,float yang,float zang);
-				void MultColor(float howmuch);
-
-				XYZ boundingboxmin,boundingboxmax;
+public:
+	short vertexNum, TriangleNum;
+
+	XYZ vertex[max_model_vertex];
+	XYZ normals[max_textured_triangle];
+	TexturedTriangle Triangles[max_textured_triangle];
+	GLfloat vArray[max_textured_triangle*27];
+
+	XYZ boundingspherecenter;
+	float boundingsphereradius;
+	int LineCheck(XYZ p1,XYZ p2, XYZ *p);
+	int LineCheck2(XYZ p1,XYZ p2, XYZ *p,XYZ move,float rotate);
+	int LineCheck2(XYZ *p1,XYZ *p2, XYZ *p,XYZ *move,float *rotate);
+	int LineCheck3(XYZ p1,XYZ p2, XYZ *p,XYZ move,float rotate,float *d);
+
+	void UpdateVertexArray();
+	void load(const char*);
+	void save(const char*);
+	void Scale(float xscale,float yscale,float zscale);
+	void ScaleNormals(float xscale,float yscale,float zscale);
+	void Translate(float xtrans,float ytrans,float ztrans);
+	void CalculateNormals();
+	void draw();
+	void draw(float r,float g,float b);
+	void draw(float r,float g,float b, float o);
+	void draw(float r,float g,float b, float x, float y, float z);
+	void Rotate(float xang,float yang,float zang);
+	void MultColor(float howmuch);
+
+	XYZ boundingboxmin, boundingboxmax;
 };
 
 #endif
diff --git a/src/misc.h b/src/misc.h
index 96b2307..fbd70cd 100644
--- a/src/misc.h
+++ b/src/misc.h
@@ -25,6 +25,20 @@ struct JointData {
 	signed char parent;
 };
 
+struct ModelData {
+	struct {
+		struct { float x, y, z; } *ptr;
+		size_t len;
+	} vertices;
+	struct {
+		struct {
+			short v[3];
+			float r, g, b;
+		} *ptr;
+		size_t len;
+	} faces;
+};
+
 struct MuscleData {
 	float length, initlen, minlen, maxlen;
 	bool flag;
@@ -38,6 +52,7 @@ extern "C" {
 #endif // __cplusplus
 	AnimationData loadAnimation(const char*);
 	void loadJoints(JointData*);
+	ModelData loadModel(const char*);
 	void loadMuscles(MuscleData*);
 	ALuint loadSound(const char*);
 	GLuint loadTexture(const char*);
diff --git a/src/misc.zig b/src/misc.zig
index 2bbb9f0..7ff4680 100644
--- a/src/misc.zig
+++ b/src/misc.zig
@@ -24,12 +24,15 @@ usingnamespace @cImport({
 });
 
 const Dir = std.fs.Dir;
+const TokenIterator = std.mem.TokenIterator;
 const al = @import("zeal");
 const allocPrint = std.fmt.allocPrint;
 const allocator = std.heap.c_allocator;
+const assert = std.debug.assert;
 const count = std.mem.count;
 const cwd = std.fs.cwd;
 const data_dir = @import("build_options").data_dir ++ [_]u8{ sep };
+const endsWith = std.mem.endsWith;
 const eql = std.mem.eql;
 const free = std.c.free;
 const join = std.fs.path.joinZ;
@@ -38,6 +41,7 @@ const parseFloat = std.fmt.parseFloat;
 const parseInt = std.fmt.parseInt;
 const sep = std.fs.path.sep;
 const span = std.mem.span;
+const startsWith = std.mem.startsWith;
 const std = @import("std");
 const tokenize = std.mem.tokenize;
 
@@ -146,6 +150,93 @@ export fn loadJoints(joints: [*]Joint) void {
     }
 }
 
+const Vertex = extern struct {
+    x: f32, y: f32, z: f32,
+};
+
+const Face = extern struct {
+    // Only support triangles
+    v: [3]u16,
+    r: f32, g: f32, b: f32,
+};
+
+const OffIterator = struct {
+    token_iterator: TokenIterator,
+
+    pub fn init(buffer: []const u8) OffIterator {
+        var self = .{ .token_iterator = tokenize(buffer, "\n") };
+        if (!endsWith(u8, self.token_iterator.next().?, "OFF"))
+            self.token_iterator.reset();
+        return self;
+    }
+
+    pub fn next(self: *OffIterator) ?TokenIterator {
+        while (self.token_iterator.next()) |line| {
+            var words = tokenize(line, " ");
+            if (words.next()) |word| { // not empty
+                if (!startsWith(u8, word, "#")) { // not comment
+                    words.reset();
+                    return words;
+                }
+            }
+        }
+        return null;
+    }
+};
+
+/// Load model from given OFF file.
+export fn loadModel(path: [*:0]const u8) extern struct {
+    vertices: extern struct {
+        ptr: [*]Vertex,
+        len: usize,
+    },
+    faces: extern struct {
+        ptr: [*]Face,
+        len: usize,
+    },
+} {
+    var dir = cwd().openDir(data_dir ++ "models", .{}) catch unreachable;
+    defer dir.close();
+    const file = dir.readFileAlloc(allocator, span(path), max_size)
+        catch unreachable;
+    defer allocator.free(file);
+    var lines = OffIterator.init(file);
+    var counts = lines.next().?;
+    const vertex_count = parseInt(usize, counts.next().?, 10) catch unreachable;
+    const face_count = parseInt(usize, counts.next().?, 10) catch unreachable;
+
+    const vertices = allocator.alloc(Vertex, vertex_count) catch unreachable;
+    for (vertices) |*vertex| {
+        var numbers = lines.next().?;
+        vertex.* = .{
+            .x = parseFloat(f32, numbers.next().?) catch unreachable,
+            .y = parseFloat(f32, numbers.next().?) catch unreachable,
+            .z = parseFloat(f32, numbers.next().?) catch unreachable,
+        };
+    }
+
+    const faces = allocator.alloc(Face, face_count) catch unreachable;
+    for (faces) |*face| {
+        var numbers = lines.next().?;
+        assert(eql(u8, numbers.next().?, "3"));
+        face.* = .{
+            .v = .{
+                parseInt(u16, numbers.next().?, 10) catch unreachable,
+                parseInt(u16, numbers.next().?, 10) catch unreachable,
+                parseInt(u16, numbers.next().?, 10) catch unreachable,
+            },
+            .r = parseFloat(f32, numbers.next().?) catch unreachable,
+            .g = parseFloat(f32, numbers.next().?) catch unreachable,
+            .b = parseFloat(f32, numbers.next().?) catch unreachable,
+        };
+    }
+
+    return .{
+        .vertices = .{ .ptr = vertices.ptr , .len = vertices.len },
+        .faces = .{ .ptr = faces.ptr , .len = faces.len },
+    };
+}
+
 const Muscle = extern struct {
     length: f32,
     initlen: f32,