Skip to content

Commit

Permalink
Merge pull request #45 from ghostboats/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ghostboats authored May 14, 2024
2 parents b386a77 + 4e39d93 commit 1f25a5f
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 22 deletions.
192 changes: 192 additions & 0 deletions commands/DDSViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const { getConfig } = require('../support_files/config');
const { CREATE_LOGGER } = require('../support_files/log_utils');

const { modName, rootModPath } = getConfig();
const modsDirPath = path.normalize(rootModPath + "\\Mods");
const bg3mh_logger = CREATE_LOGGER();


const DDS_MAGIC = 0x20534444;
const DDSD_MIPMAPCOUNT = 0x20000;
const DDPF_FOURCC = 0x4;
const DDSCAPS2_CUBEMAP = 0x200;

const headerLengthInt = 31;

const off_magic = 0;
const off_size = 1;
const off_flags = 2;
const off_height = 3;
const off_width = 4;
const off_mipmapCount = 7;
const off_pfFlags = 20;
const off_pfFourCC = 21;
const off_caps2 = 28;


// Additional parsing functions
function fourCCToInt32(value) {
return value.charCodeAt(0) +
(value.charCodeAt(1) << 8) +
(value.charCodeAt(2) << 16) +
(value.charCodeAt(3) << 24);
}

function int32ToFourCC(value) {
return String.fromCharCode(
value & 0xff,
(value >> 8) & 0xff,
(value >> 16) & 0xff,
(value >> 24) & 0xff
);
}

function parseHeaders(arrayBuffer) {
var header = new Int32Array(arrayBuffer, 0, headerLengthInt);

if (header[off_magic] !== DDS_MAGIC) {
throw new Error('Invalid magic number in DDS header');
}

if (!(header[off_pfFlags] & DDPF_FOURCC)) {
throw new Error('Unsupported format, must contain a FourCC code');
}

var fourCC = header[off_pfFourCC];
var format, blockBytes;
switch (fourCC) {
case fourCCToInt32('DXT1'):
blockBytes = 8;
format = 'dxt1';
break;
case fourCCToInt32('DXT3'):
blockBytes = 16;
format = 'dxt3';
break;
case fourCCToInt32('DXT5'):
blockBytes = 16;
format = 'dxt5';
break;
default:
throw new Error('Unsupported FourCC code: ' + int32ToFourCC(fourCC));
}

var cubemap = (header[off_caps2] & DDSCAPS2_CUBEMAP) !== 0;

return {
format: format,
width: header[off_width],
height: header[off_height],
mipMapCount: header[off_mipmapCount],
isCubemap: cubemap
};
}

let DDSViewerCommand = vscode.commands.registerCommand('bg3-mod-helper.DDSViewer', async function () {
bg3mh_logger.info('‾‾DDSViewerCommand‾‾');
const panel = vscode.window.createWebviewPanel(
'DDSViewer',
'DDS Viewer',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true
}
);

const ddsFiles = await findDDSFiles(modsDirPath);
const ddsDetails = await Promise.all(ddsFiles.map(async file => {
const data = await fs.promises.readFile(file);
return {
path: file,
info: parseHeaders(data.buffer)
};
}));

panel.webview.html = getWebviewContent(ddsDetails);

panel.webview.onDidReceiveMessage(
async message => {
try {
switch (message.command) {
case 'openFile':
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(message.path));
break;
}
} catch (err) {
panel.webview.postMessage({ command: 'alert', text: 'Error' });
}
}
);
bg3mh_logger.info('__DDSViewerCommand__');
});

async function findDDSFiles(directory) {
let files = [];
console.log("Searching in directory: ", directory);
const entries = await fs.promises.readdir(directory, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);
console.log(entry.isDirectory() ? "Directory: " + fullPath : "File: " + fullPath);
if (entry.isDirectory()) {
const subFiles = await findDDSFiles(fullPath);
files = files.concat(subFiles);
} else {
const ext = path.extname(entry.name).toLowerCase();
console.log("File extension for " + entry.name + " is " + ext);
if (ext === '.dds') {
files.push(fullPath);
console.log("DDS file found: ", fullPath);
}
}
}
if (files.length === 0) {
console.log("No DDS files found in: ", directory);
}
return files;
}


