portal64-still-alive/tools/models/model_list_utils.js
Matt Penny 1d0b22df61 Generate dynamic model lists with CMake, + refactor
* When building with CMake, dynamic model lists are now generated
* Factored common code out of generate_*_list.js files to deduplicate
* Better separated generated model lists from main game code
    `dynamic_asset_data.c` and `dynamic_animated_asset_data.c`
    previously #included the corresponding generated headers, and
    `dynamic_asset_loader.c` declared externs for the contents.

    This messiness was likely done so the generated code would be built
    automatically (the Makefile globs all C files under `src/`).

    Now, model list data is output to C source files which are built
    explicitly. This, with some refactoring, allows the previously
    mentioned source files and externs to be removed.

    This is a bit hacky in the Makefile but will be automatic under
    CMake by using target properties.
* Reorganized some files under `tools/`
2024-10-05 21:11:49 -04:00

133 lines
3.5 KiB
JavaScript

const path = require('path');
const INVALID_TOKEN_CHARACTER = /[^A-Za-z0-9_]/gim;
// Common
function sanitize(s) {
return s.replace(INVALID_TOKEN_CHARACTER, '_');
}
function wrapWithIncludeGuard(outputPath, content) {
const outputName = path.basename(outputPath);
const includeGuardName = `__${sanitize(outputName).toUpperCase()}__`;
return `#ifndef ${includeGuardName}
#define ${includeGuardName}
${content}
#endif
`;
}
function generateRelativeModelName(outputPath, modelHeader, suffix='') {
const outputDirName = path.dirname(outputPath);
const { dir: headerDirName, name: headerName } = path.parse(modelHeader);
const relative = path.join(
path.relative(outputDirName, headerDirName),
headerName
);
return sanitize(relative + suffix);
}
function generateModelName(modelHeader) {
return generateRelativeModelName(modelHeader, modelHeader);
}
function generateListDefinition(modelGroup, modelType) {
const groupPascalCase = modelGroup
.split('_')
.map(s => s && s.replace(/^(\w)/g, m => m.toUpperCase()))
.join('');
const listName = `g${groupPascalCase}s`;
return `struct ${modelType} ${listName}[]`;
}
// Header generation
function generateCount(modelGroup, modelHeaders) {
return `#define ${modelGroup.toUpperCase()}_COUNT ${modelHeaders.length}`;
}
function generateModelIndices(outputPath, modelHeaders, modelGroup) {
return modelHeaders.map((modelHeader, index) => {
const modelName = generateRelativeModelName(outputPath, modelHeader, `_${modelGroup}`);
return `#define ${modelName.toUpperCase()} ${index}`;
}).join('\n');
}
function generateListExtern(modelGroup, modelType) {
return `extern ${generateListDefinition(modelGroup, modelType)};`;
}
function generateHeader(outputPath, config) {
const { modelHeaders, modelGroup, modelType } = config;
return wrapWithIncludeGuard(outputPath,
`#include "util/dynamic_asset_model.h"
${generateCount(modelGroup, modelHeaders)}
${generateModelIndices(outputPath, modelHeaders, modelGroup)}
${generateListExtern(modelGroup, modelType)}`
);
}
// Data generation
function generateIncludes(outputPath, modelHeaders) {
return modelHeaders.map(modelHeader => {
const relativePath = path.relative(path.dirname(outputPath), modelHeader);
return `#include "${relativePath}"`;
}).join('\n');
}
function generateExterns(modelHeaders) {
return modelHeaders.map(modelHeader => {
const modelName = generateModelName(modelHeader);
return `extern char _${modelName}_geoSegmentRomStart[];
extern char _${modelName}_geoSegmentRomEnd[];
extern char _${modelName}_geoSegmentStart[];`;
}).join('\n\n');
}
function generateModelList(outputPath, config) {
const { modelHeaders, modelGroup, modelType, listEntryGenerator } = config;
return `${generateListDefinition(modelGroup, modelType)} = {
${modelHeaders.map(modelHeader => listEntryGenerator(outputPath, modelHeader)).join('\n')}
};`
}
function generateData(outputPath, config) {
const { name } = path.parse(outputPath);
const { modelHeaders } = config;
return `#include "${name}.h"
${generateIncludes(outputPath, modelHeaders)}
${generateExterns(modelHeaders)}
${generateModelList(outputPath, config)}
`;
}
module.exports = {
wrapWithIncludeGuard,
generateRelativeModelName,
generateModelName,
generateCount,
generateHeader,
generateIncludes,
generateExterns,
generateModelList,
generateData
};