Skip to content

Commit

Permalink
Extend visualisation to support all of the boxes in a packing #213
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdoug committed Mar 14, 2021
1 parent 74d5c58 commit 0cdb4c0
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 132 deletions.
8 changes: 7 additions & 1 deletion src/PackedBoxList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use function count;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use function reset;
use function round;
use Traversable;
Expand All @@ -22,7 +23,7 @@
*
* @author Doug Wright
*/
class PackedBoxList implements IteratorAggregate, Countable
class PackedBoxList implements IteratorAggregate, Countable, JsonSerializable
{
/**
* List containing boxes.
Expand Down Expand Up @@ -171,4 +172,9 @@ public function getVolumeUtilisation(): float

return round($itemVolume / $boxVolume * 100, 1);
}

public function jsonSerialize(): array
{
return $this->list;
}
}
274 changes: 143 additions & 131 deletions visualiser/babylonjs.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<body>
<script>

// replace with contents of json_encode($packedBox)
// replace with contents of json_encode($packedBox) or json_encode($packedBoxList)
const PACKING = {};

const ZOOM = 0.1; // very large containers make weird visual artifacts, reduce everything down
Expand Down Expand Up @@ -62,6 +62,8 @@
];

const createScene = () => {
const packingData = PACKING.items ? [PACKING] : PACKING;

const scene = new BABYLON.Scene(engine);

/*
Expand All @@ -76,124 +78,8 @@

const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");

const box = BABYLON.MeshBuilder.CreateBox(
"box",
{
width: ZOOM * PACKING.box.innerWidth,
depth: ZOOM * PACKING.box.innerLength,
height: ZOOM * PACKING.box.innerDepth,
}
);
hl.addExcludedMesh(box);
let material = new BABYLON.StandardMaterial("material", scene);
material.alpha = 0; // make box faces invisible
box.material = material;
box.showBoundingBox = true; // but show edges
// Babylon positions the centre of the box at (0,0,0) so compensate for that as BoxPacker measures from the corner
box.position.x = ZOOM * PACKING.box.innerWidth / 2;
box.position.z = ZOOM * PACKING.box.innerLength / 2;
box.position.y = ZOOM * PACKING.box.innerDepth / 2;

for (let itemsKey in PACKING.items) {
let item = BABYLON.MeshBuilder.CreateBox(
"item" + itemsKey,
{
width: ZOOM * PACKING.items[itemsKey].width,
depth: ZOOM * PACKING.items[itemsKey].length,
height: ZOOM * PACKING.items[itemsKey].depth,
}
);
let material = new BABYLON.StandardMaterial("material", scene);
material.diffuseColor = ITEM_COLOURS[itemsKey % 28];
material.alpha = 0.7;
item.material = material;
// Babylon positions the centre of the item at (0,0,0) not a corner so compensate for that
item.position.x = ZOOM * PACKING.items[itemsKey].width / 2 + ZOOM * PACKING.items[itemsKey].x;
item.position.z = ZOOM * PACKING.items[itemsKey].length / 2 + ZOOM * PACKING.items[itemsKey].y;
item.position.y = ZOOM * PACKING.items[itemsKey].depth / 2 + ZOOM * PACKING.items[itemsKey].z;

let rect1 = new BABYLON.GUI.Rectangle();
advancedTexture.addControl(rect1);
rect1.width = "300px";
rect1.height ="150px";
rect1.thickness = 2;
rect1.linkOffsetX = "150px";
rect1.linkOffsetY = "-100px";
rect1.transformCenterX = 0;
rect1.transformCenterY = 1;
rect1.background = "grey";
rect1.alpha = 0.7;
rect1.scaleX = 0;
rect1.scaleY = 0;
rect1.cornerRadius = 10
rect1.linkWithMesh(item);

let text1 = new BABYLON.GUI.TextBlock();
text1.text = "Item: " + PACKING.items[itemsKey].item.description;
text1.text += "\n";
text1.text += "As specified (W×L×D): " + PACKING.items[itemsKey].item.width + '×' + PACKING.items[itemsKey].item.length + '×' + PACKING.items[itemsKey].item.depth;
text1.text += "\n";
text1.text += "As packed (W×L×D): " + PACKING.items[itemsKey].width + '×' + PACKING.items[itemsKey].length + '×' + PACKING.items[itemsKey].depth;
text1.text += "\n";
text1.text += "x: " + PACKING.items[itemsKey].x;
text1.text += "\n";
text1.text += "y: " + PACKING.items[itemsKey].y;
text1.text += "\n";
text1.text += "z: " + PACKING.items[itemsKey].z;
text1.color = "White";
text1.fontSize = 14;
text1.textWrapping = true;
text1.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
text1.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
text1.background = '#006994'
rect1.addControl(text1)
text1.alpha = (1/text1.parent.alpha);
text1.paddingTop = "20px";
text1.paddingBottom = "20px";
text1.paddingLeft = "20px";
text1.paddingRight = "20px";

let actionManager = new BABYLON.ActionManager(scene);
item.actionManager = actionManager;

let scaleXAnimation = new BABYLON.Animation("myAnimation", "scaleX", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
let scaleYAnimation = new BABYLON.Animation("myAnimation", "scaleY", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);

let keys = [];
keys.push({
frame: 0,
value: 0
});
keys.push({
frame: 10,
value: 1
});

scaleXAnimation.setKeys(keys);
scaleYAnimation.setKeys(keys);
rect1.animations = [];
rect1.animations.push(scaleXAnimation);
rect1.animations.push(scaleYAnimation);

actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOverTrigger, function(ev){
hl.addMesh(item, BABYLON.Color3.Green());
scene.beginAnimation(rect1, 0, 10, false);
}));
//if hover is over remove highlight of the mesh
actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOutTrigger, function(ev){
hl.removeMesh(item);
scene.beginAnimation(rect1, 10, 0, false);
}));

}


const camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", -0.8 * Math.PI, 0.5 * Math.PI, ZOOM * PACKING.box.innerDepth * 2.5, new BABYLON.Vector3(0, ZOOM * PACKING.box.innerDepth / 2, 0), scene);
camera.panningSensibility = 10;
camera.attachControl(canvas, true, true);

// draw **BoxPacker** axes (y/z are flipped compared to Babylon)
let showAxis = function (size) {
const showAxis = function (xSize, ySize, zSize, xPos) {
let makeTextPlane = function (text, color, size) {
let dynamicTexture = new BABYLON.DynamicTexture("DynamicTexture", 50, scene, true);
dynamicTexture.hasAlpha = true;
Expand All @@ -207,28 +93,154 @@
};

let axisX = BABYLON.Mesh.CreateLines("axisX", [
new BABYLON.Vector3.Zero(), new BABYLON.Vector3(size, 0, 0), new BABYLON.Vector3(size * 0.95, 0.05 * size, 0),
new BABYLON.Vector3(size, 0, 0), new BABYLON.Vector3(size * 0.95, -0.05 * size, 0)
new BABYLON.Vector3(xPos, 0, 0), new BABYLON.Vector3(xPos + xSize, 0, 0), new BABYLON.Vector3(xPos + xSize * 0.95, 0.05 * xSize, 0),
new BABYLON.Vector3(xPos + xSize, 0, 0), new BABYLON.Vector3(xPos + xSize * 0.95, -0.05 * xSize, 0)
], scene);
axisX.color = new BABYLON.Color3(1, 0, 0);
let xChar = makeTextPlane("X", "red", size / 10);
xChar.position = new BABYLON.Vector3(0.9 * size, -0.05 * size, 0);
let xChar = makeTextPlane("X", "red", xSize / 10);
xChar.position = new BABYLON.Vector3(0.9 * xSize + xPos, -0.05 * xSize, 0);
let axisY = BABYLON.Mesh.CreateLines("axisY", [
new BABYLON.Vector3.Zero(), new BABYLON.Vector3(0, size, 0), new BABYLON.Vector3(-0.05 * size, size * 0.95, 0),
new BABYLON.Vector3(0, size, 0), new BABYLON.Vector3(0.05 * size, size * 0.95, 0)
new BABYLON.Vector3(xPos, 0, 0), new BABYLON.Vector3(xPos, zSize, 0), new BABYLON.Vector3(xPos -0.05 * zSize, zSize * 0.95, 0),
new BABYLON.Vector3(xPos, zSize, 0), new BABYLON.Vector3(xPos + 0.05 * zSize, zSize * 0.95, 0)
], scene);
axisY.color = new BABYLON.Color3(0, 1, 0);
let yChar = makeTextPlane("Z", "green", size / 10);
yChar.position = new BABYLON.Vector3(0, 0.9 * size, -0.05 * size);
let yChar = makeTextPlane("Z", "green", zSize / 10);
yChar.position = new BABYLON.Vector3(xPos, 0.9 * zSize, -0.05 * zSize);
let axisZ = BABYLON.Mesh.CreateLines("axisZ", [
new BABYLON.Vector3.Zero(), new BABYLON.Vector3(0, 0, size), new BABYLON.Vector3(0, -0.05 * size, size * 0.95),
new BABYLON.Vector3(0, 0, size), new BABYLON.Vector3(0, 0.05 * size, size * 0.95)
new BABYLON.Vector3(xPos, 0, 0), new BABYLON.Vector3(xPos, 0, ySize), new BABYLON.Vector3(xPos, -0.05 * ySize, ySize * 0.95),
new BABYLON.Vector3(xPos, 0, ySize), new BABYLON.Vector3(xPos, 0.05 * ySize, ySize * 0.95)
], scene);
axisZ.color = new BABYLON.Color3(0, 0, 1);
let zChar = makeTextPlane("Y", "blue", size / 10);
zChar.position = new BABYLON.Vector3(0, 0.05 * size, 0.9 * size);
let zChar = makeTextPlane("Y", "blue", ySize / 10);
zChar.position = new BABYLON.Vector3(xPos, 0.05 * ySize, 0.9 * ySize);
};
showAxis(ZOOM * Math.max(PACKING.box.innerWidth, PACKING.box.innerLength, PACKING.box.innerDepth));


let boxPlacementX = 0;

for (let packedBoxKey in packingData) {
showAxis(ZOOM * packingData[packedBoxKey].box.innerWidth, ZOOM * packingData[packedBoxKey].box.innerLength, ZOOM * packingData[packedBoxKey].box.innerDepth, boxPlacementX);

const box = BABYLON.MeshBuilder.CreateBox(
"box",
{
width: ZOOM * packingData[packedBoxKey].box.innerWidth,
depth: ZOOM * packingData[packedBoxKey].box.innerLength,
height: ZOOM * packingData[packedBoxKey].box.innerDepth,
}
);
hl.addExcludedMesh(box);
let material = new BABYLON.StandardMaterial("material", scene);
material.alpha = 0; // make box faces invisible
box.material = material;
box.showBoundingBox = true; // but show edges
// Babylon positions the centre of the box at (0,0,0) so compensate for that as BoxPacker measures from the corner
box.position.x = boxPlacementX + ZOOM * packingData[packedBoxKey].box.innerWidth / 2;
box.position.z = ZOOM * packingData[packedBoxKey].box.innerLength / 2;
box.position.y = ZOOM * packingData[packedBoxKey].box.innerDepth / 2;

for (let itemsKey in packingData[packedBoxKey].items) {
let item = BABYLON.MeshBuilder.CreateBox(
"item" + itemsKey,
{
width: ZOOM * packingData[packedBoxKey].items[itemsKey].width,
depth: ZOOM * packingData[packedBoxKey].items[itemsKey].length,
height: ZOOM * packingData[packedBoxKey].items[itemsKey].depth,
}
);
let material = new BABYLON.StandardMaterial("material", scene);
material.diffuseColor = ITEM_COLOURS[itemsKey % 28];
material.alpha = 0.7;
item.material = material;
// Babylon positions the centre of the item at (0,0,0) not a corner so compensate for that
item.position.x = boxPlacementX + ZOOM * packingData[packedBoxKey].items[itemsKey].width / 2 + ZOOM * packingData[packedBoxKey].items[itemsKey].x;
item.position.z = ZOOM * packingData[packedBoxKey].items[itemsKey].length / 2 + ZOOM * packingData[packedBoxKey].items[itemsKey].y;
item.position.y = ZOOM * packingData[packedBoxKey].items[itemsKey].depth / 2 + ZOOM * packingData[packedBoxKey].items[itemsKey].z;

let rect1 = new BABYLON.GUI.Rectangle();
advancedTexture.addControl(rect1);
rect1.width = "300px";
rect1.height = "175px";
rect1.thickness = 2;
rect1.linkOffsetX = "150px";
rect1.linkOffsetY = "-100px";
rect1.transformCenterX = 0;
rect1.transformCenterY = 1;
rect1.background = "grey";
rect1.alpha = 0.7;
rect1.scaleX = 0;
rect1.scaleY = 0;
rect1.cornerRadius = 10
rect1.linkWithMesh(item);

let text1 = new BABYLON.GUI.TextBlock();
text1.text = "Box: " + packingData[packedBoxKey].box.reference;
text1.text += "\n";
text1.text += "Item: " + packingData[packedBoxKey].items[itemsKey].item.description;
text1.text += "\n";
text1.text += "As specified (W×L×D): " + packingData[packedBoxKey].items[itemsKey].item.width + '×' + packingData[packedBoxKey].items[itemsKey].item.length + '×' + packingData[packedBoxKey].items[itemsKey].item.depth;
text1.text += "\n";
text1.text += "As packed (W×L×D): " + packingData[packedBoxKey].items[itemsKey].width + '×' + packingData[packedBoxKey].items[itemsKey].length + '×' + packingData[packedBoxKey].items[itemsKey].depth;
text1.text += "\n";
text1.text += "x: " + packingData[packedBoxKey].items[itemsKey].x;
text1.text += "\n";
text1.text += "y: " + packingData[packedBoxKey].items[itemsKey].y;
text1.text += "\n";
text1.text += "z: " + packingData[packedBoxKey].items[itemsKey].z;
text1.color = "White";
text1.fontSize = 14;
text1.textWrapping = true;
text1.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
text1.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
text1.background = '#006994'
rect1.addControl(text1)
text1.alpha = (1 / text1.parent.alpha);
text1.paddingTop = "20px";
text1.paddingBottom = "20px";
text1.paddingLeft = "20px";
text1.paddingRight = "20px";

let actionManager = new BABYLON.ActionManager(scene);
item.actionManager = actionManager;

let scaleXAnimation = new BABYLON.Animation("myAnimation", "scaleX", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);
let scaleYAnimation = new BABYLON.Animation("myAnimation", "scaleY", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT);

let keys = [];
keys.push({
frame: 0,
value: 0
});
keys.push({
frame: 10,
value: 1
});

scaleXAnimation.setKeys(keys);
scaleYAnimation.setKeys(keys);
rect1.animations = [];
rect1.animations.push(scaleXAnimation);
rect1.animations.push(scaleYAnimation);

actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOverTrigger, function (ev) {
hl.addMesh(item, BABYLON.Color3.Green());
scene.beginAnimation(rect1, 0, 10, false);
}));
//if hover is over remove highlight of the mesh
actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPointerOutTrigger, function (ev) {
hl.removeMesh(item);
scene.beginAnimation(rect1, 10, 0, false);
}));

}

boxPlacementX += 2 * ZOOM * packingData[packedBoxKey].box.innerWidth
}


const camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", -0.8 * Math.PI, 0.5 * Math.PI, ZOOM * packingData[0].box.innerDepth * 2.5, new BABYLON.Vector3(0, ZOOM * packingData[0].box.innerDepth / 2, 0), scene);
camera.panningSensibility = 10;
camera.attachControl(canvas, true, true);

return scene;
};
Expand Down

0 comments on commit 0cdb4c0

Please sign in to comment.