Repository: s-macke/VoxelSpace
Branch: master
Commit: 60d7ae12f232
Files: 12
Total size: 51.5 KB
Directory structure:
gitextract_0m7qnuu5/
├── LICENSE
├── README.md
├── VoxelSpace.html
├── images/
│ └── thumbnails/
│ └── makethumbs
└── tools/
├── README.md
├── animations/
│ ├── anim.py
│ ├── drawmap.py
│ ├── run.sh
│ ├── rundrawmap.sh
│ └── runwebdemo.sh
├── comanche2extract/
│ └── extract.c
└── comanche3extract/
└── extract.c
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Sebastian Macke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Voxel Space

# **[Web Demo of the Voxel Space Engine][project demo]**
## History
Let us go back to the year 1992. The CPUs were 1000 times slower than today and the acceleration via a GPU was unknown or unaffordable. 3D games were calculated exclusively on the CPU and the rendering engine rendered filled polygons with a single color.

*Game Gunship 2000 published by MicroProse in 1991*
It was during that year [NovaLogic](http://www.novalogic.com/) published the game [Comanche](https://en.wikipedia.org/wiki/Comanche_(video_game_series)).

*Game Comanche published by NovaLogic in 1992*
The graphics were breathtaking for the time being and in my opinion 3 years ahead of its time. You see many more details such as textures on mountains and valleys, and for the first time a neat shading and even shadows. Sure, it's pixelated, but all games in those years were pixelated.
## Render algorithm
[Comanche](https://en.wikipedia.org/wiki/Comanche_(video_game_series)) uses a technique called [Voxel Space](https://en.wikipedia.org/wiki/Voxel_Space), which is based on the same ideas like [ray casting](https://en.wikipedia.org/wiki/Ray_casting). Hence the Voxel Space engine is a 2.5D engine, it doesn't have all the levels of freedom that a regular 3D engine offers.
### Height map and color map
The easiest way to represent a terrain is through a height map and color map. For the game Comanche a 1024 \* 1024 one byte height map and a 1024 \* 1024 one byte color map is used which you can download on this site. These maps are periodic:

Such maps limit the terrain to "one height per position on the map" - Complex geometries such as buildings or trees are not possible to represent. However, a great advantage of the colormap is, that it already contains the shading and shadows. The Voxel Space engine just takes the color and doesn't have to compute illumination during the render process.
### Basic algorithm
For a 3D engine the rendering algorithm is amazingly simple. The Voxel Space engine rasters the height and color map and draws vertical lines. The following figure demonstrate this technique.

* Clear Screen.
* To guarantee occlusion start from the back and render to the front. This is called painter algorithm.
* Determine the line on the map, which corresponds to the same optical distance from the observer. Consider the field of view and the [perspective projection](https://en.wikipedia.org/wiki/3D_projection) (Objects are smaller farther away)
* Raster the line so that it matches the number of columns of the screen.
* Retrieve the height and color from the 2D maps corresponding of the segment of the line.
* Perform the [perspective projection](https://en.wikipedia.org/wiki/3D_projection) for the height coordinate.
* Draw a vertical line with the corresponding color with the height retrieved from the perspective projection.
The core algorithm contains in its simplest form only a few lines of code (python syntax):
```python
def Render(p, height, horizon, scale_height, distance, screen_width, screen_height):
# Draw from back to the front (high z coordinate to low z coordinate)
for z in range(distance, 1, -1):
# Find line on map. This calculation corresponds to a field of view of 90°
pleft = Point(-z + p.x, -z + p.y)
pright = Point( z + p.x, -z + p.y)
# segment the line
dx = (pright.x - pleft.x) / screen_width
# Raster line and draw a vertical line for each segment
for i in range(0, screen_width):
height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])
pleft.x += dx
# Call the render function with the camera parameters:
# position, height, horizon line position,
# scaling factor for the height, the largest distance,
# screen width and the screen height parameter
Render( Point(0, 0), 50, 120, 120, 300, 800, 600 )
```
### Add rotation
With the algorithm above we can only view to the north. A different angle needs a few more lines of code to rotate the coordinates.

```python
def Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):
# precalculate viewing angle parameters
var sinphi = math.sin(phi);
var cosphi = math.cos(phi);
# Draw from back to the front (high z coordinate to low z coordinate)
for z in range(distance, 1, -1):
# Find line on map. This calculation corresponds to a field of view of 90°
pleft = Point(
(-cosphi*z - sinphi*z) + p.x,
( sinphi*z - cosphi*z) + p.y)
pright = Point(
( cosphi*z - sinphi*z) + p.x,
(-sinphi*z - cosphi*z) + p.y)
# segment the line
dx = (pright.x - pleft.x) / screen_width
dy = (pright.y - pleft.y) / screen_width
# Raster line and draw a vertical line for each segment
for i in range(0, screen_width):
height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])
pleft.x += dx
pleft.y += dy
# Call the render function with the camera parameters:
# position, viewing angle, height, horizon line position,
# scaling factor for the height, the largest distance,
# screen width and the screen height parameter
Render( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )
```
### More performance
There are of course a lot of tricks to achieve higher performance.
* Instead of drawing from back to the front we can draw from front to back. The advantage is, the we don't have to draw lines to the bottom of the screen every time because of occlusion. However, to guarantee occlusion we need an additional y-buffer. For every column, the highest y position is stored. Because we are drawing from the front to back, the visible part of the next line can only be larger then the highest line previously drawn.
* Level of Detail. Render more details in front but less details far away.

```python
def Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):
# precalculate viewing angle parameters
var sinphi = math.sin(phi);
var cosphi = math.cos(phi);
# initialize visibility array. Y position for each column on screen
ybuffer = np.zeros(screen_width)
for i in range(0, screen_width):
ybuffer[i] = screen_height
# Draw from front to the back (low z coordinate to high z coordinate)
dz = 1.
z = 1.
while z < distance
# Find line on map. This calculation corresponds to a field of view of 90°
pleft = Point(
(-cosphi*z - sinphi*z) + p.x,
( sinphi*z - cosphi*z) + p.y)
pright = Point(
( cosphi*z - sinphi*z) + p.x,
(-sinphi*z - cosphi*z) + p.y)
# segment the line
dx = (pright.x - pleft.x) / screen_width
dy = (pright.y - pleft.y) / screen_width
# Raster line and draw a vertical line for each segment
for i in range(0, screen_width):
height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon
DrawVerticalLine(i, height_on_screen, ybuffer[i], colormap[pleft.x, pleft.y])
if height_on_screen < ybuffer[i]:
ybuffer[i] = height_on_screen
pleft.x += dx
pleft.y += dy
# Go to next line and increase step size when you are far away
z += dz
dz += 0.2
# Call the render function with the camera parameters:
# position, viewing angle, height, horizon line position,
# scaling factor for the height, the largest distance,
# screen width and the screen height parameter
Render( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )
```
## Links
[Web Project demo][project demo] page
[Voxel terrain engine - an introduction](https://web.archive.org/web/20131113094653/http://www.codermind.com/articles/Voxel-terrain-engine-building-the-terrain.html)
[Personal website](http://www.simulationcorner.net)
## Maps
[color](maps/C1W.png),
[height](maps/D1.png)


[color](maps/C2W.png),
[height](maps/D2.png)


[color](maps/C3.png),
[height](maps/D3.png)


[color](maps/C4.png),
[height](maps/D4.png)


[color](maps/C5W.png),
[height](maps/D5.png)


[color](maps/C6W.png),
[height](maps/D6.png)


[color](maps/C7W.png),
[height](maps/D7.png)


[color](maps/C8.png),
[height](maps/D6.png)


[color](maps/C9W.png),
[height](maps/D9.png)


[color](maps/C10W.png),
[height](maps/D10.png)


[color](maps/C11W.png),
[height](maps/D11.png)


[color](maps/C12W.png),
[height](maps/D11.png)


[color](maps/C13.png),
[height](maps/D13.png)


[color](maps/C14.png),
[height](maps/D14.png)


[color](maps/C14W.png),
[height](maps/D14.png)


[color](maps/C15.png),
[height](maps/D15.png)


[color](maps/C16W.png),
[height](maps/D16.png)


[color](maps/C17W.png),
[height](maps/D17.png)


[color](maps/C18W.png),
[height](maps/D18.png)


[color](maps/C19W.png),
[height](maps/D19.png)


[color](maps/C20W.png),
[height](maps/D20.png)


[color](maps/C21.png),
[height](maps/D21.png)


[color](maps/C22W.png),
[height](maps/D22.png)


[color](maps/C23W.png),
[height](maps/D21.png)


[color](maps/C24W.png),
[height](maps/D24.png)


[color](maps/C25W.png),
[height](maps/D25.png)


[color](maps/C26W.png),
[height](maps/D18.png)


[color](maps/C27W.png),
[height](maps/D15.png)


[color](maps/C28W.png),
[height](maps/D25.png)


[color](maps/C29W.png),
[height](maps/D16.png)


## License
The software part of the repository is under the MIT license. Please read the license file for more information. Please keep in mind, that the Voxel Space technology might be still [patented](https://patents.justia.com/assignee/novalogic-inc) in some countries. The color and height maps are reverse engineered from the game Comanche and are therefore excluded from the license.
[project demo]: https://s-macke.github.io/VoxelSpace/VoxelSpace.html
================================================
FILE: VoxelSpace.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Voxel Space project demonstration</title>
<meta charset="UTF-8">
<meta name="description" content="Demonstration of the Voxel Space technique">
<meta name="author" content="Sebastian Macke">
<meta name="keywords" content="Voxel, VoxelSpace, Voxel Space, Comanche, landscape, rendering">
<style>
html, body {margin: 0; height: 100%; overflow: hidden}
canvas { width: 100%; height: 100%; }
a { color: white; }
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
z-index:100;
color: white;
font-family: "Arial", Times, serif;
font-size: 120%;
}
#fps {
float: right;
position: absolute;
top: 0px;
right: 10px;
z-index:100;
padding: 5px;
color: white;
font-family: "Arial", Times, serif;
font-size: 120%;
}
</style>
</head>
<body scroll="no">
<div id="fps">
</div>
<div id="info">
Fly controls
<b>WASD</b> or <b>Cursor Keys</b> or <b>left click</b> move, <b>R|F</b> up | down, <b>Q|E</b> pitch,
<br>
<select name="Mapselector" size="1" onchange="LoadMap(this.value);" value="C1W;D1">
<option value="C1W;D1">Map C1W</option>
<option value="C2W;D2">Map C2W</option>
<option value="C3;D3">Map C3</option>
<option value="C4;D4">Map C4</option>
<option value="C5W;D5">Map C5W</option>
<option value="C6W;D6">Map C6W</option>
<option value="C7W;D7">Map C7W</option>
<option value="C8;D6">Map C8</option>
<option value="C9W;D9">Map C9W</option>
<option value="C10W;D10">Map C10W</option>
<option value="C11W;D11">Map C11W</option>
<option value="C12W;D11">Map C12W</option>
<option value="C13;D13">Map C13</option>
<option value="C14;D14">Map C14</option>
<option value="C14W;D14">Map C14W</option>
<option value="C15;D15">Map C15</option>
<option value="C16W;D16">Map C16W</option>
<option value="C17W;D17">Map C17W</option>
<option value="C18W;D18">Map C18W</option>
<option value="C19W;D19">Map C19W</option>
<option value="C20W;D20">Map C20W</option>
<option value="C21;D21">Map C21</option>
<option value="C22W;D22">Map C22W</option>
<option value="C23W;D21">Map C23W</option>
<option value="C24W;D24">Map C24W</option>
<option value="C25W;D25">Map C25W</option>
<option value="C26W;D18">Map C26W</option>
<option value="C27W;D15">Map C27W</option>
<option value="C28W;D25">Map C28W</option>
<option value="C29W;D16">Map C29W</option>
</select>
<label for="distancerange">Distance</label>
<input id="distancerange" type="range" min="100" max="2000" step="1" onchange="camera.distance = this.value">
<a href="https://github.com/s-macke/VoxelSpace">Github project page</a>
</div>
<canvas id="fullscreenCanvas" width="800" height="400">
Your browser does not support the canvas element.
</canvas>
<script>
"use strict";
// ---------------------------------------------
// Viewer information
var camera =
{
x: 512., // x position on the map
y: 800., // y position on the map
height: 78., // height of the camera
angle: 0., // direction of the camera
horizon: 100., // horizon position (look up and down)
distance: 800 // distance of map
};
// ---------------------------------------------
// Landscape data
var map =
{
width: 1024,
height: 1024,
shift: 10, // power of two: 2^10 = 1024
altitude: new Uint8Array(1024*1024), // 1024 * 1024 byte array with height information
color: new Uint32Array(1024*1024) // 1024 * 1024 int array with RGB colors
};
// ---------------------------------------------
// Screen data
var screendata =
{
canvas: null,
context: null,
imagedata: null,
bufarray: null, // color data
buf8: null, // the same array but with bytes
buf32: null, // the same array but with 32-Bit words
backgroundcolor: 0xFFE09090
};
// ---------------------------------------------
// Keyboard and mouse interaction
var input =
{
forwardbackward: 0,
leftright: 0,
updown: 0,
lookup: false,
lookdown: false,
mouseposition: null,
keypressed: false
}
var updaterunning = false;
var time = new Date().getTime();
// for fps display
var timelastframe = new Date().getTime();
var frames = 0;
// Update the camera for next frame. Dependent on keypresses
function UpdateCamera()
{
var current = new Date().getTime();
input.keypressed = false;
if (input.leftright != 0)
{
camera.angle += input.leftright*0.1*(current-time)*0.03;
input.keypressed = true;
}
if (input.forwardbackward != 0)
{
camera.x -= input.forwardbackward * Math.sin(camera.angle) * (current-time)*0.03;
camera.y -= input.forwardbackward * Math.cos(camera.angle) * (current-time)*0.03;
input.keypressed = true;
}
if (input.updown != 0)
{
camera.height += input.updown * (current-time)*0.03;
input.keypressed = true;
}
if (input.lookup)
{
camera.horizon += 2 * (current-time)*0.03;
input.keypressed = true;
}
if (input.lookdown)
{
camera.horizon -= 2 * (current-time)*0.03;
input.keypressed = true;
}
// Collision detection. Don't fly below the surface.
var mapoffset = ((Math.floor(camera.y) & (map.width-1)) << map.shift) + (Math.floor(camera.x) & (map.height-1))|0;
if ((map.altitude[mapoffset]+10) > camera.height) camera.height = map.altitude[mapoffset] + 10;
time = current;
}
// ---------------------------------------------
// Keyboard and mouse event handlers
// ---------------------------------------------
// Keyboard and mouse event handlers
function GetMousePosition(e)
{
// fix for Chrome
if (e.type.startsWith('touch'))
{
return [e.targetTouches[0].pageX, e.targetTouches[0].pageY];
} else
{
return [e.pageX, e.pageY];
}
}
function DetectMouseDown(e)
{
input.forwardbackward = 3.;
input.mouseposition = GetMousePosition(e);
time = new Date().getTime();
if (!updaterunning) Draw();
return;
}
function DetectMouseUp()
{
input.mouseposition = null;
input.forwardbackward = 0;
input.leftright = 0;
input.updown = 0;
return;
}
function DetectMouseMove(e)
{
e.preventDefault();
if (input.mouseposition == null) return;
if (input.forwardbackward == 0) return;
var currentMousePosition = GetMousePosition(e);
input.leftright = (input.mouseposition[0] - currentMousePosition[0]) / window.innerWidth * 2;
camera.horizon = 100 + (input.mouseposition[1] - currentMousePosition[1]) / window.innerHeight * 500;
input.updown = (input.mouseposition[1] - currentMousePosition[1]) / window.innerHeight * 10;
}
function DetectKeysDown(e)
{
switch(e.keyCode)
{
case 37: // left cursor
case 65: // a
input.leftright = +1.;
break;
case 39: // right cursor
case 68: // d
input.leftright = -1.;
break;
case 38: // cursor up
case 87: // w
input.forwardbackward = 3.;
break;
case 40: // cursor down
case 83: // s
input.forwardbackward = -3.;
break;
case 82: // r
input.updown = +2.;
break;
case 70: // f
input.updown = -2.;
break;
case 69: // e
input.lookup = true;
break;
case 81: //q
input.lookdown = true;
break;
default:
return;
break;
}
if (!updaterunning) {
time = new Date().getTime();
Draw();
}
return false;
}
function DetectKeysUp(e)
{
switch(e.keyCode)
{
case 37: // left cursor
case 65: // a
input.leftright = 0;
break;
case 39: // right cursor
case 68: // d
input.leftright = 0;
break;
case 38: // cursor up
case 87: // w
input.forwardbackward = 0;
break;
case 40: // cursor down
case 83: // s
input.forwardbackward = 0;
break;
case 82: // r
input.updown = 0;
break;
case 70: // f
input.updown = 0;
break;
case 69: // e
input.lookup = false;
break;
case 81: //q
input.lookdown = false;
break;
default:
return;
break;
}
return false;
}
// ---------------------------------------------
// Fast way to draw vertical lines
function DrawVerticalLine(x, ytop, ybottom, col)
{
x = x|0;
ytop = ytop|0;
ybottom = ybottom|0;
col = col|0;
var buf32 = screendata.buf32;
var screenwidth = screendata.canvas.width|0;
if (ytop < 0) ytop = 0;
if (ytop > ybottom) return;
// get offset on screen for the vertical line
var offset = ((ytop * screenwidth) + x)|0;
for (var k = ytop|0; k < ybottom|0; k=k+1|0)
{
buf32[offset|0] = col|0;
offset = offset + screenwidth|0;
}
}
// ---------------------------------------------
// Basic screen handling
function DrawBackground()
{
var buf32 = screendata.buf32;
var color = screendata.backgroundcolor|0;
for (var i = 0; i < buf32.length; i++) buf32[i] = color|0;
}
// Show the back buffer on screen
function Flip()
{
screendata.imagedata.data.set(screendata.buf8);
screendata.context.putImageData(screendata.imagedata, 0, 0);
}
// ---------------------------------------------
// The main render routine
function Render()
{
var mapwidthperiod = map.width - 1;
var mapheightperiod = map.height - 1;
var screenwidth = screendata.canvas.width|0;
var sinang = Math.sin(camera.angle);
var cosang = Math.cos(camera.angle);
var hiddeny = new Int32Array(screenwidth);
for(var i=0; i<screendata.canvas.width|0; i=i+1|0)
hiddeny[i] = screendata.canvas.height;
var deltaz = 1.;
// Draw from front to back
for(var z=1; z<camera.distance; z+=deltaz)
{
// 90 degree field of view
var plx = -cosang * z - sinang * z;
var ply = sinang * z - cosang * z;
var prx = cosang * z - sinang * z;
var pry = -sinang * z - cosang * z;
var dx = (prx - plx) / screenwidth;
var dy = (pry - ply) / screenwidth;
plx += camera.x;
ply += camera.y;
var invz = 1. / z * 240.;
for(var i=0; i<screenwidth|0; i=i+1|0)
{
var mapoffset = ((Math.floor(ply) & mapwidthperiod) << map.shift) + (Math.floor(plx) & mapheightperiod)|0;
var heightonscreen = (camera.height - map.altitude[mapoffset]) * invz + camera.horizon|0;
DrawVerticalLine(i, heightonscreen|0, hiddeny[i], map.color[mapoffset]);
if (heightonscreen < hiddeny[i]) hiddeny[i] = heightonscreen;
plx += dx;
ply += dy;
}
deltaz += 0.005;
}
}
// ---------------------------------------------
// Draw the next frame
function Draw()
{
updaterunning = true;
UpdateCamera();
DrawBackground();
Render();
Flip();
frames++;
if (!input.keypressed)
{
updaterunning = false;
} else
{
window.requestAnimationFrame(Draw, 0);
}
}
// ---------------------------------------------
// Init routines
// Util class for downloading the png
function DownloadImagesAsync(urls) {
return new Promise(function(resolve, reject) {
var pending = urls.length;
var result = [];
if (pending === 0) {
resolve([]);
return;
}
urls.forEach(function(url, i) {
var image = new Image();
//image.addEventListener("load", function() {
image.onload = function() {
var tempcanvas = document.createElement("canvas");
var tempcontext = tempcanvas.getContext("2d");
tempcanvas.width = map.width;
tempcanvas.height = map.height;
tempcontext.drawImage(image, 0, 0, map.width, map.height);
result[i] = tempcontext.getImageData(0, 0, map.width, map.height).data;
pending--;
if (pending === 0) {
resolve(result);
}
};
image.src = url;
});
});
}
function LoadMap(filenames)
{
var files = filenames.split(";");
DownloadImagesAsync(["maps/"+files[0]+".png", "maps/"+files[1]+".png"]).then(OnLoadedImages);
}
function OnLoadedImages(result)
{
var datac = result[0];
var datah = result[1];
for(var i=0; i<map.width*map.height; i++)
{
map.color[i] = 0xFF000000 | (datac[(i<<2) + 2] << 16) | (datac[(i<<2) + 1] << 8) | datac[(i<<2) + 0];
map.altitude[i] = datah[i<<2];
}
Draw();
}
function OnResizeWindow()
{
screendata.canvas = document.getElementById('fullscreenCanvas');
var aspect = window.innerWidth / window.innerHeight;
screendata.canvas.width = window.innerWidth<800?window.innerWidth:800;
screendata.canvas.height = screendata.canvas.width / aspect;
if (screendata.canvas.getContext)
{
screendata.context = screendata.canvas.getContext('2d');
screendata.imagedata = screendata.context.createImageData(screendata.canvas.width, screendata.canvas.height);
}
screendata.bufarray = new ArrayBuffer(screendata.imagedata.width * screendata.imagedata.height * 4);
screendata.buf8 = new Uint8Array(screendata.bufarray);
screendata.buf32 = new Uint32Array(screendata.bufarray);
Draw();
}
function Init()
{
for(var i=0; i<map.width*map.height; i++)
{
map.color[i] = 0xFF007050;
map.altitude[i] = 0;
}
LoadMap("C1W;D1");
OnResizeWindow();
// set event handlers for keyboard, mouse, touchscreen and window resize
var canvas = document.getElementById("fullscreenCanvas");
window.onkeydown = DetectKeysDown;
window.onkeyup = DetectKeysUp;
canvas.onmousedown = DetectMouseDown;
canvas.onmouseup = DetectMouseUp;
canvas.onmousemove = DetectMouseMove;
canvas.ontouchstart = DetectMouseDown;
canvas.ontouchend = DetectMouseUp;
canvas.ontouchmove = DetectMouseMove;
window.onresize = OnResizeWindow;
window.setInterval(function(){
var current = new Date().getTime();
document.getElementById('fps').innerText = (frames / (current-timelastframe) * 1000).toFixed(1) + " fps";
frames = 0;
timelastframe = current;
}, 2000);
}
Init();
</script>
</body>
================================================
FILE: images/thumbnails/makethumbs
================================================
mogrify -strip -format png8 -path thumbs -quality 100 -thumbnail 150x150 ../maps/*.png
for f in *.png8;do mv $f ${f/png8/png};done
================================================
FILE: tools/README.md
================================================
Extract the maps from the Comanche games and generate the gif animations.
================================================
FILE: tools/animations/anim.py
================================================
import math
from PIL import Image, ImageDraw, ImageFont
import numpy as np
# -----------------------------------------------------
def Init(width, height, colorfilename, heightfilename):
global colorimg, colormap, heightimg, heightmap, pal, screen, screenmap
colorimg = Image.open("../../maps/" + colorfilename)
colormap = colorimg.load()
heightimg = Image.open("../../maps/" + heightfilename)
heightmap = heightimg.load()
# the colormap uses a color palette
pal = colorimg.palette.getdata()[1];
screen = Image.new("RGB", (width, height))
screenmap = screen.load()
# -----------------------------------------------------
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# -----------------------------------------------------
def DrawVerticalLine(x, ytop, ybottom, c):
if (ytop >= ybottom): return
if (ytop < 0): ytop = 0
rgb = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]
if screen.width != 700:
for j in range(math.floor(ytop), math.floor(ybottom)):
screenmap[x+1024, j] = rgb
else:
for j in range(math.floor(ytop), math.floor(ybottom)):
screenmap[x, j] = rgb
# -----------------------------------------------------
def Store():
Store.n -= 1
if Store.n <= 0:
screen.save("images/out%03d.gif" % (Store.idx,), "GIF")
Store.idx += 1
Store.n = Store.modulo
Store.modulo += 3
Store.idx = 0
Store.n = 0
Store.modulo = 10
# -----------------------------------------------------
def Horline(p1, p2, offset, scale, horizon, pmap):
n = 700
dx = (p2.x - p1.x) / n
dy = (p2.y - p1.y) / n
for i in range(0, n):
xi = math.floor(p1.x) & 1023
yi = math.floor(p1.y) & 1023
xmap = (math.floor(p1.x) - pmap.x+256)
ymap = (math.floor(p1.y) - pmap.y+256)
if screen.width != 700:
if (xmap<512) and (ymap<512):
if (xmap>=0) and (ymap>=0):
screenmap[xmap, ymap] = 0xFFFFFF
screenmap[xmap+512, ymap] = 0xFFFFFF
DrawVerticalLine(i, (heightmap[xi, yi]+offset)*scale+horizon, 511, colormap[xi, yi])
p1.x += dx
p1.y += dy
Store()
# -----------------------------------------------------
hidden = np.zeros(700)
def HorlineHidden(p1, p2, offset, scale, horizon, pmap):
n = 700
dx = (p2.x - p1.x) / n
dy = (p2.y - p1.y) / n
for i in range(0, n):
xi = math.floor(p1.x) & 1023
yi = math.floor(p1.y) & 1023
xmap = (math.floor(p1.x) - pmap.x+256)
ymap = (math.floor(p1.y) - pmap.y+256)
if screen.width != 700:
if (xmap<512) and (ymap<512):
if (xmap>=0) and (ymap>=0):
screenmap[xmap, ymap] = 0xFFFFFF
screenmap[xmap+512, ymap] = 0xFFFFFF
heightonscreen = (heightmap[xi, yi] + offset) * scale + horizon
DrawVerticalLine(i, heightonscreen, hidden[i], colormap[xi, yi])
if heightonscreen < hidden[i]:
hidden[i] = heightonscreen
p1.x += dx
p1.y += dy
# Store()
# -----------------------------------------------------
def Rotate(p, phi):
xtemp = p.x * math.cos(phi) + p.y * math.sin(phi)
ytemp = p.x * -math.sin(phi) + p.y * math.cos(phi)
return Point(xtemp, ytemp)
# -----------------------------------------------------
def ClearAndDrawMaps(pmap):
if screen.width == 700:
for j in range(0, 512):
for i in range(0, 700):
screenmap[i, j] = 0xffa366
else:
for j in range(0, 512):
for i in range(0, 512):
h = heightmap[(i+pmap.x-256) & 1023, (j+pmap.y-256) & 1023]
c = colormap[(i+pmap.x-256) & 1023, (j+pmap.y-256) & 1023]
screenmap[i, j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]
screenmap[i+512, j] = (h<<16) | (h << 8) | h
for j in range(0, 512):
for i in range(0, 700):
screenmap[i+1024, j] = 0xffa366
# -----------------------------------------------------
def DrawBackToFront(p, phi, height, distance, pmap):
ClearAndDrawMaps(pmap)
for z in range(distance, 1, -2):
pl = Point(-z, -z)
pr = Point( z, -z)
pl = Rotate(pl, phi)
pr = Rotate(pr, phi)
Horline(
Point(p.x + pl.x, p.y + pl.y),
Point(p.x + pr.x, p.y + pr.y),
-height, -1./z*240., +120, pmap)
# -----------------------------------------------------
def DrawFrontToBack(p, phi, height, distance, pmap):
ClearAndDrawMaps(pmap)
dz = 1
z = 5
for i in range(0, 700):
hidden[i] = 511
while z < distance:
pl = Point(-z, -z)
pr = Point( z, -z)
pl = Rotate(pl, phi)
pr = Rotate(pr, phi)
HorlineHidden(
Point(p.x + pl.x, p.y + pl.y),
Point(p.x + pr.x, p.y + pr.y),
-height, -1./z*240., +100, pmap)
z += dz
#dz += 0.1
# -----------------------------------------------------
#Init(512+512+700, 512, "C1W.png", "D1.png")
#DrawBackToFront(Point(230, 0), 0, 50, 240, Point(230, 0))
#DrawFrontToBack(Point(230, 0), 0, 50, 400, Point(230, 0))
#for i in range(0, 360, 10):
# print(i)
# DrawFrontToBack(Point(590, 175), i/180.*3.141592, 50, 240, Point(590, 175))
# Store.n=1
# Store()
Init(700, 512, "C7W.png", "D7.png")
for i in range(0, 64):
print(i)
DrawFrontToBack(Point(670, 500 - i*16), 0, 120, 800, Point(670, 500 - i*16))
Store.n=1
Store()
================================================
FILE: tools/animations/drawmap.py
================================================
import math
from PIL import Image, ImageDraw, ImageFont, ImageOps
# -----------------------------------------------------
colorimg = Image.open("C1W.png")
colormap = colorimg.load()
heightimg = Image.open("D1.png")
heightmap = heightimg.load()
pal = colorimg.palette.getdata()[1];
screen = Image.new("RGB", (512, 512))
screenmap = screen.load()
# -----------------------------------------------------
def Store():
screen.save("images/out%03d.gif" % (Store.idx,), "GIF")
Store.idx += 1
Store.idx = 0
# -----------------------------------------------------
def PrintBorder(title):
draw = ImageDraw.Draw(screen)
fnt = ImageFont.truetype('/usr/share/fonts/TTF/UbuntuMono-B.ttf', 20)
draw.rectangle([(128, 128), (128+256, 128+256)], outline=(255,255,255,128))
draw.text((200, 105), "1024 pixels", font=fnt, fill=(255,255,255,128))
draw.text((0, 0), title, font=fnt, fill=(255,255,255,128))
txt = Image.new('L', (512, 512))
d = ImageDraw.Draw(txt)
d.text((200, 105), "1024 pixels", font=fnt, fill=(255))
w = txt.rotate(-90)
screen.paste( ImageOps.colorize(w, (0,0,0), (255,255,255)), (0, 0), w)
def DrawPeriodicMap():
for j in range(0, 512):
for i in range(0, 512):
screenmap[i, j] = 0
for j in range(128, 128+256):
for i in range(128, 128+256):
c = colormap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]
screenmap[i, j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]
PrintBorder("Color Map")
Store()
for j in range(0, 512):
for i in range(0, 512):
screenmap[i, j] = 0
for j in range(0, 512):
for i in range(0, 512):
c = colormap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]
screenmap[i, j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]
PrintBorder("Color Map")
Store()
#--------------------
for j in range(0, 512):
for i in range(0, 512):
screenmap[i, j] = 0
for j in range(128, 128+256):
for i in range(128, 128+256):
h = heightmap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]
screenmap[i, j] = (h<<16) | (h << 8) | h
PrintBorder("Height Map")
Store()
for j in range(0, 512):
for i in range(0, 512):
screenmap[i, j] = 0
for j in range(0, 512):
for i in range(0, 512):
h = heightmap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]
screenmap[i, j] = (h<<16) | (h << 8) | h
PrintBorder("Height Map")
Store()
#--------------------
DrawPeriodicMap()
================================================
FILE: tools/animations/run.sh
================================================
set -e
mkdir -p images
echo delete
rm -f images/*.png
rm -f images/*.gif
echo anim
python anim.py
echo convert
gifsicle --colors 256 --optimize=2 --delay=10 --loop images/*.gif > anim.gif
================================================
FILE: tools/animations/rundrawmap.sh
================================================
set -e
mkdir -p images
echo delete
rm -f images/*.png
rm -f images/*.gif
echo anim
python drawmap.py
echo convert
gifsicle --colors 256 --optimize=2 --delay=200 --loop images/*.gif > periodicmap.gif
================================================
FILE: tools/animations/runwebdemo.sh
================================================
set -e
mkdir -p images
echo delete
rm -f images/*.png
rm -f images/*.gif
echo anim
python anim.py
echo convert
gifsicle --optimize=3 --scale=0.5 --delay=5 --loop images/*.gif > anim.gif
================================================
FILE: tools/comanche2extract/extract.c
================================================
/*
* This file contains the code to
* extract the maps from the game
* Comanche
*/
#include<png.h>
unsigned char buffer[1024*1024];
unsigned int palette[256];
unsigned char palettergb[256*3];
unsigned int imagewidth;
unsigned int imageheight;
const char *maps[] =
{
"C1W", "D1",
"C2W", "D2",
"C3", "D3",
"C4", "D4",
"C5W", "D5",
"C6W", "D6",
"C7W", "D7",
"C8", "D6",
"C9W", "D9",
"C10W", "D10",
"C11W", "D11",
"C12W", "D11",
"C13", "D13",
"C14", "D14",
"C14W", "D14",
"C15", "D15",
"C16W", "D16",
"C17W", "D17",
"C18W", "D18",
"C19W", "D19",
"C20W", "D20",
"C21", "D21",
"C22W", "D22",
"C23W", "D21",
"C24W", "D24",
"C25W", "D25",
"C26W", "D18",
"C27W", "D15",
"C28W", "D25",
"C29W", "D16"
};
void LoadDTA(const char *filename)
{
for(int i=0; i<1024*1024; i++)
{
buffer[i] = 0;
}
FILE *file = fopen(filename, "rb");
if (file == NULL)
{
printf("file not found %s\n", filename);
return;
}
unsigned int width=0, height=0;
fseek(file, 8, SEEK_SET);
fread(&width, 2, 1, file);
fread(&height, 2, 1, file);
printf("%i\n", width);
printf("%i\n", height);
width++;
height++;
imagewidth = width;
imageheight = height;
fseek(file, 0x80, SEEK_SET);
unsigned int pos = 0;
unsigned int line = 0;
unsigned int x=0;
unsigned int color=0;
while(1)
{
int len = 1;
fread(&x, 1, 1, file);
color = x;
if (x > 0xc0)
{
fread(&color, 1, 1, file);
len = x & 0x3F;
}
for(int i=0; i<len; i++)
{
buffer[pos + line*1024] = color;
pos++;
if (pos >= width)
{
line++;
pos = 0;
}
}
if (line == height) break;
}
printf("%i\n", line);
printf("%i\n", ftell(file));
fread(&x, 1, 1, file);
for(unsigned int i=0; i<256; i++)
{
fread(&r, 1, 1, file);
fread(&g, 1, 1, file);
fread(&b, 1, 1, file);
palette[i] = r | (g<<8) | (b<<16);
palettergb[3*i+0] = r;
palettergb[3*i+1] = g;
palettergb[3*i+2] = b;
}
fclose(file);
}
void SavePNG(const char *filename, bool usepalette)
{
FILE *fp = fopen(filename, "wb");
if (!fp)
{
return;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
return;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return;
}
png_init_io(png_ptr, fp);
// png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
if (usepalette)
{
png_set_IHDR(png_ptr, info_ptr,
imagewidth, imageheight, 8,
PNG_COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr, info_ptr, (png_color*)palettergb, 256);
} else
{
png_set_IHDR(png_ptr, info_ptr,
imagewidth, imageheight, 8,
PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
}
png_write_info(png_ptr, info_ptr);
for (int y=0; y<imageheight; y++)
{
png_bytep row_pointer = (png_bytep)&buffer[y*1024];
png_write_row(png_ptr, row_pointer);
}
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
}
void SaveMap(const char *filename, unsigned char *buffer, unsigned int size)
{
FILE *fp = fopen(filename, "wb");
if (!fp)
{
return;
}
fwrite(buffer, size, 1, fp);
fclose(fp);
}
int main()
{
for(int i=1; i<=25; i++)
{
if (i == 8) continue;
if (i == 12) continue;
if (i == 23) continue;
char filename[512];
sprintf(filename, "/path/to/Comanche/D%i", i);
LoadDTA(filename);
sprintf(filename, "D%i.png", i);
SavePNG(filename, false);
}
for(int i=0; i<30; i++)
{
char filename[512];
sprintf(filename, "/path/to/Comanche/%s.DTA", maps[2*i+0]);
LoadDTA(filename);
sprintf(filename, "data/map%i.color", i);
SaveMap(filename, buffer, 1024*1024);
sprintf(filename, "data/map%i.palette", i);
SaveMap(filename, palettergb, 256*3);
sprintf(filename, "/path/to/Comanche/%s.DTA", maps[2*i+1]);
LoadDTA(filename);
if (imagewidth == 512)
{
char temp[1024*1024];
for(int ii=0; ii<1024*1024; ii++) temp[ii] = buffer[ii];
for(int jj=0; jj<512; jj++)
for(int ii=0; ii<512; ii++)
{
buffer[((ii<<1)+0) + ((jj<<1)+0)*1024] = temp[((ii+0)&511) + ((jj+0)&511)*1024];
buffer[((ii<<1)+1) + ((jj<<1)+0)*1024] = (temp[((ii+0)&511) + ((jj+0)&511)*1024] + temp[((ii+1)&511) + ((jj+0)&511)*1024]) / 2;
buffer[((ii<<1)+0) + ((jj<<1)+1)*1024] = (temp[((ii+0)&511) + ((jj+0)&511)*1024] + temp[((ii+0)&511) + ((jj+1)&511)*1024]) / 2;
buffer[((ii<<1)+1) + ((jj<<1)+1)*1024] =
(
(int)
temp[((ii+0)&511) + ((jj+0)&511)*1024] +
temp[((ii+1)&511) + ((jj+0)&511)*1024] +
temp[((ii+0)&511) + ((jj+1)&511)*1024] +
temp[((ii+1)&511) + ((jj+1)&511)*1024]
) / 4;
}
}
sprintf(filename, "data/map%i.height", i);
SaveMap(filename, buffer, 1024*1024);
}
/*
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C1");
SavePNG("C1.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C2");
SavePNG("C2.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C3");
SavePNG("C3.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C4");
SavePNG("C4.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C5");
SavePNG("C5.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C6");
SavePNG("C6.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C7");
SavePNG("C7.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C8");
SavePNG("C8.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C9W");
SavePNG("C9W.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C10W");
SavePNG("C10W.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C11W");
SavePNG("C11W.png", true);
LoadDTA("/cygdrive/w/UseNext/COMANCHE/C12W");
SavePNG("C12W.png", true);
for(int i=1; i<=11; i++)
{
char filename[512];
sprintf(filename, "/cygdrive/w/UseNext/comanch2/D%i", i);
LoadDTA(filename);
sprintf(filename, "D%i.png", i);
SavePNG(filename, false);
}
*/
// LoadDTA("/cygdrive/w/UseNext/COMANCHE/CREDIT1");
// FILE *file = fopen("w:/UseNext/COMANCHE/C1", "rb");
// FILE *file = fopen("w:/UseNext/COMANCHE/D1", "rb");
return 0;
}
================================================
FILE: tools/comanche3extract/extract.c
================================================
/*
* This file contains the code to
* extract the maps from the game
* Comanche3
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include<png.h>
#include <fnmatch.h>
typedef unsigned char ubyte;
// -------------------------------------
unsigned int flength(FILE *fp)
{
fseek(fp, 0L, SEEK_END);
unsigned int sz = ftell(fp);
fseek(fp, 0L, SEEK_SET);
return sz;
}
// -------------------------------------
// Reverse engenineered uncompress from Editor.exe from Comanche Gold
unsigned int GetBits(unsigned int &bitbyte_offset, ubyte **compptr, unsigned int bit_length)
{
*compptr += bitbyte_offset >> 3;
unsigned int bit_offset = (bitbyte_offset & 7);
bitbyte_offset = bit_offset + bit_length;
unsigned int eax = *((unsigned int*)*compptr);
eax = (eax >> bit_offset) &((1<<bit_length)-1);
return eax;
}
void Decompress(ubyte *compptr, ubyte *rawptr, unsigned int size)
{
unsigned int bit_length = 9;
unsigned int bitbyte_offset = 0;
unsigned int table2_size = 0x200;
unsigned int table2_index = 0x102;
unsigned int var2 = 0;
unsigned int var3 = 0;
unsigned int table1_index = 0;
ubyte varb1 = 0;
ubyte varb2 = 0;
ubyte* rawptrend = rawptr + size;
ubyte table1[0x100000];
unsigned int stack[0x10000];
unsigned int stack_index = 0;
while(1)
{
if (rawptr >= rawptrend) break;
unsigned int eax = GetBits(bitbyte_offset, &compptr, bit_length);
if (eax == 0x101) break;
if (eax == 0x100) // reset
{
bit_length = 9;
table2_size = 0x200;
table2_index = 0x102;
eax = GetBits(bitbyte_offset, &compptr, bit_length);
table1_index = eax;
var3 = eax;
varb1 = eax&0xFF;
varb2 = eax&0xFF;
*rawptr = eax&0xFF;
rawptr++;
continue;
}
table1_index = eax;
var2 = eax;
if (eax >= table2_index)
{
eax = var3;
table1_index = eax;
eax = (eax & 0xFFFFFF00) | varb2;
stack[stack_index] = eax;
stack_index++;
}
loc_430036:
if (table1_index <= 0xFF)
{
eax = table1_index;
varb2 = eax&0xFF;
varb1 = eax&0xFF;
stack[stack_index] = eax;
stack_index++;
if (stack_index != 0)
{
do
{
stack_index--;
eax = stack[stack_index];
*rawptr = eax&0xFF;
rawptr++;
}
while (stack_index != 0);
}
table1[table2_index*3 +2] = varb1;
table1[table2_index*3 +0] = var3 & 0xFF;
table1[table2_index*3 +1] = (var3>>8) & 0xFF;
table2_index++;
var3 = var2;
if (table2_index < table2_size) continue;
if (bit_length >= 0xD) continue;
bit_length++;
table2_size <<= 1;
continue;
} else
{
eax = table1[table1_index*3+2];
stack[stack_index] = eax;
stack_index++;
eax = table1[table1_index*3+0] | (table1[table1_index*3+1] << 8);
table1_index = eax;
goto loc_430036;
}
}
}
// -------------------------------------
class Image
{
public:
unsigned int width;
unsigned int height;
ubyte *buffer;
bool usepalette;
unsigned char pal[256*3];
void SetWidthHeight(unsigned int w, unsigned int h)
{
width = w;
height = h;
buffer = new unsigned char[w*h];
}
Image()
{
buffer = NULL;
usepalette = true;
}
~Image()
{
if (buffer != NULL) delete[] buffer;
}
};
void SavePNG(const char *filename, const Image &image)
{
FILE *fp = fopen(filename, "wb");
if (!fp)
{
return;
}
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
return;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return;
}
png_init_io(png_ptr, fp);
// png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
if (image.usepalette)
{
png_set_IHDR(png_ptr, info_ptr,
image.width, image.height, 8,
PNG_COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr, info_ptr, (png_color*)image.pal, 256);
} else
{
png_set_IHDR(png_ptr, info_ptr,
image.width, image.height, 8,
PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
}
png_write_info(png_ptr, info_ptr);
for (int y=0; y<image.height; y++)
{
png_bytep row_pointer = (png_bytep)&image.buffer[y*image.width];
png_write_row(png_ptr, row_pointer);
}
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
}
// -------------------------------------
void SaveMap(const char *filename, const Image &image)
{
FILE *fp = fopen(filename, "wb");
if (!fp) return;
fwrite(image.buffer, image.width * image.height, 1, fp);
fclose(fp);
}
// -------------------------------------
bool LoadPCX(const char* filename, Image &image)
{
FILE *fp;
if ( (fp = fopen(filename, "rb")) == NULL ) return false;
unsigned int complen = flength(fp);
unsigned int head, width, height;
// read header
fread(&head, 4, 1, fp);
if (head != 0x31505A4C) //LZP1
{
fclose(fp);
return false;
}
fread(&width, 4, 1, fp);
fread(&height, 4, 1, fp);
printf(" width: %i\n", width);
printf(" height: %i\n", height);
image.SetWidthHeight(width, height);
fread(&image.pal, 256, 3, fp);
fread(&head, 4, 1, fp);
if (head == 0x50726543) // Cerp
{
fseek(fp, 0x400, SEEK_CUR);
}
complen -= ftell(fp);
unsigned char *comp;
if ( (comp = (unsigned char*)malloc(complen)) == NULL )
{
return false;
}
fread(comp, complen, 1, fp);
Decompress(comp, image.buffer, width*height);
fclose(fp);
return true;
}
int main(int argc,char *argv[])
{
DIR *dp;
struct dirent *ep;
dp = opendir (".");
if (dp != NULL)
{
while (ep = readdir (dp))
{
if (fnmatch("*.PCX", ep->d_name, 0) == 0)
{
fprintf(stderr, "Open: %s\n", ep->d_name);
Image image;
if (LoadPCX(ep->d_name, image))
{
char filename[1024]; filename[0] = 0;
strcpy(filename, ep->d_name);
unsigned int len = strlen(filename);
if (len >= 3)
{
filename[len-3] = 'p';
filename[len-2] = 'n';
filename[len-1] = 'g';
fprintf(stderr, "Save: %s\n", filename);
SavePNG(filename, image);
}
} else
{
printf(" Warning: Skipping file. Reading of file failed or file format unknown\n");
}
}
}
(void) closedir (dp);
}
else
perror ("Couldn't open the directory");
/*
Image image;
if (!LoadPCX("C2M3_C.PCX", image))
{
return 1;
}
SavePNG("C2M3_C.PNG", image);
*/
return 0;
}
gitextract_0m7qnuu5/
├── LICENSE
├── README.md
├── VoxelSpace.html
├── images/
│ └── thumbnails/
│ └── makethumbs
└── tools/
├── README.md
├── animations/
│ ├── anim.py
│ ├── drawmap.py
│ ├── run.sh
│ ├── rundrawmap.sh
│ └── runwebdemo.sh
├── comanche2extract/
│ └── extract.c
└── comanche3extract/
└── extract.c
SYMBOL INDEX (27 symbols across 4 files)
FILE: tools/animations/anim.py
function Init (line 7) | def Init(width, height, colorfilename, heightfilename):
class Point (line 23) | class Point:
method __init__ (line 24) | def __init__(self, x=0, y=0):
function DrawVerticalLine (line 30) | def DrawVerticalLine(x, ytop, ybottom, c):
function Store (line 45) | def Store():
function Horline (line 58) | def Horline(p1, p2, offset, scale, horizon, pmap):
function HorlineHidden (line 81) | def HorlineHidden(p1, p2, offset, scale, horizon, pmap):
function Rotate (line 105) | def Rotate(p, phi):
function ClearAndDrawMaps (line 112) | def ClearAndDrawMaps(pmap):
function DrawBackToFront (line 131) | def DrawBackToFront(p, phi, height, distance, pmap):
function DrawFrontToBack (line 145) | def DrawFrontToBack(p, phi, height, distance, pmap):
FILE: tools/animations/drawmap.py
function Store (line 18) | def Store():
function PrintBorder (line 25) | def PrintBorder(title):
function DrawPeriodicMap (line 40) | def DrawPeriodicMap():
FILE: tools/comanche2extract/extract.c
function LoadDTA (line 49) | void LoadDTA(const char *filename)
function SavePNG (line 126) | void SavePNG(const char *filename, bool usepalette)
function SaveMap (line 190) | void SaveMap(const char *filename, unsigned char *buffer, unsigned int s...
function main (line 201) | int main()
FILE: tools/comanche3extract/extract.c
type ubyte (line 16) | typedef unsigned char ubyte;
function flength (line 19) | unsigned int flength(FILE *fp)
function GetBits (line 30) | unsigned int GetBits(unsigned int &bitbyte_offset, ubyte **compptr, unsi...
function Decompress (line 40) | void Decompress(ubyte *compptr, ubyte *rawptr, unsigned int size)
function class (line 128) | class Image
function SavePNG (line 155) | void SavePNG(const char *filename, const Image &image)
function SaveMap (line 219) | void SaveMap(const char *filename, const Image &image)
function LoadPCX (line 229) | bool LoadPCX(const char* filename, Image &image)
function main (line 269) | int main(int argc,char *argv[])
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (56K chars).
[
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2017 Sebastian Macke\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 12697,
"preview": "# Voxel Space\n\n\n\n# **[Web Demo of the Voxel Space Engine][project demo]**\n\n## Hi"
},
{
"path": "VoxelSpace.html",
"chars": 14758,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Voxel Space project demonstration</title>\n <meta charset=\"UTF-8\">\n <meta "
},
{
"path": "images/thumbnails/makethumbs",
"chars": 131,
"preview": "mogrify -strip -format png8 -path thumbs -quality 100 -thumbnail 150x150 ../maps/*.png\nfor f in *.png8;do mv $f ${f/png8"
},
{
"path": "tools/README.md",
"chars": 73,
"preview": "Extract the maps from the Comanche games and generate the gif animations."
},
{
"path": "tools/animations/anim.py",
"chars": 5626,
"preview": "import math\nfrom PIL import Image, ImageDraw, ImageFont\nimport numpy as np\n\n# ------------------------------------------"
},
{
"path": "tools/animations/drawmap.py",
"chars": 2585,
"preview": "import math\nfrom PIL import Image, ImageDraw, ImageFont, ImageOps\n\n# ---------------------------------------------------"
},
{
"path": "tools/animations/run.sh",
"chars": 192,
"preview": "set -e\n\nmkdir -p images\n\necho delete\nrm -f images/*.png\nrm -f images/*.gif\n\necho anim\npython anim.py\n\necho convert\ngifsi"
},
{
"path": "tools/animations/rundrawmap.sh",
"chars": 204,
"preview": "set -e\n\nmkdir -p images\n\necho delete\nrm -f images/*.png\nrm -f images/*.gif\n\necho anim\npython drawmap.py\n\necho convert\n\ng"
},
{
"path": "tools/animations/runwebdemo.sh",
"chars": 190,
"preview": "set -e\n\nmkdir -p images\n\necho delete\nrm -f images/*.png\nrm -f images/*.gif\n\necho anim\npython anim.py\n\necho convert\ngifsi"
},
{
"path": "tools/comanche2extract/extract.c",
"chars": 7191,
"preview": "/*\n * This file contains the code to \n * extract the maps from the game \n * Comanche\n */\n\n#include<png.h>\n\nunsigned c"
},
{
"path": "tools/comanche3extract/extract.c",
"chars": 7997,
"preview": "/*\n * This file contains the code to \n * extract the maps from the game \n * Comanche3\n */\n\n#include <stdlib.h>\n#inclu"
}
]
About this extraction
This page contains the full source code of the s-macke/VoxelSpace GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (51.5 KB), approximately 15.6k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.