function getWebviewContent(ddsDetails) {
let content = '<h1>DDS File Viewer</h1><table border="1" style="width:100%;">';
content += `<tr>
<th>File Path</th>
<th>Format</th>
<th>Dimensions</th>
<th>MipMaps</th>
<th>Cubemap</th>
</tr>`;

if (ddsDetails.length === 0) {
content += '<tr><td colspan="5">No DDS files found.</td></tr>';
} else {
ddsDetails.forEach(details => {
content += `<tr onclick="openFile('${details.path.replace(/\\/g, '\\\\')}')" style="cursor:pointer;">
<td>${details.path}</td>
<td>${details.info.format}</td>
<td>${details.info.width} x ${details.info.height}</td>
<td>${details.info.mipMapCount}</td>
<td>${details.info.isCubemap ? 'Yes' : 'No'}</td>
</tr>`;
});
}
content += '</table>';

content += `
<script>
const vscode = acquireVsCodeApi();
function openFile(path) {
vscode.postMessage({
command: 'openFile',
path: path
});
}
</script>`;

return content;
}

module.exports = DDSViewerCommand;
103 changes: 103 additions & 0 deletions commands/rotationTool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const vscode = require('vscode');
const fs = require('fs');
const path = require('path');
const { getConfig } = require('../support_files/config');
const { CREATE_LOGGER } = require('../support_files/log_utils.js');
const { modName, rootModPath } = getConfig();
const modsDirPath = path.normalize(rootModPath + "\\Mods");
const metaPath = path.normalize(modsDirPath + "\\" + modName + "\\meta.lsx");
const bg3mh_logger = CREATE_LOGGER();

let rotationToolCommand = vscode.commands.registerCommand('bg3-mod-helper.rotationTool', function () {
bg3mh_logger.info('‾‾rotationToolCommand‾‾');
const panel = vscode.window.createWebviewPanel(
'rotationTool',
'Rotation Tool',
vscode.ViewColumn.One,
{ enableScripts: true }
);

panel.webview.html = getWebviewContent();

bg3mh_logger.info('__rotationToolCommand__');
});


