我开始制作一个使用足球场svg素材的小部件。到目前为止,我一直在使用常规的2D矩形,效果不错。但是,我想用这个素材替换它:
。
我开始尝试计算在这种svg中球的位置,但进展并不顺利。我想我需要一种从常规的2D矩形模型转换为其他模型的方法,以考虑梯形图形。
也许有人可以帮助理解如何实现。假设我有以下坐标
编辑#1
我阅读了MBo发布的答案,并努力将Delphi代码重写为JavaScript。我完全不懂Delphi,但我认为这很成功,但在尝试代码时遇到了几个问题:
1.梯形是反向的(底部是较短的水平线),我试图修复它,但没有成功,尝试了几次后,我得到了我想要的结果,但是
2.我不确定计算是否正确,中心坐标似乎奇怪地向下倾斜(至少是我的视觉印象)。
3.我试图通过调整
4.从我所理解的来看,此脚本的工作方式是您指定较长的水平线
编辑#2
我更新了演示片段,它似乎以某种方式运行,目前唯一的问题是如果我指定...
实际上,梯形的大小较小(宽度和高度较小),同时以某种奇怪的方式操作该行:
![enter image description here](https://istack.dev59.com/ciekU.webp)
我开始尝试计算在这种svg中球的位置,但进展并不顺利。我想我需要一种从常规的2D矩形模型转换为其他模型的方法,以考虑梯形图形。
也许有人可以帮助理解如何实现。假设我有以下坐标
{x: 0.2, y: 0.2}
,这意味着我必须将球放在球场宽度的20%和高度的20%处。在这个例子中该怎么做?编辑#1
我阅读了MBo发布的答案,并努力将Delphi代码重写为JavaScript。我完全不懂Delphi,但我认为这很成功,但在尝试代码时遇到了几个问题:
1.梯形是反向的(底部是较短的水平线),我试图修复它,但没有成功,尝试了几次后,我得到了我想要的结果,但是
0.2,0.2
坐标出现在底部而不是靠近顶部。2.我不确定计算是否正确,中心坐标似乎奇怪地向下倾斜(至少是我的视觉印象)。
3.我试图通过调整
YShift = Hg / 4;
来解决反向梯形问题,但这会引起各种问题。想知道这究竟是如何工作的。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>