Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions challenge #6/egorbwork/CloudCircle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
class CloudCircle {
/**
* @param {Point} centerPoint
* @param {number} radius
*/
constructor(centerPoint, radius) {
this.centerPoint = centerPoint;
this.radius = radius;
this.tangents = [];
this.tangentCircles = [];
}

/**
* @param {CloudTangent} tangent
*/
addTangent(tangent) {
this.tangents.push(tangent);
}

/**
* source: http://2000clicks.com/mathhelp/GeometryConicSectionCircleIntersection.aspx
* @param {CloudCircle} circle
*/
calculateCloudTangentsForCircle(circle) {
let distanceBetweenCircles = this.centerPoint.getDistanceFor(circle.centerPoint);
let areaOfCirclesTriangle = 0.25 * Math.sqrt(
((this.radius + circle.radius) ** 2 - distanceBetweenCircles ** 2)
* (distanceBetweenCircles ** 2 - (this.radius - circle.radius) ** 2)
);
let firstTangentPoint = new Point(
0.5 * (circle.centerPoint.coordinateX + this.centerPoint.coordinateX)
+ 0.5 * (circle.centerPoint.coordinateX - this.centerPoint.coordinateX)
* (this.radius ** 2 - circle.radius ** 2) / distanceBetweenCircles ** 2
+ 2 * (circle.centerPoint.coordinateY - this.centerPoint.coordinateY) * areaOfCirclesTriangle
/ distanceBetweenCircles ** 2,
0.5 * (circle.centerPoint.coordinateY + this.centerPoint.coordinateY)
+ 0.5 * (circle.centerPoint.coordinateY - this.centerPoint.coordinateY)
* (this.radius ** 2 - circle.radius ** 2) / distanceBetweenCircles ** 2
- 2 * (circle.centerPoint.coordinateX - this.centerPoint.coordinateX) * areaOfCirclesTriangle
/ distanceBetweenCircles ** 2
);
let secondTangentPoint = new Point(
0.5 * (circle.centerPoint.coordinateX + this.centerPoint.coordinateX)
+ 0.5 * (circle.centerPoint.coordinateX - this.centerPoint.coordinateX)
* (this.radius ** 2 - circle.radius ** 2) / distanceBetweenCircles ** 2
- 2 * (circle.centerPoint.coordinateY - this.centerPoint.coordinateY) * areaOfCirclesTriangle
/ distanceBetweenCircles ** 2,
0.5 * (circle.centerPoint.coordinateY + this.centerPoint.coordinateY)
+ 0.5 * (circle.centerPoint.coordinateY - this.centerPoint.coordinateY)
* (this.radius ** 2 - circle.radius ** 2) / distanceBetweenCircles ** 2
+ 2 * (circle.centerPoint.coordinateX - this.centerPoint.coordinateX) * areaOfCirclesTriangle
/ distanceBetweenCircles ** 2
);
let firstTangent = new CloudTangent(firstTangentPoint, this, circle);
this.addTangent(firstTangent)
circle.addTangent(firstTangent);
let secondTangent = new CloudTangent(secondTangentPoint, this, circle);
this.addTangent(secondTangent)
circle.addTangent(secondTangent);
this.tangentCircles.push(circle);
circle.tangentCircles.push(this);
}

/**
* @param {CloudCircle} circle
* @returns {boolean}
*/
isTangentCircle(circle) {
let distanceBetweenCircles = this.centerPoint.getDistanceFor(circle.centerPoint);
return distanceBetweenCircles < (this.radius + circle.radius) && !this.isCircleInside(circle);
}

/**
* @param {Point} point
* @returns {boolean}
*/
isPointInsideOfCircle(point) {
let distanceTillPoint = point.getDistanceFor(this.centerPoint);
return distanceTillPoint <= this.radius;
}

/**
* @returns {Array<CloudTangent>}
*/
getUsedTangents() {
return this.tangents.filter(
tangent => tangent.usageStatus
);
}

/**
* @param {CloudCircle} circle
* @returns {boolean}
*/
isCircleInside(circle) {
return this.isPointInsideOfCircle(circle.centerPoint)
&& this.radius > circle.radius
&& (circle.centerPoint.getDistanceFor(this.centerPoint) + circle.radius) < this.radius;
}

/**
* source: https://gamedev.stackexchange.com/questions/33709/get-angle-in-radians-given-a-point-on-a-circle
* @param {Point} point
* @returns {number}
*/
calculateArcAngleForPoint(point) {
return Math.atan2(point.coordinateY - this.centerPoint.coordinateY, point.coordinateX - this.centerPoint.coordinateX);
}
}
203 changes: 203 additions & 0 deletions challenge #6/egorbwork/CloudDrawingEngine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
class CloudDrawingEngine {
/**
* @param {CanvasRenderingContext2D} drawingContext
* @param {number} drawingContext
*/
constructor(drawingContext, lineWidth) {
this.drawingContext = drawingContext;
this.drawingContext.lineWidth = lineWidth;
this.drawingContext.strokeStyle = 'black';
}

/**
* @param {Array<CloudCircle>}
*/
drawCloud(cloud) {
this.prepareCloud(cloud);
this.drawingContext.clearRect(0, 0, 5000, 5000);
for(let circle of cloud.sort(this.compareCircleByUsedTangents)) {
let currentTangents = circle.getUsedTangents();
if (currentTangents.length > 2) {
for (let tangentsGroup of this.getGroupedTangents(currentTangents)) {
let {first: firstTangent, second: secondTangent} = tangentsGroup;
this.drawingContext.beginPath();
this.drawingContext.arc(
circle.centerPoint.coordinateX,
circle.centerPoint.coordinateY,
circle.radius,
// Full circle visibility for Testing
// 0, 2 * Math.PI,
circle.calculateArcAngleForPoint(firstTangent.point),
circle.calculateArcAngleForPoint(secondTangent.point),
this.getArcClockWiseStatusForTangent(firstTangent, circle, cloud)
);
firstTangent.addDrawnWith(secondTangent);
secondTangent.addDrawnWith(firstTangent);
this.drawingContext.stroke();
// Tangent Points drawing for testing
// this.drawingContext.beginPath();
// this.drawingContext.fillStyle = 'red';
// this.drawingContext.fillRect(firstTangent.point.coordinateX, firstTangent.point.coordinateY, 5, 5);
// this.drawingContext.fillRect(secondTangent.point.coordinateX, secondTangent.point.coordinateY, 5, 5);
}
} else {
let [firstTangent, secondTangent] = currentTangents;
this.drawingContext.beginPath();
this.drawingContext.arc(
circle.centerPoint.coordinateX,
circle.centerPoint.coordinateY,
circle.radius,
// Full circle visibility for Testing
// 0, 2 * Math.PI,
circle.calculateArcAngleForPoint(firstTangent.point),
circle.calculateArcAngleForPoint(secondTangent.point),
this.getArcClockWiseStatusForTangent(firstTangent, circle, cloud)
);
firstTangent.addDrawnWith(secondTangent);
secondTangent.addDrawnWith(firstTangent);
this.drawingContext.stroke();
// Tangent Points drawing for testing
// this.drawingContext.beginPath();
// this.drawingContext.fillStyle = 'red';
// this.drawingContext.fillRect(firstTangent.point.coordinateX, firstTangent.point.coordinateY, 5, 5);
// this.drawingContext.fillRect(secondTangent.point.coordinateX, secondTangent.point.coordinateY, 5, 5);
}
}

}

/**
* @param {Array<CloudCircle>}
*/
prepareCloud(cloud) {
let preparedList = [];
for (let circle of cloud) {
preparedList.push(circle);
// Calculate tangents
let remainingCircles = cloud.filter(remainingCircle => !preparedList.includes(remainingCircle));
for (let remainingCircle of remainingCircles) {
if (circle.isTangentCircle(remainingCircle)) {
circle.calculateCloudTangentsForCircle(remainingCircle);
}
}
// Mark not used in cloud tangents
for (let tangent of circle.tangents) {
for (let remainingCircle of cloud) {
if (remainingCircle !== tangent.firstCircle
&& remainingCircle !== tangent.secondCircle
&& remainingCircle.isPointInsideOfCircle(tangent.point)
) {
tangent.setUsed(false);
}
}
}
}
}

/**
* @param {CloudTangent} tangent
* @param {CloudCircle} circle
* @param {Array<CloudCircle>} circles
* @returns {boolean}
*/
getArcClockWiseStatusForTangent(tangent, circle, circles) {
let testPoint = this.getClockWiseTestPoint(tangent, circle);
for (let testCircle of circles) {
if (testCircle === circle) {
continue;
} else if(testCircle.isPointInsideOfCircle(testPoint)) {
return false;
}
}
return true;
}

/**
* @param {CloudTangent} tangent
* @param {CloudTangent} secondTangent
* @param {CloudCircle} circle
* @returns {Point}
*/
getClockWiseTestPoint(tangent, circle) {
let modifier = 1;
if (tangent.point.coordinateX > circle.centerPoint.coordinateX
&& tangent.point.coordinateY >= circle.centerPoint.coordinateY
) {
return new Point(
tangent.point.coordinateX + (
tangent.point.coordinateY === circle.centerPoint.coordinateY
? (-modifier) : modifier
),
tangent.point.coordinateY - modifier
);
} else if (tangent.point.coordinateX >= circle.centerPoint.coordinateX
&& tangent.point.coordinateY < circle.centerPoint.coordinateY
) {
return new Point(
tangent.point.coordinateX - modifier,
tangent.point.coordinateY + (
tangent.point.coordinateX === circle.centerPoint.coordinateX
? modifier : (-modifier)
)
);
} else if (tangent.point.coordinateX < circle.centerPoint.coordinateX
&& tangent.point.coordinateY <= circle.centerPoint.coordinateY
) {
return new Point(
tangent.point.coordinateX + (
tangent.point.coordinateY === circle.centerPoint.coordinateY
? modifier : (-modifier)
),
tangent.point.coordinateY + modifier
);
} else {
return new Point(
tangent.point.coordinateX + modifier,
tangent.point.coordinateY + (
tangent.point.coordinateX === circle.centerPoint.coordinateX
? (-modifier) : modifier
)
);
}
}

/**
* @param {Array<CloudTangent>} tangents
*/
* getGroupedTangents(tangents) {
let usedTangents = [];
let remainingTangents = tangents.slice(0);
for (let tangent of tangents) {
remainingTangents.splice(remainingTangents.indexOf(tangent), 1);
if (tangent.drawnWith.length === 2 || usedTangents.includes(tangent)) {
continue;
}
let availableTangent;
let minimumDistance;
for (let comparableTangent of remainingTangents) {
if (comparableTangent.drawnWith.length === 2 || comparableTangent.drawnWith.includes(tangent)
|| usedTangents.includes(comparableTangent)
) {
continue;
}
let distanceBetweenTangentPoints = tangent.point.getDistanceFor(comparableTangent.point);
if (!minimumDistance || minimumDistance > distanceBetweenTangentPoints) {
minimumDistance = distanceBetweenTangentPoints;
availableTangent = comparableTangent;
}
}
usedTangents.push(availableTangent);

yield {first: tangent, second: availableTangent};
}
}

/**
* @param {CloudCircle} firstCircle
* @param {CloudCircle} secondCircle
* @returns {number}
*/
compareCircleByUsedTangents(firstCircle, secondCircle) {
return firstCircle.getUsedTangents().length - secondCircle.getUsedTangents().length;
}
}
Loading