This code analyzes Quake map files to calculate their bounding boxes and potentially adds skyboxes to them.
npm run import -- "add skybox to map"
var importer = require('../Core')
var {doIntersect} = importer.import("brush to vertex")
function getBounds(file) {
// get all brushes in map, leaf nodes with at least one vertex
var brushes = importer.regexToArray(/\{[\s\S]*?\}/ig, file)
brushes = brushes.map(b => {
var points = importer
.regexToArray(/\(((\s*[0-9\.-]+\s*)*)\)/ig, b, 1)
.map(m => m.trim().split(/\s+/ig)
.map(n => (n.includes('.')
? parseFloat(n.trim())
: parseInt(n.trim()))))
return [[
Math.min.apply(null, points.map(b => b[0])),
Math.min.apply(null, points.map(b => b[1])),
Math.min.apply(null, points.map(b => b[2]))
], [
Math.max.apply(null, points.map(b => b[0])),
Math.max.apply(null, points.map(b => b[1])),
Math.max.apply(null, points.map(b => b[2]))
]]
})
// replace all origins with scaled
// TODO: make this a function
var origins = importer
.regexToArray(/"origin"\s+"((\s*[0-9\.-]+\s*)*)"/ig, file, 1)
.map(o => o.trim().split(/\s+/ig)
.map(n => (n.includes('.')
? parseFloat(n.trim())
: parseInt(n.trim()))))
origins = origins.concat.apply(origins, brushes)
.filter(o => o && isFinite(o[0]))
return [[
Math.min.apply(null, origins.map(b => b[0])),
Math.min.apply(null, origins.map(b => b[1])),
Math.min.apply(null, origins.map(b => b[2]))
], [
Math.max.apply(null, origins.map(b => b[0])),
Math.max.apply(null, origins.map(b => b[1])),
Math.max.apply(null, origins.map(b => b[2]))
]]
}
function addSkybox(fileName) {
var file
if(typeof fileName === 'string' && fs.existsSync(fileName)) {
file = fs.readFileSync(fileName).toString('utf-8')
} else {
file = fileName
}
var brushes = importer.regexToArray(/\{[^\{}]*?\}\s*/ig, file)
brushes.forEach(b => {
if(b.includes('/sky')) {
file = file.replace(b, '')
return false
}
return true
})
var vs = getBounds(file)
// TODO: use a fancy for loop instead contains each corner and extends towards the next two points?
var points = [
[vs[0][0], vs[0][1], vs[0][2]-16],
[vs[1][0], vs[1][1], vs[0][2]],
[vs[0][0]-16, vs[0][1], vs[0][2]],
[vs[0][0], vs[1][1], vs[1][2]],
[vs[0][0], vs[0][1]-16, vs[0][2]],
[vs[1][0], vs[0][1], vs[1][2]],
[vs[0][0], vs[0][1], vs[1][2]],
[vs[1][0], vs[1][1], vs[1][2]+16],
[vs[1][0], vs[0][1], vs[0][2]],
[vs[1][0]+16, vs[1][1], vs[1][2]],
[vs[0][0], vs[1][1], vs[0][2]],
[vs[1][0], vs[1][1]+16, vs[1][2]],
]
var newBrush = ``
for(var i = 0; i < points.length / 2; i++) {
var p1 = points[i*2]
var p2 = points[i*2+1]
newBrush += `
{ // brush 0
( ${p1[0]} ${p1[1]} ${p2[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) ( ${p1[0]} ${p2[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p2[0]} ${p2[1]} ${p2[2]} ) ( ${p2[0]} ${p2[1]} ${p1[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p2[0]} ${p1[1]} ${p2[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p2[1]} ${p2[2]} ) ( ${p1[0]} ${p2[1]} ${p1[2]} ) ( ${p2[0]} ${p2[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p2[1]} ${p1[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p1[1]} ${p2[2]} ) ( ${p1[0]} ${p2[1]} ${p2[2]} ) ( ${p2[0]} ${p2[1]} ${p2[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
}
`
}
var exp = (/\{*\s*\/\/\s*brush\s*0\s*\{*/ig)
var match = exp.exec(file)
var pos = exp.lastIndex
file = file.substr(0, pos - match[0].length) + newBrush + file.substr(pos - match[0].length)
if(typeof fileName === 'string' && fs.existsSync(fileName)) {
console.log(`writing ${fileName}`)
fs.writeFileSync(fileName, file)
} else {
return file
}
}
module.exports = {
addSkybox,
getBounds
}
const { importCore } = require('../Core');
const { doIntersect } = importCore.import('brush to vertex');
const fs = require('fs');
const regexToArray = (pattern, string, flags) => {
const matches = string.match(new RegExp(pattern, flags)) || [];
return matches.map((match, index) => {
const groups = match.match(new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\${OUTPUT}amp;'), flags));
return groups.map((group, index) => group.trim().split(/\s+/ig).map((value) => {
return value.includes('.')? parseFloat(value.trim()) : parseInt(value.trim());
}));
});
};
const getBounds = (file) => {
const importer = importCore;
const brushes = importer.regexToArray(/\{[\s\S]*?\}/ig, file);
const points = brushes.flatMap((brush) => {
const points = importer.regexToArray(/\(((\s*[0-9\.-]+\s*)*)\)/ig, brush, 1).map((point) => point.trim().split(/\s+/ig).map((value) => {
return value.includes('.')? parseFloat(value.trim()) : parseInt(value.trim());
}));
return points;
});
const origins = importer.regexToArray(/"origin"\s+"((\s*[0-9\.-]+\s*)*)"/ig, file, 1).map((origin) => origin.trim().split(/\s+/ig).map((value) => {
return value.includes('.')? parseFloat(value.trim()) : parseInt(value.trim());
})).flat();
const min = [Math.min(...points.map((point) => point[0])), Math.min(...points.map((point) => point[1])), Math.min(...points.map((point) => point[2]))];
const max = [Math.max(...points.map((point) => point[0])), Math.max(...points.map((point) => point[1])), Math.max(...points.map((point) => point[2]))];
const allPoints = origins.concat(points);
const newMin = [Math.min(...allPoints.map((point) => point[0])), Math.min(...allPoints.map((point) => point[1])), Math.min(...allPoints.map((point) => point[2]))];
const newMax = [Math.max(...allPoints.map((point) => point[0])), Math.max(...allPoints.map((point) => point[1])), Math.max(...allPoints.map((point) => point[2]))];
return [[newMin[0], newMin[1], newMin[2]], [newMax[0], newMax[1], newMax[2]]];
}
const addSkybox = (fileName) => {
if (typeof fileName ==='string' && fs.existsSync(fileName)) {
const file = fs.readFileSync(fileName, 'utf-8');
} else {
const file = fileName;
}
const brushes = importCore.regexToArray(/\{[^\{}]*?\}\s*/ig, file);
const skyboxBrushes = brushes.filter((brush) => brush.includes('/sky'));
skyboxBrushes.forEach((brush) => {
file = file.replace(brush, '');
});
const vs = getBounds(file);
const points = [
[vs[0][0], vs[0][1], vs[0][2] - 16],
[vs[1][0], vs[1][1], vs[0][2]],
[vs[0][0] - 16, vs[0][1], vs[0][2]],
[vs[0][0], vs[1][1], vs[1][2]],
[vs[0][0], vs[0][1] - 16, vs[0][2]],
[vs[1][0], vs[0][1], vs[1][2]],
[vs[0][0], vs[0][1], vs[1][2]],
[vs[1][0], vs[1][1], vs[1][2] + 16],
[vs[1][0], vs[0][1], vs[0][2]],
[vs[1][0] + 16, vs[1][1], vs[1][2]],
[vs[0][0], vs[1][1], vs[0][2]],
[vs[1][0], vs[1][1] + 16, vs[1][2]],
];
const newBrush = points.flatMap((point, index) => {
const p1 = points[index];
const p2 = points[index + 1];
return `
( ${p1[0]} ${p1[1]} ${p2[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) ( ${p1[0]} ${p2[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p2[0]} ${p2[1]} ${p2[2]} ) ( ${p2[0]} ${p2[1]} ${p1[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p2[0]} ${p1[1]} ${p2[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p2[1]} ${p2[2]} ) ( ${p1[0]} ${p2[1]} ${p1[2]} ) ( ${p2[0]} ${p2[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p2[1]} ${p1[2]} ) ( ${p1[0]} ${p1[1]} ${p1[2]} ) ( ${p2[0]} ${p1[1]} ${p1[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
( ${p1[0]} ${p1[1]} ${p2[2]} ) ( ${p1[0]} ${p2[1]} ${p2[2]} ) ( ${p2[0]} ${p2[1]} ${p2[2]} ) e1u1/sky1 0 0 0 1 1 0 0 0
`;
}).join('\n');
const exp = /\{*\s*\/\/\s*brush\s*0\s*\{*/ig;
const match = exp.exec(file);
const pos = exp.lastIndex;
file = file.substr(0, pos - match[0].length) + newBrush + file.substr(pos - match[0].length);
if (typeof fileName ==='string' && fs.existsSync(fileName)) {
console.log(`writing ${fileName}`);
fs.writeFileSync(fileName, file);
} else {
return file;
}
};
module.exports = {
addSkybox,
getBounds
}
This code snippet analyzes a Quake map file to determine its bounding box and potentially adds a skybox.
Here's a breakdown:
getBounds
Function:
addSkybox
Function:
Purpose:
This code likely serves as a utility for analyzing and potentially modifying Quake map files. The getBounds
function provides a way to determine the spatial extent of a map, which can be useful for various purposes such as collision detection or level design analysis. The addSkybox
function suggests an intention to automate the addition of skybox elements to maps.