如何计算旋转角度以使宽度在透视模式下适合所需的大小? [英] How to calculate angle of rotation to make width fit desired size in perspective mode?

查看:108
本文介绍了如何计算旋转角度以使宽度在透视模式下适合所需的大小?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过CSS绕Y轴透视旋转图像,以使最终的可见宽度等于所需像素数。



例如,我可能想旋转 300px 图片,以便在应用旋转和透视后,图片的宽度现在为 240px (原始金额的80%)。通过反复试验,我知道我可以设置 transform:Perspective(300)rotationY(-12.68),它会将左上角的点设置为 -240px (这是使用图片的右侧作为原点)



我不太清楚如何对它进行反向工程,以便任何给定的图像宽度,透视图和所需的宽度,我都可以计算出必要的旋转角度。



例如对于相同的 300px 图片,我现在希望旋转后的宽度为 150px -所需的计算是什么以获得必要的角度?



这里是一个操场,可让您大致了解我要寻找的东西,我已经复制了透视图和旋转变换完成的数学运算来计算最左边的点的最终位置,但是在矩阵数学和涉及多个步骤的情况下,我仍无法弄清楚如何求解角度。





想象一下,我们正在研究顶端。红线是我们旋转的元素。大黑点是我们的视角,与场景的距离等于 p (这是我们的观点)。由于 transform-origin 是正确的,因此将这一点放在右边是合乎逻辑的。否则,它应该位于中心。



现在,我们看到的是 R 和<$设计的宽度c $ c> W 是我们没有视角看到的宽度。显然,从大角度来看,没有角度我们会看到几乎相同的



  .box {宽度:200像素;高度:200px; border:1px solid;} img {transform-origin:right;}  

 < div class = box> < img src = https://picsum.photos/id/1/200/200 style = transform:rotationY(-30deg)< / div>< div class = box> < img src = https://picsum.photos/id/1/200/200 style = transform:perspective(9999px)rotationY(-30deg)< / div>  



从较小的角度看,我们看到的宽度较小



  .box {width:200px;高度:200px; border:1px solid;} img {transform-origin:right;}  

 < div class = box> < img src = https://picsum.photos/id/1/200/200 style = transform:rotationY(-30deg)< / div>< div class = box> < img src = https://picsum.photos/id/1/200/200 style = transform:perspective(15px)rotationY(-30deg)>< / div>  



如果我们考虑 O 在图中,我们可以写出以下公式:

  tan(O)= R / p 

  tan( O)= W /(L + p)

因此我们将得到 R = p * W /(L + p),其中 W = cos(-angle)* D = cos(angle)* D L = sin(-angle)* D = -sin(angle)* D 这将给我们:

  R =(p * cos(角度)* D)/(p-(sin(角度)* D))






要找到角度,我们可以将公式转换为:

  R * p-R * D * sin(angle)= p * D * cos(angle)
R * p = D *(p * cos(angle)+ R * sin (角度))

然后像此处 1 我们可以得到以下等式:

  angle = sin-1((R * p)/(D * sqrt (p²+R²)))-tan-1(p / R)

如果需要透视图等于 190px 和R等于 150px D 200px ,您需要旋转等于 -15.635deg



  .box {width:200px;高度:200px;边框:1px实线;背景:向右线性渐变(红色,红色)/ 150px 100%不重复; position:relative;} img {transform-origin:right;}  

 < div class = box> < img src = https://picsum.photos/id/1/200/200 style = transform:perspective(190px)rotationY(-15.635deg)>< / div>  






1多亏了 https://math.stackexchange.com 社区,该社区帮助我确定了正确的公式


I am trying to come up with a way to rotate an image in perspective around the Y axis via CSS so that the final visible width equals a desired number of pixels.

For example, I might want to rotate a 300px image so that, after rotation and perspective is applied, the width of the image is now 240px (80% of original). By trial and error I know I can set transform: perspective(300) rotateY(-12.68) and it puts the top left point at -240px (this is using the right side of the image as the origin)

I can't quite figure out how to reverse engineer this so that for any given image width, perspective and desired width I can calculate the necessary rotation.

Eg. For the same 300px image, I now want it to be a width of 150px after rotation - what is the calculation required to get the necessary angle?

Here's a playground to give you an idea of what I'm looking for, I've replicated the math done by the perspective and rotation transforms to calculate the final position of the left-most point, but I haven't been able to figure out how to solve for the angle given the matrix math and multiple steps involved.

https://repl.it/@BenSlinger/PerspectiveWidthDemo

const calculateLeftTopPointAfterTransforms = (perspective, rotation, width) => {

  // convert degrees to radians
  const rRad = rotation * (Math.PI / 180);

  // place the camera
  const cameraMatrix = math.matrix([0, 0, -perspective]);

  // get the upper left point of the image based on middle right transform origin
  const leftMostPoint = math.matrix([-width, -width / 2, 0]);

  const rotateYMatrix = math.matrix([
    [Math.cos(-rRad), 0, -Math.sin(-rRad)],
    [0, 1, 0],
    [Math.sin(-rRad), 0, Math.cos(-rRad)],
  ]);

  // apply rotation to point
  const rotatedPoint = math.multiply(rotateYMatrix, leftMostPoint);

  const cameraProjection = math.subtract(rotatedPoint, cameraMatrix);

  const pointInHomogenizedCoords = math.multiply(math.matrix([
    [1, 0, 0 / perspective, 0],
    [0, 1, 0 / perspective, 0],
    [0, 0, 1, 0],
    [0, 0, 1 / perspective, 0],
  ]), cameraProjection.resize([4], 1));

  const finalPoint = [
    math.subset(pointInHomogenizedCoords, math.index(0))
    / math.subset(pointInHomogenizedCoords, math.index(3)),
    math.subset(pointInHomogenizedCoords, math.index(1))
    / math.subset(pointInHomogenizedCoords, math.index(3)),
  ];

  return finalPoint;
}

<div id="app"></div>


	<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.js"></script>
 
 	<script type="text/babel" data-plugins="transform-class-properties" >
  // GOAL: Given the percentage defined in desiredWidth, calculate the rotation required for the transformed image to fill that space (shown by red background)

// eg: With desiredWidth 80 at perspective 300 and image size 300, rotation needs to be 12.68, putting the left point at 300 * .8 = 240.
// How do I calculate that rotation for any desired width, perspective and image size?


// factor out some styles
const inputStyles = { width: 50 };

const PerspDemo = () => {

  const [desiredWidth, setDesiredWidth] = React.useState(80);
  const [rotation, setRotation] = React.useState(25);
  const [perspective, setPerspective] = React.useState(300);
  const [imageSize, setImageSize] = React.useState(300);
  const [transformedPointPosition, setTPP] = React.useState([0, 0]);

  const boxStyles = { outline: '1px solid red', width: imageSize + 'px', height: imageSize + 'px', margin: '10px', position: 'relative' };

  React.useEffect(() => {
    setTPP(calculateLeftTopPointAfterTransforms(perspective, rotation, imageSize))
  }, [rotation, perspective]);


  return <div>
    <div>
      <label>Image size</label>
      <input
        style={inputStyles}
        type="number"
        onChange={(e) => setImageSize(e.target.value)}
        value={imageSize}
      />
    </div>
    <div>
      <label>Desired width after transforms (% of size)</label>
      <input
        style={inputStyles}
        type="number"
        onChange={(e) => setDesiredWidth(e.target.value)}
        value={desiredWidth}
      />
    </div>

    <div>
      <label>Rotation (deg)</label>
      <input
        style={inputStyles}
        type="number"
        onChange={(e) => setRotation(e.target.value)}
        value={rotation}
      />
    </div>

    <div>
      <label>Perspective</label>
      <input
        style={inputStyles}
        type="number"
        onChange={(e) => setPerspective(e.target.value)}
        value={perspective}
      />
    </div>



    <div>No transforms:</div>
    <div style={boxStyles}>
      <div>
        <img src={`https://picsum.photos/${imageSize}/${imageSize}`} />
      </div>
    </div>

    <div>With rotation and perspective:</div>
    <div style={boxStyles}>
      <div style={{ display: 'flex', position: 'absolute', height: '100%', width: '100%' }}>
        <div style={{ backgroundColor: 'white', flexBasis: 100 - desiredWidth + '%' }} />
        <div style={{ backgroundColor: 'red', flexGrow: 1 }} />

      </div>
      <div style={{
        transform: `perspective(${perspective}px) rotateY(-${rotation}deg)`,
        transformOrigin: '100% 50% 0'
      }}>
        <img src={`https://picsum.photos/${imageSize}/${imageSize}`} />
      </div>
    </div>
    <div>{transformedPointPosition.toString()}</div>
  </div>;
};

ReactDOM.render(<PerspDemo />, document.getElementById('app'));

  </script>
  
  
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/6.0.4/math.min.js"></script>

Any help is much appreciated!

解决方案

I would consider a different way to find the formula without matrix calculation1 to obtain the following:

R = (p * cos(angle) * D)/(p - (sin(angle) * D))

Where p is the perspective and angle is the angle rotation and D is the element width and R is the new width we are searching for.

If we have an angle of -45deg and a perspective equal to 100px and an initial width 200px then the new width will be: 58.58px

.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  background:
    linear-gradient(red,red) right/58.58px 100% no-repeat;
  position:relative;
}

img {
 transform-origin:right;
}

<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform:perspective(100px) rotateY(-45deg)">
</div>

If we have an angle of -30deg and a perspective equal to 200px and an initial width 200px then the new width will be 115.46px

.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  background:
    linear-gradient(red,red) right/115.46px 100% no-repeat;
  position:relative;
}

img {
 transform-origin:right;
}

<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform:perspective(200px) rotateY(-30deg)">
</div>

1 To better understand the formula let's consider the following figure:

Imagine that we are looking at everything from the top. The red line is our rotated element. The big black dot is our point of view with a distance equal to p from the scene (this is our perspective). Since the transform-origin is the right, it's logical to have this point at the right. Otherwise, it should at the center.

Now, what we see is the width designed by R and W is the width we see without perspective. It's clear that with a big perspective we see almost the same without perspective

.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
}

img {
 transform-origin:right;
}

<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform: rotateY(-30deg)">
</div>
<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform:perspective(9999px) rotateY(-30deg)">
</div>

and with a small perspective we see a small width

.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
}

img {
 transform-origin:right;
}

<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform: rotateY(-30deg)">
</div>
<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform:perspective(15px) rotateY(-30deg)">
</div>

If we consider the angle noted by O in the figure we can write the following formula:

tan(O) = R/p

and

tan(O) = W/(L + p)

So we will have R = p*W /(L + p) with W = cos(-angle)*D = cos(angle)*D and L = sin(-angle)*D = -sin(angle)*D which will give us:

R = (p * cos(angle) * D)/(p - (sin(angle) * D))


To find the angle we can transform our formula to be:

R*p - R*D*sin(angle) = p*D*cos(angle)
R*p = D*(p*cos(angle) + R*sin(angle))

Then like described here1 we can obtain the following equation:

angle = sin-1((R*p)/(D*sqrt(p²+R²))) - tan-1(p/R)

If you want a perspective equal to 190px and R equal to 150px and D equal to 200px you need a rotation equal to -15.635deg

.box {
  width: 200px;
  height: 200px;
  border: 1px solid;
  background:
    linear-gradient(red,red) right/150px 100% no-repeat;
  position:relative;
}

img {
 transform-origin:right;
}

<div class="box">
  <img src="https://picsum.photos/id/1/200/200" style="transform:perspective(190px) rotateY(-15.635deg)">
</div>


1 Thanks to the https://math.stackexchange.com community that helped me identify the right formula

这篇关于如何计算旋转角度以使宽度在透视模式下适合所需的大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