function getWebviewContent() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rotation Tool</title>
<style>
body { padding: 10px; }
label { margin-right: 8px; display: block; margin-top: 10px; }
input[type="text"] { margin: 5px; }
</style>
</head>
<body>
<h1>Rotation Tool</h1>
<div>
<label><input type="radio" name="angleFormat" value="degrees" checked> Degrees</label>
<label><input type="radio" name="angleFormat" value="radians"> Radians</label>
</div>
<hr>
<h2>Input Vector (Y treated as angle in degrees)</h2>
<div>
<label>X:
<input type="text" id="x" value="0" oninput="updateQuaternion()" readonly>
</label>
<label>Y:
<input type="text" id="y" value="0" oninput="updateQuaternion()">
</label>
<label>Z:
<input type="text" id="z" value="0" oninput="updateQuaternion()" readonly>
</label>
</div>
<hr>
<h2>Output (Quaternion)</h2>
<div>
<label>X:
<input type="text" id="outQx" readonly>
</label>
<label>Y:
<input type="text" id="outQy" readonly>
</label>
<label>Z:
<input type="text" id="outQz" readonly>
</label>
<label>W (real part):
<input type="text" id="outQw" readonly>
</label>
</div>
<script>
function updateQuaternion() {
const angleDeg = parseFloat(document.getElementById('y').value) || 0;
const angleRad = angleDeg * Math.PI / 180;
const sinHalfAngle = Math.sin(angleRad / 2);
const cosHalfAngle = Math.cos(angleRad / 2);
// Since rotation is only around the Y-axis
const qx = 0;
const qy = sinHalfAngle;
const qz = 0;
const qw = cosHalfAngle;
document.getElementById('outQx').value = qx.toFixed(4);
document.getElementById('outQy').value = qy.toFixed(4);
document.getElementById('outQz').value = qz.toFixed(4);
document.getElementById('outQw').value = qw.toFixed(4);
}
// Initialize quaternion outputs with defaults
updateQuaternion();
</script>
</body>
</html>
`;
}



module.exports = rotationToolCommand;
25 changes: 20 additions & 5 deletions extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const { xmlToLocaCommand, locaToXmlCommand, lsxToLsfCommand, lsfToLsxCommand } =

const openConverterCommand = require('./commands/openConverter');
const versionGeneratorCommand = require('./commands/versionGenerator');
const rotationToolCommand = require('./commands/rotationTool');
const DDSViewerCommand = require('./commands/DDSViewer');

const AutoCompleteProvider = require('./autocomplete/autoCompleteProvider');

Expand All @@ -33,10 +35,7 @@ const { resizeImageTooltip, resizeImageController, resizeImageHotbar, resizeImag

const { getFullPath } = require('./support_files/helper_functions');

/**
* Adds a file to the exclusion list.
* @param {vscode.Uri} fileUri - The URI of the file to be excluded.
*/

async function addToExcludeList(fileUri) {
const config = vscode.workspace.getConfiguration('bg3ModHelper');
let excludedFiles = config.get('excludedFiles') || [];
Expand All @@ -52,6 +51,19 @@ async function addToExcludeList(fileUri) {
}
}

async function removeFromExcludeList(fileUri) {
const config = vscode.workspace.getConfiguration('bg3ModHelper');
let excludedFiles = config.get('excludedFiles') || [];
const filePath = fileUri.fsPath.replace(/\\/g, '/');

if (excludedFiles.includes(filePath)) {
excludedFiles = excludedFiles.filter(p => p !== filePath); // Remove the file from the list
await config.update('excludedFiles', excludedFiles, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage(`${filePath} removed from conversion exclusion list.`);
} else {
vscode.window.showWarningMessage(`${filePath} not in the exclusion list.`);
}
}

/**
* @param {vscode.ExtensionContext} context
Expand Down Expand Up @@ -144,7 +156,8 @@ function activate(context) {

let createModTemplateCommand = vscode.commands.registerCommand('bg3-mod-helper.createModTemplate', createModTemplateImport);
context.subscriptions.push(vscode.commands.registerCommand('bg3-mod-helper.addToExcludeList', addToExcludeList));
context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNG, PNGToDDS, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand);
context.subscriptions.push(vscode.commands.registerCommand('bg3-mod-helper.removeFromExcludeList', removeFromExcludeList));
context.subscriptions.push(uuidsHandlesHoverProvider, functionsHoverProvider, DDSToPNG, PNGToDDS, resizeTooltipCommand, resizeControllerCommand, resizeHotbarCommand, resizeCustomCommand, createModTemplateCommand, addIconBackgroundCommand, openConverterCommand, versionGeneratorCommand, rotationToolCommand);
}


Expand All @@ -171,6 +184,8 @@ function aSimpleDataProvider() {
{ label: 'Generate Folder Structure', command: 'bg3-mod-helper.createModTemplate' },
{ label: 'Supply a folder of icons to make an atlas and its corresponding .dds with those icons', command: 'bg3-mod-helper.createAtlas' },
{ label: 'Version Generator', command: 'bg3-mod-helper.versionGenerator' },
{ label: 'Rotation Tool (in development)', command: 'bg3-mod-helper.rotationTool' },
{ label: 'DDS Viewer (in development)', command: 'bg3-mod-helper.DDSViewer' },
{ label: 'Debug Command', command: 'bg3-mod-helper.debugCommand' }
]);
} else if (element.id === 'conversion') {
Expand Down
2 changes: 1 addition & 1 deletion node_modules/.package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1f25a5f

Please sign in to comment.