[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Sebastian Macke\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Voxel Space\n\n![web demonstration](images/webdemo.gif)\n\n# **[Web Demo of the Voxel Space Engine][project demo]**\n\n## History\n\nLet 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.\n\n![Game Gunship 2000 in 1991](images/gunship2000-1991.gif)\n*Game Gunship 2000 published by MicroProse in 1991*\n\nIt was during that year [NovaLogic](http://www.novalogic.com/) published the game [Comanche](https://en.wikipedia.org/wiki/Comanche_(video_game_series)).\n\n![Game Comanche in 1992](images/comanche-1992.gif)\n*Game Comanche published by NovaLogic in 1992*\n\nThe 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.\n\n## Render algorithm\n\n[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.\n\n### Height map and color map\n\nThe 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:\n\n![periodic map](images/periodicmap.gif)\n\nSuch 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.\n\n### Basic algorithm\nFor 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.\n\n![Line by line](images/linebyline.gif)\n\n * Clear Screen.\n * To guarantee occlusion start from the back and render to the front. This is called painter algorithm.\n * 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)\n * Raster the line so that it matches the number of columns of the screen.\n * Retrieve the height and color from the 2D maps corresponding of the segment of the line.\n * Perform the [perspective projection](https://en.wikipedia.org/wiki/3D_projection) for the height coordinate.\n * Draw a vertical line with the corresponding color with the height retrieved from the perspective projection.\n\nThe core algorithm contains in its simplest form only a few lines of code (python syntax):\n\n```python\ndef Render(p, height, horizon, scale_height, distance, screen_width, screen_height):\n    # Draw from back to the front (high z coordinate to low z coordinate)\n    for z in range(distance, 1, -1):\n        # Find line on map. This calculation corresponds to a field of view of 90°\n        pleft  = Point(-z + p.x, -z + p.y)\n        pright = Point( z + p.x, -z + p.y)\n        # segment the line\n        dx = (pright.x - pleft.x) / screen_width\n        # Raster line and draw a vertical line for each segment\n        for i in range(0, screen_width):\n            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon\n            DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])\n            pleft.x += dx\n\n# Call the render function with the camera parameters:\n# position, height, horizon line position,\n# scaling factor for the height, the largest distance, \n# screen width and the screen height parameter\nRender( Point(0, 0), 50, 120, 120, 300, 800, 600 )\n```\n\n### Add rotation\n\nWith the algorithm above we can only view to the north. A different angle needs a few more lines of code to rotate the coordinates.\n\n![rotation](images/rotate.gif)\n\n```python\ndef Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):\n    # precalculate viewing angle parameters\n    var sinphi = math.sin(phi);\n    var cosphi = math.cos(phi);\n\n    # Draw from back to the front (high z coordinate to low z coordinate)\n    for z in range(distance, 1, -1):\n\n        # Find line on map. This calculation corresponds to a field of view of 90°\n        pleft = Point(\n            (-cosphi*z - sinphi*z) + p.x,\n            ( sinphi*z - cosphi*z) + p.y)\n        pright = Point(\n            ( cosphi*z - sinphi*z) + p.x,\n            (-sinphi*z - cosphi*z) + p.y)\n        \n        # segment the line\n        dx = (pright.x - pleft.x) / screen_width\n        dy = (pright.y - pleft.y) / screen_width\n\n        # Raster line and draw a vertical line for each segment\n        for i in range(0, screen_width):\n            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon\n            DrawVerticalLine(i, height_on_screen, screen_height, colormap[pleft.x, pleft.y])\n            pleft.x += dx\n            pleft.y += dy\n\n# Call the render function with the camera parameters:\n# position, viewing angle, height, horizon line position, \n# scaling factor for the height, the largest distance, \n# screen width and the screen height parameter\nRender( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )\n```\n\n### More performance\n\nThere are of course a lot of tricks to achieve higher performance.\n\n* 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.\n* Level of Detail. Render more details in front but less details far away. \n\n![front to back rendering](images/fronttoback.gif)\n\n```python\ndef Render(p, phi, height, horizon, scale_height, distance, screen_width, screen_height):\n    # precalculate viewing angle parameters\n    var sinphi = math.sin(phi);\n    var cosphi = math.cos(phi);\n    \n    # initialize visibility array. Y position for each column on screen \n    ybuffer = np.zeros(screen_width)\n    for i in range(0, screen_width):\n        ybuffer[i] = screen_height\n\n    # Draw from front to the back (low z coordinate to high z coordinate)\n    dz = 1.\n    z = 1.\n    while z < distance\n        # Find line on map. This calculation corresponds to a field of view of 90°\n        pleft = Point(\n            (-cosphi*z - sinphi*z) + p.x,\n            ( sinphi*z - cosphi*z) + p.y)\n        pright = Point(\n            ( cosphi*z - sinphi*z) + p.x,\n            (-sinphi*z - cosphi*z) + p.y)\n\n        # segment the line\n        dx = (pright.x - pleft.x) / screen_width\n        dy = (pright.y - pleft.y) / screen_width\n\n        # Raster line and draw a vertical line for each segment\n        for i in range(0, screen_width):\n            height_on_screen = (height - heightmap[pleft.x, pleft.y]) / z * scale_height. + horizon\n            DrawVerticalLine(i, height_on_screen, ybuffer[i], colormap[pleft.x, pleft.y])\n            if height_on_screen < ybuffer[i]:\n                ybuffer[i] = height_on_screen\n            pleft.x += dx\n            pleft.y += dy\n\n        # Go to next line and increase step size when you are far away\n        z += dz\n        dz += 0.2\n\n# Call the render function with the camera parameters:\n# position, viewing angle, height, horizon line position, \n# scaling factor for the height, the largest distance, \n# screen width and the screen height parameter\nRender( Point(0, 0), 0, 50, 120, 120, 300, 800, 600 )\n```\n\n## Links\n\n[Web Project demo][project demo] page\n\n[Voxel terrain engine - an introduction](https://web.archive.org/web/20131113094653/http://www.codermind.com/articles/Voxel-terrain-engine-building-the-terrain.html)\n\n[Personal website](http://www.simulationcorner.net)\n\n\n\n## Maps\n[color](maps/C1W.png),\n[height](maps/D1.png)\n\n![C1W.png](images/thumbnails/C1W.png)\n![D1.png](images/thumbnails/D1.png)\n\n[color](maps/C2W.png),\n[height](maps/D2.png)\n\n![C2W.png](images/thumbnails/C2W.png)\n![D2.png](images/thumbnails/D2.png)\n\n[color](maps/C3.png),\n[height](maps/D3.png)\n\n![C3.png](images/thumbnails/C3.png)\n![D3.png](images/thumbnails/D3.png)\n\n[color](maps/C4.png),\n[height](maps/D4.png)\n\n![C4.png](images/thumbnails/C4.png)\n![D4.png](images/thumbnails/D4.png)\n\n[color](maps/C5W.png),\n[height](maps/D5.png)\n\n![C5W.png](images/thumbnails/C5W.png)\n![D5.png](images/thumbnails/D5.png)\n\n[color](maps/C6W.png),\n[height](maps/D6.png)\n\n![C6W.png](images/thumbnails/C6W.png)\n![D6.png](images/thumbnails/D6.png)\n\n[color](maps/C7W.png),\n[height](maps/D7.png)\n\n![C7W.png](images/thumbnails/C7W.png)\n![D7.png](images/thumbnails/D7.png)\n\n[color](maps/C8.png),\n[height](maps/D6.png)\n\n![C8.png](images/thumbnails/C8.png)\n![D6.png](images/thumbnails/D6.png)\n\n[color](maps/C9W.png),\n[height](maps/D9.png)\n\n![C9W.png](images/thumbnails/C9W.png)\n![D9.png](images/thumbnails/D9.png)\n\n[color](maps/C10W.png),\n[height](maps/D10.png)\n\n![C10W.png](images/thumbnails/C10W.png)\n![D10.png](images/thumbnails/D10.png)\n\n[color](maps/C11W.png),\n[height](maps/D11.png)\n\n![C11W.png](images/thumbnails/C11W.png)\n![D11.png](images/thumbnails/D11.png)\n\n[color](maps/C12W.png),\n[height](maps/D11.png)\n\n![C12W.png](images/thumbnails/C12W.png)\n![D11.png](images/thumbnails/D11.png)\n\n[color](maps/C13.png),\n[height](maps/D13.png)\n\n![C13.png](images/thumbnails/C13.png)\n![D13.png](images/thumbnails/D13.png)\n\n[color](maps/C14.png),\n[height](maps/D14.png)\n\n![C14.png](images/thumbnails/C14.png)\n![D14.png](images/thumbnails/D14.png)\n\n[color](maps/C14W.png),\n[height](maps/D14.png)\n\n![C14W.png](images/thumbnails/C14W.png)\n![D14.png](images/thumbnails/D14.png)\n\n[color](maps/C15.png),\n[height](maps/D15.png)\n\n![C15.png](images/thumbnails/C15.png)\n![D15.png](images/thumbnails/D15.png)\n\n[color](maps/C16W.png),\n[height](maps/D16.png)\n\n![C16W.png](images/thumbnails/C16W.png)\n![D16.png](images/thumbnails/D16.png)\n\n[color](maps/C17W.png),\n[height](maps/D17.png)\n\n![C17W.png](images/thumbnails/C17W.png)\n![D17.png](images/thumbnails/D17.png)\n\n[color](maps/C18W.png),\n[height](maps/D18.png)\n\n![C18W.png](images/thumbnails/C18W.png)\n![D18.png](images/thumbnails/D18.png)\n\n[color](maps/C19W.png),\n[height](maps/D19.png)\n\n![C19W.png](images/thumbnails/C19W.png)\n![D19.png](images/thumbnails/D19.png)\n\n[color](maps/C20W.png),\n[height](maps/D20.png)\n\n![C20W.png](images/thumbnails/C20W.png)\n![D20.png](images/thumbnails/D20.png)\n\n[color](maps/C21.png),\n[height](maps/D21.png)\n\n![C21.png](images/thumbnails/C21.png)\n![D21.png](images/thumbnails/D21.png)\n\n[color](maps/C22W.png),\n[height](maps/D22.png)\n\n![C22W.png](images/thumbnails/C22W.png)\n![D22.png](images/thumbnails/D22.png)\n\n[color](maps/C23W.png),\n[height](maps/D21.png)\n\n![C23W.png](images/thumbnails/C23W.png)\n![D21.png](images/thumbnails/D21.png)\n\n[color](maps/C24W.png),\n[height](maps/D24.png)\n\n![C24W.png](images/thumbnails/C24W.png)\n![D24.png](images/thumbnails/D24.png)\n\n[color](maps/C25W.png),\n[height](maps/D25.png)\n\n![C25W.png](images/thumbnails/C25W.png)\n![D25.png](images/thumbnails/D25.png)\n\n[color](maps/C26W.png),\n[height](maps/D18.png)\n\n![C26W.png](images/thumbnails/C26W.png)\n![D18.png](images/thumbnails/D18.png)\n\n[color](maps/C27W.png),\n[height](maps/D15.png)\n\n![C27W.png](images/thumbnails/C27W.png)\n![D15.png](images/thumbnails/D15.png)\n\n[color](maps/C28W.png),\n[height](maps/D25.png)\n\n![C28W.png](images/thumbnails/C28W.png)\n![D25.png](images/thumbnails/D25.png)\n\n[color](maps/C29W.png),\n[height](maps/D16.png)\n\n![C29W.png](images/thumbnails/C29W.png)\n![D16.png](images/thumbnails/D16.png)\n\n## License\n\nThe 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.\n\n[project demo]: https://s-macke.github.io/VoxelSpace/VoxelSpace.html\n"
  },
  {
    "path": "VoxelSpace.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Voxel Space project demonstration</title>\n    <meta charset=\"UTF-8\">\n    <meta name=\"description\" content=\"Demonstration of the Voxel Space technique\">\n    <meta name=\"author\" content=\"Sebastian Macke\">\n    <meta name=\"keywords\" content=\"Voxel, VoxelSpace, Voxel Space, Comanche, landscape, rendering\">\n    <style>\n        html, body {margin: 0; height: 100%; overflow: hidden}\n        canvas { width: 100%; height: 100%; }\n        a { color: white; }\n        #info {\n            position: absolute;\n            top: 0px;\n            width: 100%;\n            padding: 5px;\n            z-index:100;\n            color: white;\n            font-family: \"Arial\", Times, serif;\n            font-size: 120%;\n            }\n        #fps {\n            float: right;\n            position: absolute;\n            top: 0px;\n            right: 10px;\n            z-index:100;\n            padding: 5px;\n            color: white;\n            font-family: \"Arial\", Times, serif;\n            font-size: 120%;\n            }\n    </style>\n</head>\n\n<body scroll=\"no\">\n\n<div id=\"fps\">\n</div>\n\n<div id=\"info\">\n    Fly controls\n    <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,\n<br>\n\n<select name=\"Mapselector\" size=\"1\" onchange=\"LoadMap(this.value);\" value=\"C1W;D1\">\n<option value=\"C1W;D1\">Map C1W</option>\n<option value=\"C2W;D2\">Map C2W</option>\n<option value=\"C3;D3\">Map C3</option>\n<option value=\"C4;D4\">Map C4</option>\n<option value=\"C5W;D5\">Map C5W</option>\n<option value=\"C6W;D6\">Map C6W</option>\n<option value=\"C7W;D7\">Map C7W</option>\n<option value=\"C8;D6\">Map C8</option>\n<option value=\"C9W;D9\">Map C9W</option>\n<option value=\"C10W;D10\">Map C10W</option>\n<option value=\"C11W;D11\">Map C11W</option>\n<option value=\"C12W;D11\">Map C12W</option>\n<option value=\"C13;D13\">Map C13</option>\n<option value=\"C14;D14\">Map C14</option>\n<option value=\"C14W;D14\">Map C14W</option>\n<option value=\"C15;D15\">Map C15</option>\n<option value=\"C16W;D16\">Map C16W</option>\n<option value=\"C17W;D17\">Map C17W</option>\n<option value=\"C18W;D18\">Map C18W</option>\n<option value=\"C19W;D19\">Map C19W</option>\n<option value=\"C20W;D20\">Map C20W</option>\n<option value=\"C21;D21\">Map C21</option>\n<option value=\"C22W;D22\">Map C22W</option>\n<option value=\"C23W;D21\">Map C23W</option>\n<option value=\"C24W;D24\">Map C24W</option>\n<option value=\"C25W;D25\">Map C25W</option>\n<option value=\"C26W;D18\">Map C26W</option>\n<option value=\"C27W;D15\">Map C27W</option>\n<option value=\"C28W;D25\">Map C28W</option>\n<option value=\"C29W;D16\">Map C29W</option>\n</select>\n\n<label for=\"distancerange\">Distance</label>\n<input id=\"distancerange\" type=\"range\" min=\"100\" max=\"2000\" step=\"1\" onchange=\"camera.distance = this.value\">\n\n<a href=\"https://github.com/s-macke/VoxelSpace\">Github project page</a>\n\n</div>\n\n<canvas id=\"fullscreenCanvas\" width=\"800\" height=\"400\">\n    Your browser does not support the canvas element.\n</canvas>\n\n<script>\n\"use strict\";\n\n// ---------------------------------------------\n// Viewer information\n\nvar camera =\n{\n    x:        512., // x position on the map\n    y:        800., // y position on the map\n    height:    78., // height of the camera\n    angle:      0., // direction of the camera\n    horizon:  100., // horizon position (look up and down)\n    distance: 800   // distance of map\n};\n\n// ---------------------------------------------\n// Landscape data\n\nvar map =\n{\n    width:    1024,\n    height:   1024,\n    shift:    10,  // power of two: 2^10 = 1024\n    altitude: new Uint8Array(1024*1024), // 1024 * 1024 byte array with height information\n    color:    new Uint32Array(1024*1024) // 1024 * 1024 int array with RGB colors\n};\n\n// ---------------------------------------------\n// Screen data\n\nvar screendata =\n{\n    canvas:    null,\n    context:   null,\n    imagedata: null,\n\n    bufarray:  null, // color data\n    buf8:      null, // the same array but with bytes\n    buf32:     null, // the same array but with 32-Bit words\n\n    backgroundcolor: 0xFFE09090\n};\n\n// ---------------------------------------------\n// Keyboard and mouse interaction\n\nvar input =\n{\n    forwardbackward: 0,\n    leftright:       0,\n    updown:          0,\n    lookup:          false,\n    lookdown:        false,\n    mouseposition:   null,\n    keypressed:      false\n}\n\nvar updaterunning = false;\n\nvar time = new Date().getTime();\n\n\n// for fps display\nvar timelastframe = new Date().getTime();\nvar frames = 0;\n\n// Update the camera for next frame. Dependent on keypresses\nfunction UpdateCamera()\n{\n    var current = new Date().getTime();\n\n    input.keypressed = false;\n    if (input.leftright != 0)\n    {\n        camera.angle += input.leftright*0.1*(current-time)*0.03;\n        input.keypressed = true;\n    }\n    if (input.forwardbackward != 0)\n    {\n        camera.x -= input.forwardbackward * Math.sin(camera.angle) * (current-time)*0.03;\n        camera.y -= input.forwardbackward * Math.cos(camera.angle) * (current-time)*0.03;\n        input.keypressed = true;\n    }\n    if (input.updown != 0)\n    {\n        camera.height += input.updown * (current-time)*0.03;\n        input.keypressed = true;\n    }\n    if (input.lookup)\n    {\n        camera.horizon += 2 * (current-time)*0.03;\n        input.keypressed = true;\n    }\n    if (input.lookdown)\n    {\n        camera.horizon -= 2 * (current-time)*0.03;\n        input.keypressed = true;\n    }\n\n    // Collision detection. Don't fly below the surface.\n    var mapoffset = ((Math.floor(camera.y) & (map.width-1)) << map.shift) + (Math.floor(camera.x) & (map.height-1))|0;\n    if ((map.altitude[mapoffset]+10) > camera.height) camera.height = map.altitude[mapoffset] + 10;\n\n    time = current;\n\n}\n\n// ---------------------------------------------\n// Keyboard and mouse event handlers\n// ---------------------------------------------\n// Keyboard and mouse event handlers\n\nfunction GetMousePosition(e)\n{\n    // fix for Chrome\n    if (e.type.startsWith('touch'))\n    {\n        return [e.targetTouches[0].pageX, e.targetTouches[0].pageY];\n    } else\n    {\n        return [e.pageX, e.pageY];\n    }\n}\n\n\nfunction DetectMouseDown(e)\n{\n    input.forwardbackward = 3.;\n    input.mouseposition = GetMousePosition(e);\n    time = new Date().getTime();\n\n    if (!updaterunning) Draw();\n    return;\n}\n\nfunction DetectMouseUp()\n{\n    input.mouseposition = null;\n    input.forwardbackward = 0;\n    input.leftright = 0;\n    input.updown = 0;\n    return;\n}\n\nfunction DetectMouseMove(e)\n{\n    e.preventDefault();\n    if (input.mouseposition == null) return;\n    if (input.forwardbackward == 0) return;\n\n    var currentMousePosition = GetMousePosition(e);\n\n    input.leftright = (input.mouseposition[0] - currentMousePosition[0]) / window.innerWidth * 2;\n    camera.horizon  = 100 + (input.mouseposition[1] - currentMousePosition[1]) / window.innerHeight * 500;\n    input.updown    = (input.mouseposition[1] - currentMousePosition[1]) / window.innerHeight * 10;\n}\n\n\nfunction DetectKeysDown(e)\n{\n    switch(e.keyCode)\n    {\n    case 37:    // left cursor\n    case 65:    // a\n        input.leftright = +1.;\n        break;\n    case 39:    // right cursor\n    case 68:    // d\n        input.leftright = -1.;\n        break;\n    case 38:    // cursor up\n    case 87:    // w\n        input.forwardbackward = 3.;\n        break;\n    case 40:    // cursor down\n    case 83:    // s\n        input.forwardbackward = -3.;\n        break;\n    case 82:    // r\n        input.updown = +2.;\n        break;\n    case 70:    // f\n        input.updown = -2.;\n        break;\n    case 69:    // e\n        input.lookup = true;\n        break;\n    case 81:    //q\n        input.lookdown = true;\n        break;\n    default:\n        return;\n        break;\n    }\n\n    if (!updaterunning) {\n        time = new Date().getTime();\n        Draw();\n    }\n    return false;\n}\n\nfunction DetectKeysUp(e)\n{\n    switch(e.keyCode)\n    {\n    case 37:    // left cursor\n    case 65:    // a\n        input.leftright = 0;\n        break;\n    case 39:    // right cursor\n    case 68:    // d\n        input.leftright = 0;\n        break;\n    case 38:    // cursor up\n    case 87:    // w\n        input.forwardbackward = 0;\n        break;\n    case 40:    // cursor down\n    case 83:    // s\n        input.forwardbackward = 0;\n        break;\n    case 82:    // r\n        input.updown = 0;\n        break;\n    case 70:    // f\n        input.updown = 0;\n        break;\n    case 69:    // e\n        input.lookup = false;\n        break;\n    case 81:    //q\n        input.lookdown = false;\n        break;\n    default:\n        return;\n        break;\n    }\n    return false;\n}\n\n// ---------------------------------------------\n// Fast way to draw vertical lines\n\nfunction DrawVerticalLine(x, ytop, ybottom, col)\n{\n    x = x|0;\n    ytop = ytop|0;\n    ybottom = ybottom|0;\n    col = col|0;\n    var buf32 = screendata.buf32;\n    var screenwidth = screendata.canvas.width|0;\n    if (ytop < 0) ytop = 0;\n    if (ytop > ybottom) return;\n\n    // get offset on screen for the vertical line\n    var offset = ((ytop * screenwidth) + x)|0;\n    for (var k = ytop|0; k < ybottom|0; k=k+1|0)\n    {\n        buf32[offset|0] = col|0;\n        offset = offset + screenwidth|0;\n    }\n}\n\n// ---------------------------------------------\n// Basic screen handling\n\nfunction DrawBackground()\n{\n    var buf32 = screendata.buf32;\n    var color = screendata.backgroundcolor|0;\n    for (var i = 0; i < buf32.length; i++) buf32[i] = color|0;\n}\n\n// Show the back buffer on screen\nfunction Flip()\n{\n    screendata.imagedata.data.set(screendata.buf8);\n    screendata.context.putImageData(screendata.imagedata, 0, 0);\n}\n\n// ---------------------------------------------\n// The main render routine\n\nfunction Render()\n{\n    var mapwidthperiod = map.width - 1;\n    var mapheightperiod = map.height - 1;\n\n    var screenwidth = screendata.canvas.width|0;\n    var sinang = Math.sin(camera.angle);\n    var cosang = Math.cos(camera.angle);\n\n    var hiddeny = new Int32Array(screenwidth);\n    for(var i=0; i<screendata.canvas.width|0; i=i+1|0)\n        hiddeny[i] = screendata.canvas.height;\n\n    var deltaz = 1.;\n\n    // Draw from front to back\n    for(var z=1; z<camera.distance; z+=deltaz)\n    {\n        // 90 degree field of view\n        var plx =  -cosang * z - sinang * z;\n        var ply =   sinang * z - cosang * z;\n        var prx =   cosang * z - sinang * z;\n        var pry =  -sinang * z - cosang * z;\n\n        var dx = (prx - plx) / screenwidth;\n        var dy = (pry - ply) / screenwidth;\n        plx += camera.x;\n        ply += camera.y;\n        var invz = 1. / z * 240.;\n        for(var i=0; i<screenwidth|0; i=i+1|0)\n        {\n            var mapoffset = ((Math.floor(ply) & mapwidthperiod) << map.shift) + (Math.floor(plx) & mapheightperiod)|0;\n            var heightonscreen = (camera.height - map.altitude[mapoffset]) * invz + camera.horizon|0;\n            DrawVerticalLine(i, heightonscreen|0, hiddeny[i], map.color[mapoffset]);\n            if (heightonscreen < hiddeny[i]) hiddeny[i] = heightonscreen;\n            plx += dx;\n            ply += dy;\n        }\n        deltaz += 0.005;\n    }\n}\n\n\n// ---------------------------------------------\n// Draw the next frame\n\nfunction Draw()\n{\n    updaterunning = true;\n    UpdateCamera();\n    DrawBackground();\n    Render();\n    Flip();\n    frames++;\n\n    if (!input.keypressed)\n    {\n        updaterunning = false;\n    } else\n    {\n        window.requestAnimationFrame(Draw, 0);\n    }\n}\n\n// ---------------------------------------------\n// Init routines\n\n// Util class for downloading the png\nfunction DownloadImagesAsync(urls) {\n    return new Promise(function(resolve, reject) {\n\n        var pending = urls.length;\n        var result = [];\n        if (pending === 0) {\n            resolve([]);\n            return;\n        }\n        urls.forEach(function(url, i) {\n            var image = new Image();\n            //image.addEventListener(\"load\", function() {\n            image.onload = function() {\n                var tempcanvas = document.createElement(\"canvas\");\n                var tempcontext = tempcanvas.getContext(\"2d\");\n                tempcanvas.width = map.width;\n                tempcanvas.height = map.height;\n                tempcontext.drawImage(image, 0, 0, map.width, map.height);\n                result[i] = tempcontext.getImageData(0, 0, map.width, map.height).data;\n                pending--;\n                if (pending === 0) {\n                    resolve(result);\n                }\n            };\n            image.src = url;\n        });\n    });\n}\n\nfunction LoadMap(filenames)\n{\n    var files = filenames.split(\";\");\n    DownloadImagesAsync([\"maps/\"+files[0]+\".png\", \"maps/\"+files[1]+\".png\"]).then(OnLoadedImages);\n}\n\nfunction OnLoadedImages(result)\n{\n    var datac = result[0];\n    var datah = result[1];\n    for(var i=0; i<map.width*map.height; i++)\n    {\n        map.color[i] = 0xFF000000 | (datac[(i<<2) + 2] << 16) | (datac[(i<<2) + 1] << 8) | datac[(i<<2) + 0];\n        map.altitude[i] = datah[i<<2];\n    }\n    Draw();\n}\n\nfunction OnResizeWindow()\n{\n    screendata.canvas = document.getElementById('fullscreenCanvas');\n\n    var aspect = window.innerWidth / window.innerHeight;\n\n    screendata.canvas.width = window.innerWidth<800?window.innerWidth:800;\n    screendata.canvas.height = screendata.canvas.width / aspect;\n\n    if (screendata.canvas.getContext)\n    {\n        screendata.context = screendata.canvas.getContext('2d');\n        screendata.imagedata = screendata.context.createImageData(screendata.canvas.width, screendata.canvas.height);\n    }\n\n    screendata.bufarray = new ArrayBuffer(screendata.imagedata.width * screendata.imagedata.height * 4);\n    screendata.buf8     = new Uint8Array(screendata.bufarray);\n    screendata.buf32    = new Uint32Array(screendata.bufarray);\n    Draw();\n}\n\nfunction Init()\n{\n    for(var i=0; i<map.width*map.height; i++)\n    {\n        map.color[i] = 0xFF007050;\n        map.altitude[i] = 0;\n    }\n\n    LoadMap(\"C1W;D1\");\n    OnResizeWindow();\n\n    // set event handlers for keyboard, mouse, touchscreen and window resize\n    var canvas = document.getElementById(\"fullscreenCanvas\");\n    window.onkeydown    = DetectKeysDown;\n    window.onkeyup      = DetectKeysUp;\n    canvas.onmousedown  = DetectMouseDown;\n    canvas.onmouseup    = DetectMouseUp;\n    canvas.onmousemove  = DetectMouseMove;\n    canvas.ontouchstart = DetectMouseDown;\n    canvas.ontouchend   = DetectMouseUp;\n    canvas.ontouchmove  = DetectMouseMove;\n\n    window.onresize       = OnResizeWindow;\n\n    window.setInterval(function(){\n        var current = new Date().getTime();\n        document.getElementById('fps').innerText = (frames / (current-timelastframe) * 1000).toFixed(1) + \" fps\";\n        frames = 0;\n        timelastframe = current;\n    }, 2000);\n\n}\n\nInit();\n\n</script>\n\n</body>\n"
  },
  {
    "path": "images/thumbnails/makethumbs",
    "content": "mogrify -strip -format png8 -path thumbs -quality 100 -thumbnail 150x150 ../maps/*.png\nfor f in *.png8;do mv $f ${f/png8/png};done\n"
  },
  {
    "path": "tools/README.md",
    "content": "Extract the maps from the Comanche games and generate the gif animations."
  },
  {
    "path": "tools/animations/anim.py",
    "content": "import math\nfrom PIL import Image, ImageDraw, ImageFont\nimport numpy as np\n\n# -----------------------------------------------------\n\ndef Init(width, height, colorfilename, heightfilename):\n    global colorimg, colormap, heightimg, heightmap, pal, screen, screenmap\n    colorimg = Image.open(\"../../maps/\" + colorfilename)\n    colormap = colorimg.load()\n\n    heightimg = Image.open(\"../../maps/\" + heightfilename)\n    heightmap = heightimg.load()\n\n    # the colormap uses a color palette\n    pal = colorimg.palette.getdata()[1];\n\n    screen = Image.new(\"RGB\", (width, height))\n    screenmap = screen.load()\n\n# -----------------------------------------------------\n\nclass Point:\n    def __init__(self, x=0, y=0):\n        self.x = x\n        self.y = y\n\n# -----------------------------------------------------\n\ndef DrawVerticalLine(x, ytop, ybottom, c):\n    if (ytop >= ybottom): return\n    if (ytop < 0): ytop = 0\n\n    rgb = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]\n    if screen.width != 700:\n        for j in range(math.floor(ytop), math.floor(ybottom)):\n            screenmap[x+1024, j] = rgb\n    else:\n        for j in range(math.floor(ytop), math.floor(ybottom)):\n            screenmap[x, j] = rgb\n\n\n# -----------------------------------------------------\n\ndef Store():\n    Store.n -= 1\n    if Store.n <= 0:\n        screen.save(\"images/out%03d.gif\" % (Store.idx,), \"GIF\")\n        Store.idx += 1\n        Store.n = Store.modulo\n        Store.modulo += 3\nStore.idx = 0\nStore.n = 0\nStore.modulo = 10\n\n# -----------------------------------------------------\n\ndef Horline(p1, p2, offset, scale, horizon, pmap):\n    n = 700\n    dx = (p2.x - p1.x) / n\n    dy = (p2.y - p1.y) / n\n    for i in range(0, n):\n        xi = math.floor(p1.x) & 1023\n        yi = math.floor(p1.y) & 1023\n        xmap = (math.floor(p1.x) - pmap.x+256)\n        ymap = (math.floor(p1.y) - pmap.y+256)\n        if screen.width != 700:\n            if (xmap<512) and (ymap<512):\n                if (xmap>=0) and (ymap>=0):\n                    screenmap[xmap, ymap] = 0xFFFFFF\n                    screenmap[xmap+512, ymap] = 0xFFFFFF\n        DrawVerticalLine(i, (heightmap[xi, yi]+offset)*scale+horizon, 511, colormap[xi, yi])\n        p1.x += dx\n        p1.y += dy\n        Store()\n\n# -----------------------------------------------------\n\nhidden = np.zeros(700)\n\ndef HorlineHidden(p1, p2, offset, scale, horizon, pmap):\n    n = 700\n    dx = (p2.x - p1.x) / n\n    dy = (p2.y - p1.y) / n\n    for i in range(0, n):\n        xi = math.floor(p1.x) & 1023\n        yi = math.floor(p1.y) & 1023\n        xmap = (math.floor(p1.x) - pmap.x+256)\n        ymap = (math.floor(p1.y) - pmap.y+256)\n        if screen.width != 700:\n            if (xmap<512) and (ymap<512):\n                if (xmap>=0) and (ymap>=0):\n                    screenmap[xmap, ymap] = 0xFFFFFF\n                    screenmap[xmap+512, ymap] = 0xFFFFFF\n        heightonscreen = (heightmap[xi, yi] + offset) * scale + horizon\n        DrawVerticalLine(i, heightonscreen, hidden[i], colormap[xi, yi])\n        if heightonscreen < hidden[i]:\n            hidden[i] = heightonscreen\n        p1.x += dx\n        p1.y += dy\n#        Store()\n\n# -----------------------------------------------------\n\ndef Rotate(p, phi):\n    xtemp = p.x *  math.cos(phi) + p.y * math.sin(phi)\n    ytemp = p.x * -math.sin(phi) + p.y * math.cos(phi)\n    return Point(xtemp, ytemp)\n\n# -----------------------------------------------------\n\ndef ClearAndDrawMaps(pmap):\n    if screen.width == 700:\n        for j in range(0, 512):\n            for i in range(0, 700):\n                screenmap[i, j] = 0xffa366\n    else:\n        for j in range(0, 512):\n            for i in range(0, 512):\n                h = heightmap[(i+pmap.x-256) & 1023, (j+pmap.y-256) & 1023]\n                c =  colormap[(i+pmap.x-256) & 1023, (j+pmap.y-256) & 1023]\n                screenmap[i,     j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]\n                screenmap[i+512, j] = (h<<16) | (h << 8) | h\n\n        for j in range(0, 512):\n            for i in range(0, 700):\n                screenmap[i+1024, j] = 0xffa366\n\n# -----------------------------------------------------\n\ndef DrawBackToFront(p, phi, height, distance, pmap):\n    ClearAndDrawMaps(pmap)\n    for z in range(distance, 1, -2):\n        pl = Point(-z, -z)\n        pr = Point( z, -z)\n        pl = Rotate(pl, phi)\n        pr = Rotate(pr, phi)\n        Horline(\n            Point(p.x + pl.x, p.y + pl.y),\n            Point(p.x + pr.x, p.y + pr.y),\n            -height, -1./z*240., +120, pmap)\n\n# -----------------------------------------------------\n\ndef DrawFrontToBack(p, phi, height, distance, pmap):\n    ClearAndDrawMaps(pmap)\n    dz = 1\n    z = 5\n\n    for i in range(0, 700):\n        hidden[i] = 511\n\n    while z < distance:\n        pl = Point(-z, -z)\n        pr = Point( z, -z)\n        pl = Rotate(pl, phi)\n        pr = Rotate(pr, phi)\n        HorlineHidden(\n            Point(p.x + pl.x, p.y + pl.y),\n            Point(p.x + pr.x, p.y + pr.y),\n            -height, -1./z*240., +100, pmap)\n        z += dz\n        #dz += 0.1\n\n# -----------------------------------------------------\n\n#Init(512+512+700, 512, \"C1W.png\", \"D1.png\")\n\n#DrawBackToFront(Point(230, 0), 0, 50, 240, Point(230, 0))\n#DrawFrontToBack(Point(230, 0), 0, 50, 400, Point(230, 0))\n\n#for i in range(0, 360, 10):\n#    print(i)\n#    DrawFrontToBack(Point(590, 175), i/180.*3.141592, 50, 240, Point(590, 175))\n#    Store.n=1\n#    Store()\n\nInit(700, 512, \"C7W.png\", \"D7.png\")\nfor i in range(0, 64):\n    print(i)\n    DrawFrontToBack(Point(670, 500 - i*16), 0, 120, 800, Point(670, 500 - i*16))\n    Store.n=1\n    Store()\n"
  },
  {
    "path": "tools/animations/drawmap.py",
    "content": "import math\nfrom PIL import Image, ImageDraw, ImageFont, ImageOps\n\n# -----------------------------------------------------\n\ncolorimg = Image.open(\"C1W.png\")\ncolormap = colorimg.load()\n\nheightimg = Image.open(\"D1.png\")\nheightmap = heightimg.load()\npal = colorimg.palette.getdata()[1];\n\nscreen = Image.new(\"RGB\", (512, 512))\nscreenmap = screen.load()\n\n# -----------------------------------------------------\n\ndef Store():\n    screen.save(\"images/out%03d.gif\" % (Store.idx,), \"GIF\")\n    Store.idx += 1\nStore.idx = 0\n\n# -----------------------------------------------------\n\ndef PrintBorder(title):\n    draw = ImageDraw.Draw(screen)\n    fnt = ImageFont.truetype('/usr/share/fonts/TTF/UbuntuMono-B.ttf', 20)\n    draw.rectangle([(128, 128), (128+256, 128+256)], outline=(255,255,255,128))\n    draw.text((200, 105), \"1024 pixels\", font=fnt, fill=(255,255,255,128))\n    draw.text((0, 0), title, font=fnt, fill=(255,255,255,128))\n\n    txt = Image.new('L', (512, 512))\n    d = ImageDraw.Draw(txt)\n    d.text((200, 105), \"1024 pixels\", font=fnt, fill=(255))\n    w = txt.rotate(-90)\n    screen.paste( ImageOps.colorize(w, (0,0,0), (255,255,255)), (0, 0),  w)\n\n\n\ndef DrawPeriodicMap():\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            screenmap[i, j] = 0\n\n    for j in range(128, 128+256):\n        for i in range(128, 128+256):\n            c =  colormap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]\n            screenmap[i, j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]\n    PrintBorder(\"Color Map\")\n    Store()\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            screenmap[i, j] = 0\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            c =  colormap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]\n            screenmap[i, j] = (pal[c*3+2]<< 16) | (pal[c*3+1] << 8) | pal[c*3+0]\n    PrintBorder(\"Color Map\")\n    Store()\n\n#--------------------\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            screenmap[i, j] = 0\n\n    for j in range(128, 128+256):\n        for i in range(128, 128+256):\n            h = heightmap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]\n            screenmap[i, j] = (h<<16) | (h << 8) | h\n    PrintBorder(\"Height Map\")\n    Store()\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            screenmap[i, j] = 0\n\n    for j in range(0, 512):\n        for i in range(0, 512):\n            h = heightmap[((i<<2)+512) & 1023, ((j<<2)+512) & 1023]\n            screenmap[i, j] = (h<<16) | (h << 8) | h\n    PrintBorder(\"Height Map\")\n    Store()\n\n#--------------------\n\nDrawPeriodicMap()\n\n"
  },
  {
    "path": "tools/animations/run.sh",
    "content": "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\ngifsicle --colors 256 --optimize=2 --delay=10 --loop images/*.gif > anim.gif\n"
  },
  {
    "path": "tools/animations/rundrawmap.sh",
    "content": "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\ngifsicle --colors 256 --optimize=2 --delay=200 --loop images/*.gif > periodicmap.gif\n"
  },
  {
    "path": "tools/animations/runwebdemo.sh",
    "content": "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\ngifsicle --optimize=3 --scale=0.5 --delay=5 --loop images/*.gif > anim.gif\n"
  },
  {
    "path": "tools/comanche2extract/extract.c",
    "content": "/*\n *  This file contains the code to \n *  extract the maps from the game \n *  Comanche\n */\n\n#include<png.h>\n\nunsigned char buffer[1024*1024];\nunsigned int palette[256];\nunsigned char palettergb[256*3];\nunsigned int imagewidth;\nunsigned int imageheight;\n\nconst char *maps[] =\n{\n\"C1W\",        \"D1\",\n\"C2W\",        \"D2\",\n\"C3\",         \"D3\", \n\"C4\",         \"D4\",\n\"C5W\",        \"D5\",\n\"C6W\",        \"D6\",\n\"C7W\",        \"D7\",\n\"C8\",         \"D6\",\n\"C9W\",        \"D9\",\n\"C10W\",       \"D10\",  \n\"C11W\",       \"D11\",  \n\"C12W\",       \"D11\",\n\"C13\",        \"D13\",   \n\"C14\",        \"D14\",   \n\"C14W\",       \"D14\",  \n\"C15\",        \"D15\",   \n\"C16W\",       \"D16\",  \n\"C17W\",       \"D17\",  \n\"C18W\",       \"D18\",  \n\"C19W\",       \"D19\",  \n\"C20W\",       \"D20\",  \n\"C21\",        \"D21\",   \n\"C22W\",       \"D22\",  \n\"C23W\",       \"D21\",\n\"C24W\",       \"D24\",  \n\"C25W\",       \"D25\",  \n\"C26W\",       \"D18\",  \n\"C27W\",       \"D15\",  \n\"C28W\",       \"D25\",\n\"C29W\",       \"D16\"\n};\n\nvoid LoadDTA(const char *filename)\n{\n    for(int i=0; i<1024*1024; i++)\n    {\n        buffer[i] = 0;\n    }\n\n    FILE *file = fopen(filename, \"rb\");\n\n    if (file == NULL) \n    {\n        printf(\"file not found %s\\n\", filename);\n        return;\n    }\n    unsigned int width=0, height=0;\n\n    fseek(file, 8, SEEK_SET);\n    fread(&width,  2, 1, file);\n    fread(&height, 2, 1, file);\n    printf(\"%i\\n\", width);\n    printf(\"%i\\n\", height);\n    width++;\n    height++;\n    imagewidth = width;\n    imageheight = height;\n\n    fseek(file, 0x80, SEEK_SET);\n\n    unsigned int pos = 0;\n    unsigned int line = 0;\n    unsigned int x=0;\n    unsigned int color=0;\n\nwhile(1)\n{\n    int len = 1;\n    fread(&x, 1, 1, file);\n    color = x;\n    if (x > 0xc0)\n    {\n        fread(&color, 1, 1, file);\n        len = x & 0x3F;\n    }\n\n    for(int i=0; i<len; i++)\n    {\n        buffer[pos + line*1024] = color;\n        pos++;\n\n        if (pos >= width)\n        {\n            line++;\n            pos = 0;\n        }\n    }\n    if (line == height) break;\n}\n\nprintf(\"%i\\n\", line);\nprintf(\"%i\\n\", ftell(file));\n\n    fread(&x, 1, 1, file);\n\n    for(unsigned int i=0; i<256; i++)\n    {\n        fread(&r, 1, 1, file);\n        fread(&g, 1, 1, file);\n        fread(&b, 1, 1, file);\n        palette[i] = r | (g<<8) | (b<<16);\n        palettergb[3*i+0] = r;\n        palettergb[3*i+1] = g;\n        palettergb[3*i+2] = b;\n    }\n    fclose(file);\n}\n\n\nvoid SavePNG(const char *filename, bool usepalette)\n{\n    FILE *fp = fopen(filename, \"wb\");\n    if (!fp)\n    {\n        return;\n    }\n\n    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n    if (!png_ptr)\n    {\n       return;\n    }\n    \n    png_infop info_ptr = png_create_info_struct(png_ptr);\n    if (!info_ptr)\n    {\n       png_destroy_write_struct(&png_ptr, (png_infopp)NULL);\n       return;\n    }\n    \n    if (setjmp(png_jmpbuf(png_ptr)))\n    {\n       png_destroy_write_struct(&png_ptr, &info_ptr);\n       fclose(fp);\n       return;\n    }\n    \n    png_init_io(png_ptr, fp);\n    \n    //    png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);\n    if (usepalette)\n    {\n        png_set_IHDR(png_ptr, info_ptr, \n        imagewidth, imageheight, 8, \n           PNG_COLOR_TYPE_PALETTE, \n           PNG_INTERLACE_NONE,\n           PNG_COMPRESSION_TYPE_DEFAULT, \n           PNG_FILTER_TYPE_DEFAULT);\n            \n        png_set_PLTE(png_ptr, info_ptr, (png_color*)palettergb, 256);\n    } else\n    {\n        png_set_IHDR(png_ptr, info_ptr, \n        imagewidth, imageheight, 8, \n           PNG_COLOR_TYPE_GRAY, \n           PNG_INTERLACE_NONE,\n           PNG_COMPRESSION_TYPE_DEFAULT, \n           PNG_FILTER_TYPE_DEFAULT);\n    }\n    png_write_info(png_ptr, info_ptr);\n\n    for (int y=0; y<imageheight; y++)\n    {\n        png_bytep row_pointer = (png_bytep)&buffer[y*1024];\n        png_write_row(png_ptr, row_pointer);\n    }\n\n    png_write_end(png_ptr, info_ptr);\n    png_destroy_write_struct(&png_ptr, &info_ptr);\n\n    fclose(fp);\n}\n\nvoid SaveMap(const char *filename, unsigned char *buffer, unsigned int size)\n{\n    FILE *fp = fopen(filename, \"wb\");\n    if (!fp)\n    {\n        return;\n    }\n    fwrite(buffer, size, 1, fp);\n    fclose(fp);\n}\n\nint main()\n{\n\n\nfor(int i=1; i<=25; i++)\n{\n    if (i == 8) continue;\n    if (i == 12) continue;\n    if (i == 23) continue;\n    char filename[512];\n    sprintf(filename, \"/path/to/Comanche/D%i\", i);\n    LoadDTA(filename);\n    sprintf(filename, \"D%i.png\", i);\n    SavePNG(filename, false);\n}\n\n\nfor(int i=0; i<30; i++)\n{\n    char filename[512];\n\n    sprintf(filename, \"/path/to/Comanche/%s.DTA\", maps[2*i+0]);\n    LoadDTA(filename);\n    sprintf(filename, \"data/map%i.color\", i);\n    SaveMap(filename, buffer, 1024*1024);\n    sprintf(filename, \"data/map%i.palette\", i);\n    SaveMap(filename, palettergb, 256*3);\n\n\n    sprintf(filename, \"/path/to/Comanche/%s.DTA\", maps[2*i+1]);\n    LoadDTA(filename);\n    \n    if (imagewidth == 512)\n    {\n        char temp[1024*1024];\n        for(int ii=0; ii<1024*1024; ii++) temp[ii] = buffer[ii];\n    \n        for(int jj=0; jj<512; jj++)\n        for(int ii=0; ii<512; ii++)\n        {\n            buffer[((ii<<1)+0) + ((jj<<1)+0)*1024] =  temp[((ii+0)&511) + ((jj+0)&511)*1024];\n            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;\n            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;\n            buffer[((ii<<1)+1) + ((jj<<1)+1)*1024] = \n            (\n            (int)\n            temp[((ii+0)&511) + ((jj+0)&511)*1024] + \n            temp[((ii+1)&511) + ((jj+0)&511)*1024] + \n            temp[((ii+0)&511) + ((jj+1)&511)*1024] + \n            temp[((ii+1)&511) + ((jj+1)&511)*1024]\n            ) / 4;\n        }\n    }\n    sprintf(filename, \"data/map%i.height\", i);\n    SaveMap(filename, buffer, 1024*1024);\n}\n\n/*\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C1\");\n    SavePNG(\"C1.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C2\");\n    SavePNG(\"C2.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C3\");\n    SavePNG(\"C3.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C4\");\n    SavePNG(\"C4.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C5\");\n    SavePNG(\"C5.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C6\");\n    SavePNG(\"C6.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C7\");\n    SavePNG(\"C7.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C8\");\n    SavePNG(\"C8.png\", true);\n\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C9W\");\n    SavePNG(\"C9W.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C10W\");\n    SavePNG(\"C10W.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C11W\");\n    SavePNG(\"C11W.png\", true);\n    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/C12W\");\n    SavePNG(\"C12W.png\", true);\n\nfor(int i=1; i<=11; i++)\n{\n    char filename[512];\n    sprintf(filename, \"/cygdrive/w/UseNext/comanch2/D%i\", i);\n    LoadDTA(filename);\n    sprintf(filename, \"D%i.png\", i);\n    SavePNG(filename, false);\n}\n*/\n\n//    LoadDTA(\"/cygdrive/w/UseNext/COMANCHE/CREDIT1\");\n//    FILE *file = fopen(\"w:/UseNext/COMANCHE/C1\", \"rb\");\n//    FILE *file = fopen(\"w:/UseNext/COMANCHE/D1\", \"rb\");\n    \n    return 0;\n}\n"
  },
  {
    "path": "tools/comanche3extract/extract.c",
    "content": "/*\n *  This file contains the code to \n *  extract the maps from the game \n *  Comanche3\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n#include <sys/types.h>\n#include <dirent.h>\n#include<png.h>\n#include <fnmatch.h>\n\ntypedef unsigned char ubyte;\n// -------------------------------------\n\nunsigned int flength(FILE *fp)\n{\n    fseek(fp, 0L, SEEK_END);\n    unsigned int sz = ftell(fp);\n    fseek(fp, 0L, SEEK_SET);\n    return sz;\n}\n\n// -------------------------------------\n// Reverse engenineered uncompress from Editor.exe from Comanche Gold\n\nunsigned int GetBits(unsigned int &bitbyte_offset, ubyte **compptr, unsigned int bit_length)\n{\n    *compptr += bitbyte_offset >> 3;\n    unsigned int bit_offset = (bitbyte_offset & 7);\n    bitbyte_offset = bit_offset + bit_length;\n    unsigned int eax = *((unsigned int*)*compptr);\n    eax = (eax >> bit_offset) &((1<<bit_length)-1);\n    return eax;\n}\n\nvoid Decompress(ubyte *compptr, ubyte *rawptr, unsigned int size)\n{\n    unsigned int bit_length = 9;\n    unsigned int bitbyte_offset = 0;\n    unsigned int table2_size = 0x200;\n    unsigned int table2_index = 0x102;\n    unsigned int var2 = 0;\n    unsigned int var3 = 0;\n    unsigned int table1_index = 0;\n    ubyte varb1 = 0;\n    ubyte varb2 = 0;\n    ubyte* rawptrend = rawptr + size;\n    ubyte table1[0x100000];\n\n    unsigned int stack[0x10000];\n    unsigned int stack_index = 0;\n    \n    while(1)\n    {\n        if (rawptr >= rawptrend) break;\n        unsigned int eax = GetBits(bitbyte_offset, &compptr, bit_length);\n        if (eax == 0x101) break;\n        if (eax == 0x100) // reset\n        {\n            bit_length = 9;\n            table2_size = 0x200;\n            table2_index = 0x102;\n            eax = GetBits(bitbyte_offset, &compptr, bit_length);\n            table1_index = eax;\n            var3 = eax;\n            varb1 = eax&0xFF;\n            varb2 = eax&0xFF;\n            *rawptr = eax&0xFF;\n            rawptr++;\n            continue;            \n        }\n        table1_index = eax;\n        var2 = eax;\n        if (eax >= table2_index)\n        {\n            eax = var3;\n            table1_index = eax;\n            eax  = (eax & 0xFFFFFF00) | varb2;\n            stack[stack_index] = eax;\n            stack_index++;\n        }\n        loc_430036:\n        if (table1_index <= 0xFF)\n        { \n            eax = table1_index;\n            varb2 = eax&0xFF;\n            varb1 = eax&0xFF;\n            stack[stack_index] = eax;\n            stack_index++;\n            if (stack_index != 0)\n            {\n                do\n                {\n                stack_index--;\n                eax = stack[stack_index];\n                *rawptr = eax&0xFF;\n                rawptr++;\n                } \n                while (stack_index != 0);                \n            } \n            table1[table2_index*3 +2] = varb1;\n            table1[table2_index*3 +0] = var3 & 0xFF;\n            table1[table2_index*3 +1] = (var3>>8) & 0xFF;\n            table2_index++;\n            var3 = var2;\n            if (table2_index < table2_size) continue;\n            if (bit_length >= 0xD) continue;\n            bit_length++;\n            table2_size <<= 1;\n            continue;\n        } else\n        {\n            eax  = table1[table1_index*3+2];\n            stack[stack_index] = eax;\n            stack_index++;\n            eax  = table1[table1_index*3+0]  | (table1[table1_index*3+1] << 8);\n            table1_index = eax;\n            goto loc_430036;\n        }        \n    }    \n}\n// -------------------------------------\n\nclass Image\n{\n    public:\n    unsigned int width;\n    unsigned int height;\n    ubyte *buffer;\n    bool usepalette;\n    unsigned char pal[256*3];\n    \n    void SetWidthHeight(unsigned int w, unsigned int h)\n    {\n        width = w;\n        height = h;\n        buffer = new unsigned char[w*h];\n    }\n    \n    Image()\n    {\n            buffer = NULL;\n            usepalette = true;\n    }\n    ~Image()\n    {\n        if (buffer != NULL) delete[] buffer;\n    }    \n};\n\nvoid SavePNG(const char *filename, const Image &image)\n{\n    FILE *fp = fopen(filename, \"wb\");\n    if (!fp)\n    {\n        return;\n    }\n\n    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n    if (!png_ptr)\n    {\n       return;\n    }\n    \n    png_infop info_ptr = png_create_info_struct(png_ptr);\n    if (!info_ptr)\n    {\n       png_destroy_write_struct(&png_ptr, (png_infopp)NULL);\n       return;\n    }\n    \n    if (setjmp(png_jmpbuf(png_ptr)))\n    {\n       png_destroy_write_struct(&png_ptr, &info_ptr);\n       fclose(fp);\n       return;\n    }    \n    png_init_io(png_ptr, fp);\n    \n//    png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);\nif (image.usepalette)\n{\n    png_set_IHDR(png_ptr, info_ptr, \n    image.width, image.height, 8, \n       PNG_COLOR_TYPE_PALETTE, \n       PNG_INTERLACE_NONE,\n       PNG_COMPRESSION_TYPE_DEFAULT, \n       PNG_FILTER_TYPE_DEFAULT);\n        \n    png_set_PLTE(png_ptr, info_ptr, (png_color*)image.pal, 256);\n} else\n{\n    png_set_IHDR(png_ptr, info_ptr, \n    image.width, image.height, 8, \n       PNG_COLOR_TYPE_GRAY, \n       PNG_INTERLACE_NONE,\n       PNG_COMPRESSION_TYPE_DEFAULT, \n       PNG_FILTER_TYPE_DEFAULT);\n}       \n    png_write_info(png_ptr, info_ptr);\n    \n    for (int y=0; y<image.height; y++)\n    {\n        png_bytep row_pointer = (png_bytep)&image.buffer[y*image.width];\n        png_write_row(png_ptr, row_pointer);\n    }\n\n    png_write_end(png_ptr, info_ptr);\n    png_destroy_write_struct(&png_ptr, &info_ptr);\n\n    fclose(fp);\n}\n// -------------------------------------\n\nvoid SaveMap(const char *filename, const Image &image)\n{\n    FILE *fp = fopen(filename, \"wb\");\n    if (!fp) return;\n    fwrite(image.buffer, image.width * image.height, 1, fp);\n    fclose(fp);\n}\n\n// -------------------------------------\n\nbool LoadPCX(const char* filename, Image &image)\n{\n    FILE *fp;\n    if ( (fp = fopen(filename, \"rb\")) == NULL ) return false;\n\n    unsigned int complen = flength(fp);\n    unsigned int head, width, height;\n    \n    // read header\n    fread(&head, 4, 1, fp); \n    if (head != 0x31505A4C) //LZP1\n    {\n        fclose(fp);\n        return false;\n    }\n    fread(&width, 4, 1, fp); \n    fread(&height, 4, 1, fp);\n    printf(\"  width: %i\\n\", width);\n    printf(\"  height: %i\\n\", height);\n    image.SetWidthHeight(width, height);\n    fread(&image.pal, 256, 3, fp); \n\n    fread(&head, 4, 1, fp);\n    if (head == 0x50726543) // Cerp\n    {\n        fseek(fp, 0x400, SEEK_CUR);\n    }\n    \n    complen -= ftell(fp);\n    unsigned char *comp;\n    if ( (comp = (unsigned char*)malloc(complen)) == NULL )\n    {\n        return false;\n    }\n    fread(comp, complen, 1, fp);\n    Decompress(comp, image.buffer, width*height);\n    fclose(fp);\n    return true;\n}\n\nint main(int argc,char *argv[])\n{\n  DIR *dp;\n  struct dirent *ep;     \n  dp = opendir (\".\");\n\n  if (dp != NULL)\n  {\n    while (ep = readdir (dp))\n    {\n        if (fnmatch(\"*.PCX\", ep->d_name, 0) == 0)\n        {\n            fprintf(stderr, \"Open: %s\\n\", ep->d_name);\n            Image image;\n            if (LoadPCX(ep->d_name, image))\n            {\n                char filename[1024]; filename[0] = 0;\n                strcpy(filename, ep->d_name);\n                unsigned int len = strlen(filename);\n                if (len >= 3)\n                {\n                    filename[len-3] = 'p';\n                    filename[len-2] = 'n';\n                    filename[len-1] = 'g';\n                    fprintf(stderr, \"Save: %s\\n\", filename);\n                    SavePNG(filename, image);\n                }\n                \n                \n            } else\n            {\n                printf(\"  Warning: Skipping file. Reading of file failed or file format unknown\\n\");\n            }\n            \n        }\n    }\n    (void) closedir (dp);\n  }\n  else\n    perror (\"Couldn't open the directory\");\n\n/*\n    Image image;\n    if (!LoadPCX(\"C2M3_C.PCX\", image))\n    {\n        return 1;\n    }\n    SavePNG(\"C2M3_C.PNG\", image);\n*/\n    \n\nreturn 0;\n\n}\n\n\n\n\n"
  }
]