Separate properties operations and rigid body behaviors in different modules + Doc
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Hydroxycarbamide 2022-08-11 23:35:40 +02:00
parent faa058e57d
commit 61b72f6a35
5 changed files with 449 additions and 340 deletions

View file

@ -0,0 +1,280 @@
import { IContainerModel } from '../../Interfaces/ContainerModel';
import { SizePointer } from '../../Interfaces/SizePointer';
/**
* "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 = Number(parentProperties.width);
const parentHeight = Number(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 = Number(containerProperties.x);
const containerY = Number(containerProperties.y);
const containerWidth = Number(containerProperties.width);
const containerHeight = Number(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 > width) {
containerProperties.x = x + width - containerWidth;
}
// Check vertical out of bound
if (containerY < y) {
containerProperties.y = y;
}
if (containerY + containerHeight > 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
*/
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 = Number(container.properties.x);
// Sort the available width to find the closest one
availableWidths.sort(
(width1, width2) =>
Math.abs(width1.x - containerX) - Math.abs(width2.x - containerX)
);
// Check if there is still some space
if (availableWidths.length === 0) {
throw new Error(
'No available space found on the parent container. Try to free the parent a little before placing it inside.'
);
}
// Check if the container actually fit inside
// It will usually fit if it was alrady fitting
const availableWidthFound = availableWidths.find((width) =>
isFitting(container, 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
// The end goal is that the code never show the error message no matter what action is done
// TODO: Actually give an option to not fit and show the error message shown below
const availableWidth = availableWidths[0];
container.properties.x = availableWidth.x;
container.properties.width = availableWidth.width;
// throw new Error('[constraintBodyInsideUnallocatedWidth] BIGERR: No available space found on the parent container, even though there is some.');
return container;
}
return constraintBodyInsideSpace(
container,
availableWidthFound.x,
0,
availableWidthFound.width,
Number(container.parent.properties.height)
);
}
/**
* 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 {SizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
*/
function getAvailableWidths(
container: IContainerModel,
exception: IContainerModel
): SizePointer[] {
// Initialize the first size pointer
// which takes full width of the available space
const x = 0;
const width = Number(container.properties.width);
let unallocatedSpaces: SizePointer[] = [{ x, width }];
// We will only uses containers that also have the rigid bodies
// as out-of-bound or enormouse containers should be ignored
const rigidBodies = container.children.filter(
(child) => child.properties.isRigidBody
);
for (const child of rigidBodies) {
// Ignore the exception
if (child === exception) {
continue;
}
// get the space of the child that is inside the parent
let newUnallocatedSpace: SizePointer[] = [];
// 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.x,
unallocatedSpace.x + unallocatedSpace.width,
child.properties.x,
child.properties.x + Number(child.properties.width)
);
// 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 min1 left of the first line
* @param max1 rigth of the first line
* @param min2 left of the second line
* @param max2 right of the second line
* @returns Available widths
*/
function getAvailableWidthsTwoLines(
min1: number,
max1: number,
min2: number,
max2: number
): SizePointer[] {
if (min2 < min1 && max2 > max1) {
// object 2 is overlapping full width
return [];
}
if (min1 >= min2) {
// object 2 is partially overlapping on the left
return [
{
x: max2,
width: max1 - max2
}
];
}
if (max2 >= max1) {
// object 2 is partially overlapping on the right
return [
{
x: min2,
width: max2 - min1
}
];
}
// object 2 is overlapping in the middle
return [
{
x: min1,
width: min2 - min1
},
{
x: min2,
width: max1 - max2
}
];
}
/**
* Check if a container can fit inside a size space
* @param container Container to check
* @param sizePointer Size space to check
* @returns
*/
const isFitting = (
container: IContainerModel,
sizePointer: SizePointer
): boolean => Number(container.properties.width) <= sizePointer.width;