如何应用 matrix3d 映射变幻
如何应用 matrix3d 映射变幻
先上 demo
记得是在 2015 看到过的一个 html5 演示效果, 很惊艳
当时没明白如何实现,现在我会了,做一个类似的:
又弄了一个拖动的 demo
我数学真的很差
“你好老师!学这个矩阵具体有什么用?”
老师喝着水貌似想了一会儿回答:“考试用”..
这个问题我真问过老师
在校期间数学课上学到矩阵行列式相关的课程时,曾经问过当时的数学老师,学这个做啥用的,老师犹豫了半天给我的答案是 “考试用”
果然.. 最后我的成绩是不及格
当然不能怪老师,我本来的数学成绩一直也是垫底的
对于我这样的学渣来说,学习不能学以致用就像玩的游戏没有及时正反馈一样的难受
我是真的很难提起兴趣去学
为啥选了前端开发这个坑呢?就是因为代码写了刷新一下浏览器就能看到效果,真实时反馈
直到工作多年以后,学到图形学相关的知识才一知半解
为啥是一知半解呢,因为知道有这个东西,也知道用在哪里,可就是不会用,至少在大部分的前端开发工作上用不到
能在网上找到的相关应用就是 css 中利用 transform 对某个元素进行 旋转,平移,缩放,倾斜等, 这些基础应用都被讲烂了
像这样:
.box {
transform-origin:0 0;
transform: rotate(10deg) translateX(30px) scale(1.1, 1.1) skew(3deg, 3deg);
}
transform 后:
但你应该明白它最终是以矩阵形式表达的,八股文中有没有要背的我不知道,毕竟这些知识很多人已经讲过了。当然如果你这都不知道,可能八股还得再八股一下
通过火狐的开发者工具查看 computed 面板中可以看到其实转换成了对应的 matrix
结果 matrix:
线性变幻 linear transform 包括以下几种
- scale 缩放
- rotate 旋转
- skew 斜切
- translate 平移
用矩阵形式表达如下图示:
这些都可以组合在一起形成 单个 matrix 矩阵实现变幻,
这在之前的我的 "EaselJS 源码分析系列--第二篇" 中有提到过,宽高,旋转,斜切,转换成 matrix 后一次变幻到位
那么它到底是怎么应用 matrix 变幻的呢?
拿上面的例子举例: 实质是对绿色 box 四个坐标 (0,0)、(200,0)、(0,200)、(200,200) 分别点乘了 (Dot product) matrix 矩阵:
matrix(1.07328, 0.247786, -0.13424, 1.0933, 29.5442, 5.20945)
得到新坐标:
(29,5), (244.2002, 54.76665), (2.6962, 223.86945), (217, 273)
怎么点乘?
说到点乘简单复习一下大学数学基础知识(看看就行了,你不需要自己算)
2 · 2
3 · 3
虽然你不用自己算,但 matrix 函数参数得搞明白
注意函数 matrix 参数 a, b, c, d, tx, ty 对应的位置:
matrix(a, b, c, d, tx, ty)
手动计算是不可能的我们可以使用 numeric.js 数学库用于点乘计算:
// 在 numeric.dot 中的位置
numeric.dot([
[a, c, tx],
[b, d ty],
[0, 0, 1]
]
, [0, 0, 1]
)
这是上面四个坐标的计算:
// matrix(1.07328, 0.247786, -0.13424, 1.0933, 29.5442, 5.20945);
// 位置 (0, 0)
const pos1 = numeric.dot([
[1.07328, -0.13424, 29.5442],
[0.247786, 1.0933, 5.20945],
[0, 0, 1]]
, [0, 0, 1])
// 位置 (200 ,0 )
const pos2 = numeric.dot([
[1.07328, -0.13424, 29.5442],
[0.247786, 1.0933, 5.20945],
[0, 0, 1]]
, [200, 0, 1])
// 位置 (0, 200)
const pos3 = numeric.dot([
[1.07328, -0.13424, 29.5442],
[0.247786, 1.0933, 5.20945],
[0, 0, 1]]
, [0, 200, 1])
// 位置 (200, 200)
const pos4 = numeric.dot([
[1.07328, -0.13424, 29.5442],
[0.247786, 1.0933, 5.20945],
[0, 0, 1]]
, [200, 200, 1])
console.log(pos1, pos2, pos3, pos4)
// (29,5), (244.2002, 54.76665), (2.6962, 223.86945), (217, 273)
讲了这么多,虽然看起来很厉害,但这又有什么卵用呢?
再仔细想想,想要仿射变换直接使用 matrix 是符合直觉的
旧坐标 · matrix = 新坐标
所以想要自由就得用到 matrix
“仿射变换”的理论基础
先要进行亿点点线性代数运算
我们现在给四个角(坐标点)对应编号(“旧坐标”):
(x1,y1), (x2,y2) , (x3,y3) , (x4,y4)
目的是将它们映射(变幻)到对应的(“新坐标”):
(u1,v1), (u2,v2) , (u3,v3) , (u4,v4)
即将坐标 (xi, yi) 映射到 (ui, vi)
坐标 (x, y) 被表示为 (kx, ky, k), k 不为 0
在齐次坐标中 (3,2,1) 和 (6,4,2) 都可以表示 (3,2)
于是我们要求得转置矩阵 H 要满足上面等式中所有已知的角 (xi, yi), (ui, vi)
满足 H 的值不是唯一的,举例,给 H 缩放乘以一个常数,结果矩阵依然会映射对应的点(右侧的 ki 也是一样的)
为了简化问题设假定将两边都缩放直到 h8 为 1 (简化问题计算)
然后将乘数乘进去
现在将第3行等式代入前两行把 ki 去掉先
记住我们要解决的是 hi 所以我们应该尝试先把它们分开来
两个等式中 h0..h7 空出缺少的部分用 0 填充 (为何要填充:H 等于 ki 若要等式相等则 H 必须是 h0..h7 完整)
将 h 提出来来,用矩形形式表示就是:
由于我们要表示的是四个坐标点的映射,所以我们可以写成这样:
至此已经可以了,就是一个 Ah=b 的问题(即矩阵中常见的 Ax=b),可以用线性代数库(比如:numeric.js 的 numeric.solve)来求解 h ,解得的 h 对应的 hi 用于 transform 形变矩阵
最后一个小问题,就是 Matrix3d 需要的是 4x4 的矩阵,我们从开始就忽略掉了 z 轴值(由于四个点都在同一个平面,所以 z = 0), 所以把 z 重新映射回矩阵
matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1)
这就是最后用于 css 上的 matrix3d 的矩阵
将其应用到 HTML/Javascript 内
创建一个 id 为 box 的 div 作为变化源
创建一个 id 为 targetBox 的 div 作为目标
创建一个 getPoints 用于获取四个点坐标
... 省略获取 targetBox,box 的代码 function getPoints(element){
const rect = element.getBoundingClientRect();
return [
[rect.left, rect.top],
[rect.left, rect.bottom],
[rect.right, rect.top],
[rect.right, rect.bottom],
];
} const target = getPoints(targetBox)
const origin = getPoints(box)
分别转换成相对坐标点
function getFromPoints(points){
// 映射前四个点相对坐标
const result = [];
const len = points.length;
for (let k = 0; k < len; k++) {
let p = points[k];
result.push({
x: p[0] - points[0][0],
y: p[1] - points[0][1]
});
}
/**
result
[
{x1, y1},
{x2, y2},
{x3, y3},
{x4, y4},
]
*/
return result;
}
function getToPoints(origin, target){
// 映射后四个点相对坐标
const result = [];
for (let k = 0, len = target.length; k < len; k++) {
p = target[k];
result.push({
x: p[0] - origin[0][0],
y: p[1] - origin[0][1]
});
}
return result;
} const from = getFromPoints(origin);
const to = getToPoints(origin, target);
通过 from 与 to 获取 H
function getTransform(from, to) {
var A, H, b, h;
A = []; // 8x8
// 四个点的坐标
for (let i =0 ; i < 4; i++) {
A.push([from[i].x, from[i].y, 1, 0, 0, 0, -from[i].x * to[i].x, -from[i].y * to[i].x]);
A.push([0, 0, 0, from[i].x, from[i].y, 1, -from[i].x * to[i].y, -from[i].y * to[i].y]);
}
b = []; // 8x1
for (let i = 0; i < 4; i++) {
b.push(to[i].x);
b.push(to[i].y);
}
// Solve A * h = b for h
// 即矩阵中常见的 Ax=b
// numeric.solve eg:
// IN> numeric.solve([[1,2],[3,4]],[17,39])
// OUT> [5,6]
// https://ccc-js.github.io/numeric2/documentation.html
h = numeric.solve(A, b); /**
解得: h matrix
[
h0, h1, 0 h2
h3, h4, 0 h5
0, 0, 0, 1
h6, h7, 0 h8
]
*/
H = [
[h[0], h[1], 0, h[2]],
[h[3], h[4], 0, h[5]],
[0, 0, 1, 0],
[h[6], h[7], 0, 1]
];
return H;
}; const H = getTransform(from, to);
生成 css 值应用到 div 上
function getMatrixCSSParameters(H){
// 获取 css matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1) 参数
const result = [];
for (let i =0; i < 4; i++) {
const result1 = [];
for (let j = 0; j < 4; j++) {
result1.push(H[j][i].toFixed(20));
}
result.push(result1);
}
return result.join(',');
} div.style.transform = `matrix3d(${getMatrixCSSParameters(H)})`;
走了一大圈,现在终于可以实现“仿射变换”了
用上面的代码实现 demo
demo 1 matrix3d 动画变化
https://github.com/willian12345/blogpost/blob/main/matrix/free-css-3d-transform/transform3d.html
demo 2 matrix3d 四角拖动变化
https://github.com/willian12345/blogpost/blob/main/matrix/free-css-3d-transform/drag.html
直接使用现成的库
我们碰到的问题大概率编程的前辈们都碰到过了
通过搜索后发现“仿射变换” 在 opencv 中有现成的函数
const H = cv.getPerspectiveTransform(from, to);
然后就 github 上找了一下,果然是有人实现了
然后用它提供的函数实现一遍 demo2
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform-function/matrix
https://angrytools.com/css-generator/transform/
https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
https://www.zweigmedia.com/RealWorld/tutorialsf1/frames3_2.html
https://ccc-js.github.io/numeric2/
https://github.com/fccm/getPerspectiveTransform
注:转载请注明出处博客园:王二狗Sheldon池中物 (willian12345@126.com)
如何应用 matrix3d 映射变幻的更多相关文章
- Hibernatel框架关联映射
Hibernatel框架关联映射 Hibernate程序执行流程: 1.集合映射 需求:网络购物时,用户购买商品,填写地址 每个用户会有不确定的地址数目,或者只有一个或者有很多.这个时候不能把每条地址 ...
- hibernate多对多关联映射
关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...
- Dapper逆天入门~强类型,动态类型,多映射,多返回值,增删改查+存储过程+事物案例演示
Dapper的牛逼就不扯蛋了,答应群友做个入门Demo的,现有园友需要,那么公开分享一下: 完整Demo:http://pan.baidu.com/s/1i3TcEzj 注 意 事 项:http:// ...
- ElasticSearch 5学习(9)——映射和分析(string类型废弃)
在ElasticSearch中,存入文档的内容类似于传统数据每个字段一样,都会有一个指定的属性,为了能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理成字符串值,Elasticsearc ...
- .NET平台开源项目速览(14)最快的对象映射组件Tiny Mapper
好久没有写文章,工作甚忙,但每日还是关注.NET领域的开源项目.五一休息,放松了一下之后,今天就给大家介绍一个轻量级的对象映射工具Tiny Mapper:号称是.NET平台最快的对象映射组件.那就一起 ...
- ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系
ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...
- mybatis_映射查询
一.一对一映射查询: 第一种方式(手动映射):借助resultType属性,定义专门的pojo类作为输出类型,其中该po类中封装了查询结果集中所有的字段.此方法较为简单,企业中使用普遍. <!- ...
- 问题记录:EntityFramework 一对一关系映射
EntityFramework 一对一关系映射有很多种,比如主键作为关联,配置比较简单,示例代码: public class Teacher { public int Id { get; set; } ...
- 内存映射文件MemoryMappedFile使用
参考资料: http://blog.csdn.net/bitfan/article/details/4438458 所谓内存映射文件,其实就是在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特 ...
- MyBatis3:SQL映射
前言 前面学习了config.xml,下面就要进入MyBatis的核心SQL映射了,第一篇文章的时候,student.xml里面是这么写的: <?xml version="1.0&qu ...
随机推荐
- 2019-6-11-C#-标准性能测试
title author date CreateTime categories C# 标准性能测试 lindexi 2019-06-11 08:36:22 +0800 2018-06-18 15:58 ...
- Java equals(),== 和 hashcode()
首先来看看equals() 和 "==" 的关系 1.在Java中==是用来比较两个对象的内存地址是否相同的,如果是基本类型的话将会比较其值. 2.equals()我们如果使用的是 ...
- 日志服务 HarmonyOS NEXT 日志采集最佳实践
背景信息 随着数字化新时代的全面展开以及 5G 与物联网(IoT)技术的迅速普及,操作系统正面临前所未有的变革需求.在这个背景下,华为公司自主研发的鸿蒙操作系统(HarmonyOS)应运而生,旨在满足 ...
- 什么是SQL 语句中相关子查询与非相关子查询
1.什么是SQL子查询 要理解相关子查询和非相关子查询,我们得首先理解什么是子查询,子查询是指在一个查询语句中嵌套的另一个查询语句. 子查询可以嵌套在其他查询语句中,如 SELECT.INSERT.U ...
- 羽夏闲谈——TeeWorlds 中文问题
不久前 削微寒 园友发布了一篇博文 误入 GitHub 游戏区,意外地收获颇丰 ,看到了一个游戏 TeeWorlds .有一说一挺好玩的,下面是那个博客的原图: 官方的下载连接:https:/ ...
- 4G 信令中的 PCO 字段
目录 文章目录 目录 Protocol Configuration Option Protocol Configuration Option PCO(Protocol Configuration Op ...
- 推荐一款模拟浏览器自动化操作神器!Mechanize
大家好,我是狂师! 今天给大家推荐一款用于模拟浏览器行为以进行网页自动化操作Python库:Mechanize. 1.介绍 Mechanize是Python中的一个库,它被设计用来自动化网页浏览和数据 ...
- sass 混合指令 (Mixin Directives)详解
混合指令(Mixin)用于定义可重复使用的样式,避免了使用无语意的 class,比如 .float-left.混合指令可以包含所有的 CSS 规则,绝大部分 Sass 规则,甚至通过参数功能引入变 ...
- 【题解】A18536.星光交错的律动
题目跳转 思路:这道题可能跟博弈论有一点关系,没有学习过博弈论做起来应该问题也不大.思考一个问题,先手必胜的前提是什么? 有关更多的内容可以前往:浅谈有向无环图 先手必胜的前提是,在任何一种局面下,先 ...
- 面试题--mysql的数据库优化
mysql的数据库优化 当有人问你如何对数据库进行优化时,很多人第一反应想到的就是 SQL 优化,如何创建索引,如何改写 SQL,他们把数据库优化与 SQL 优化划上了等号. 当然这不能算是完全错误的 ...