231 lines
9 KiB
Lua
231 lines
9 KiB
Lua
-- Wireframe 3D models — vertex + edge data traced from the public 1980 arcade
|
||
-- AVG ROM (vertex table at $388e, draw-command table at $2472, both
|
||
-- documented by the 6502 disassembly preservation project).
|
||
--
|
||
-- Axis translation: the arcade uses +X forward, +Y right, +Z up. We use
|
||
-- +X right, +Y up, +Z forward. The arcade math box effectively halves X/Z
|
||
-- at draw time (visualizer's X and Z, which are arcade's right-axis and
|
||
-- forward-axis), so those are stored 2× and we halve here. Arcade's Z (up)
|
||
-- axis is stored at native scale.
|
||
--
|
||
-- All models pre-scaled ×0.025 to fit our world; bottoms shifted to Y=0
|
||
-- except the shell, which is centred so it can fly at arbitrary Y.
|
||
local Models = {}
|
||
|
||
local function buildModel(verts, lines, scale)
|
||
scale = scale or 1
|
||
local edges = {}
|
||
for _, pair in ipairs(lines) do
|
||
local a, b = verts[pair[1]], verts[pair[2]]
|
||
edges[#edges + 1] = {
|
||
a[1] * scale, a[2] * scale, a[3] * scale,
|
||
b[1] * scale, b[2] * scale, b[3] * scale,
|
||
}
|
||
end
|
||
return edges
|
||
end
|
||
|
||
-- Slow (enemy) tank — arcade ROM shape 2 — 24v, 38e.
|
||
-- Stepped hull, moderate height, forward-extending turret / antenna.
|
||
local tankVerts = {
|
||
{ 6.40, 0.00, -9.20}, { -6.40, 0.00, -9.20},
|
||
{ -6.40, 0.00, 12.10}, { 6.40, 0.00, 12.10},
|
||
{ 7.10, 2.80, -12.80}, { -7.10, 2.80, -12.80},
|
||
{ -7.10, 2.80, 15.60}, { 7.10, 2.80, 15.60},
|
||
{ 4.30, 5.00, -8.50}, { -4.30, 5.00, -8.50},
|
||
{ -4.30, 5.00, 8.50}, { 4.30, 5.00, 8.50},
|
||
{ 2.10, 9.20, -6.40}, { -2.10, 9.20, -6.40},
|
||
{ 0.50, 7.80, -1.60}, { -0.50, 7.80, -1.60},
|
||
{ -0.50, 6.80, 1.60}, { 0.50, 6.80, 1.60},
|
||
{ -0.50, 7.80, 14.00}, { -0.50, 6.80, 14.00},
|
||
{ 0.50, 7.80, 14.00}, { 0.50, 6.80, 14.00},
|
||
{ 0.00, 9.20, -6.40}, { 0.00, 10.00, -6.40},
|
||
}
|
||
local tankLines = {
|
||
{24,23},{13,14},{15,21},{21,19},{19,16},{16,15},{15,18},{18,17},
|
||
{17,20},{20,22},{22,18},{16,17},{20,19},{21,22},{4,1},{1,5},
|
||
{5,8},{8,7},{7,3},{3,4},{4,8},{8,12},{12,11},{11,7},
|
||
{7,6},{6,10},{10,11},{11,14},{14,10},{10,9},{9,12},{12,13},
|
||
{13,9},{9,5},{5,6},{6,2},{2,3},{2,1},
|
||
}
|
||
Models.tank = buildModel(tankVerts, tankLines)
|
||
|
||
-- Super tank — arcade ROM shape 33 — 25v, 34e.
|
||
-- Similar stepped profile but slightly taller and with rear antenna mount.
|
||
local supertankVerts = {
|
||
{ -4.60, 0.00, 18.20}, { -6.90, 0.00, -5.70},
|
||
{ 6.90, 0.00, -5.70}, { 4.60, 0.00, 18.20},
|
||
{ -5.70, 5.70, -5.70}, { 5.70, 5.70, -5.70},
|
||
{ 0.00, 1.10, 13.70}, { -3.40, 5.10, -3.40},
|
||
{ -3.40, 5.70, -5.70}, { 3.40, 5.70, -5.70},
|
||
{ 3.40, 5.10, -3.40}, { -2.30, 9.10, -3.40},
|
||
{ -2.30, 9.10, -5.70}, { 2.30, 9.10, -5.70},
|
||
{ 2.30, 9.10, -3.40}, { -1.10, 6.90, 16.00},
|
||
{ -1.10, 6.90, 1.10}, { 1.10, 6.90, 1.10},
|
||
{ 1.10, 6.90, 16.00}, { -1.10, 8.00, 16.00},
|
||
{ -1.10, 8.00, -1.10}, { 1.10, 8.00, -1.10},
|
||
{ 1.10, 8.00, 16.00}, { 0.00, 9.10, -5.70},
|
||
{ 0.00, 14.90, -5.70},
|
||
}
|
||
local supertankLines = {
|
||
{1,2},{2,5},{5,1},{1,4},{4,3},{3,6},{6,4},{3,2},
|
||
{5,6},{10,11},{11,7},{7,15},{15,14},{14,10},{10,9},{9,8},
|
||
{8,7},{7,12},{12,13},{13,9},{13,14},{15,12},{20,23},{23,22},
|
||
{22,21},{21,17},{17,16},{16,19},{19,18},{18,17},{16,20},{23,19},
|
||
{18,22},{24,25},
|
||
}
|
||
Models.supertank = buildModel(supertankVerts, supertankLines)
|
||
|
||
-- Guided missile — arcade ROM shape 22 (type $16) — 26v, 43e.
|
||
local missileVerts = {
|
||
{ -1.80, 4.20, -4.80}, { -0.90, 5.40, -4.80},
|
||
{ 0.90, 5.40, -4.80}, { 1.80, 4.20, -4.80},
|
||
{ 0.90, 3.00, -4.80}, { -0.90, 3.00, -4.80},
|
||
{ -3.60, 4.20, -1.20}, { -2.40, 6.60, -1.20},
|
||
{ 2.40, 6.60, -1.20}, { 3.60, 4.20, -1.20},
|
||
{ 2.40, 1.80, -1.20}, { -2.40, 1.80, -1.20},
|
||
{ 0.00, 4.20, 14.40}, { 0.00, 4.20, 17.40},
|
||
{ 1.80, 0.00, -1.80}, { -1.80, 0.00, -1.80},
|
||
{ -1.80, 0.00, 1.80}, { 1.80, 0.00, 1.80},
|
||
{ 0.60, 1.90, -0.60}, { -0.60, 1.90, -0.60},
|
||
{ -0.60, 2.10, 0.60}, { 0.60, 2.10, 0.60},
|
||
{ 0.00, 6.60, -1.20}, { -0.90, 5.40, 6.60},
|
||
{ 0.90, 5.40, 6.60}, { 0.00, 7.80, 0.60},
|
||
}
|
||
local missileLines = {
|
||
{14,13},{13,7},{7,1},{1,2},{2,8},{8,9},{9,10},{10,11},
|
||
{11,12},{12,7},{7,8},{8,13},{13,9},{9,3},{3,4},{4,10},
|
||
{10,13},{13,11},{11,5},{5,6},{6,12},{12,13},{25,24},{24,23},
|
||
{23,25},{25,26},{26,24},{26,23},{2,3},{4,5},{6,1},{19,20},
|
||
{20,21},{21,22},{22,19},{19,15},{15,16},{16,17},{17,18},{18,15},
|
||
{16,20},{21,17},{18,22},
|
||
}
|
||
Models.missile = buildModel(missileVerts, missileLines)
|
||
|
||
-- Saucer / UFO — arcade ROM shape 32 (type $20) — 17v, 32e.
|
||
local saucerVerts = {
|
||
{ 0.00, 0.00, -3.00}, { -2.00, 0.00, -2.00},
|
||
{ -3.00, 0.00, 0.00}, { -2.00, 0.00, 2.00},
|
||
{ 0.00, 0.00, 3.00}, { 2.00, 0.00, 2.00},
|
||
{ 3.00, 0.00, 0.00}, { 2.00, 0.00, -2.00},
|
||
{ 0.00, 3.00, -12.00}, { -8.50, 3.00, -8.50},
|
||
{ -12.00, 3.00, 0.00}, { -8.50, 3.00, 8.50},
|
||
{ 0.00, 3.00, 12.00}, { 8.50, 3.00, 8.50},
|
||
{ 12.00, 3.00, 0.00}, { 8.50, 3.00, -8.50},
|
||
{ 0.00, 8.00, 0.00},
|
||
}
|
||
local saucerLines = {
|
||
{17,9},{9,10},{10,17},{17,11},{11,12},{12,17},{17,13},{13,14},
|
||
{14,17},{17,15},{15,16},{16,17},{1,8},{8,16},{16,9},{9,1},
|
||
{1,2},{2,10},{10,11},{11,3},{3,4},{4,12},{12,13},{13,5},
|
||
{5,6},{6,14},{14,15},{15,7},{7,8},{7,6},{5,4},{3,2},
|
||
}
|
||
Models.ufo = buildModel(saucerVerts, saucerLines)
|
||
|
||
-- Low/wide pyramid obstacle — arcade ROM shape 12 — 5v, 8e.
|
||
local pyramidVerts = {
|
||
{ 10.00, 0.00, -10.00}, { -10.00, 0.00, -10.00},
|
||
{ -10.00, 0.00, 10.00}, { 10.00, 0.00, 10.00},
|
||
{ 0.00, 18.00, 0.00},
|
||
}
|
||
local pyramidLines = {
|
||
{1,5},{5,2},{2,1},{1,4},{4,5},{5,3},{3,4},{3,2},
|
||
}
|
||
Models.pyramid = buildModel(pyramidVerts, pyramidLines)
|
||
|
||
-- Tall / narrower pyramid obstacle — arcade ROM shape 0 — 5v, 8e.
|
||
local pyramidTallVerts = {
|
||
{ 6.40, 0.00, -6.40}, { -6.40, 0.00, -6.40},
|
||
{ -6.40, 0.00, 6.40}, { 6.40, 0.00, 6.40},
|
||
{ 0.00, 16.00, 0.00},
|
||
}
|
||
local pyramidTallLines = {
|
||
{1,5},{5,2},{2,1},{1,4},{4,5},{5,3},{3,4},{3,2},
|
||
}
|
||
Models.pyramidTall = buildModel(pyramidTallVerts, pyramidTallLines)
|
||
|
||
-- Tall block / cube — arcade ROM shape 1 — 8v, 12e.
|
||
-- Taller than wide — matches arcade's "tall box" obstacle.
|
||
local cubeVerts = {
|
||
{ 6.40, 0.00, -6.40}, { -6.40, 0.00, -6.40},
|
||
{ -6.40, 0.00, 6.40}, { 6.40, 0.00, 6.40},
|
||
{ 6.40, 16.00, -6.40}, { -6.40, 16.00, -6.40},
|
||
{ -6.40, 16.00, 6.40}, { 6.40, 16.00, 6.40},
|
||
}
|
||
local cubeLines = {
|
||
{1,2},{2,3},{3,4},{4,1},{1,5},{5,6},{6,7},{7,8},
|
||
{8,5},{6,2},{3,7},{8,4},
|
||
}
|
||
Models.cube = buildModel(cubeVerts, cubeLines)
|
||
|
||
-- Low block / slab — arcade ROM shape 15 — 8v, 12e.
|
||
local halfPrismVerts = {
|
||
{ 8.00, 0.00, -8.00}, { -8.00, 0.00, -8.00},
|
||
{ -8.00, 0.00, 8.00}, { 8.00, 0.00, 8.00},
|
||
{ 8.00, 7.00, -8.00}, { -8.00, 7.00, -8.00},
|
||
{ -8.00, 7.00, 8.00}, { 8.00, 7.00, 8.00},
|
||
}
|
||
local halfPrismLines = {
|
||
{1,2},{2,3},{3,4},{4,1},{1,5},{5,6},{6,7},{7,8},
|
||
{8,5},{6,2},{3,7},{8,4},
|
||
}
|
||
Models.halfPrism = buildModel(halfPrismVerts, halfPrismLines)
|
||
|
||
-- Tank shell — the arcade draws in-flight shells as a single bright point,
|
||
-- so there's no ROM model to trace. We use a small square-base pyramid with
|
||
-- the apex pointing forward (+Z) so the player can actually see their shot.
|
||
local shellVerts = {
|
||
{ 2.0, 1.5, 0.0}, { 2.0, -1.5, 0.0},
|
||
{ -2.0, -1.5, 0.0}, { -2.0, 1.5, 0.0},
|
||
{ 0.0, 0.0, 8.0},
|
||
}
|
||
local shellLines = {
|
||
{1,2},{2,3},{3,4},{4,1},
|
||
{1,5},{2,5},{3,5},{4,5},
|
||
}
|
||
Models.shell = buildModel(shellVerts, shellLines)
|
||
|
||
-- Debris — three shrapnel shapes used by the tank-destruction explosion.
|
||
local debris1Verts = {
|
||
{ 0, -3.9, 0}, { 0, 0, 3}, { 0, 3, -0.75}, { 0, 1.5, -2.1},
|
||
{ 3, 0, 0}, {-3, 0, -0.3},
|
||
}
|
||
local debris1Lines = {
|
||
{1,2},{2,3},{3,4},{4,1},
|
||
{1,5},{2,5},{3,5},{4,5},
|
||
{1,6},{2,6},{3,6},{4,6},
|
||
}
|
||
Models.debris1 = buildModel(debris1Verts, debris1Lines)
|
||
|
||
local debris2Verts = {
|
||
{ 0, -3, 9}, { 2.1, -3, 7.5}, { 1.5, -3, 0}, { 0.6, -3, -0.75},
|
||
{-1.2, -3, 6}, { 0, 0, 1.5}, { 0, 4.5, 0},
|
||
}
|
||
local debris2Lines = {
|
||
{1,2},{2,3},{3,4},{4,5},{5,1},
|
||
{1,6},{6,7},{7,4},{7,3},
|
||
}
|
||
Models.debris2 = buildModel(debris2Verts, debris2Lines)
|
||
|
||
local debris3Verts = {
|
||
{ 0, 3, 12}, { 0, 3, -3},
|
||
{ 1.5, 0, 11.1}, { 1.5, 0, 0}, { 0, 0, -3}, {-1.5, 0, 0}, {-1.5, 0, 10.5},
|
||
{ 0, -3, 9}, { 0, -3, 0},
|
||
{ 0, -6, 0}, { 1.5, -6, 0}, { 0, -6, -3}, {-1.5, -6, 0},
|
||
}
|
||
local debris3Lines = {
|
||
{1,2},
|
||
{3,4},{4,5},{5,6},{6,7},
|
||
{8,9},
|
||
{1,8},{1,3},{1,7},{3,8},{7,8},
|
||
{9,10},
|
||
{10,11},{11,12},{12,13},{13,10},
|
||
{11,4},{13,6},{12,5},
|
||
{4,9},{6,9},
|
||
{2,5},{2,4},{2,6},
|
||
}
|
||
Models.debris3 = buildModel(debris3Verts, debris3Lines)
|
||
|
||
Models.debrisPool = { Models.debris1, Models.debris2, Models.debris3 }
|
||
|
||
return Models
|