Constraints of movement of rectangle inside a polygon - javascript

I'm using a library (PolyK) to calculate the position where a rectangle could be dragged inside a polygon (U shape).
I'm using ray-cast to get the delta point ( point outside of the polygon ) to calculate the new position but I cannot get it to work.
Fiddle
Any one have some advice, or know any library?
JavaScript:
// a basic poly
var p=[30, 30, 30, 193, 168, 193, 168, 142, 340, 142, 340, 396, 498, 396, 498, 30];
var s = document.getElementById('ray').getContext('2d');
var heroCanvas = document.getElementById('hero').getContext('2d');
var hero = { x: 0, y: 0, width: 50, height: 40 };
var canvas = document.getElementById('canvas').getContext('2d');
canvas.fillStyle = '#f00';
canvas.beginPath();
canvas.moveTo(p[0], p[1]);
for(var i = 2; i< p.length; i+=2){
canvas.lineTo(p[i], p[i+1]);
}
canvas.closePath();
canvas.stroke();
document.onmousemove = function(event){
var x = event.offsetX;
var y = event.offsetY;
var resolve = calculatePosition(x, y);
updateHero(resolve.x, resolve.y);
}
function calculatePosition(x, y){
// get bounds of poly
var bounds = PolyK.GetAABB(p);
// get vertices
var v1 = {x: x, y: y};
var v2 = {x: x, y: y + hero.height};
var v3 = {x: x + hero.width, y: y + hero.height};
var v4 = {x: x + hero.width, y: y};
// check which vertice is outside of polygon
var c1 = PolyK.ContainsPoint(p, v1.x, v1.y);
var c2 = PolyK.ContainsPoint(p, v2.x, v2.y);
var c3 = PolyK.ContainsPoint(p, v3.x, v3.y);
var c4 = PolyK.ContainsPoint(p, v4.x, v4.y);
// constrain movement inside the poly bounds
if( x < bounds.x )
x = bounds.x;
if( x + hero.width > bounds.x + bounds.width)
x = bounds.x + bounds.width - hero.width;
if( y < bounds.y )
y = bounds.y;
if( y + hero.height > bounds.y + bounds.height)
y = bounds.y + bounds.height - hero.height;
// get delta position with ray cast
var delta = {x: 0, y: 0};
console.log(c1, c2, c3, c4);
if(c1 && !c2 && c3 && c4){
delta = getDelta(v2, 3);
x = x+ delta.x;
y = y+ delta.y;
} else if(!c1 && !c2 && c3 && c4){
delta = getDelta(v2, 0);
x = x + delta.x;
y = y;
} else if(!c1 && !c2 && !c3 && c4){
delta = getDelta(v1, 0);
x = x + delta.x;
y = y;
} else if(c1 && !c2 && !c3 && c4){
delta = getDelta(v2, 3);
x = x;
y = y + delta.y;
} else if(c1 && c2 && !c3 && c4){
delta = getDelta(v3, 3);
x = x+ delta.x;
y = y+ delta.y;
} else if (c1 && c2 && !c3 && !c4){
delta = getDelta(v3, 2);
x = x + delta.x;
y = y;
} else if(c1 && !c2 && !c3 && !c4) {
delta = getDelta(v1, 2);
x = x;
y = y + delta.y;
}
return {
x: x,
y: y
};
}
function getDelta(point, n){
var points = [];
var rn = 4;
var diff = 2*Math.PI/rn;
var iscc = {dist:0, edge:0, norm:{x:0, y:0}, refl:{x:0, y:0}};
for(var i=0; i<rn; i++)
{
var dx = Math.cos(i*diff);
var dy = Math.sin(i*diff);
var isc = PolyK.Raycast(p, point.x, point.y, dx, dy, iscc);
if(!isc) iscc.dist = 4000;
points.push({x: dx*iscc.dist, y:dy*iscc.dist});
}
return points[n];
}
function updateHero(x, y){
heroCanvas.clearRect(0,0,600,500);
heroCanvas.beginPath();
heroCanvas.fillStyle = '#ff0000';
heroCanvas.rect(x, y, hero.width, hero.height);
heroCanvas.stroke();
}
thanks

