2021-07-24 14:00:52 +00:00
|
|
|
/**
|
|
|
|
* Generated data tables for BG1 rotation
|
|
|
|
*
|
2021-07-27 14:45:28 +00:00
|
|
|
* The trickiest issue to address is that, when calculating the rotation indices, at
|
2021-07-24 14:00:52 +00:00
|
|
|
* a 45 degree angle, the mapped address for a fixed rectangle can be outside of the
|
|
|
|
* pixel buffer. To fix this we make a compromise.
|
|
|
|
*
|
|
|
|
* To keep speed, image data is drawn one word at a time, so the effective width of the
|
|
|
|
* rotation is 82 units wide. Since each work contains 4 pixels, we will only rotate
|
2021-07-27 14:45:28 +00:00
|
|
|
* a quarter of the vertical image -- an effective 52 units -- and display the same offset
|
2021-07-24 14:00:52 +00:00
|
|
|
* for four consecutive lines.
|
|
|
|
*
|
|
|
|
* Further, the image data will be the center of the BG1 buffer, so the middle 52 lines.
|
|
|
|
*
|
|
|
|
* When rotating we may still calculate address "outside" of the buffer by a factor of
|
|
|
|
* sqrt(2) (~40%) -- or 32 words horizontally and 21 lines vertically. There is extra
|
|
|
|
* data vertically to fill this and, since the BG1 buffer is stored with a stride of
|
|
|
|
* 256 bytes (128 words) there are an extra 46 words of empty space that can be zeroed
|
2021-07-27 14:45:28 +00:00
|
|
|
* out or filled with content to improve the rotation visuals.
|
2021-07-24 14:00:52 +00:00
|
|
|
*/
|
|
|
|
const fs = require('fs').promises;
|
|
|
|
const process = require('process');
|
|
|
|
const { Buffer } = require('buffer');
|
|
|
|
|
2021-07-27 21:28:18 +00:00
|
|
|
// When calculating addresses, do we use the floor() function or round()?
|
|
|
|
const USE_FLOOR = true;
|
|
|
|
|
2021-07-24 14:00:52 +00:00
|
|
|
const NUM_ANGLES = 64;
|
|
|
|
|
|
|
|
const BUFFER_HEIGHT = 208;
|
2021-07-27 21:28:18 +00:00
|
|
|
const BUFFER_WIDTH = 164; // In bytes
|
|
|
|
const BUFFER_STRIDE = 256; // In bytes
|
2021-07-24 14:00:52 +00:00
|
|
|
|
2021-07-27 21:28:18 +00:00
|
|
|
const TEXTURE_WIDTH = BUFFER_WIDTH / 2; // Full width (in words)
|
2021-07-24 14:00:52 +00:00
|
|
|
const TEXTURE_HEIGHT = BUFFER_HEIGHT / 4; // Quarter height
|
|
|
|
const TEXTURE_STRIDE = BUFFER_STRIDE;
|
|
|
|
|
|
|
|
const BUFFER_START = 0x1800;
|
|
|
|
const BUFFER_END = BUFFER_START + BUFFER_STRIDE * BUFFER_HEIGHT;
|
|
|
|
|
|
|
|
console.log(`; The BG1 buffer lives at [${toHex(BUFFER_START)}, ${toHex(BUFFER_END)}]`);
|
|
|
|
|
|
|
|
// The texture portion of BG starts at the left edge of line 77 and
|
|
|
|
// extends down to line
|
|
|
|
const TEXTURE_START = BUFFER_STRIDE * (BUFFER_HEIGHT - TEXTURE_HEIGHT) / 2;
|
|
|
|
const TEXTURE_END = BUFFER_STRIDE * (BUFFER_HEIGHT + TEXTURE_HEIGHT) / 2;
|
2021-07-27 14:45:28 +00:00
|
|
|
const TEXTURE_CENTER = BUFFER_START + TEXTURE_START + Math.floor(TEXTURE_HEIGHT / 2) * BUFFER_STRIDE + Math.floor(BUFFER_WIDTH / 2);
|
2021-07-24 14:00:52 +00:00
|
|
|
|
|
|
|
console.log(`; The texture is this range of the BG1 buffer [${toHex(TEXTURE_START)}, ${toHex(TEXTURE_END)}]`);
|
|
|
|
|
|
|
|
// Define some other constants
|
|
|
|
const x_half = Math.floor(TEXTURE_WIDTH / 2);
|
|
|
|
const y_half = Math.floor(TEXTURE_HEIGHT / 2);
|
|
|
|
|
|
|
|
// Calculate some bias values to keep everything positive
|
|
|
|
BIAS_X = Math.floor(TEXTURE_CENTER / 2) + 0x200;
|
|
|
|
BIAS_Y = TEXTURE_CENTER - BIAS_X;
|
|
|
|
|
|
|
|
const angles = Array.from({ length: NUM_ANGLES}).map((x, i) => (i * 2 * Math.PI) / NUM_ANGLES);
|
|
|
|
|
|
|
|
main(process.argv.slice(2)).then(
|
|
|
|
() => process.exit(0),
|
|
|
|
(e) => {
|
|
|
|
console.error(e);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
function toHex(n) {
|
|
|
|
return '$' + n.toString(16).toUpperCase().padStart(4, '0');
|
|
|
|
}
|
|
|
|
|
|
|
|
function f_x(x, angle) {
|
|
|
|
// Calculate x in units of bytes
|
|
|
|
// return Math.floor(a(x - x_half, angle)) + x_half + BIAS_X;
|
2021-07-27 21:28:18 +00:00
|
|
|
return a(x - x_half, angle) + BIAS_X;
|
2021-07-24 14:00:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function f_y(y, angle) {
|
|
|
|
// return Math.floor(b(y - y_half, angle)) + (y_half * TEXTURE_STRIDE) + BIAS_Y;
|
2021-07-27 21:28:18 +00:00
|
|
|
return b(y - y_half, angle) + BIAS_Y;
|
2021-07-24 14:00:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function check_sample(_a, x, y) {
|
|
|
|
const angle = angles[_a];
|
|
|
|
|
|
|
|
const degrees = Math.round(360 * angle / (2 * Math.PI));
|
|
|
|
const fx = f_x(x, angle);
|
|
|
|
const fy = f_y(y, angle);
|
|
|
|
const ptr = fx + fy;
|
|
|
|
|
|
|
|
if (fx < 0 || fy < 0 || ptr < 0x1800 || ptr >= 0xE800) {
|
|
|
|
console.log(`(a = ${degrees}, x = ${x}, y = ${y}) : f_x = ${toHex(fx)}, f_y = ${toHex(fy)}, p = ${toHex(ptr)}`);
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async function main(argv) {
|
|
|
|
|
|
|
|
// Inspired by https://www.youtube.com/watch?v=glWIf0gfWSE&t=1196s
|
|
|
|
//
|
|
|
|
// We will support 64 rotation angles (~5.5 degree increments) which gives nice
|
|
|
|
// power-of-2 values from the common angles or 45, 90, 135, etc.
|
|
|
|
|
|
|
|
// Do a brute force check to make sure that we can generate addresses that stay within
|
|
|
|
// a proper range
|
|
|
|
for (let a = 0; a < NUM_ANGLES; a += 1) {
|
|
|
|
for (let x = 0; x < TEXTURE_WIDTH; x += 1) {
|
|
|
|
for (let y = 0; y < TEXTURE_HEIGHT; y += 1) {
|
|
|
|
check_sample(a, x, y);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const degrees = Math.round(360 * angles[a] / (2 * Math.PI));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now generate the tables to stdout as merlin source code
|
|
|
|
const _ = console.log;
|
|
|
|
|
2021-07-30 13:01:16 +00:00
|
|
|
_("ANGLEBNK\tENT");
|
|
|
|
_("x_angles\tENT");
|
2021-07-24 14:00:52 +00:00
|
|
|
for (let a = 0; a < NUM_ANGLES; a += 1) {
|
|
|
|
_(`\tdw\t:x_a_${a}`);
|
|
|
|
}
|
|
|
|
for (let a = 0; a < NUM_ANGLES; a += 1) {
|
|
|
|
const angle = angles[a];
|
|
|
|
const label = `:x_a_${a}`;
|
|
|
|
const fx = [];
|
|
|
|
for (let x = 0; x < TEXTURE_WIDTH; x += 1) {
|
|
|
|
fx.push(f_x(x, angle));
|
|
|
|
}
|
|
|
|
const arr = fx.map(toHex).join(',');
|
|
|
|
|
|
|
|
// Double every array for fast copies
|
|
|
|
_(`${label}\tdw\t${arr}`);
|
|
|
|
_(`\tdw\t${arr}`);
|
|
|
|
}
|
|
|
|
|
2021-07-30 13:01:16 +00:00
|
|
|
_("y_angles\tENT");
|
2021-07-24 14:00:52 +00:00
|
|
|
for (let a = 0; a < NUM_ANGLES; a += 1) {
|
|
|
|
_(`\tdw\t:y_a_${a}`);
|
|
|
|
}
|
|
|
|
for (let a = 0; a < NUM_ANGLES; a += 1) {
|
|
|
|
const angle = angles[a];
|
|
|
|
const label = `:y_a_${a}`;
|
|
|
|
const fy = [];
|
|
|
|
for (let y = 0; y < TEXTURE_HEIGHT; y += 1) {
|
|
|
|
const value = f_y(y, angle);
|
|
|
|
fy.push(value);
|
|
|
|
}
|
|
|
|
const arr = fy.map(toHex).join(',');
|
|
|
|
// Double every array for fast output
|
|
|
|
_(`${label}\tdw\t${arr}`);
|
|
|
|
_(`\tdw\t${arr}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function a(x, angle) {
|
2021-07-27 21:28:18 +00:00
|
|
|
if (USE_FLOOR) {
|
|
|
|
return (Math.floor(2 * x * Math.cos(angle)) & ~1) + Math.floor(x * Math.sin(angle)) * TEXTURE_STRIDE;
|
|
|
|
}
|
|
|
|
return (Math.round(2 * x * Math.cos(angle)) & ~1) + Math.round(x * Math.sin(angle)) * TEXTURE_STRIDE;
|
2021-07-24 14:00:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function b(y, angle) {
|
2021-07-27 21:28:18 +00:00
|
|
|
if (USE_FLOOR) {
|
|
|
|
return Math.floor(y * Math.cos(angle)) * TEXTURE_STRIDE - (Math.floor(2 * y * Math.sin(angle)) & ~1);
|
|
|
|
}
|
|
|
|
return Math.round(y * Math.cos(angle)) * TEXTURE_STRIDE - (Math.round(2 * y * Math.sin(angle)) & ~1);
|
2021-07-24 14:00:52 +00:00
|
|
|
}
|