Loading...
首先需要明确一些概念,例如包围盒、坐标、锚点、变换中心等。了解它们有助于更好地使用具体的 API。
在场景图中我们了解到可以在图形之间构建父子关系,这种父子关系有时会与我们的直觉相悖,例如给一根直线(Line)添加一个子节点文本(Text):
line.appendChild(text);
但本质上这种层次结构只是定义了一种父子关系,在计算变换时把它考虑进去。例如我们不需要再单独移动直线以及文本,基于这种父子关系,移动直线即可,文本会跟随它移动。在变换过程中,文本相对于直线的位置始终并没有变,即文本在父节点直线的局部坐标系下的坐标没有变。
为了简化计算,我们需要用一个规则的几何体包裹住图形,通常使用轴对齐包围盒(Axis Aligned Bounding Box),它是一个非旋转的立方体,下图来自:https://developer.mozilla.org/zh-CN/docs/Games/Techniques/3D_collision_detection#axis-aligned_bounding_boxes%EF%BC%88aabb%E5%8C%85%E5%9B%B4%E7%9B%92%EF%BC%89
我们使用如下定义:
interface AABB {center: [number, number, number]; // 中心坐标halfExtents: [number, number, number]; // 长宽高的一半min: [number, number, number]; // 左上角坐标max: [number, number, number]; // 右下角坐标}
在不同情况下,包围盒有不同的含义。我们先看针对单一图形的包围盒代表什么。下图展示了一个半径为 100,边框宽度为 20 的圆,为了更好的说明我们把边框设置成了半透明,同时它还带有阴影效果。
对于用户而言,通常希望使用图形的几何定义,例如这个圆的尺寸就是 100 * 100
,我们不希望鼠标滑过阴影区域也判定拾取到这个圆。
而对于渲染管线而言,这些样式属性显然都需要考虑进去,例如:
我们很容易根据不同类型的图形定义几何包围盒:
前面介绍过基于场景图的层次结构,一旦一个图形拥有了子节点,它在计算包围盒时也应当考虑,例如我们想对它做整体旋转时,需要找到这个包围盒的中心作为旋转中心。因此以下包围盒都是会考虑层次结构的:
在下图中,ul1 拥有两个字节点 li1 和 li2,在计算自身的 Geometry Bounds 时不会考虑它们,而在计算 Bounds 时需要。由于 ul1 还有阴影,因此它的 Render Bounds 要大一圈:
一个图形的锚点(原点)应该如何定义呢?我们可以基于 Geometry Bounds 定义,取值范围 [0, 0] ~ [1, 1]
,其中 [0, 0]
代表 Geometry Bounds 左上角,[1, 1]
代表右下角。而不同图形由于几何定义不同,默认锚点如下:
[0.5, 0.5]
[0, 0]
[0, 0]
,设置此属性也无效有时我们希望改变一个基础图形的原点定义,例如将 Rect 的原点定义为中心而非左上角,示例:
rect.style.anchor = [0.5, 0.5];
那锚点的改变会影响图形在局部/世界坐标系下的坐标吗?答案是不会。我们只是把图形的原点放在这个坐标下而已,无论原点的定义如何修改,这个“位置”坐标始终不会改变:
rect.getPosition(); // [200, 200]rect.style.anchor = [0.5, 0.5];rect.getPosition(); // [200, 200]
对图形进行缩放、旋转变换时,需要指定一个变换中心。例如同样是 scale(2)
,以圆心作为变换中心与圆的 Geometry Bounds 左上角为变换中心,最终得到的效果完全不一样。在 gl-matrix
这样的库中,得到 RTS 变换矩阵通常也需要指定变换中心:
mat4.fromRotationTranslationScaleOrigin();
在某些场景下,用一些字面量或者百分比定义会更方便。例如 CSS 就提供了 transform-origin
属性,它正是相对于 Bounds 进行定义的,下图来自:https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin:
当我们想实现“绕中心点旋转”时,只需要使用字面量或者百分比,这样就能避免进行 Bounds 的获取:
group.style.transformOrigin = 'center';group.style.transformOrigin = 'center center';group.style.transformOrigin = '50% 50%';