359 lines
11 KiB
TypeScript
359 lines
11 KiB
TypeScript
/**
|
|
* @module RigidBodyBehaviors
|
|
* Apply the following contraints to the `container` :
|
|
* - The container must be kept inside its parent
|
|
* - The container must find an unallocated space within the parent
|
|
* If the contraints fails, an error message will be returned
|
|
*/
|
|
|
|
import Swal from 'sweetalert2';
|
|
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
|
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
|
|
|
/**
|
|
* "Transform the container into a rigid body"
|
|
* Apply the following contraints to the `container` :
|
|
* - The container must be kept inside its parent
|
|
* - The container must find an unallocated space within the parent
|
|
* If the contraints fails, an error message will be returned
|
|
* @param container Container to apply its rigid body properties
|
|
* @returns A rigid body container
|
|
*/
|
|
export function RecalculatePhysics(
|
|
container: IContainerModel
|
|
): IContainerModel {
|
|
container = constraintBodyInsideParent(container);
|
|
container = constraintBodyInsideUnallocatedWidth(container);
|
|
return container;
|
|
}
|
|
|
|
/**
|
|
* Limit a rect inside a parent rect by applying the following rules :
|
|
* it cannot be bigger than the parent
|
|
* it cannot go out of bound
|
|
* Mutates and returns the container
|
|
* @param container
|
|
* @returns Updated container
|
|
*/
|
|
function constraintBodyInsideParent(
|
|
container: IContainerModel
|
|
): IContainerModel {
|
|
if (container.parent === null || container.parent === undefined) {
|
|
return container;
|
|
}
|
|
|
|
const parentProperties = container.parent.properties;
|
|
const parentWidth = parentProperties.width;
|
|
const parentHeight = parentProperties.height;
|
|
|
|
return constraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight);
|
|
}
|
|
|
|
/**
|
|
* Limit a container inside a rectangle
|
|
* Mutates and returns the container
|
|
* @param container A container
|
|
* @param x x of top left of the rectangle
|
|
* @param y y of top left of the rectangle
|
|
* @param width width of the rectangle
|
|
* @param height height of the rectangle
|
|
* @returns Updated container
|
|
*/
|
|
function constraintBodyInsideSpace(
|
|
container: IContainerModel,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number
|
|
): IContainerModel {
|
|
const containerProperties = container.properties;
|
|
const containerX = containerProperties.x;
|
|
const containerY = containerProperties.y;
|
|
const containerWidth = containerProperties.width;
|
|
const containerHeight = containerProperties.height;
|
|
|
|
// Check size bigger than parent
|
|
const isBodyLargerThanParent = containerWidth > width;
|
|
const isBodyTallerThanParentHeight = containerHeight > height;
|
|
if (isBodyLargerThanParent || isBodyTallerThanParentHeight) {
|
|
if (isBodyLargerThanParent) {
|
|
containerProperties.x = x;
|
|
containerProperties.width = width;
|
|
}
|
|
if (isBodyTallerThanParentHeight) {
|
|
containerProperties.y = y;
|
|
containerProperties.height = height;
|
|
}
|
|
return container;
|
|
}
|
|
|
|
// Check horizontal out of bound
|
|
if (containerX < x) {
|
|
containerProperties.x = x;
|
|
}
|
|
if (containerX + containerWidth > x + width) {
|
|
containerProperties.x = x + width - containerWidth;
|
|
}
|
|
|
|
// Check vertical out of bound
|
|
if (containerY < y) {
|
|
containerProperties.y = y;
|
|
}
|
|
if (containerY + containerHeight > y + height) {
|
|
containerProperties.y = y + height - containerHeight;
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
/**
|
|
* Constraint the container inside unallocated width/space of the parent container
|
|
* If there is no unalloacted width/space, an error will be thrown
|
|
* Mutates and returns the container
|
|
* @param container
|
|
* @returns Updated container
|
|
*/
|
|
export function constraintBodyInsideUnallocatedWidth(
|
|
container: IContainerModel
|
|
): IContainerModel {
|
|
if (container.parent === null) {
|
|
return container;
|
|
}
|
|
|
|
// Get the available spaces of the parent
|
|
const availableWidths = getAvailableWidths(container.parent, container);
|
|
const containerX = container.properties.x;
|
|
const containerWidth = container.properties.width;
|
|
|
|
// Check if there is still some space
|
|
if (availableWidths.length === 0) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Not enough space!',
|
|
text: 'Try to free the parent a little bit!'
|
|
});
|
|
throw new Error(
|
|
'No available space found on the parent container. Try to free the parent a bit.'
|
|
);
|
|
}
|
|
|
|
const middle = containerX + containerWidth / 2;
|
|
// Sort the available width to find the space with the closest position
|
|
availableWidths.sort(
|
|
(width1, width2) => {
|
|
let compared1X = width1.x;
|
|
if (width1.x < containerX) {
|
|
compared1X = width1.x + width1.width - containerWidth;
|
|
}
|
|
|
|
let compared2X = width2.x;
|
|
if (width2.x < containerX) {
|
|
compared2X = width2.x + width2.width - containerWidth;
|
|
}
|
|
|
|
return Math.abs(compared1X - middle) - Math.abs(compared2X - middle);
|
|
}
|
|
);
|
|
|
|
// Check if the container actually fit inside
|
|
// It will usually fit if it was alrady fitting
|
|
const availableWidthFound = availableWidths.find((width) =>
|
|
isFitting(container.properties.width, width)
|
|
);
|
|
|
|
if (availableWidthFound === undefined) {
|
|
// Otherwise, it is possible that it does not fit
|
|
// There is two way to reach this part of the code
|
|
// 1) Enable isRigidBody such as width > availableWidth.width
|
|
// 2) Resize a container such as width > availableWidth.width
|
|
|
|
// We want the container to fit automatically inside the available space
|
|
// even if it means to resize the container
|
|
const availableWidth: ISizePointer | undefined = availableWidths.find((width) => {
|
|
return isFitting(container.properties.minWidth, width);
|
|
});
|
|
|
|
if (availableWidth === undefined) {
|
|
console.warn(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large. Consequently, its rigid body property is disabled.`);
|
|
Swal.fire({
|
|
position: 'top-end',
|
|
title: `Container ${container.properties.id} cannot fit!`,
|
|
text: 'Its rigid body property is now disabled. Change its the minimum width or free the parent container.',
|
|
timerProgressBar: true,
|
|
showConfirmButton: false,
|
|
timer: 5000
|
|
});
|
|
container.properties.isRigidBody = false;
|
|
return container;
|
|
}
|
|
|
|
container.properties.x = availableWidth.x;
|
|
container.properties.width = availableWidth.width;
|
|
return container;
|
|
}
|
|
|
|
return constraintBodyInsideSpace(
|
|
container,
|
|
availableWidthFound.x,
|
|
0,
|
|
availableWidthFound.width,
|
|
container.parent.properties.height
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if a container can fit inside a size space
|
|
* @param container Container to check
|
|
* @param sizePointer Size space to check
|
|
* @returns
|
|
*/
|
|
const isFitting = (
|
|
containerWidth: number,
|
|
sizePointer: ISizePointer
|
|
): boolean => containerWidth <= sizePointer.width;
|
|
|
|
/**
|
|
* Get the unallocated widths inside a container
|
|
* An allocated width is defined by its the widths of the children that are rigid bodies.
|
|
* An example of this allocation system is the disk space of an hard drive
|
|
* (except the fact that disk space is divided by block).
|
|
* @param container Container where to find an available width
|
|
* @param exception Container to exclude of the widths (since a container will be moved, it might need to be excluded)
|
|
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
|
*/
|
|
function getAvailableWidths(
|
|
container: IContainerModel,
|
|
exception: IContainerModel
|
|
): ISizePointer[] {
|
|
// Initialize the first size pointer
|
|
// which takes full width of the available space
|
|
const x = 0;
|
|
const width = container.properties.width;
|
|
let unallocatedSpaces: ISizePointer[] = [{ x, width }];
|
|
|
|
// We will only uses containers that also are rigid or are anchors
|
|
const solidBodies = container.children.filter(
|
|
(child) => child.properties.isRigidBody || child.properties.isAnchor
|
|
);
|
|
|
|
for (const child of solidBodies) {
|
|
// Ignore the exception
|
|
if (child === exception) {
|
|
continue;
|
|
}
|
|
const childX = child.properties.x;
|
|
const childWidth = child.properties.width;
|
|
|
|
// get the space of the child that is inside the parent
|
|
let newUnallocatedSpace: ISizePointer[] = [];
|
|
|
|
// We will iterate on a mutable variable in order to divide it
|
|
for (const unallocatedSpace of unallocatedSpaces) {
|
|
// In order to find unallocated space,
|
|
// We need to calculate the overlap between the two containers
|
|
// We only works with widths meaning in 1D (with lines)
|
|
const newUnallocatedWidths = getAvailableWidthsTwoLines(
|
|
unallocatedSpace,
|
|
childX,
|
|
childWidth
|
|
);
|
|
|
|
// Concat the new list of SizePointer pointing to availables spaces
|
|
newUnallocatedSpace = newUnallocatedSpace.concat(newUnallocatedWidths);
|
|
}
|
|
// Finally update the availables spaces found, loop again with it
|
|
unallocatedSpaces = newUnallocatedSpace;
|
|
}
|
|
|
|
return unallocatedSpaces;
|
|
}
|
|
|
|
/**
|
|
* Returns the unallocated widths between two lines in 1D
|
|
* @param unalloctedSpace unallocated space
|
|
* @param rectX left of the second line
|
|
* @param rectWidth width of the second line
|
|
* @returns Available widths
|
|
*/
|
|
function getAvailableWidthsTwoLines(
|
|
unallocatedSpace: ISizePointer,
|
|
rectX: number,
|
|
rectWidth: number
|
|
): ISizePointer[] {
|
|
const unallocatedSpaceRight = unallocatedSpace.x + unallocatedSpace.width;
|
|
const rectRight = rectX + rectWidth;
|
|
|
|
const isNotOverlapping = unallocatedSpaceRight < rectX ||
|
|
unallocatedSpace.x > rectRight;
|
|
if (isNotOverlapping) {
|
|
return [unallocatedSpace];
|
|
}
|
|
|
|
const isOverlappingFullWidth = rectX < unallocatedSpace.x && rectRight > unallocatedSpaceRight;
|
|
if (isOverlappingFullWidth) {
|
|
return [];
|
|
}
|
|
|
|
const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX;
|
|
if (isOverlappingOnTheLeft) {
|
|
return getAvailableWidthsLeft(unallocatedSpaceRight, rectRight);
|
|
}
|
|
|
|
const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight;
|
|
if (isOverlappingOnTheRight) {
|
|
return getAvailableWidthsRight(unallocatedSpace.x, rectX);
|
|
}
|
|
|
|
return getAvailableWidthsMiddle(unallocatedSpace.x, unallocatedSpaceRight, rectX, rectRight);
|
|
}
|
|
|
|
function getAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number): ISizePointer[] {
|
|
if (unallocatedSpaceRight - rectRight <= 0) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
x: rectRight,
|
|
width: unallocatedSpaceRight - rectRight
|
|
}
|
|
];
|
|
}
|
|
|
|
function getAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] {
|
|
if (rectX - unallocatedSpaceX <= 0) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
x: unallocatedSpaceX,
|
|
width: rectX - unallocatedSpaceX
|
|
}
|
|
];
|
|
}
|
|
|
|
function getAvailableWidthsMiddle(
|
|
unallocatedSpaceX: number,
|
|
unallocatedSpaceRight: number,
|
|
rectX: number,
|
|
rectRight: number
|
|
): ISizePointer[] {
|
|
const sizePointers: ISizePointer[] = [];
|
|
|
|
if (rectX - unallocatedSpaceX > 0) {
|
|
sizePointers.push({
|
|
x: unallocatedSpaceX,
|
|
width: rectX - unallocatedSpaceX
|
|
});
|
|
}
|
|
|
|
if (unallocatedSpaceRight - rectRight > 0) {
|
|
sizePointers.push({
|
|
x: rectRight,
|
|
width: unallocatedSpaceRight - rectRight
|
|
});
|
|
}
|
|
|
|
return sizePointers;
|
|
}
|