获得两个点之间的两个偏移点

时间:2018-09-30 08:03:59

标签: coordinates trigonometry

您好,我需要帮助找到与直线的两个端点偏移的坐标或点。在我的程序中,我想指定两个点和偏移量。然后我需要计算两个偏移坐标。

我使用三角函数得出了一些结果,但是它仅在某些情况下以及当直线在正象限内时有效。

以下是描述我需要查找的图像: Points on line

好,所以我需要找到X3,Y3和X4,Y4坐标。

我遵循的方法: 计算角度: Ang = atan((Y2-Y1)/(X2-X1))

要查找X3: X3 = X1 +偏移* Cos(Ang)

Y3的概念相同

问题是,如果线在不同的象限中,则点信息不正确...请提供任何帮助。

1 个答案:

答案 0 :(得分:1)

这个问题是使用2D向量数学的明显案例。这个想法是,我们从p2中减去p1,以得到一个描述线条长度和方向的向量。然后,我们对该向量进行归一化,使其长度为1。如果您将此归一化向量乘以要从末端移开的单位数,并将结果添加到端点,则将有一个新观点。

考虑一个沿x轴行走的示例:

p1 = 0,0 p2 = 10,0

dif = p2-p1 =(10,0)

长度是10,所以它太长了10倍-我们将其除以10,就得到1个单位长的向量。

如果我们再移动5次(1,0),我们将以5,0-5个单位的距离结束,糟糕!

这是可以实现相同功能的函数:

function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
    var p1 = new vec2d(x1,y1);
    var p2 = new vec2d(x2,y2);

    var delta = p2.sub(p1);
    var dirVec = delta.clone();
    dirVec.normalize();

    dirVec.timesEquals(distTowardsP2fromP1);
    var resultPoint = p1.add(dirVec);

    return resultPoint;
}

如您所见,这利用了我称为vec2d的东西。以下代码段中有一个副本:

"use strict";
function byId(id){return document.getElemetById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
	var end1 = new vec2d(0,0);
	var end2 = new vec2d(10,0);
	
	var midPoint = calcOffsetPoint(end1.x,end1.y, end2.x,end2.y, 5);
	console.log( midPoint.toStringN(2) );
	
}

