Full Code of s-macke/VoxelSpace for AI

master 60d7ae12f232 cached
12 files
51.5 KB
15.6k tokens
27 symbols
1 requests
Download .txt
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 demonstration](images/webdemo.gif)

# **[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 in 1991](images/gunship2000-1991.gif)
*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 in 1992](images/comanche-1992.gif)
*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:

![periodic map](images/periodicmap.gif)

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.

![Line by line](images/linebyline.gif)

 * 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.

![rotation](images/rotate.gif)

```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. 

![front to back rendering](images/fronttoback.gif)

```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)

![C1W.png](images/thumbnails/C1W.png)
![D1.png](images/thumbnails/D1.png)

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

![C2W.png](images/thumbnails/C2W.png)
![D2.png](images/thumbnails/D2.png)

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

![C3.png](images/thumbnails/C3.png)
![D3.png](images/thumbnails/D3.png)

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

![C4.png](images/thumbnails/C4.png)
![D4.png](images/thumbnails/D4.png)

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

![C5W.png](images/thumbnails/C5W.png)
![D5.png](images/thumbnails/D5.png)

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

![C6W.png](images/thumbnails/C6W.png)
![D6.png](images/thumbnails/D6.png)

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

![C7W.png](images/thumbnails/C7W.png)
![D7.png](images/thumbnails/D7.png)

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

![C8.png](images/thumbnails/C8.png)
![D6.png](images/thumbnails/D6.png)

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

![C9W.png](images/thumbnails/C9W.png)
![D9.png](images/thumbnails/D9.png)

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

![C10W.png](images/thumbnails/C10W.png)
![D10.png](images/thumbnails/D10.png)

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

![C11W.png](images/thumbnails/C11W.png)
![D11.png](images/thumbnails/D11.png)

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

![C12W.png](images/thumbnails/C12W.png)
![D11.png](images/thumbnails/D11.png)

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

![C13.png](images/thumbnails/C13.png)
![D13.png](images/thumbnails/D13.png)

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

![C14.png](images/thumbnails/C14.png)
![D14.png](images/thumbnails/D14.png)

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

![C14W.png](images/thumbnails/C14W.png)
![D14.png](images/thumbnails/D14.png)

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

![C15.png](images/thumbnails/C15.png)
![D15.png](images/thumbnails/D15.png)

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

![C16W.png](images/thumbnails/C16W.png)
![D16.png](images/thumbnails/D16.png)

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

![C17W.png](images/thumbnails/C17W.png)
![D17.png](images/thumbnails/D17.png)

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

![C18W.png](images/thumbnails/C18W.png)
![D18.png](images/thumbnails/D18.png)

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

![C19W.png](images/thumbnails/C19W.png)
![D19.png](images/thumbnails/D19.png)

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

![C20W.png](images/thumbnails/C20W.png)
![D20.png](images/thumbnails/D20.png)

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

![C21.png](images/thumbnails/C21.png)
![D21.png](images/thumbnails/D21.png)

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

![C22W.png](images/thumbnails/C22W.png)
![D22.png](images/thumbnails/D22.png)

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

![C23W.png](images/thumbnails/C23W.png)
![D21.png](images/thumbnails/D21.png)

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

![C24W.png](images/thumbnails/C24W.png)
![D24.png](images/thumbnails/D24.png)

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

![C25W.png](images/thumbnails/C25W.png)
![D25.png](images/thumbnails/D25.png)

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

![C26W.png](images/thumbnails/C26W.png)
![D18.png](images/thumbnails/D18.png)

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

![C27W.png](images/thumbnails/C27W.png)
![D15.png](images/thumbnails/D15.png)

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

![C28W.png](images/thumbnails/C28W.png)
![D25.png](images/thumbnails/D25.png)

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

![C29W.png](images/thumbnails/C29W.png)
![D16.png](images/thumbnails/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;

}




Download .txt
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
Download .txt
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![web demonstration](images/webdemo.gif)\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.

Copied to clipboard!