我开始构建一个使用svg资产的小部件,该资产是一个足球场。到目前为止,我正在使用常规的2d矩形,效果很好。但是我想用这一资产来代替该资产:
我开始制作关于如何计算这种svg中球位置的原型,并且效果不佳。我想我需要的是从常规2D矩形模型到某种其他可以解释飞人图形的转换。
也许有人可以帮助您了解其工作方式。可以说我有跟随{x: 0.2, y: 0.2}
的坐标,这意味着我必须将球放在球场宽度的20%和球场高度的20%内。在此示例中我该怎么办?
编辑#1
我读了MBo发布的答案,并努力将delphi代码重写为JavaScript。我一点都不了解delphi,但我认为它运行良好,但是在尝试代码后我遇到了一些问题:
空中飞人被扭转(底部更短的水平线),我尝试修复它,但没有成功,经过几次尝试,我都想按此方法进行操作,但是0.2, 0.2
坐标出现在底部而不是靠近顶部。
我不确定总体上计算是否正确,中心坐标似乎偏向底部(至少是我的视觉印象)
我尝试通过玩YShift = Hg / 4;
来解决空中飞人的反向问题,但这会导致各种问题。想知道这是如何工作的
据我了解,该脚本的工作方式是您指定更长的水平线Wd
和高度Hg
,这会为您带来飞人,对吗?
编辑#2
我更新了演示代码段,它似乎以某种方式起作用,当前我唯一遇到的问题是如果我指定
Wd = 600; // width of source
Hg = 200; // height of source
实际的空中飞人较小(宽度和高度较小),
也以某种奇怪的方式操纵了这一行:
YShift = Hg / 4;
更改空中飞人的实际高度。
这当时很难实施,就像给我指定大小的svg法院一样,我需要能够为函数提供实际的大小,这样坐标计算才是准确的。
让我们说我会在我知道4个角的情况下获得法庭的审判,因此我需要能够计算坐标。不幸的是,这是我的演示摘录中的实现。
有人可以帮助更改代码或提供更好的实现吗?
编辑#3-分辨率
这是最终的实现:
var math = {
inv: function (M){
if(M.length !== M[0].length){return;}
var i=0, ii=0, j=0, dim=M.length, e=0, t=0;
var I = [], C = [];
for(i=0; i<dim; i+=1){
I[I.length]=[];
C[C.length]=[];
for(j=0; j<dim; j+=1){
if(i==j){ I[i][j] = 1; }
else{ I[i][j] = 0; }
C[i][j] = M[i][j];
}
}
for(i=0; i<dim; i+=1){
e = C[i][i];
if(e==0){
for(ii=i+1; ii<dim; ii+=1){
if(C[ii][i] != 0){
for(j=0; j<dim; j++){
e = C[i][j];
C[i][j] = C[ii][j];
C[ii][j] = e;
e = I[i][j];
I[i][j] = I[ii][j];
I[ii][j] = e;
}
break;
}
}
e = C[i][i];
if(e==0){return}
}
for(j=0; j<dim; j++){
C[i][j] = C[i][j]/e;
I[i][j] = I[i][j]/e;
}
for(ii=0; ii<dim; ii++){
if(ii==i){continue;}
e = C[ii][i];
for(j=0; j<dim; j++){
C[ii][j] -= e*C[i][j];
I[ii][j] -= e*I[i][j];
}
}
}
return I;
},
multiply: function(m1, m2) {
var temp = [];
for(var p = 0; p < m2.length; p++) {
temp[p] = [m2[p]];
}
m2 = temp;
var result = [];
for (var i = 0; i < m1.length; i++) {
result[i] = [];
for (var j = 0; j < m2[0].length; j++) {
var sum = 0;
for (var k = 0; k < m1[0].length; k++) {
sum += m1[i][k] * m2[k][j];
}
result[i][j] = sum;
}
}
return result;
}
};
// standard soccer court dimensions
var soccerCourtLength = 105; // [m]
var soccerCourtWidth = 68; // [m]
// soccer court corners in clockwise order with center = (0,0)
var courtCorners = [
[-soccerCourtLength/2., soccerCourtWidth/2.],
[ soccerCourtLength/2., soccerCourtWidth/2.],
[ soccerCourtLength/2.,-soccerCourtWidth/2.],
[-soccerCourtLength/2.,-soccerCourtWidth/2.]];
// screen corners in clockwise order (unit: pixel)
var screenCorners = [
[50., 150.],
[450., 150.],
[350., 50.],
[ 150., 50.]
];
// compute projective mapping M from court to screen
// / a b c \
// M = ( d e f )
// \ g h 1 /
// set up system of linear equations A X = B for X = [a,b,c,d,e,f,g,h]
var A = [];
var B = [];
var i;
for (i=0; i<4; ++i)
{
var cc = courtCorners[i];
var sc = screenCorners[i];
A.push([cc[0], cc[1], 1., 0., 0., 0., -sc[0]*cc[0], -sc[0]*cc[1]]);
A.push([0., 0., 0., cc[0], cc[1], 1., -sc[1]*cc[0], -sc[1]*cc[1]]);
B.push(sc[0]);
B.push(sc[1]);
}
var AInv = math.inv(A);
var X = math.multiply(AInv, B); // [a,b,c,d,e,f,g,h]
// generate matrix M of projective mapping from computed values
X.push(1);
M = [];
for (i=0; i<3; ++i)
M.push([X[3*i], X[3*i+1], X[3*i+2]]);
// given court point (array [x,y] of court coordinates): compute corresponding screen point
function calcScreenCoords(pSoccer) {
var ch = [pSoccer[0],pSoccer[1],1]; // homogenous coordinates
var sh = math.multiply(M, ch); // projective mapping to screen
return [sh[0]/sh[2], sh[1]/sh[2]]; // dehomogenize
}
function courtPercToCoords(xPerc, yPerc) {
return [(xPerc-0.5)*soccerCourtLength, (yPerc-0.5)*soccerCourtWidth];
}
var pScreen = calcScreenCoords(courtPercToCoords(0.2,0.2));
var hScreen = calcScreenCoords(courtPercToCoords(0.5,0.5));
// Custom code
document.querySelector('.trapezoid').setAttribute('d', `
M ${screenCorners[0][0]} ${screenCorners[0][1]}
L ${screenCorners[1][0]} ${screenCorners[1][1]}
L ${screenCorners[2][0]} ${screenCorners[2][1]}
L ${screenCorners[3][0]} ${screenCorners[3][1]}
Z
`);
document.querySelector('.point').setAttribute('cx', pScreen[0]);
document.querySelector('.point').setAttribute('cy', pScreen[1]);
document.querySelector('.half').setAttribute('cx', hScreen[0]);
document.querySelector('.half').setAttribute('cy', hScreen[1]);
document.querySelector('.map-pointer').setAttribute('style', 'left:' + (pScreen[0] - 15) + 'px;top:' + (pScreen[1] - 25) + 'px;');
document.querySelector('.helper.NW-SE').setAttribute('d', `M ${screenCorners[3][0]} ${screenCorners[3][1]} L ${screenCorners[1][0]} ${screenCorners[1][1]}`);
document.querySelector('.helper.SW-NE').setAttribute('d', `M ${screenCorners[0][0]} ${screenCorners[0][1]} L ${screenCorners[2][0]} ${screenCorners[2][1]}`);
body {
margin:0;
}
.container {
width:500px;
height:200px;
position:relative;
border:solid 1px #000;
}
.view {
background:#ccc;
width:100%;
height:100%;
}
.trapezoid {
fill:none;
stroke:#000;
}
.point {
stroke:none;
fill:red;
}
.half {
stroke:none;
fill:blue;
}
.helper {
fill:none;
stroke:#000;
}
.map-pointer {
background-image:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaWQ9IkxheWVyXzEiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48Zz48cGF0aCBkPSJNMjU1LjksNmMtMjEuNywwLTQzLjQsNS4zLTYyLjMsMTZjLTMzLjksMTkuMi01Ny45LDU1LjMtNjEuOSw5NC4xYy0zLjcsMzYuMSw4LjksNzEuOCwyMiwxMDUuNyAgIGMxNS4xLDM4LjksMTAyLjEsMjI4LjksMTAyLjEsMjI4LjlzODcuNi0xOTEuNCwxMDIuOC0yMzAuOWMxMy4xLTM0LjIsMjUuNy03MC4yLDIxLjItMTA2LjVjLTUuMi00Mi4xLTM0LjctNzkuOS03My42LTk2LjggICBDMjkwLjUsOS41LDI3My4yLDYsMjU1LjksNnogTTI1NS45LDE4OS44Yy0zMywwLTU5LjgtMjYuOC01OS44LTU5LjhzMjYuOC01OS44LDU5LjgtNTkuOFMzMTUuNyw5NywzMTUuNywxMzAgICBTMjg5LDE4OS44LDI1NS45LDE4OS44eiIvPjxwYXRoIGQ9Ik0yOTIuMiwzOTcuMWMtNC4xLDguOS03LjksMTcuMi0xMS40LDI0LjdjMzYuOCwzLjYsNjMuNiwxNS4yLDYzLjYsMjguOGMwLDE2LjYtMzkuNiwzMC04OC40LDMwICAgYy00OC44LDAtODguNC0xMy40LTg4LjQtMzBjMC0xMy42LDI2LjgtMjUuMiw2My41LTI4LjhjLTMuNS03LjQtNy40LTE1LjgtMTEuNC0yNC43Yy02MC4yLDYuMy0xMDQuNSwyNy45LTEwNC41LDUzLjUgICBjMCwzMC42LDYzLjEsNTUuNCwxNDAuOCw1NS40czE0MC44LTI0LjgsMTQwLjgtNTUuNEMzOTYuOCw0MjUsMzUyLjQsNDAzLjQsMjkyLjIsMzk3LjF6IiBpZD0iWE1MSURfMV8iLz48L2c+PC9zdmc+');
display:block;
width:32px;
height:32px;
background-repeat:no-repeat;
background-size:32px 32px;
position:absolute;
opacity:.3;
}
<div class="container">
<svg class="view">
<path class="trapezoid"></path>
<circle class="point" r="3"></circle>
<circle class="half" r="3"></circle>
<path class="helper NW-SE"></path>
<path class="helper SW-NE"></path>
</svg>
<span class="map-pointer"></span>
</div>
答案 0 :(得分:1)
您正在寻找从法院平面的(x,y)
到屏幕平面的(u,v)
的投影映射。投影映射如下所示:
(x,y,1)
M
相乘,以获得屏幕像素的均匀坐标(u',v',l)
(u,v) = (u'/l, v'/l)
例如,可以通过求解相应的方程来计算适当的矩阵M
。角落。选择法院中心使其与原点和沿较长边的x
轴重合,并从您的图像中测量角点坐标,对于标准105x68法院,我们得到以下相应坐标:
(-52.5, 34) -> (174, 57)
( 52.5, 34) -> (566, 57)
( 52.5,-34) -> (690,214)
(-52.5,-34) -> ( 50,214)
设置矩阵投影映射的方程式
/ a b c \
M = ( d e f )
\ g h 1 /
导致具有8个方程和8个未知数的线性系统,因为每个点对应关系(x,y) -> (u,v)
贡献两个方程:
x*a + y*b + 1*c + 0*d + 0*e + 0*f - (u*x)*g - (u*y)*h = u
0*a + 0*b + 0*c + x*d + y*e + 1*f - (v*x)*g - (v*y)*h = v
(通过将M (x y 1)^T = (l*u l*v l*1)^T
扩展为三个方程并将l
的值从第三个方程替换为前两个方程,可以得到这两个方程。)
将8个未知数a,b,c,...,h
放入矩阵的解决方案是:
/ 4.63 2.61 370 \
M = ( 0 -1.35 -116.64 )
\ 0 0.00707 1 /
例如法院中心为{x: 0.5, y: 0.5}
,则必须先将其转换为上述坐标系,以获得(x,y) = (0,0)
。然后您必须计算
/ x \ / 4.63 2.61 370 \ / 0 \ / 370 \
M ( y ) = ( 0 -1.35 -116.64 ) ( 0 ) = ( 116.64 )
\ 1 / \ 0 0.00707 1 / \ 1 / \ 1 /
通过去均质化,您可以获得中心的屏幕坐标为
(u,v) = (370/1, 116.64/1) ~= (370,117)
JavaScript实现可能如下所示:
// using library https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.2.1/math.js
// standard soccer court dimensions
var soccerCourtLength = 105; // [m]
var soccerCourtWidth = 68; // [m]
// soccer court corners in clockwise order with center = (0,0)
var courtCorners = [
[-soccerCourtLength/2., soccerCourtWidth/2.],
[ soccerCourtLength/2., soccerCourtWidth/2.],
[ soccerCourtLength/2.,-soccerCourtWidth/2.],
[-soccerCourtLength/2.,-soccerCourtWidth/2.]];
// screen corners in clockwise order (unit: pixel)
var screenCorners = [
[174., 57.],
[566., 57.],
[690.,214.],
[ 50.,214.]];
// compute projective mapping M from court to screen
// / a b c \
// M = ( d e f )
// \ g h 1 /
// set up system of linear equations A X = B for X = [a,b,c,d,e,f,g,h]
var A = [];
var B = [];
var i;
for (i=0; i<4; ++i)
{
var cc = courtCorners[i];
var sc = screenCorners[i];
A.push([cc[0], cc[1], 1., 0., 0., 0., -sc[0]*cc[0], -sc[0]*cc[1]]);
A.push([0., 0., 0., cc[0], cc[1], 1., -sc[1]*cc[0], -sc[1]*cc[1]]);
B.push(sc[0]);
B.push(sc[1]);
}
var AInv = math.inv(A);
var X = math.multiply(AInv, B); // [a,b,c,d,e,f,g,h]
// generate matrix M of projective mapping from computed values
X.push(1);
M = [];
for (i=0; i<3; ++i)
M.push([X[3*i], X[3*i+1], X[3*i+2]]);
// given court point (array [x,y] of court coordinates): compute corresponding screen point
function calcScreenCoords(pSoccer) {
var ch = [pSoccer[0],pSoccer[1],1]; // homogenous coordinates
var sh = math.multiply(M, ch); // projective mapping to screen
return [sh[0]/sh[2], sh[1]/sh[2]]; // dehomogenize
}
function courtPercToCoords(xPerc, yPerc) {
return [(xPerc-0.5)*soccerCourtLength, (yPerc-0.5)*soccerCourtWidth];
}
var pScreen = calcScreenCoords(courtPercToCoords(0.2,0.2))
答案 1 :(得分:0)
我已经用纯HTML和JavaScript实现了它。您必须根据需要调整变量。 A和B是平行小边和大边的长度,H是飞人的高度。 x0,y0是该字段的左下角的坐标。如果对您有用,我将解释数学。
jQuery(function($){
var $field2d = $('.field2d'), $ball = $('.ball');
$field2d.on('mousemove', function(e){
var pos = translateBallPosition(e.offsetX, e.offsetY);
$ball.css({left: pos.x, top: pos.y});
});
var FB = {x0: 50, y0: 215, B: 640, A: 391, H: 158, P: 0};
FB.Wd = $field2d.width();
FB.Ht = $field2d.height();
FB.P = FB.B * FB.H / (FB.B - FB.A);
function translateBallPosition(X, Y){
var x = X / FB.Wd * FB.B, y = (FB.Ht - Y) / FB.Ht * FB.H;
y = y * FB.B * FB.H / (FB.A * FB.H + y * (FB.B - FB.A));
x = x / FB.P * (FB.P - y) + y * FB.B / FB.P / 2;
return {x: FB.x0 + x, y: FB.y0 - y};
}
});
.field2d {
position: relative;
border: 1px dashed gray;
background: #b0fdb5;
width: 400px;
height: 200px;
margin: 5px auto;
cursor: crosshair;
text-align: center;
}
.field3d {
position: relative;
width: 743px;
margin: auto;
}
.field3d>img {
width: 100%;
height: auto;
}
.ball {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background: red;
border-radius: 10px;
margin: -20px 0 0 -10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="field3d">
<img src="https://i.stack.imgur.com/ciekU.png" />
<div class="ball"></div>
</div>
<div class="field2d">
Hover over this div to see corresponding ball position
</div>