class vec2d
{
	constructor(x=0, y=0)
	{
		this.mX = x;
		this.mY = y;
	}
	get x(){return this.mX;}
	set x(newX){this.mX = newX;}
	get y(){return this.mY;}
	set y(newY){this.mY = newY;}
	add(other)
	{
		return new vec2d(this.x+other.x, this.y+other.y);
	}
	sub(other)
	{
		return new vec2d(this.x-other.x, this.y-other.y);
	}
	timesEquals(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;
		return this;
	}
	divByEquals(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		return this;
	}
	dotProd(other)
	{
		return this.x*other.x + this.y*other.y;
	}
	length()
	{
		return Math.hypot(this.x, this.y);
	}
	normalize()
	{
		this.divByEquals( this.length() );
		return this;
	}
	perpendicular()
	{
		var tmp = this.x;
		this.x = -this.y;
		this.y = tmp;
		return this;
	}
	clone()
	{
		return vec2d.clone(this);
	}
	static clone(other)
	{
		return new vec2d(other.x, other.y);
	}
	toString(){return `vec2d {x: ${this.x}, y: ${this.y}}`}
	toStringN(n){return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}`}
}

function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
	var p1 = new vec2d(x1,y1);
	var p2 = new vec2d(x2,y2);
	
	var delta = p2.sub(p1);
	var dirVec = delta.clone();
	dirVec.normalize();
	
	dirVec.timesEquals(distTowardsP2fromP1);
	var resultPoint = p1.add(dirVec);
	
	return resultPoint;
}

我在周末有一些空闲时间,因此将您发布的图像的工作演示放在一起。玩一玩。确保在全屏模式下运行它,以便可以看到设置p3和p4偏移量的滑块。忽略坐标系转换的内容,仅是为了使我能够使图像的尺寸与您的图像相同,而又方便地在面积约为5%的窗口中显示该图像。问题来自我在周末阅读的一本旧教科书的练习部分。

"use strict";

class vec2d
{
	constructor(x=0,y=0)
	{
		this.x = x;
		this.y = y;
	}
	abs()
	{
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);
		return this;
	}
	add(vec1)
	{
		return new vec2d(this.x+vec1.x, this.y+vec1.y);
	}
	sub(vec1)
	{
		return new vec2d(this.x-vec1.x, this.y-vec1.y);
	}
	mul(scalar)
	{
		return new vec2d(this.x*scalar, this.y*scalar);
	}
	plusEquals(vec1)
	{
		this.x += vec1.x;
		this.y += vec1.y;
		return this;
	}
	minusEquals(vec1)
	{
		this.x -= vec1.x;
		this.y -= vec1.y;
		return this;
	}
	timesEquals(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;
		return this;
	}
	divByEquals(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		return this;
	}
	normalize()
	{
		var len = this.length;
		this.x /= len;
		this.y /= len;
		return this;
	}
	get length()
	{
		//return Math.sqrt( (this.x*this.x)+(this.y*this.y) );
		return Math.hypot( this.x, this.y );
	}
	set length(newLen)
	{
		var invLen = newLen / this.length;
		this.timesEquals(invLen);
	}	
	dotProd(vec1)
	{
		return this.x*vec1.x + this.y*vec1.y;
	}
	perp()
	{
		var tmp = this.x;
		this.x = -this.y;
		this.y = tmp;
		return this;
	}
	wedge(other)
	{	// computes an area for parallelograms
		return this.x*other.y - this.y*other.x;
	}
	static clone(other)
	{
		var result = new vec2d(other.x, other.y);
		return result;
	}
	clone()	// clone self
	{
		return vec2d.clone(this);
	}
	setTo(other)
	{
		this.x = other.x;
		this.y = other.y;
	}
	get(){ return {x:this.x, y:this.y}; }
	toString(){ return `vec2d {x: ${this.x}, y: ${this.y}}` }
	toStringN(n){ return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
	print(){console.log(this.toString())}
};

class mat3
{
	static clone(other)
	{
		var result = new mat3();
		other.elems.forEach( 
								function(el, index, collection)
								{
									result.elems[index] = el;
								}
							);
		return result;
	}
	clone()
	{
		return mat3.clone(this);
	}
	constructor(a,b,c,d,e,f)
	{
		if (arguments.length < 6)
			this.setIdentity();
		else
			this.elems = [a,b,0,c,d,0,e,f,1];
	}
	setIdentity()
	{
		this.elems = [1,0,0, 0,1,0, 0,0,1];
	}
	multiply(other, shouldPrepend)
	{
		var a, b, c = new mat3();
		if (shouldPrepend === true)
		{
			a = other;
			b = this;
		}
		else
		{
			a = this;
			b = other;
		}
		c.elems[0] = a.elems[0]*b.elems[0] + a.elems[1]*b.elems[3] + a.elems[2]*b.elems[6];
		c.elems[1] = a.elems[0]*b.elems[1] + a.elems[1]*b.elems[4] + a.elems[2]*b.elems[7];
		c.elems[2] = a.elems[0]*b.elems[2] + a.elems[1]*b.elems[5] + a.elems[2]*b.elems[8];

		// row 1
		c.elems[3] = a.elems[3]*b.elems[0] + a.elems[4]*b.elems[3] + a.elems[5]*b.elems[6];
		c.elems[4] = a.elems[3]*b.elems[1] + a.elems[4]*b.elems[4] + a.elems[5]*b.elems[7];
		c.elems[5] = a.elems[3]*b.elems[2] + a.elems[4]*b.elems[5] + a.elems[5]*b.elems[8];

		// row 2
		c.elems[6] = a.elems[6]*b.elems[0] + a.elems[7]*b.elems[3] + a.elems[8]*b.elems[6];
		c.elems[7] = a.elems[6]*b.elems[1] + a.elems[7]*b.elems[4] + a.elems[8]*b.elems[7];
		c.elems[8] = a.elems[6]*b.elems[2] + a.elems[7]*b.elems[5] + a.elems[8]*b.elems[8];
		
		for (var i=0; i<9; i++)
			this.elems[i] = c.elems[i];
	}
	
	transformVec2s(pointList)
	{
		var i, n = pointList.length;
		for (i=0; i<n; i++)
		{
			var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
			var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
			pointList[i].x = x;
			pointList[i].y = y;
		}
	}
	
	makeTransformedPoints(pointList)
	{
		var result = [];
		for (var i=0,n=pointList.length;i<n;i++)
		{
			var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
			var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
			result.push( new vec2d(x,y) );
		}
		return result;
	}
	
	rotate(degrees, shouldPrepend)
	{
		var tmp = new mat3();
		tmp.elems[0] = Math.cos( degrees/180.0 * Math.PI );
		tmp.elems[1] = -Math.sin( degrees/180.0 * Math.PI );
		tmp.elems[3] = -tmp.elems[1];
		tmp.elems[4] = tmp.elems[0];
		this.multiply(tmp, shouldPrepend);
	}

	scaleEach(scaleX, scaleY, shouldPrepend)
	{
		var tmp = new mat3();
		tmp.elems[0] = scaleX;
		tmp.elems[4] = scaleY;
		this.multiply(tmp, shouldPrepend);
	}
	
	scaleBoth(scaleAmount, shouldPrepend)
	{
		var tmp = new mat3();
		tmp.elems[0] = scaleAmount;
		tmp.elems[4] = scaleAmount;
		this.multiply(tmp, shouldPrepend);
	}
	
	translate(transX, transY, shouldPrepend)
	{
		var tmp = new mat3();
		tmp.elems[6] = transX;
		tmp.elems[7] = transY;
		this.multiply(tmp, shouldPrepend);
	}
	
	determinant()
	{
		var result, a, b;
		
		a =         ( (this.elems[0]*this.elems[4]*this.elems[8])
					+ (this.elems[1]*this.elems[5]*this.elems[6])
					+ (this.elems[2]*this.elems[3]*this.elems[7]) );
		b = 		( (this.elems[2]*this.elems[4]+this.elems[6])
					+ (this.elems[1]*this.elems[3]+this.elems[8])
					+ (this.elems[0]*this.elems[5]+this.elems[7]) );
		result = a - b;
		return result;
	}
	
	isInvertible()
	{
		return (this.determinant() != 0);
	}
	
	invert()
	{
		var det = this.determinant();
		if (det == 0)
			return;
		
		var a,b,c,d,e,f,g,h,i;
		a = this.elems[0]; b = this.elems[1]; c = this.elems[2];
		d = this.elems[3]; e = this.elems[4]; f = this.elems[5];
		g = this.elems[6]; h = this.elems[7]; i = this.elems[8];
		
		this.elems[0] =  (e*i - f*h);     this.elems[1] = -((b*i) - (c*h));     this.elems[2] = (b*f)-(c*e);
		this.elems[3] = -(d*i - f*g);     this.elems[4] =   (a*i) - (c*g);      this.elems[5] = -( (a*f) - (c*d) );
		this.elems[6] =  (d*h - e*g);     this.elems[7] = -((a*h) - (b*g));     this.elems[8] = (a*e)-(b*d);
		
		var detInv = 1.0 / det;
		for (var i=0; i<9; i++)
			this.elems[i] *= detInv;
		
		return this;
	}
	
	reset()
	{
		this.setIdentity();		
	}
	
	print()
	{
		var str = '';
		for (var i=0; i<9; i++)
		{
			if (i && i%3==0)
				str += "\n";
			str += " " + this.elems[i].toFixed(5);
		}
		console.log(str);
	}
}


function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);

function onDocLoaded(evt)
{
	byId('output').addEventListener('mousemove', onMouseMove, false);
	byId('slider1').addEventListener('input', onSliderInput, false);
	byId('slider2').addEventListener('input', onSliderInput, false);

	draw();
}

//(400-48)/400 = 0.88
var invMat, svgInvMat;

function onMouseMove(evt)
{
	var mousePos = new vec2d(evt.offsetX,evt.offsetY);
	var worldPos = mousePos.clone();
	invMat.transformVec2s( [worldPos] );
	
	byId('screenMouse').textContent = `screen: ${mousePos.x},${mousePos.y}`;
	byId('worldMouse').textContent = `world: ${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)}`;
}

function onSliderInput(evt)
{
	draw();
}

function updateSliderLabels()
{
	byId('ofset1Output').textContent = byId('slider1').value;
	byId('ofset2Output').textContent = byId('slider2').value;
}

function draw()
{
	var can = byId('output');
	var ctx = can.getContext('2d');
	ctx.clearRect(0,0,can.width,can.height);
	
	var orientMat = evaluateViewOrientationMatrix(0.06*can.width,can.height-24, 0,-1);
	var scaleMat = computeWindowToViewPortMatrix(2052,1317, can.width,can.height);
	var viewMat = scaleMat.clone();
	viewMat.multiply(orientMat);
	
	console.log('viewMat');
	viewMat.print();
	
	invMat = viewMat.clone().invert();
	for (var i=0; i<9; i++)
		invMat.elems[i] /= invMat.elems[8];
	
	ctx.strokeStyle = '#fff';

	var axisPts = [ new vec2d(0,1070), new vec2d(0,0), new vec2d(0.88*2052,0) ];	// xAxis line 88% of image width
	var axis = viewMat.makeTransformedPoints(axisPts);
	drawLine(axis[0].x,axis[0].y, axis[1].x,axis[1].y, ctx);
	drawLine(axis[1].x,axis[1].y, axis[2].x,axis[2].y, ctx);
	
	var lineEnds = [new vec2d(330,263), new vec2d(1455,809)];
	var pts2 = viewMat.makeTransformedPoints(lineEnds);
	
	drawCircle(pts2[0].x,pts2[0].y, 4, ctx);
	drawCircle(pts2[1].x,pts2[1].y, 4, ctx);
	drawLine(pts2[0].x,pts2[0].y, pts2[1].x,pts2[1].y, ctx);
	
	var rawP3 = calcOffsetCoords(lineEnds[0].x,lineEnds[0].y, lineEnds[1].x,lineEnds[1].y, byId('slider1').value);
	var rawP4 = calcOffsetCoords(lineEnds[1].x,lineEnds[1].y, lineEnds[0].x,lineEnds[0].y, byId('slider2').value);
	var ofsPts = viewMat.makeTransformedPoints( [rawP3, rawP4] );
	drawCircle(ofsPts[0].x,ofsPts[0].y, 4, ctx);
	drawCircle(ofsPts[1].x,ofsPts[1].y, 4, ctx);
	
	updateSliderLabels();
}

function calcOffsetCoords(x1,y1, x2,y2, offset)
{
	var dx = x2 - x1;
	var dy = y2 - y1;
	var lineLen = Math.hypot(dx, dy);
	var normDx=0, normDy=0;
	if (lineLen != 0)
	{
		normDx = dx / lineLen;
		normDy = dy / lineLen;
	}
	
	var resultX = x1 + (offset * normDx);
	var resultY = y1 + (offset * normDy);
	
	return {x:resultX,y:resultY};//new vec2d(resultX,resultY); //{x:resultX,y:resultY};
}




// Exercise 6-1: 
//	Write a procedure to implement the evaluateViewOrientationMatrix function that calculates the elements of the
//	matrix for transforming world coordinates to viewing coordinates, given the viewing coordinate origin Porigin and
//	the viewUp vector
function evalViewOrientMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
	var worldUp = {x: worldUpVectorX, y: worldUpVectorY};
	var len = Math.hypot(worldUp.x, worldUp.y);
	if (len != 0)
		len = 1.0 / len;
	worldUp.x *= len;
	worldUp.y *= len;
	
	var worldRight = {x: worldUp.y, y: -worldUp.x};
	
	var rotMat = svg.createSVGMatrix();
	rotMat.a = worldRight.x;
	rotMat.b = worldRight.y;
	rotMat.c = worldUp.x;
	rotMat.d = worldUp.y;
	
	var transMat = svg.createSVGMatrix();
	transMat = transMat.translate(screenOriginX, screenOriginY);
	
	var result = rotMat.multiply(transMat);
	return result;
}

function evaluateViewOrientationMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
	var worldUp = new vec2d(worldUpVectorX, worldUpVectorY);
	worldUp.normalize();
	var worldRight = worldUp.clone().perp();

	var rotMat = new mat3();
	rotMat.elems[0] = worldRight.x; rotMat.elems[1] = worldRight.y;
	rotMat.elems[3] = worldUp.x; rotMat.elems[4] = worldUp.y;
	
	var transMat = new mat3();
	transMat.translate(screenOriginX,screenOriginY);

	var result = rotMat.clone();
	result.multiply(transMat);
	return result;
}
/*
0	1	2
3	4	5
6	7	8

translation
-----------
1	0	0
0	1	0
tX	tY	1

scaling
---------
sX	0	0
0	sY	0
0	0	1

rotation
--------
cosX	-sinX	0
sinX	cosX	0
0		0		1
*/

// Exercise 6-2:
//	Derive the window to viewport transformation equations 6-3 by first scaling the window to 
//	the size of the viewport and then translating the scaled window to the viewport position
function computeWindowToViewPortMatrix(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
	var result = new mat3();
	result.scaleEach(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
	return result;
}

// returns an SVGMatrix
function compWnd2ViewMat(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
	var result = svg.createSVGMatrix();
	return result.scaleNonUniform(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
}


function drawLine(x1,y1,x2,y2,ctx)
{
	ctx.beginPath();
		ctx.moveTo(x1,y1);
		ctx.lineTo(x2,y2);
	ctx.stroke();
}
function drawCircle(x,y,radius,ctx)
{
	ctx.beginPath();
		ctx.arc(x, y, radius, 0, (Math.PI/180)*360, false);
		ctx.stroke();
	ctx.closePath();
}
canvas
{
	background-color: black;
}
.container
{
	display: inline-block;
	background-color: #888;
	border: solid 4px #555;
}
#screenMouse, #worldMouse, .control
{
	display: inline-block;
	width: calc(513px/2 - 2*8px);
	margin-left: 8px;
}
<body>
	<div class='container'>
		<canvas id='output' width='513' height='329'></canvas><br>
		<div id='screenMouse'></div><div id='worldMouse'></div>
		<div>
			<div class='control'>P2 ofs: <input id='slider1' type='range' min='0' max='500' value='301'><span id='ofset1Output'></span></div>
			<div class='control'>P3 ofs: <input id='slider2' type='range' min='0' max='500' value='285'><span id='ofset2Output'></span></div>
		</div>
	</div>
</body>