rework atlas to load recipes and deduplicate, start on block recipe selector
This commit is contained in:
parent
846ed1266d
commit
deea8abdd4
|
|
@ -20,15 +20,19 @@ async function getTextureSize(data) {
|
|||
});
|
||||
}
|
||||
|
||||
function putTextureOnCanvas(ctx, atlasSize, index, data) {
|
||||
const atlasIndexX = index % atlasSize;
|
||||
const atlasIndexY = (index - atlasIndexX) / atlasSize;
|
||||
const x = atlasIndexX * data.size[0];
|
||||
const y = atlasIndexY * data.size[1];
|
||||
ctx.drawImage(data.image, x, y);
|
||||
function putTextureOnCanvas(ctx, atlasSize, data) {
|
||||
if (!data.duplicate) {
|
||||
const atlasIndexX = data.index % atlasSize;
|
||||
const atlasIndexY = (data.index - atlasIndexX) / atlasSize;
|
||||
const x = atlasIndexX * data.size[0];
|
||||
const y = atlasIndexY * data.size[1];
|
||||
ctx.drawImage(data.image, x, y);
|
||||
}
|
||||
|
||||
// free up image data
|
||||
delete data.image;
|
||||
delete data.texture;
|
||||
delete data.duplicate;
|
||||
}
|
||||
|
||||
export async function buildAtlas(data) {
|
||||
|
|
@ -39,16 +43,34 @@ export async function buildAtlas(data) {
|
|||
...Object.entries(data.configuration),
|
||||
...data.addons.flatMap(a => Object.entries(a.configuration)),
|
||||
]
|
||||
.map(([t, d]) => [t, d.modules["plannerator:configuration_metadata"], d])
|
||||
.map(([ config, data ]) =>
|
||||
[ config, data.modules["plannerator:configuration_metadata"], data ])
|
||||
// .map(v => {console.log(v); return v})
|
||||
.flatMap(([config, addon, data]) => Object.entries(data)
|
||||
.map(([type, entries]) => [config, type, addon, entries]))
|
||||
.flatMap(([c, t, a, e]) => Array.isArray(e)
|
||||
? [[c, t, a, e]]
|
||||
: t === "modules" && e["plannerator:global_elements"]
|
||||
? [[c, "plannerator:global_elements", a, e["plannerator:global_elements"].elements]]
|
||||
: [])
|
||||
.flatMap(([c, t, a, l]) => l.map((v) => [c, t, a, v]))
|
||||
.flatMap(([ config, addon, data ]) => Object.entries(data)
|
||||
.map(([ type, entries ]) => [ config, type, addon, entries ]))
|
||||
.flatMap(([ config, type, addon, entry ]) => {
|
||||
if (Array.isArray(entry)) {
|
||||
return [[ config, type, addon, entry ]];
|
||||
}
|
||||
if (type === "modules" && entry["plannerator:global_elements"]) {
|
||||
return [[
|
||||
config,
|
||||
"plannerator:global_elements",
|
||||
addon,
|
||||
entry["plannerator:global_elements"].elements,
|
||||
]];
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.flatMap(([ config, type, addon, list ]) => list.flatMap((entry) => {
|
||||
if (entry.modules && entry.modules["ncpf:block_recipes"]) {
|
||||
return [
|
||||
[ config, type, addon, entry ],
|
||||
...entry.modules["ncpf:block_recipes"].recipes.map((recipe) => [ config, "ncpf:block_recipes", addon, recipe ]),
|
||||
];
|
||||
}
|
||||
return [[ config, type, addon, entry ]];
|
||||
}))
|
||||
.filter(([,,, b]) => b && b.modules && b.modules["plannerator:texture"])
|
||||
.map(([c, t, a, b]) => ({
|
||||
configuration: c,
|
||||
|
|
@ -71,18 +93,25 @@ export async function buildAtlas(data) {
|
|||
throw new Error("Configuration processing failed - textures must be square");
|
||||
}
|
||||
|
||||
const dedupTextureMap = new Map();
|
||||
t.forEach(texture => {
|
||||
if (dedupTextureMap.has(texture.texture)) {
|
||||
texture.index = dedupTextureMap.get(texture.texture)
|
||||
texture.duplicate = true;
|
||||
} else {
|
||||
texture.index = dedupTextureMap.size;
|
||||
dedupTextureMap.set(texture.texture, texture.index);
|
||||
texture.duplicate = false;
|
||||
}
|
||||
});
|
||||
|
||||
const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(t.length)));
|
||||
const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0];
|
||||
|
||||
const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
await Promise.all(t.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b)));
|
||||
|
||||
t.forEach((m, i) => {
|
||||
m.index = offset + i;
|
||||
delete m.texture;
|
||||
});
|
||||
await Promise.all(t.map(b => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, b)));
|
||||
|
||||
const blob = await canvas.convertToBlob({ type: "image/png" });
|
||||
atlases.push({
|
||||
|
|
@ -154,15 +183,13 @@ export function getAtlasMetadata(index) {
|
|||
}
|
||||
|
||||
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
|
||||
let index = 0;
|
||||
for (const atlas of atlases) {
|
||||
for (const atlasItemMeta of atlas.metadata) {
|
||||
if (checkEquivalence(metadata, atlasItemMeta)
|
||||
&& atlasItemMeta.configuration === configuration
|
||||
&& (!configurationList || atlasItemMeta.configurationList === configurationList)) {
|
||||
return index;
|
||||
return atlasItemMeta.index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,87 @@ registerLoadCallback(() => {
|
|||
listenTo([ "planner:selection:removed", "planner:selection:added"], rescanSelectionRecipes);
|
||||
});
|
||||
|
||||
function initializeRecipePicker(params) {
|
||||
const { parent, title, entryListType, multiblockType, recipes, currentSelection, updateSelection } = params;
|
||||
|
||||
parent.querySelectorAll('[data-text="recipe:type"]')
|
||||
.forEach(e => e.innerText = title);
|
||||
|
||||
function updateSelectedRecipe(selectedRecipe) {
|
||||
if (!selectedRecipe) {
|
||||
parent.querySelectorAll('[data-text="recipe:selected"]')
|
||||
.forEach(e => e.innerText = "None");
|
||||
parent.querySelectorAll('[data-atlas="recipe:selected"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(-1)));
|
||||
return;
|
||||
}
|
||||
const selectedRecipeName = selectedRecipe.modules["plannerator:display_name"].display_name;
|
||||
const selectedAtlasId = getAtlasIndexFromMetadata(selectedRecipe, multiblockType, entryListType);
|
||||
const metadata = getAtlasMetadata(selectedAtlasId);
|
||||
parent.querySelectorAll('[data-text="recipe:selected"]')
|
||||
.forEach(e => e.innerText = selectedRecipeName);
|
||||
parent.querySelectorAll('[data-atlas="recipe:selected"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(selectedAtlasId)));
|
||||
parent.querySelectorAll('.recipe--selected')
|
||||
.forEach(e => setTooltip(e, [...getRecipeTooltip(selectedRecipe, metadata)]));
|
||||
}
|
||||
updateSelectedRecipe(currentSelection);
|
||||
|
||||
function handleDismiss() {
|
||||
console.log("dismiss");
|
||||
parent.classList.remove("selecting");
|
||||
window.removeEventListener("click", handleDismiss);
|
||||
}
|
||||
parent.addEventListener("click", e => e.stopPropagation());
|
||||
|
||||
const filterElem = parent.querySelector('[data-input="recipe:filter"]');
|
||||
|
||||
function updateRecipeList() {
|
||||
const filter = Filter.fromInput(filterElem);
|
||||
const recipeList = new DomList(parent.querySelector(":scope > .select"));
|
||||
recipeList.clear();
|
||||
console.log(recipes);
|
||||
for (let i = 0; i < recipes.length; i++) {
|
||||
const recipe = recipes[i];
|
||||
const recipeItem = recipeList.makeItem();
|
||||
|
||||
const recipeName = recipe.modules["plannerator:display_name"].display_name;
|
||||
const atlasId = getAtlasIndexFromMetadata(recipe, multiblockType, entryListType);
|
||||
const metadata = getAtlasMetadata(atlasId);
|
||||
|
||||
recipeItem.querySelectorAll('[data-text="recipe:item"]')
|
||||
.forEach(e => e.innerText = recipeName);
|
||||
recipeItem.querySelectorAll('[data-atlas="recipe:item"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(atlasId)));
|
||||
|
||||
recipeItem.addEventListener("click", () => {
|
||||
handleDismiss();
|
||||
updateSelectedRecipe(recipe);
|
||||
updateSelection(recipe);
|
||||
});
|
||||
|
||||
const tooltipData = [...getRecipeTooltip(recipe, metadata)];
|
||||
setTooltip(recipeItem, tooltipData);
|
||||
if (!filter.test(recipeName, ...tooltipDataToPlain(tooltipData))) {
|
||||
recipeItem.classList.add("hidden");
|
||||
}
|
||||
|
||||
recipeList.addItem(recipeItem);
|
||||
}
|
||||
}
|
||||
|
||||
updateRecipeList();
|
||||
filterElem.addEventListener("input", debounce(updateRecipeList));
|
||||
|
||||
parent.querySelectorAll(':scope > .recipe--selected').forEach(e => {
|
||||
e.addEventListener("click", () => {
|
||||
parent.classList.add("selecting");
|
||||
parent.querySelector('[autofocus]').focus();
|
||||
window.addEventListener("click", handleDismiss);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function rescanDesignRecipes() {
|
||||
console.log("rescan recipes");
|
||||
console.log(configuration);
|
||||
|
|
@ -50,87 +131,26 @@ function rescanDesignRecipes() {
|
|||
.flatMap(([, v]) => v.coolant_recipes);
|
||||
|
||||
const designItem = designRecipes.makeItem();
|
||||
designItem.querySelectorAll('[data-text="recipe:type"]')
|
||||
.forEach(e => e.innerText = "Coolant Recipe");
|
||||
|
||||
const plannerDom = getPlannerDom();
|
||||
const plannerMetadata = getSolverMetadata(plannerDom);
|
||||
let selectedRecipe = plannerMetadata?.coolant_recipe;
|
||||
let currentSelection = plannerMetadata?.coolant_recipe;
|
||||
if (!plannerMetadata) {
|
||||
selectedRecipe = defaultRecipes["nuclearcraft:overhaul_sfr"].coolant_recipe;
|
||||
currentSelection = defaultRecipes[multiblockType].coolant_recipe;
|
||||
setSolverMetadata(plannerDom, {
|
||||
coolant_recipe: selectedRecipe,
|
||||
coolant_recipe: currentSelection,
|
||||
});
|
||||
}
|
||||
selectedRecipe = findFullEntry("coolant_recipes", selectedRecipe, { configurationHint: multiblockType });
|
||||
currentSelection = findFullEntry("coolant_recipes", currentSelection, { configurationHint: multiblockType });
|
||||
|
||||
function updateSelectedRecipe() {
|
||||
const selectedRecipeName = selectedRecipe.modules["plannerator:display_name"].display_name;
|
||||
const selectedAtlasId = getAtlasIndexFromMetadata(selectedRecipe, multiblockType, "coolant_recipes");
|
||||
const metadata = getAtlasMetadata(selectedAtlasId);
|
||||
designItem.querySelectorAll('[data-text="recipe:selected"]')
|
||||
.forEach(e => e.innerText = selectedRecipeName);
|
||||
designItem.querySelectorAll('[data-atlas="recipe:selected"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(selectedAtlasId)));
|
||||
designItem.querySelectorAll('.recipe--selected')
|
||||
.forEach(e => setTooltip(e, [...getRecipeTooltip(selectedRecipe, metadata)]));
|
||||
}
|
||||
updateSelectedRecipe();
|
||||
|
||||
function handleDismiss() {
|
||||
console.log("dismiss");
|
||||
designItem.classList.remove("selecting");
|
||||
window.removeEventListener("click", handleDismiss);
|
||||
}
|
||||
designItem.addEventListener("click", e => e.stopPropagation());
|
||||
|
||||
const filterElem = designItem.querySelector('[data-input="recipe:filter"]');
|
||||
|
||||
function updateRecipeList() {
|
||||
const filter = Filter.fromInput(filterElem);
|
||||
const recipeList = new DomList(designItem.querySelector(":scope > .select"));
|
||||
recipeList.clear();
|
||||
for (let i = 0; i < coolantRecipes.length; i++) {
|
||||
const recipe = coolantRecipes[i];
|
||||
const recipeItem = recipeList.makeItem();
|
||||
|
||||
const recipeName = recipe.modules["plannerator:display_name"].display_name;
|
||||
const atlasId = getAtlasIndexFromMetadata(recipe, multiblockType, "coolant_recipes");
|
||||
const metadata = getAtlasMetadata(atlasId);
|
||||
|
||||
recipeItem.querySelectorAll('[data-text="recipe:item"]')
|
||||
.forEach(e => e.innerText = recipeName);
|
||||
recipeItem.querySelectorAll('[data-atlas="recipe:item"]')
|
||||
.forEach(e => e.replaceChildren(makeAtlasImage(atlasId)));
|
||||
|
||||
recipeItem.addEventListener("click", () => {
|
||||
handleDismiss();
|
||||
selectedRecipe = recipe;
|
||||
updateSelectedRecipe();
|
||||
setSolverMetadata(plannerDom, {
|
||||
coolant_recipe: selectedRecipe,
|
||||
});
|
||||
});
|
||||
|
||||
const tooltipData = [...getRecipeTooltip(recipe, metadata)];
|
||||
setTooltip(recipeItem, tooltipData);
|
||||
if (!filter.test(recipeName, ...tooltipDataToPlain(tooltipData))) {
|
||||
recipeItem.classList.add("hidden");
|
||||
}
|
||||
|
||||
recipeList.addItem(recipeItem);
|
||||
}
|
||||
}
|
||||
|
||||
updateRecipeList();
|
||||
filterElem.addEventListener("input", debounce(updateRecipeList));
|
||||
|
||||
designItem.querySelectorAll(':scope > .recipe--selected').forEach(e => {
|
||||
e.addEventListener("click", () => {
|
||||
designItem.classList.add("selecting");
|
||||
designItem.querySelector('[autofocus]').focus();
|
||||
window.addEventListener("click", handleDismiss);
|
||||
});
|
||||
initializeRecipePicker({
|
||||
parent: designItem,
|
||||
title: "Coolant Recipe",
|
||||
entryListType: "coolant_recipes",
|
||||
recipes: coolantRecipes,
|
||||
updateSelection: r => setSolverMetadata(plannerDom, { coolant_recipe: r }),
|
||||
multiblockType,
|
||||
currentSelection,
|
||||
});
|
||||
|
||||
designRecipes.addItem(designItem);
|
||||
|
|
@ -160,7 +180,17 @@ function rescanSelectionRecipes() {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log(tile);
|
||||
const designItem = designRecipes.makeItem();
|
||||
|
||||
initializeRecipePicker({
|
||||
parent: designItem,
|
||||
title: "Block Recipe",
|
||||
entryType: "ncpf:block_recipes",
|
||||
recipes: tile.modules["ncpf:block_recipes"].recipes,
|
||||
multiblockType,
|
||||
});
|
||||
|
||||
designRecipes.addItem(designItem);
|
||||
}
|
||||
|
||||
function* getRecipeTooltip(recipe, metadata) {
|
||||
|
|
@ -189,8 +219,10 @@ function* getRecipeTooltip(recipe, metadata) {
|
|||
console.log("unknown recipe", recipe, metadata);
|
||||
}
|
||||
|
||||
yield {
|
||||
type: "italics",
|
||||
text: metadata.configurationAddon.name,
|
||||
};
|
||||
if (metadata.configurationAddon) {
|
||||
yield {
|
||||
type: "italics",
|
||||
text: metadata.configurationAddon.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue