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) {
|
function putTextureOnCanvas(ctx, atlasSize, data) {
|
||||||
const atlasIndexX = index % atlasSize;
|
if (!data.duplicate) {
|
||||||
const atlasIndexY = (index - atlasIndexX) / atlasSize;
|
const atlasIndexX = data.index % atlasSize;
|
||||||
|
const atlasIndexY = (data.index - atlasIndexX) / atlasSize;
|
||||||
const x = atlasIndexX * data.size[0];
|
const x = atlasIndexX * data.size[0];
|
||||||
const y = atlasIndexY * data.size[1];
|
const y = atlasIndexY * data.size[1];
|
||||||
ctx.drawImage(data.image, x, y);
|
ctx.drawImage(data.image, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
// free up image data
|
// free up image data
|
||||||
delete data.image;
|
delete data.image;
|
||||||
|
delete data.texture;
|
||||||
|
delete data.duplicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildAtlas(data) {
|
export async function buildAtlas(data) {
|
||||||
|
|
@ -39,16 +43,34 @@ export async function buildAtlas(data) {
|
||||||
...Object.entries(data.configuration),
|
...Object.entries(data.configuration),
|
||||||
...data.addons.flatMap(a => Object.entries(a.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})
|
// .map(v => {console.log(v); return v})
|
||||||
.flatMap(([ config, addon, data ]) => Object.entries(data)
|
.flatMap(([ config, addon, data ]) => Object.entries(data)
|
||||||
.map(([ type, entries ]) => [ config, type, addon, entries ]))
|
.map(([ type, entries ]) => [ config, type, addon, entries ]))
|
||||||
.flatMap(([c, t, a, e]) => Array.isArray(e)
|
.flatMap(([ config, type, addon, entry ]) => {
|
||||||
? [[c, t, a, e]]
|
if (Array.isArray(entry)) {
|
||||||
: t === "modules" && e["plannerator:global_elements"]
|
return [[ config, type, addon, entry ]];
|
||||||
? [[c, "plannerator:global_elements", a, e["plannerator:global_elements"].elements]]
|
}
|
||||||
: [])
|
if (type === "modules" && entry["plannerator:global_elements"]) {
|
||||||
.flatMap(([c, t, a, l]) => l.map((v) => [c, t, a, v]))
|
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"])
|
.filter(([,,, b]) => b && b.modules && b.modules["plannerator:texture"])
|
||||||
.map(([c, t, a, b]) => ({
|
.map(([c, t, a, b]) => ({
|
||||||
configuration: c,
|
configuration: c,
|
||||||
|
|
@ -71,18 +93,25 @@ export async function buildAtlas(data) {
|
||||||
throw new Error("Configuration processing failed - textures must be square");
|
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 atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(t.length)));
|
||||||
const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0];
|
const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0];
|
||||||
|
|
||||||
const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
|
const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
await Promise.all(t.map((b, i) => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, i, b)));
|
await Promise.all(t.map(b => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, b)));
|
||||||
|
|
||||||
t.forEach((m, i) => {
|
|
||||||
m.index = offset + i;
|
|
||||||
delete m.texture;
|
|
||||||
});
|
|
||||||
|
|
||||||
const blob = await canvas.convertToBlob({ type: "image/png" });
|
const blob = await canvas.convertToBlob({ type: "image/png" });
|
||||||
atlases.push({
|
atlases.push({
|
||||||
|
|
@ -154,15 +183,13 @@ export function getAtlasMetadata(index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
|
export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
|
||||||
let index = 0;
|
|
||||||
for (const atlas of atlases) {
|
for (const atlas of atlases) {
|
||||||
for (const atlasItemMeta of atlas.metadata) {
|
for (const atlasItemMeta of atlas.metadata) {
|
||||||
if (checkEquivalence(metadata, atlasItemMeta)
|
if (checkEquivalence(metadata, atlasItemMeta)
|
||||||
&& atlasItemMeta.configuration === configuration
|
&& atlasItemMeta.configuration === configuration
|
||||||
&& (!configurationList || atlasItemMeta.configurationList === configurationList)) {
|
&& (!configurationList || atlasItemMeta.configurationList === configurationList)) {
|
||||||
return index;
|
return atlasItemMeta.index;
|
||||||
}
|
}
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,87 @@ registerLoadCallback(() => {
|
||||||
listenTo([ "planner:selection:removed", "planner:selection:added"], rescanSelectionRecipes);
|
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() {
|
function rescanDesignRecipes() {
|
||||||
console.log("rescan recipes");
|
console.log("rescan recipes");
|
||||||
console.log(configuration);
|
console.log(configuration);
|
||||||
|
|
@ -50,87 +131,26 @@ function rescanDesignRecipes() {
|
||||||
.flatMap(([, v]) => v.coolant_recipes);
|
.flatMap(([, v]) => v.coolant_recipes);
|
||||||
|
|
||||||
const designItem = designRecipes.makeItem();
|
const designItem = designRecipes.makeItem();
|
||||||
designItem.querySelectorAll('[data-text="recipe:type"]')
|
|
||||||
.forEach(e => e.innerText = "Coolant Recipe");
|
|
||||||
|
|
||||||
const plannerDom = getPlannerDom();
|
const plannerDom = getPlannerDom();
|
||||||
const plannerMetadata = getSolverMetadata(plannerDom);
|
const plannerMetadata = getSolverMetadata(plannerDom);
|
||||||
let selectedRecipe = plannerMetadata?.coolant_recipe;
|
let currentSelection = plannerMetadata?.coolant_recipe;
|
||||||
if (!plannerMetadata) {
|
if (!plannerMetadata) {
|
||||||
selectedRecipe = defaultRecipes["nuclearcraft:overhaul_sfr"].coolant_recipe;
|
currentSelection = defaultRecipes[multiblockType].coolant_recipe;
|
||||||
setSolverMetadata(plannerDom, {
|
setSolverMetadata(plannerDom, {
|
||||||
coolant_recipe: selectedRecipe,
|
coolant_recipe: currentSelection,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
selectedRecipe = findFullEntry("coolant_recipes", selectedRecipe, { configurationHint: multiblockType });
|
currentSelection = findFullEntry("coolant_recipes", currentSelection, { configurationHint: multiblockType });
|
||||||
|
|
||||||
function updateSelectedRecipe() {
|
initializeRecipePicker({
|
||||||
const selectedRecipeName = selectedRecipe.modules["plannerator:display_name"].display_name;
|
parent: designItem,
|
||||||
const selectedAtlasId = getAtlasIndexFromMetadata(selectedRecipe, multiblockType, "coolant_recipes");
|
title: "Coolant Recipe",
|
||||||
const metadata = getAtlasMetadata(selectedAtlasId);
|
entryListType: "coolant_recipes",
|
||||||
designItem.querySelectorAll('[data-text="recipe:selected"]')
|
recipes: coolantRecipes,
|
||||||
.forEach(e => e.innerText = selectedRecipeName);
|
updateSelection: r => setSolverMetadata(plannerDom, { coolant_recipe: r }),
|
||||||
designItem.querySelectorAll('[data-atlas="recipe:selected"]')
|
multiblockType,
|
||||||
.forEach(e => e.replaceChildren(makeAtlasImage(selectedAtlasId)));
|
currentSelection,
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
designRecipes.addItem(designItem);
|
designRecipes.addItem(designItem);
|
||||||
|
|
@ -160,7 +180,17 @@ function rescanSelectionRecipes() {
|
||||||
return;
|
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) {
|
function* getRecipeTooltip(recipe, metadata) {
|
||||||
|
|
@ -189,8 +219,10 @@ function* getRecipeTooltip(recipe, metadata) {
|
||||||
console.log("unknown recipe", recipe, metadata);
|
console.log("unknown recipe", recipe, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata.configurationAddon) {
|
||||||
yield {
|
yield {
|
||||||
type: "italics",
|
type: "italics",
|
||||||
text: metadata.configurationAddon.name,
|
text: metadata.configurationAddon.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue