【问题标题】:How to calculate angle of rotation to make width fit desired size in perspective mode?如何计算旋转角度以使宽度适合透视模式下的所需尺寸?
【发布时间】:2019-12-17 06:06:54
【问题描述】:

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

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

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

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

这是一个让您了解我正在寻找什么的游乐场,我已经复制了透视和旋转变换完成的数学运算来计算最左边点的最终位置,但我没有在给出矩阵数学和涉及的多个步骤的情况下,能够弄清楚如何求解角度。

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>

非常感谢任何帮助!

【问题讨论】:

  • 如果你知道数学规则,你可以计算新的宽度并用js设置它。试试看这个:stackoverflow.com/a/46107713/11368483也许你可以在数学专业社区问
  • @red,对不起,我最初的问题一定不够清楚。我知道如何求解宽度,实际上我正在尝试求解角度,以便生成的图像具有给定的宽度。我已经编辑了问题以澄清。 (我尝试在 math.stackexchange.com 上提问,但被认为是题外话)

标签: javascript css math css-transforms perspective


【解决方案1】:

我会考虑另一种方法来找到没有矩阵计算的公式1 以获得以下结果:

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

其中p 是透视图,angle 是角度旋转,D 是元素宽度,R 是我们正在搜索的新宽度。

如果我们的角度为-45deg,视角等于100px,初始宽度为200px,那么新宽度将为: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>

如果我们的角度为-30deg,视角等于200px,初始宽度为200px,那么新宽度将为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 为了更好地理解公式,让我们考虑下图:

想象一下,我们正在从顶部查看所有内容。红线是我们的旋转元素。大黑点是我们的视角,与场景的距离等于p(这是我们的视角)。由于transform-origin 是正确的,因此将这一点放在右侧是合乎逻辑的。否则,它应该在中心。

现在,我们看到的是R 设计的宽度,W 是我们在没有透视的情况下看到的宽度。很明显,大视角下我们看到的几乎一样,没有视角

.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>

从一个小的视角,我们看到一个小的宽度

.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>

如果我们考虑图中O所标注的角度,我们可以写出如下公式:

tan(O) = R/p

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

所以我们将拥有R = p*W /(L + p)W = cos(-angle)*D = cos(angle)*DL = sin(-angle)*D = -sin(angle)*D,这将给我们:

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

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

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

然后像here1 中描述的那样,我们可以得到以下等式:

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

如果你想要一个等于190px 和R 等于150pxD 等于200px 的透视,你需要一个等于-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 感谢https://math.stackexchange.com 社区帮助我确定了正确的公式

【讨论】:

  • 您好 Temani,感谢您的详细回复,但我认为您误解了这个问题。我已经能够看到将给定角度和透视的宽度,但我正在尝试重新排列该等式,以便在给定所需宽度(在您的示例中为 R)我可以计算所需的角度。所以求解角度,而不是宽度。抱歉,如果原始问题不清楚,我将对其进行编辑以澄清。
  • @BenSlinger 我知道,这就是我最后提到的答案中缺少的内容。我能够识别公式,但现在需要更改它以表达与其他参数的角度但我们至少有我们可以开始使用的公式
  • 啊,明白了,我没明白你的意思,对不起!感谢您的帮助!
  • @BenSlinger 我使用公式进行了第一次更新以找到角度,但在特定情况下。我想很难处理所有情况,因为在某些情况下,由于没有定义角度,所以无法找到角度
  • 非常感谢您的工作 - 我不确定当透视不等于图像大小时如何使其工作,但通常情况并非如此。我也不太确定如何确定最终方程中 R 的值,R 不是从较早的角度得出的吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 2018-09-21
  • 1970-01-01
  • 1970-01-01
  • 2014-10-28
相关资源
最近更新 更多