[英]How to calculate angle of rotation to make width fit desired size in perspective mode?
我试图想出一种方法,通过 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>
任何帮助深表感谢!
我会考虑另一种方法来找到没有矩阵计算的公式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)*D
和L = 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))
angle = sin-1((R*p)/(D*sqrt(p²+R²))) - tan-1(p/R)
如果您希望透视等于190px
,R 等于150px
, D
等于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社区帮助我确定了正确的公式
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.