1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-16 11:41:50 +00:00

Belt path drawing optimization

This commit is contained in:
tobspr 2022-01-20 17:07:03 +01:00
parent 38cca33985
commit 670c07cba8
2 changed files with 156 additions and 8 deletions

View File

@ -1,7 +1,9 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { smoothenDpi } from "../core/dpi_manager";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle"; import { Rectangle } from "../core/rectangle";
import { ORIGINAL_SPRITE_SCALE } from "../core/sprites";
import { clamp, epsilonCompare, round4Digits } from "../core/utils"; import { clamp, epsilonCompare, round4Digits } from "../core/utils";
import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector"; import { enumDirection, enumDirectionToVector, enumInvertedDirections, Vector } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization"; import { BasicSerializableObject, types } from "../savegame/serialization";
@ -1430,6 +1432,12 @@ export class BeltPath extends BasicSerializableObject {
let trackPos = 0.0; let trackPos = 0.0;
/**
* @type {Array<[Vector, BaseItem]>}
*/
let drawStack = [];
let drawStackProp = "";
// Iterate whole track and check items // Iterate whole track and check items
for (let i = 0; i < this.entityPath.length; ++i) { for (let i = 0; i < this.entityPath.length; ++i) {
const entity = this.entityPath[i]; const entity = this.entityPath[i];
@ -1449,25 +1457,165 @@ export class BeltPath extends BasicSerializableObject {
const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile(); const worldPos = staticComp.localTileToWorld(localPos).toWorldSpaceCenterOfTile();
const distanceAndItem = this.items[currentItemIndex]; const distanceAndItem = this.items[currentItemIndex];
const item = distanceAndItem[1 /* item */];
const nextItemDistance = distanceAndItem[0 /* nextDistance */];
distanceAndItem[1 /* item */].drawItemCenteredClipped( if (drawStack.length > 1) {
worldPos.x, // Check if we can append to the stack, since its already a stack of two same items
worldPos.y, const referenceItem = drawStack[0];
parameters, if (Math.abs(referenceItem[0][drawStackProp] - worldPos[drawStackProp]) < 0.001) {
globalConfig.defaultItemDiameter // Will continue stack
); } else {
// Start a new stack, since item doesn't follow in row
this.drawDrawStack(drawStack, parameters, drawStackProp);
drawStack = [];
drawStackProp = "";
}
} else if (drawStack.length === 1) {
const firstItem = drawStack[0];
// Check if we can make it a stack
if (firstItem[1 /* item */].equals(item)) {
// Same item, check if it is either horizontal or vertical
const startPos = firstItem[0 /* pos */];
if (Math.abs(startPos.x - worldPos.x) < 0.001) {
drawStackProp = "x";
} else if (Math.abs(startPos.y - worldPos.y) < 0.001) {
drawStackProp = "y";
} else {
// Start a new stack
this.drawDrawStack(drawStack, parameters, drawStackProp);
drawStack = [];
drawStackProp = "";
}
} else {
// Start a new stack, since item doesn't equal
this.drawDrawStack(drawStack, parameters, drawStackProp);
drawStack = [];
drawStackProp = "";
}
} else {
// First item of stack, do nothing
}
drawStack.push([worldPos, item]);
// Check for the next item // Check for the next item
currentItemPos += distanceAndItem[0 /* nextDistance */]; currentItemPos += nextItemDistance;
++currentItemIndex; ++currentItemIndex;
if (nextItemDistance > globalConfig.itemSpacingOnBelts + 0.002 || drawStack.length > 20) {
// If next item is not directly following, abort drawing
this.drawDrawStack(drawStack, parameters, drawStackProp);
drawStack = [];
drawStackProp = "";
}
if (currentItemIndex >= this.items.length) { if (currentItemIndex >= this.items.length) {
// We rendered all items // We rendered all items
this.drawDrawStack(drawStack, parameters, drawStackProp);
return; return;
} }
} }
trackPos += beltLength; trackPos += beltLength;
} }
this.drawDrawStack(drawStack, parameters, drawStackProp);
}
/**
*
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} context
* @param {number} w
* @param {number} h
* @param {number} dpi
* @param {object} param0
* @param {string} param0.direction
* @param {Array<[Vector, BaseItem]>} param0.stack
* @param {GameRoot} param0.root
* @param {number} param0.zoomLevel
*/
drawShapesInARow(canvas, context, w, h, dpi, { direction, stack, root, zoomLevel }) {
context.scale(dpi, dpi);
context.fillStyle = "rgba(0, 0, 255, 0.1)";
context.fillRect(1, 1, w - 2, h - 2);
const parameters = new DrawParameters({
context,
desiredAtlasScale: ORIGINAL_SPRITE_SCALE,
root,
visibleRect: new Rectangle(-1000, -1000, 2000, 2000),
zoomLevel,
});
const itemSize = globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
const item = stack[0];
const pos = new Vector(itemSize / 2, itemSize / 2);
for (let i = 0; i < stack.length; i++) {
item[1].drawItemCenteredClipped(pos.x, pos.y, parameters, globalConfig.defaultItemDiameter);
pos[direction] += globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
}
}
/**
* @param {Array<[Vector, BaseItem]>} stack
* @param {DrawParameters} parameters
*/
drawDrawStack(stack, parameters, directionProp) {
if (stack.length === 0) {
return;
}
const firstItem = stack[0];
const firstItemPos = firstItem[0];
if (stack.length === 1) {
firstItem[1].drawItemCenteredClipped(
firstItemPos.x,
firstItemPos.y,
parameters,
globalConfig.defaultItemDiameter
);
return;
}
const itemSize = globalConfig.itemSpacingOnBelts * globalConfig.tileSize;
const inverseDirection = directionProp === "x" ? "y" : "x";
const dimensions = new Vector(itemSize, itemSize);
dimensions[inverseDirection] *= stack.length;
const directionVector = firstItemPos.copy().sub(stack[1][0]);
const dpi = smoothenDpi(globalConfig.shapesSharpness * parameters.zoomLevel);
const sprite = this.root.buffers.getForKey({
key: "beltpaths",
subKey: "stack-" + directionProp + "-" + dpi + "-" + stack.length + firstItem[1].serialize(),
dpi,
w: dimensions.x,
h: dimensions.y,
redrawMethod: this.drawShapesInARow.bind(this),
additionalParams: {
direction: inverseDirection,
stack,
root: this.root,
zoomLevel: parameters.zoomLevel,
},
});
const anchor = directionVector[inverseDirection] < 0 ? firstItem : stack[stack.length - 1];
parameters.context.drawImage(
sprite,
anchor[0].x - itemSize / 2,
anchor[0].y - itemSize / 2,
dimensions.x,
dimensions.y
);
} }
} }

View File

@ -58,7 +58,7 @@ export class GameLogic {
* @param {Vector=} param0.offset Optional, move the entity by the given offset first * @param {Vector=} param0.offset Optional, move the entity by the given offset first
* @returns {boolean} true if the entity could be placed there * @returns {boolean} true if the entity could be placed there
*/ */
checkCanPlaceEntity(entity, { allowReplaceBuildings = false, offset = null }) { checkCanPlaceEntity(entity, { allowReplaceBuildings = true, offset = null }) {
// Compute area of the building // Compute area of the building
const rect = entity.components.StaticMapEntity.getTileSpaceBounds(); const rect = entity.components.StaticMapEntity.getTileSpaceBounds();
if (offset) { if (offset) {