Related

How to draw polygon when last point clicked?

I want to complete polygon when starting point of polygon clicked, i think i need to hide or set opacity of last line which is linking to first point of line.
goal is to achieve exactly what is happening in this GIF.
also i want to show angles and length on each line while drawing just like exactly same as GIF.
var bw = window.innerWidth -20;
var bh = window.innerHeight -20;
var p = 10;
var cw = bw + (p*2) + 1;
var ch = bh + (p*2) + 1;
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
function drawBoard(){
for (var x = 0; x <= bw; x += 30) {
context.moveTo(0.5 + x + p, p);
context.lineTo(0.5 + x + p, bh + p);
}
for (var x = 0; x <= bh; x += 30) {
context.moveTo(p, 0.5 + x + p);
context.lineTo(bw + p, 0.5 + x + p);
}
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0, 122, 204,0.7)';
context.stroke();
}
drawBoard();
//---------------------------------------------
requestAnimationFrame(update)
mouse = {x : 0, y : 0, button : 0, lx : 0, ly : 0, update : true};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
context.lineWidth = 2;
context.strokeStyle = "red";
const point = (x,y) => ({x,y});
const poly = () => ({
points : [],
addPoint(p){ this.points.push(point(p.x,p.y)) },
draw() {
context.lineWidth = 2;
context.strokeStyle = "red";
context.fillStyle = 'rgba(255, 0, 0, 0.3)';
context.beginPath();
for (const p of this.points) { context.lineTo(p.x,p.y) }
context.closePath();
context.stroke();
context.fill();
context.fillStyle = 'rgba(0, 0, 0, 0.3)';
context.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
context.beginPath();
context.moveTo(p.x + 10,p.y);
context.arc(p.x,p.y,10,0,Math.PI *2);
context.fill();
context.stroke();
}
},
closest(pos, dist = 8) {
var i = 0, index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
function drawCircle(pos,color="black",size=8){
context.strokeStyle = color;
context.beginPath();
context.arc(pos.x,pos.y,size,0,Math.PI *2);
context.stroke();
}
const polygon = poly();
var activePoint,cursor;
var dragging= false;
function update(){
if (mouse.update) {
cursor = "crosshair";
context.clearRect(0,0,canvas.width,canvas.height);
drawBoard();
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if(activePoint) {
if (mouse.button) {
if(dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else { dragging = true }
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
<html>
<body style=" background: lightblue;">
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
</body>
</html>
Drawing part of code i copied from: https://stackoverflow.com/a/53437943/3877726
You can use ctx.setTransform to to align text to a line.
First normalize the vector between the end points and use that normalized vector to build the transform. See example.
To prevent text from reading back to front you need to check the x component of the normalized vector. If it is < 0 then reverse the vector.
Almost the same for the angle. See example.
Example
Snippets contain the functions drawLineText and drawAngleText (near top) that implement the additional features.
var bw = innerWidth - 20, bh = innerHeight - 20;
var cw = bw + (p * 2) + 1, ch = bh + (p * 2) + 1;
var p = 10;
var activePoint, cursor, dragging = false;
const mouse = {x: 0, y: 0, button: 0, lx: 0, ly: 0, update: true};
const TEXT_OFFSET = 5;
const TEXT_COLOR = "#000";
const TEXT_SIZE = 16;
const FONT = "arial";
const TEXT_ANGLE_OFFSET = 25;
const DEG = "°";
canvas.width = bw;
canvas.height = bh;
var ctx = canvas.getContext("2d");
function drawLineText(p1, p2, text, textOffset = TEXT_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var x = p1.x, y = p1.y;
var nx = p2.x - x, ny = p2.y - y, len = (nx * nx + ny * ny) ** 0.5;
nx /= len;
ny /= len;
ctx.font = textSize + "px " + font;
ctx.textAlign = "center";
ctx.fillStyle = textColor;
if (nx < 0) {
ctx.textBaseline = "top";
x = p2.x;
y = p2.y;
nx = -nx;
ny = -ny;
textOffset = -textOffset;
} else { ctx.textBaseline = "bottom" }
len /= 2;
ctx.setTransform(nx, ny, -ny, nx, x, y);
ctx.fillText(text, len, -textOffset);
}
// angle between p2-p1 and p2-p3
function drawAngleText(p1, p2, p3, textAngleOffset = TEXT_ANGLE_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var ang;
var x = p2.x, y = p2.y;
var nx1 = p1.x - x, ny1 = p1.y - y, len1 = (nx1 * nx1 + ny1 * ny1) ** 0.5;
var nx2 = p3.x - x, ny2 = p3.y - y, len2 = (nx2 * nx2 + ny2 * ny2) ** 0.5;
nx1 /= len1;
ny1 /= len1;
nx2 /= len2;
ny2 /= len2;
const cross = nx1 * ny2 - ny1 * nx2;
const dot = nx1 * nx2 + ny1 * ny2;
if (dot < 0) {
ang = cross < 0 ? -Math.PI - Math.asin(cross) : Math.PI - Math.asin(cross);
} else {
ang = Math.asin(cross);
}
const angDeg = Math.abs(ang * (180 / Math.PI)).toFixed(0) + DEG;
ctx.font = textSize + "px " + font;
ctx.fillStyle = textColor;
ctx.textBaseline = "middle";
const centerAngle = Math.atan2(ny1, nx1) + ang / 2;
const nx = Math.cos(centerAngle);
const ny = Math.sin(centerAngle);
if (nx < 0) {
ctx.textAlign = "right";
ctx.setTransform(-nx, -ny, ny, -nx, x, y);
textAngleOffset = -textAngleOffset;
} else {
ctx.textAlign = "left";
ctx.setTransform(nx, ny, -ny, nx, x, y);
}
ctx.fillText(angDeg, textAngleOffset, 0);
}
//---------------------------------------------
requestAnimationFrame(update)
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown", "mouseup", "mousemove"].forEach(name => document.addEventListener(name, mouseEvents));
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
const point = (x, y) => ({x, y});
const poly = () => ({
points: [],
closed: false,
addPoint(p) { this.points.push(point(p.x, p.y)) },
draw() {
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
this.closed && ctx.closePath();
ctx.stroke();
this.closed && ctx.fill();
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
ctx.beginPath();
ctx.moveTo(p.x + 10, p.y);
ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
this.points.length > 1 && this.drawLengthText();
this.points.length > 2 && this.drawAngleText();
},
drawLengthText() {
const len = this.points.length;
var p1, i = 0;
p1 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p2 = this.points[((i++) + 1) % len];
const lineLength = ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
drawLineText(p1, p2, lineLength.toFixed(0) + "px");
if (len < 3) { break }
p1 = p2;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
drawAngleText() {
const len = this.points.length;
var p1, p2, i = this.closed ? 0 : 1;
p1 = this.points[(i + len - 1) % len];
p2 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p3 = this.points[((i++) + 1) % len];
drawAngleText(p1, p2, p3);
p1 = p2;
p2 = p3;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
closest(pos, dist = 8) {
var i = 0,
index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
const polygon = poly();
function drawCircle(pos, color = "black", size = 8) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(pos.x, pos.y, size, 0, Math.PI * 2);
ctx.stroke();
}
function update() {
if (mouse.update) {
cursor = "crosshair";
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if (activePoint) {
if (mouse.button) {
if (dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else {
if (!polygon.closed && polygon.points.length > 2 && activePoint === polygon.points[0]) {
polygon.closed = true;
}
dragging = true
}
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
Click to add points
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
Note though not mandatory it is customarily considered polite to include attributions when copying code.

Is there an error in the way this simulation calculates gravitational attraction and body collision?

N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).

Move dimension lines away from a polygon

So I have multiple points which creates a polygon. I have calculated the center point of the polygon and used that to create dimension lines for each side of a polygon. My problem is that some dimension lines don't move to a new position that I calculated. To have a better view here's an image of what I have and what I want to happen.
What I have:
What I want to achieve:
I added a red circle to the issue I'm having it should move to new point which is the blue lines.
Here's code snippet:
var viewHeight = window.innerHeight / 2;
var viewWidth = window.innerWidth / 2;
var scene = new THREE.Scene();
var camera = new THREE.OrthographicCamera(
viewWidth / - 2, viewWidth / 2, viewHeight / 2, viewHeight / - 2, 1, 1000);
camera.translateZ(15);
camera.updateProjectionMatrix();
scene.add(camera);
var points = [
{
start: {x: -99.74425000000002, y: 68.61000000000001},
end: {x: -99.74425000000002, y: -37.87599999999999}
},
{
start: {x: -99.74425000000002, y: -37.87599999999999},
end: {x: 69.22574999999995, y: -37.87599999999999}
},
{
start: {x: 69.22574999999995, y: -37.87599999999999},
end: {x: 69.22574999999995, y: -1.2999999999999832}
},
{
start: {x: 69.22574999999995, y: -1.2999999999999832},
end: {x: 120.02574999999996, y: -1.2999999999999827}
},
{
start: {x: 120.02574999999996, y: -1.2999999999999827},
end: {x: 120.02574999999996, y: 68.61000000000001}
},
{
start: {x: 120.02574999999996, y: 68.61000000000001},
end: {x: -99.74425000000002, y: 68.61000000000001}
}
];
var vertices = [];
for (var i in points) {
var line = points[i];
vertices.push(new THREE.Vector2(line.start.x, line.start.y));
};
// build floor
var floor = buildFloor(vertices);
scene.add(floor);
// get center point of floor
var center = findCentroid(vertices);
// build dimension lines
for (var i in points) {
var line = points[i];
var lineCenterPoint = findLineCenterPoint(line.start, line.end);
var degrees = getAngle(center.x, center.y, lineCenterPoint.x, lineCenterPoint.y);
var radians = degrees * Math.PI / 180;
var startX = line.start.x;
var startY = line.start.y;
var endX = line.end.x;
var endY = line.end.y;
if (startY == endY) {
// horizontal
// move point y outward to a new point
var newY = startY + Math.sin(radians) * 20;
startY = newY;
endY = newY;
} else if (startX == endX) {
// vertical
// move point x outward to a new point
var x = startX + Math.cos(radians) * 20;
startX = x;
endX = x;
} else {
// todo diagonal lines
// don't know what should be the formula for this
}
var from = new THREE.Vector3(startX, startY, 0);
var to = new THREE.Vector3(endX, endY, 0);
var direction = to.clone().sub(from);
var length = direction.length();
var hex = 0x0;
var arrorGroupHelper = new THREE.Group();
arrorGroupHelper.add(new THREE.ArrowHelper(direction.normalize(), from, length, hex, 1, 10));
arrorGroupHelper.add(new THREE.ArrowHelper(direction.negate(), to, length, hex, 1, 10));
scene.add(arrorGroupHelper);
var text = document.createElement('div');
text.style.position = 'absolute';
text.style.zIndex = 5;
text.style.backgroundColor = "ffffff";
text.innerHTML = length.toFixed(2);
let interiorCenter = to.clone().add(from).multiplyScalar(0.5);
let textPos = interiorCenter.project( camera );
var widthHalf = viewWidth;
var heightHalf = viewHeight;
var style = 'translate(-50%,-50%) translate(' + ( textPos.x * widthHalf + widthHalf ) + 'px,' + ( - textPos.y * heightHalf + heightHalf ) + 'px)';
text.style.transform = style;
document.getElementById('container').append(text);
}
var threejsCanvas = document.getElementById('threejs-canvas');
var renderer = new THREE.WebGLRenderer({
canvas : threejsCanvas,
alpha : true
});
renderer.autoClear = false;
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.render( scene, camera );
animate();
window.addEventListener( 'resize', onWindowResize, false );
function onWindowResize() {
var width = window.innerWidth / 2;
var height = window.innerHeight / 2;
camera.left = -width / 2;
camera.right = width / 2
camera.top = height / 2;
camera.bottom = -height / 2;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
renderer.clear();
renderer.render( scene, camera );
}
function findLineCenterPoint(a, b) {
return { x: (b.x - a.x) / 2 + a.x, y: (b.y - a.y) / 2 + a.y };
}
function findCentroid (arr) {
var minX, maxX, minY, maxY;
for (var i = 0; i < arr.length; i++)
{
var x = arr[i]['x'], y = arr[i]['y'];
minX = (x < minX || minX == null) ? x : minX;
maxX = (x > maxX || maxX == null) ? x : maxX;
minY = (y < minY || minY == null) ? y : minY;
maxY = (y > maxY || maxY == null) ? y : maxY;
}
return {x:(minX + maxX) / 2, y:(minY + maxY) / 2};
}
function buildFloor(vertices) {
var material = new THREE.MeshBasicMaterial({
color: 0xcccccc,
});
var shape = new THREE.Shape(vertices);
var geometry = new THREE.ShapeGeometry(shape);
var floor = new THREE.Mesh(geometry, material);
return floor;
}
function getAngle(originX, originY, targetX, targetY) {
var dx = originX - targetX;
var dy = originY - targetY;
var theta = Math.atan2(-dy, -dx);
theta *= 180 / Math.PI;
if (theta < 0) theta += 360;
return theta;
}
#container {
position: relative;
}
#threejs-canvas {
position:absolute;
z-index: 2;
}
<script src="//cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<div id="container">
<canvas id="threejs-canvas"></canvas>
</div>
Here's my question:
How do I move the dimension lines encircled on red on the image above to the new points which is the blue lines.
Thanks. Hope someone could shed a light on this.

What is wrong with my perlin noise function?

So I am currently working on a perlin noise generator, for some reason I'm getting incorrect output:
The perlin noise is in a "grid" and does not resemble traditional perlin noise.
Here is my code:
var perlinNoise = {
vectors: [{x:1,y:0},{x:1,y:-1},{x:0,y:-1},{x:-1,y:-1},{x:-1,y:0},{x:-1,y:1},{x:0,y:1},{x:1,y:1}],
fade: function(t){
return t * t * t * (t * (t * 6 - 15) + 10);
},
pointToCell: function(x, y){
cellX = Math.floor(x);
cellY = Math.floor(y);
return {x:cellX, y:cellY};
},
cellToVectors: function(cellX, cellY){
halfCell = .5;
//I use the four intercardinal directions to label the vectors.
//The random values are multiplied by 8 to map them to the 8 entries of the vectors array.
NEvector = this.vectors[Math.floor(randomFromCoords(cellX + halfCell, cellY + halfCell)*8)];
SEvector = this.vectors[Math.floor(randomFromCoords(cellX + halfCell, cellY - halfCell)*8)];
SWvector = this.vectors[Math.floor(randomFromCoords(cellX - halfCell, cellY - halfCell)*8)];
NWvector = this.vectors[Math.floor(randomFromCoords(cellX - halfCell, cellY + halfCell)*8)];
return {NE: NEvector, SE: SEvector, SW: SWvector, NW: NWvector};
},
dotProduct: function(vector1, vector2){
//Another way to calculate the dot product. This is more performance friendly than cosine calculations.
return vector1.x * vector2.x + vector1.y * vector2.y;
},
lerp: function(value1, value2, t){
return (1 - t) * value1 + t * value2;
},
perlinNoise: function(x, y){
var cellCoord = this.pointToCell(x, y);
//Get the positions of the x and y coordinants relative to the cell
var Xoffset = this.fade(x - cellCoord.x);
var Yoffset = this.fade(y - cellCoord.y);
var vectors = this.cellToVectors(cellCoord.x, cellCoord.y);
//The offset from each corner is calculated.
//Then the dotproduct between the offset vector and the random vector is calculated.
var NEoffset = {x: 1 - Xoffset, y: 1 - Yoffset};
var NEdotProduct = this.dotProduct(NEoffset, vectors.NE);
var SEoffset = {x: 1 - Xoffset, y: Yoffset};
var SEdotProduct = this.dotProduct(SEoffset, vectors.SE);
var SWoffset = {x: Xoffset, y: Yoffset};
var SWdotProduct = this.dotProduct(SWoffset, vectors.SW);
var NWoffset = {x: Xoffset, y: 1 - Yoffset};
var NWdotProduct = this.dotProduct(NWoffset, vectors.NW);
var Nlerp = this.lerp(NWdotProduct, NEdotProduct, Xoffset);
var Slerp = this.lerp(SWdotProduct, SEdotProduct, Xoffset);
var finalValue = this.lerp(Slerp, Nlerp, Yoffset);
if(finalValue < -.5 ){
console.log("less than -.5");
}
return finalValue;
},
noise: function(width, height){
var values = [];
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
values[x + y * width] = (this.perlinNoise(x/30,y/30)+1)/2;
}
}
return values;
},
element: document.getElementById("PerlinDisplay1"),
loop: function (){},
initialize: function(){
initCanvas("PerlinDisplay1Canvas");
var values = this.noise(canvas.width, canvas.height);
//maps values from 0 to 1 to grey scale pixels, already tested.
var valuesAsPixels = values.map(x => ValueToPixel(x));
UpdateCanvas(valuesAsPixels);
},
deinitialize: function(){}
}
The functions initCanvas, ValueToPixel, and UpdateCanvas work and have been tested with other use cases. I would really appreciate some insight as to what I am doing wrong.
Edit: Upon request I am adding the functions ValueToPixel and UpdateCanvas.
function UpdateCanvas(pixels){
var n = 0;
for (var y = 0; y < canvas.height; ++y) {
for (var x = 0; x < canvas.width; ++x) {
var index = (y * canvas.width + x) * 4;
for(var channel = 0; channel < 4; ++channel){
data[index + channel] = pixels[index/4][channel];
n++;
}
}
}
context.putImageData(imageData, 0, 0);
}
function ValueToPixel(value){
//red, green, blue, alpha
return [value*255, value*255, value*255, 255];
}
I figured it out! I was calculating the offsets from the corners incorrectly. I was wrongly calculating the absolute values of the distance from the corners rather than the offset from the corner. Here is the corrected code snipped below:
//The offset from each corner is calculated.
//Then the dotproduct between the offset vector and the random vector is calculated.
var NEoffset = {x: Xoffset - 1, y: Yoffset - 1};
var NEdotProduct = this.dotProduct(NEoffset, vectors.NE);
var SEoffset = {x: Xoffset - 1, y: Yoffset};
var SEdotProduct = this.dotProduct(SEoffset, vectors.SE);
var SWoffset = {x: Xoffset, y: Yoffset};
var SWdotProduct = this.dotProduct(SWoffset, vectors.SW);
var NWoffset = {x: Xoffset, y: Yoffset - 1};
var NWdotProduct = this.dotProduct(NWoffset, vectors.NW);
Note that instead of subtracting the offset from 1, 1 is being subtracted from the offset.

Draw lines and circles and fill the hole shape in JS

I read the contents of a dxf-file (only 2D) in NodeJS with a dxf parser (https://github.com/bjnortier/dxf) and then i get an array with the following output:
LINE: start.x, start.y, end.x, end.y
CIRCLE: x, y, radius
ARC: x, y ,radius, startAngle, endAngle
I wrote 3 functions based on the Bresenham-Algorithm to set the needed pixels in an array, which i want to use later to draw an canvas.
The input-parameters are
data: the denorm dxf data in an array
coordSystem: the array where to set the needed pixels
module.exports: {
processLINE: function(data, coordSystem) {
var setPixel = function(x, y) {
x = Math.ceil(x);
y = Math.ceil(y);
coordSystem[x][y] = 1;
}
var line = function(x0, y0, x1, y1) {
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
var e2;
while(true) {
setPixel(x0,y0);
if ((x0===x1) && (y0===y1)) break;
e2 = 2*err;
if (e2 >-dy){ err -= dy; x0 += sx; }
if (e2 < dx){ err += dx; y0 += sy; }
}
}
line(Math.ceil(data.start.x), Math.ceil(data.start.y), Math.ceil(data.end.x), Math.ceil(data.end.y))
return coordSystem;
},
processCIRCLE: function(data, coordSystem) {
var setPixel = function(x, y) {
x = Math.ceil(x);
y = Math.ceil(y);
coordSystem[x][y] = 1;
}
var createCircle = function(x0, y0, radius)
{
var f = 1 - radius;
var ddF_x = 0;
var ddF_y = -2 * radius;
var x = 0;
var y = radius;
setPixel(x0, y0 + radius);
setPixel(x0, y0 - radius);
setPixel(x0 + radius, y0);
setPixel(x0 - radius, y0);
while(x < y)
{
if(f >= 0)
{
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x + 1;
setPixel(x0 + x, y0 + y);
setPixel(x0 - x, y0 + y);
setPixel(x0 + x, y0 - y);
setPixel(x0 - x, y0 - y);
setPixel(x0 + y, y0 + x);
setPixel(x0 - y, y0 + x);
setPixel(x0 + y, y0 - x);
setPixel(x0 - y, y0 - x);
}
}
createCircle(data.x, data.y, data.r);
return coordSystem;
},
processARC: function(data, coordSystem) {
var setPixel = function(x, y, coordinates) {
x = Math.ceil(x);
y = Math.ceil(y);
coordSystem[x][y] = 1;
}
var createPartialcircle = function()
{
startAngle = data.startAngle*180/Math.PI;
endAngle = data.endAngle*180/Math.PI;
if(startAngle>endAngle) {
for (var i=startAngle; i>endAngle; i--) {
var radians = i * Math.PI / 180;
var px = data.x - data.r * Math.cos(radians);
var py = data.y - data.r * Math.sin(radians);
setPixel(px, py, coordinates);
}
} else {
for (var i=startAngle; i<endAngle; i++) {
var radians = i * Math.PI / 180;
var px = data.x + data.r * Math.cos(radians);
var py = data.y + data.r * Math.sin(radians);
setPixel(px, py, coordinates);
}
}
}
createPartialcircle(data.x, data.y, data.r);
return coordSystem;
}
}
With this i get the following shape:
As you can see it works, but there are some "holes" and because of this my last function which should fill the hole shape (scan-line-algorithm), doesn't work well...
Here is how i fill the shape
I took this code from HERE and wrote it in JavaScript-Style.
function scanLineFill(config, data, x, y, fillColor) {
function getPixel(x,y) {
return data[x][y];
}
function setPixel(x,y) {
data[x][y] = fillColor;
}
// Config
var nMinX = 0;
var nMinY = 0;
var nMaxX = config.maxValues.x;
var nMaxY = config.maxValues.y;
var seedColor = getPixel(x,y);
function lineFill(x1, x2, y) {
var xL,xR;
if( y < nMinY || nMaxY < y || x1 < nMinX || nMaxX < x1 || x2 < nMinX || nMaxX < x2 )
return;
for( xL = x1; xL >= nMinX; --xL ) { // scan left
if( getPixel(xL,y) !== seedColor )
break;
setPixel(xL,y);
}
if( xL < x1 ) {
lineFill(xL, x1, y-1); // fill child
lineFill(xL, x1, y+1); // fill child
++x1;
}
for( xR = x2; xR <= nMaxX; ++xR ) { // scan right
console.log('FOR: xR --> ', xR)
if( getPixel(xR,y) !== seedColor )
break;
setPixel(xR,y);
}
if( xR > x2 ) {
lineFill(x2, xR, y-1); // fill child
lineFill(x2, xR, y+1); // fill child
--x2;
}
for( xR = x1; xR <= x2 && xR <= nMaxX; ++xR ) { // scan betweens
if( getPixel(xR,y) === seedColor )
setPixel(xR,y);
else {
if( x1 < xR ) {
// fill child
lineFill(x1, xR-1, y-1);
// fill child
lineFill(x1, xR-1, y+1);
x1 = xR;
}
// Note: This function still works if this step is removed.
for( ; xR <= x2 && xR <= nMaxX; ++xR) { // skip over border
if( getPixel(xR,y) === seedColor ) {
x1 = xR--;
break;
}
}
}
}
}
if( fillColor !== seedColor ) {
lineFill(x, x, y);
}
return data;
}
And the result is this:
I think if the shape has no holes, the fill-function would fill the shape correct. But how can i achieve this?

Categories