<?xml version="1.0" encoding="utf-8"?>
<search> 
  
  
    
    <entry>
      <title>Rust 学习资源</title>
      <link href="/2022/01/18/rust-study-resource/"/>
      <url>/2022/01/18/rust-study-resource/</url>
      
        <content type="html"><![CDATA[<p>博主发现咸鱼咸鱼着居然到 2022 年了，忽然良心不安，因此先从这篇比较水的资源总结开始吧！</p><p>这篇文章主要是总结下学 Rust 参考过的资料，会随着博主对 Rust 的关注随缘更新。</p><span id="more"></span><ul><li>更新日志<br>2023/03/02 新增 <a href="https://gist.github.com/noxasaxon/7bf5ebf930e281529161e51cd221cf8a">noxasaxon/learning_rust.md</a>。<br>2023/01/06 新增 mouse 姐姐新书<a href="https://marabos.nl/atomics/">《Rust Atomics and Locks: Low-Level Concurrency in Practice》</a> 和STATE MACHINES。<br>2022/12/28 新增 <a href="https://misterdanb.github.io/raytracinginrust/">Ray Tracing in One Week</a><br>2022/12/27 新增 <a href="https://skyzh.github.io/mini-lsm/">LSM in a week</a><br>2022/12/25 新增 <a href="https://tfpk.github.io/macrokata">tfpk/macrokata</a><br>2022/12/22 新增 <a href="https://google.github.io/comprehensive-rust/welcome.html">Comprehensive Rust</a><br>2022/12/06 新增 <a href="https://github.com/night-cruise/async-rust">night-cruise/async-rust</a><br>2022/11/28 新增 <a href="https://smallcultfollowing.com/babysteps/">《dyn async traits》系列</a> <a href="https://zjp-cn.github.io/translation/dyn-async-traits.html">中文版</a><br>2022/11/28 新增 <a href="https://rust-hosted-langs.github.io/book/introduction.html">Writing Interpreters in Rust: a Guide</a> <a href="https://www.bitfalter.com/webassembly-compiler-text-format-and-ast">WebAssembly Compiler</a><br>2022/11/15 新增 <a href="https://pku-minic.github.io/online-doc/#/">北京大学编译原理课程在线文档</a>, <a href="https://github.com/jondot/rust-how-do-i-start">jondot/rust-how-do-i-start</a><br>2022/11/13 新增 <a href="https://www.jendrikillner.com/tags/rust/page/2/">Rust Game Series</a><br>2022/10/17 新增 <a href="https://github.com/smallnest/concurrency-programming-via-rust">smallnest/concurrency-programming-via-rust 《Rust 并发编程实战课》</a><br>2022/09/26 新增 <a href="https://jinleili.github.io/learn-wgpu-zh/">Learn wgpu 中文版</a>, <a href="https://kylemayes.github.io/vulkanalia/introduction.html">Vulkan Tutorial(Rust)</a><br>2022/08/24 新增 <a href="https://github.com/awesome-kusion/rust-code-book">Rust 源码剖析 中文版</a><br>2022/08/24 新增 <a href="https://github.com/Boshen/javascript-parser-in-rust">Boshen/javascript-parser-in-rust</a> <a href="https://litchipi.github.io/series/container_in_rust">Writing a container in Rust</a><br>2022/07/05 新增 <a href="https://www.youtube.com/watch?v=rDoqT-a6UFg">Visualizing memory layout of Rust’s data types</a> <a href="https://learningos.github.io/rust-based-os-comp2022/">2022年开源操作系统训练营</a></li></ul><h2 id="基础">基础</h2><ul><li><a href="http://rust-lang.github.io/book/">The Rust Programming Language</a><ul><li>堪称 Rust 的 “The Book”，是目前最权威的 Rust 系统教程，入门必读，最近也更新到了 2021 版本。</li><li><a href="https://kaisery.github.io/trpl-zh-cn/title-page.html">中文版(经常更新)</a> <a href="https://rustwiki.org/zh-CN/book/">rustwiki 中文版</a></li></ul></li><li><a href="https://rustwiki.org/zh-CN/rust-by-example/">Rust by Example</a><ul><li>实例化的讲解方法，通过一个个可实际运行的例子去介绍 Rust 的特性和用法，有的时候，代码是最好的老师。</li><li><a href="https://rustwiki.org/zh-CN/rust-by-example/">中文版</a></li></ul></li><li><a href="https://github.com/rustcc/RustPrimer">The Rust primer for beginners</a><ul><li>给初学者的 Rust 中文教程。</li></ul></li><li><a href="https://rust-book.junmajinlong.com/about.html">Rust入门秘籍</a><ul><li>这是一本 Rust 的入门书籍，相比官方书籍《The Rust Programming Language》，本书要更详细、更具系统性，本书也尽量追求准确性。</li></ul></li><li><a href="https://docs.microsoft.com/en-gb/learn/paths/rust-first-steps/">Rust First Steps</a><ul><li>微软的 Rust 教程，简短精炼，适合初学者。</li><li><a href="https://docs.microsoft.com/zh-cn/learn/paths/rust-first-steps/">官方中文</a></li></ul></li><li><a href="https://rust-lang-nursery.github.io/rust-cookbook/">Rust Cookbook</a><ul><li>Rust 程序设计语言（<a href="https://rustwiki.org/zh-CN/book">Rust 官方教程简体中文版</a>）的简要实例示例集合：展示了在 Rust 生态系统中，使用各类 crate 来完成常见编程任务的良好实践。</li><li><a href="https://rustwiki.org/zh-CN/rust-cookbook/">中文版</a></li></ul></li><li><a href="https://github.com/rust-lang/rustlings">Rustlings</a><ul><li>官方出品，涵盖大量小练习，打怪通关学习 Rust。</li><li>Jetbrains IDE 可以直接<a href="https://plugins.jetbrains.com/plugin/16631-rustlings">下载课程</a>，编辑器内写代码做练习。</li></ul></li><li><a href="https://rust-unofficial.github.io/too-many-lists/index.html">Learning Rust With Entirely Too Many Linked Lists</a><ul><li>通过写双链表来学习 Rust</li></ul></li><li><a href="https://readrust.net/getting-started">Read Rust - Getting Started</a><ul><li><a href="https://readrust.net/">Read Rust</a> 是一个集合了有价值的 Rust 文章/博客的网站，其中 <a href="https://readrust.net/getting-started">Getting Started</a> 部分有各种 Rust 知识点相关的十分优秀的文章。</li></ul></li><li><a href="https://reberhardt.com/cs110l/spring-2020/handouts/course-information/">Stanford CS 110L：Safety in Systems Programming</a><ul><li>This class is focused on safety and robustness in systems programming. We will use the Rust programming language as a vehicle to teach mental models and paradigms that have been shown to be helpful in preventing errors, and we will look at how these features have made their way back into C++.</li><li><a href="https://www.bilibili.com/video/BV1Ra411A7kN">2020 年课程的 B 站中文字幕版</a></li><li><a href="https://reberhardt.com/cs110l/spring-2021/">2021 年课程主页</a>、<a href="https://web.stanford.edu/class/cs110l/">2022 年课程主页</a></li></ul></li><li>Rust 语言圣经(Rust 教程 Rust Course)<ul><li><a href="https://github.com/sunface/rust-course">rust-course</a> 国人写的 Rust 教程，对Rust语言进行全面且深入的讲解，书中辅以生动的示例和习题。</li></ul></li><li><a href="https://rust.purewhite.io/">Rust 官方文档中文教程</a><ul><li><a href="https://github.com/rust-lang-cn">rust-lang-cn 组织</a>翻译的官方文档，另外这个组织也翻译了很多 Rust 相关的书籍。</li></ul></li><li><a href="https://www.youtube.com/watch?v=rDoqT-a6UFg">Visualizing memory layout of Rust’s data types</a><ul><li>可视化了 Rust 的类型在内存中的布局，入门必看。</li></ul></li><li><a href="https://rust-guide.budshome.com/">Rust 实践指南</a><ul><li><a href="https://github.com/zzy/rust-guide">zzy/rust-guide</a> 《Rust 实践指南》，聚焦重要的主题，展示可能的解决方案。以开发中的实际问题为导向，以优雅的解决方案为目标，以完整的实例实践解决方案。</li></ul></li><li><a href="https://google.github.io/comprehensive-rust/welcome.html">Comprehensive Rust</a><ul><li>Google Android 团队的四天 Rust 教程。</li></ul></li><li><a href="https://space.bilibili.com/361469957/video">Bilibili：软件工艺师</a><ul><li>微软 MVP，做了不少 C#、Go、Rust 的教程，其中 Rust 相关的有 <a href="https://www.bilibili.com/video/BV1hp4y1k7SV">Rust编程语言入门教程</a> 和 <a href="https://www.bilibili.com/video/BV1RP4y1G7KF">Rust Web 全栈开发教程</a></li></ul></li><li><a href="https://cheats.rs/">Rust Language Cheat Sheet</a></li><li><a href="https://quickref.me/rust">quickref.me Rust cheatsheet</a><ul><li><a href="http://quickref.me">quickref.me</a> 是一个汇聚了大部分语言的语法索引页, 其中也包含了 Rust, 可以帮助大家快速找到想用的语法。</li></ul></li><li><a href="https://github.com/rust-lang/api-guidelines">rust-lang/api-guidelines</a><ul><li><a href="https://zjp-cn.github.io/api-guidelines/about.html">中文版：Rust API 编写指南</a> 这是一组关于如何设计和呈现 Rust APIs 的建议。 这些建议主要由 Rust library 团队编写， 总结了 Rust 生态下构建标准库和其他 crates 的经验。</li></ul></li><li><a href="https://blog.fudenglong.site/Programming-Rust/">《Programming Rust, 2nd Edition》简单的翻译</a><ul><li>第一版图灵社区有翻译：<a href="https://www.ituring.com.cn/book/2101">Rust程序设计</a>，第二版多了两章，可以考虑买第一版的电子版 pdf。</li></ul></li><li><a href="https://github.com/rustlang-cn/Rustt">rustlang-cn/Rustt</a><ul><li>RustCn 翻译计划，翻译一些 Rust 的技术文章。</li></ul></li><li><a href="https://github.com/suhanyujie/article-transfer-rs/">suhanyujie/article-transfer-rs</a><ul><li>一些 Rust/Go 文章翻译</li></ul></li></ul><h2 id="进阶">进阶</h2><ul><li><a href="https://doc.rust-lang.org/std/index.html">Rust Standard Library Reference</a></li><li><a href="https://doc.rust-lang.org/reference/index.html">The Rust Reference</a><ul><li>Rust 语言的 reference manual，你应该收藏好，以便于在对某个语言细节不清楚时在这里进行查阅。</li><li><a href="https://rustwiki.org/zh-CN/reference/">中文版</a></li></ul></li><li><a href="https://doc.rust-lang.org/nightly/unstable-book/index.html">The unsafe Book</a></li><li><a href="https://doc.rust-lang.org/nomicon/">The Rustonomicon</a><ul><li>Rust 死灵书主要讲 Rust 高级特性，如何使用 unsafe Rust。</li><li><a href="https://nomicon.purewhite.io/">中文版</a></li></ul></li><li><a href="https://veykril.github.io/tlborm/introduction.html">The Little Book of Rust Macros</a><ul><li>对于 Rust 宏有详细的讲解，里面的注释很全面。</li><li><a href="https://zjp-cn.github.io/tlborm/">中文版</a></li></ul></li><li><a href="https://github.com/night-cruise/async-rust">night-cruise/async-rust</a><ul><li>介绍 Rust 中 async/await 语法和异步运行时的原理和工作机制的电子书</li></ul></li><li><a href="https://smallcultfollowing.com/babysteps/">《dyn async traits》系列</a><ul><li>Niko 是 Rust 语言诸多特性的设计者（比如 NLL）。这个系列主要探索在 trait 中支持 async fn，因此主要聚焦于思路梳理与原型设计。</li><li><a href="https://zjp-cn.github.io/translation/dyn-async-traits.html">中文版</a></li></ul></li><li><a href="https://github.com/smallnest/concurrency-programming-via-rust">smallnest/concurrency-programming-via-rust 《Rust 并发编程实战课》</a><ul><li>《Go 并发编程实战课》的作者鸟窝系统整理的 Rust 的并发编程的相关资料。主要是从入门入手，让大家了解和熟悉这些并发原语，在工作中用起来。</li></ul></li><li><a href="https://marabos.nl/atomics/">《Rust Atomics and Locks: Low-Level Concurrency in Practice》</a><ul><li>mouse 姐姐出版的关于 Rust 并发的书，可以在她博客免费阅读，亚马逊也可以购买：<a href="https://www.amazon.com/Rust-Atomics-Locks-Low-Level-Concurrency/dp/1098119444">Amazon</a>。</li></ul></li><li><a href="https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html">Asynchronous Programming in Rust</a><ul><li><a href="https://github.com/bestgopher/rust-async-book">不是很新的中文版</a></li></ul></li><li><a href="https://locka99.gitbooks.io/a-guide-to-porting-c-to-rust/content/">A Guide to Porting C/C++ to Rust</a></li><li><a href="https://jakegoulding.com/rust-ffi-omnibus">The Rust FFI Omnibus</a><ul><li>使用 Rust 编写代码用到其他语言的示例集合.</li><li><a href="http://llever.com/rust-ffi-omnibus/">中文版</a></li></ul></li><li><a href="https://www.youtube.com/channel/UC_iD0xppBwwsrM9DegC5cQQ">Jon Gjengset YouTube Channel</a> (<a href="https://www.youtube.com/watch?v=rAl-9HwD858&amp;list=PLqbS7AVVErFiWDOAVrPt7aYmnuuOLYvOa">Crust of Rust Playlist</a>)</li><li><a href="https://rust-unofficial.github.io/patterns/">Rust Design Patterns</a><ul><li>有许多问题具有共同的形式。由于事实上 Rust 并不完全是面向对象的，设计模式也与其他面向对象的编程语言不同。 细节不同的同时，因为他们有相同的形式，他们可以用同样的基本方法解决。</li><li><a href="http://chuxiuhong.com/chuxiuhong-rust-patterns-zh/">中文版</a></li></ul></li><li><a href="https://nnethercote.github.io/perf-book/introduction.html">The Rust Performance Book</a><ul><li>介绍很多优化 Rust 程序性能的工具、技巧、调试方法等方面的书。</li></ul></li><li><a href="https://github.com/QMHTMY/RustBook">Problem-solving with algorithms and data structures using Rust</a><ul><li>国人写的一本 Rust 书籍，包括算法分析，基本数据结构和算法，外加一些实战。</li></ul></li><li><a href="https://github.com/awesome-kusion/rust-code-book">Rust 源码剖析 中文版</a><ul><li>国人写的一本 Rust 书籍，针对 Rust 语言本身和开源库的代码进行分析。</li></ul></li><li><a href="https://github.com/dtolnay/case-studies">dtolnay/case-studies</a><ul><li>dtolnay 是 anyhow, thiserror, cxx 等库的作者，这是他对一些 tricky Rust code 的分析。</li></ul></li><li><a href="https://space.bilibili.com/275673537">Bilibili：Databend</a><ul><li>Databend 社区持续做了不少 Rust 的公开课。<a href="https://github.com/wubx/rust-in-databend/">仓库地址</a></li></ul></li><li><a href="https://space.bilibili.com/500416539/">Bilibili：爆米花胡了</a><ul><li>这个 up 主做了很多 Rust 过程宏的视频教程。</li></ul></li><li><a href="https://space.bilibili.com/39222989">Bilibili：喜欢历史的程序君</a><ul><li>陈天在极客时间开了门 Rust 的课，同时也在持续输出一些 Rust 视频教程。</li></ul></li><li><a href="https://www.youtube.com/playlist?list=PL5aMzERQ_OZ9j40DJNlsem2qAGoFbfwb4">KAIST CS431: Concurrent Programming</a>  <a href="https://github.com/kaist-cp/cs431">Github repo</a><ul><li>本课程面向对并行计算机系统的现代理论和实践感兴趣的计算机科学（或相关学科）的高年级本科生（或研究生）。</li></ul></li><li><a href="https://www.chiark.greenend.org.uk/~ianmdlvl/rust-polyglot/index.html">Rust for the Polyglot Programmer</a><ul><li>面对有经验的程序员的 Rust 指南。</li></ul></li><li><a href="https://highassurance.rs/landing.html">High Assurance Rust: Developing Secure and Robust Software</a><ul><li>本书介绍了如何构建我们可以合理信任(justifiably trust)的高性能软件。这意味着有足够的数据来支持对我们代码的功能和安全性的信心。可信性是高安全性(high assurance)软件的一个标志。</li></ul></li><li><a href="https://github.com/Warrenren/inside-rust-std-library">Warrenren/inside-rust-std-library</a><ul><li>本书主要对 Rust 的标准库代码进行分析。按照内存相关，基本数据类型，ops Trait, Option 类型，Result 类型，Iterator，切片类型，智能指针类型等逐一进行源码分析。</li></ul></li></ul><h2 id="有潜力的教程">有潜力的教程</h2><ul><li><a href="https://www.youtube.com/watch?v=xOgdaL6A_AY&amp;list=PL-zQguBgjr2O4d-B9g_b0WYh1fyXr6MMq">Rust 101 Lecture Series</a><ul><li>与伦敦帝国理工学院计算社会系合作的 Rust 系列讲座</li><li><a href="https://www.reddit.com/r/rust/comments/smt5ef/rust_lecture_series_with_imperial_college_londons/">Rust Lecture Series with Imperial College London’s Department of Computing Society</a></li></ul></li><li><a href="https://lurklurk.org/effective-rust/intro.html">Effective Rust</a></li></ul><h2 id="练习实战的小项目">练习实战的小项目</h2><ul><li><a href="https://www.zhihu.com/question/34665842">知乎-学习Rust适合写什么练手项目？</a></li><li><a href="https://exercism.org/tracks/rust/exercises">Exercism.io</a></li><li><a href="https://github.com/course-rs/tokio-course">course-rs/tokio-course</a><ul><li>《Tokio 异步编程》翻译并扩展了 tokio 官网的教程， 深入讲述了如何编写 Rust 高并发异步程序</li></ul></li><li><a href="https://github.com/cfsamson?tab=repositories&amp;q=&amp;type=source&amp;language=&amp;sort=stargazers">Github: cfsamson</a><ul><li>这哥们喜欢用 Rust 实现一些小例子如：Futures、greenthreads、async、epoll 等。</li></ul></li><li>STATE MACHINES <a href="https://blog.yoshuawuyts.com/state-machines/">Part1</a> <a href="https://blog.yoshuawuyts.com/state-machines-2/">Part2</a> <a href="https://blog.yoshuawuyts.com/state-machines-3/">Part3</a> [2022.12]<ul><li>用 Rust 实现状态机</li></ul></li><li><a href="https://skyzh.github.io/mini-lsm/">LSM in a week</a> [2022.12]<ul><li>Build a simple LSM-Tree storage engine in Rust. <a href="https://github.com/skyzh/mini-lsm">Github</a></li></ul></li><li><a href="https://tfpk.github.io/macrokata">tfpk/macrokata</a> [2022.12]<ul><li>MacroKata, a set of exercises which you can use to learn how to write macros in Rust. <a href="https://github.com/tfpk/macrokata">Github</a></li></ul></li><li><a href="https://rust-hosted-langs.github.io/book/introduction.html">Writing Interpreters in Rust: a Guide</a> [2022.11]<ul><li>用 Rust 写解释器，<a href="https://github.com/rust-hosted-langs/book">仓库</a></li></ul></li><li><a href="https://github.com/Boshen/javascript-parser-in-rust">Boshen/javascript-parser-in-rust</a> [2022.08]<ul><li>A book on writing a JavaScript Parser in Rust</li></ul></li><li><a href="https://learningos.github.io/rust-based-os-comp2022/">2022年开源操作系统训练营</a> [2022.07]<ul><li>教程共分为八章，主要展示如何从零开始，用 Rust 语言写一个基于 RISC-V 架构的类 Unix 内核。</li></ul></li><li><a href="https://pku-minic.github.io/online-doc/#/">北京大学编译原理课程在线文档</a> [2022.06]<ul><li>作者 MaxXSoft 是本科编译原理课程的助教。为了让本科生更好地理解编译器工作的原理，在参考多个其他教程之后，他设计了一套全新的，教你从零开始写个编译器的教程。</li><li><a href="https://github.com/pku-minic/kira-rs">Rust 实现</a> <a href="https://zhuanlan.zhihu.com/p/471250907">知乎-课程介绍</a></li></ul></li><li><a href="https://litchipi.github.io/series/container_in_rust">Writing a container in Rust</a> [2022.05]<ul><li>用 Rust 写容器。</li></ul></li><li><a href="https://vishpat.github.io/lisp-rs/">Lisp interpreter in Rust</a> [2022.05]<ul><li><a href="https://github.com/vishpat/lisp-rs">lisp-rs</a> 项目用 Rust 实现了一个解释器，用于 Scheme 的一个小子集，即 Lisp 方言。</li></ul></li><li><a href="https://memo.barrucadu.co.uk/dns-cache.html">Implementing a size-bounded LRU cache with expiring entries for my DNS server (in Rust)</a> [2022.03]<ul><li>使用 Rust 实现一个有大小限制可过期的 LRU 缓存。</li></ul></li><li><a href="https://www.youtube.com/watch?v=doFowk4xj7Q">Implementing and Optimizing a Wordle Solver in Rust</a> [2022.03]<ul><li>Jon Gjengset 的六小时一镜到底视频流教程，这次是实现一个 Wordle 求解器。</li></ul></li><li><a href="https://www.youtube.com/playlist?list=PLkpGh2gaaueyzEAn07jf44LdscDeWRyzy">Writing a Programming Language (in Rust)</a> [2022.02 updating]</li><li><a href="https://fosdem.org/2022/schedule/event/misc_ntfs_rust/">Implementing the NTFS filesystem in Rust</a> [2022.02]</li><li><a href="https://github.com/dtolnay/proc-macro-workshop">Rust Latam: procedural macros workshop</a> [2022.01 updating]<ul><li>实战学习写 Rust 过程宏。</li></ul></li><li><a href="https://www.ihcblog.com/rust-runtime-design-1/">Rust Runtime 设计与实现</a> [2021.12]<ul><li>系列文章主要介绍如何设计和实现一个基于 io-uring 的 Thread-per-core 模型的 Runtime。</li></ul></li><li><a href="https://www.youtube.com/watch?v=NtUkr_z7l84">Building a GUI app in Rust</a>  <a href="https://www.youtube.com/watch?v=NtUkr_z7l84">Building a web app in Rust</a>  [2021.10]<ul><li>作者用 egui 库去实现了 newsapi 的客户端和网页端（WebAssembly）。</li></ul></li><li><a href="https://frank-king.github.io/rustblog-zh/2021-01-proc-macro/00.html">Rust过程宏入门</a></li><li><a href="https://stopachka.essay.dev/post/5/risp-in-rust-lisp">(Risp (in (Rust) (Lisp)))</a> [2021.07]<ul><li>Rust 实现 Lisp 解释器</li></ul></li><li><a href="https://www.bitfalter.com/webassembly-compiler-text-format-and-ast">WebAssembly Compiler</a> [2021.05]<ul><li>Rust 实现 WebAssembly Parser, Compiler and Runtime.</li></ul></li><li><a href="https://pwy.io/en/posts/learning-to-fly-pt1/">Learning to Fly: Let’s simulate evolution in Rust! (pt 1)</a> [2021.01]<ul><li>利用神经网络和遗传算法创建一个进化模拟，并编译应用程序到 WebAssembly</li></ul></li><li><a href="https://misterdanb.github.io/raytracinginrust/">Ray Tracing in One Week</a> [2020.12]<ul><li>Ray Tracing in One Week 系列的 Rust 版本 <a href="https://github.com/misterdanb/raytracinginrust">Github</a></li></ul></li><li><a href="https://www.jendrikillner.com/tags/rust/page/2/">Rust Game Series</a> [2020.11]<ul><li>用 Rust 和 winapi 来用 D3D11 写三消游戏</li></ul></li><li><a href="https://www.youtube.com/watch?v=rHRBJKWbbw0">Building a Pixel Editor in Rust &amp; WebAssembly (and Javascript)</a> [2020.08]<ul><li>作者用 Rust 和 WebAssembly 做了个网页端的简陋版像素画板。</li></ul></li><li><a href="https://bugzmanov.github.io/nes_ebook/chapter_1.html">Writing NES Emulator in Rust</a> [2020.08]<ul><li>Rust 实现 NES 模拟器，不过最后一章到现在还是 todo。</li></ul></li><li><a href="https://github.com/EmilHernvall/dnsguide/blob/master/README.md">Building a DNS server in Rust</a> [2020.06]</li><li><a href="https://github.com/pingcap/talent-plan">pingCAP/talent-plan</a> [2020.05] Rust 网络编程<ul><li><a href="https://github.com/pingcap/talent-plan/blob/master/courses/rust/README.md">TP 201: Practical Networked Applications in Rust</a></li><li><a href="https://github.com/pingcap/talent-plan/blob/master/courses/dss/README.md">TP 202: Distributed Systems in Rust</a></li></ul></li><li><a href="https://os.phil-opp.com/">Writing an OS in Rust</a> <a href="https://os.phil-opp.com/zh-CN/">部分中文版</a> [2020.05]</li><li><a href="https://picklenerd.github.io/pngme_book/">PNGme: An Intermediate Rust Project</a> [2019.06]</li><li><a href="https://www.youtube.com/playlist?list=PLqbS7AVVErFivDY3iKAQk3_VAm8SXwt1X">Implementing TCP</a> [2019.05]<ul><li>强烈推荐！Jon Gjengset 通过 Linux TUN/TAP 来实现 TCP 协议。三个视频加起来共 16 小时。</li><li>这个 up 主视频风格独特，内容有深度，录像不剪辑，每集时间巨长，好处就是可以了解一个完整项目的开发过程和解决问题的思路。</li></ul></li><li><a href="https://bodil.lol/parser-combinators/">Learning Parser Combinators With Rust</a> [2019.04]</li><li><a href="https://www.joshmcguigan.com/blog/build-your-own-shell-rust/">Build Your Own Shell using Rust</a> [2018.11]</li><li><a href="https://blog.subnetzero.io/post/building-language-vm-part-00/">So You Want to Build a Language VM</a> [2018.07]</li></ul><h2 id="游戏开发相关">游戏开发相关</h2><ul><li><a href="https://www.zhihu.com/question/511998329/answer/2314160111">有哪些值得推荐的Rust游戏引擎或图形渲染库？</a></li><li><a href="https://gamedev.rs/">Rust GameDev WG</a></li><li><a href="https://kylemayes.github.io/vulkanalia/introduction.html">Vulkan Tutorial(Rust)</a><ul><li>这老哥给自己的 Vulkan Rust 绑定 <a href="https://github.com/KyleMayes/vulkanalia">vulkanalia</a> 参考 <a href="https://vulkan-tutorial.com/">Vulkan Tutorial</a> 写的教程。</li><li>我们也可以用 <a href="https://github.com/ash-rs/ash">ash</a> 来参考着写，两个 Vulkan binding crate 语法很像。</li></ul></li><li><a href="https://github.com/jakobwesthoff/the_ray_tracer_challenge_in_rust">The Ray Tracer Challenge</a> [2022.02 updating]<ul><li>这老哥用 Rust 从零写一个 Raytracer，并把 live coding 的过程也录制上传在<a href="https://www.youtube.com/playlist?list=PLy68GuC77sUTyOUvDhVboQoOlHoa4XrSO">系列视频链接</a></li></ul></li><li><a href="https://nikitablack.github.io/post/vulkan_with_rust_by_example_0_introduction/">Vulkan with Rust by example</a><ul><li>又是用 Rust 和 <a href="https://github.com/ash-rs/ash">ash</a> crate 来写 Vulkan 的一系列博文。</li></ul></li><li><a href="https://hoj-senna.github.io/ashen-aetna/">Ashen Aetna</a> [2022.01 updating]<ul><li>作者兴趣使然用 Rust 和 <a href="https://github.com/ash-rs/ash">ash</a> crate 来学习图形学的教程。</li><li><a href="https://github.com/MaikKlein/ash">ash</a> 是跨平台图形接口 Vulkan 的 Rust 绑定。</li></ul></li><li><a href="https://bevy-cheatbook.github.io/">Unofficial Bevy Cheat Book</a><ul><li>Rust 游戏引擎 Bevy 的书。</li><li>中文版：<a href="https://yiviv.com/bevy-cheatbook/introduction.html">Bevy 游戏引擎开发指南</a></li></ul></li><li><a href="https://sotrh.github.io/learn-wgpu/">Learn Wgpu</a><ul><li><a href="https://jinleili.github.io/learn-wgpu-zh/">中文版</a></li><li><a href="https://github.com/gfx-rs/wgpu">Wgpu</a> 是 <a href="https://gpuweb.github.io/gpuweb/">WebGPU API</a> 规范的一个 Rust 实现。</li><li>WebGPU 是由 GPU for the Web Community Group 发布的一个规范。它的目的是允许网络代码以安全和可靠的方式访问 GPU 功能。</li><li>它通过模仿 Vulkan API，并将其转换为主机硬件使用的任何 API（DirectX、Metal、Vulkan）来实现。</li><li>很多 Rust 游戏引擎都基于这一层图形 HAL。</li></ul></li><li><a href="https://ianjk.com/game-engine-in-rust/">Tutorial: Writing a Tiny Rust Game Engine for Web</a> [2022.01]</li><li><a href="https://tomassedovic.github.io/roguelike-tutorial/">Roguelike Tutorial in Rust + tcod</a> [2020.04]</li><li><a href="https://a5huynh.github.io/posts/2018/adventures-in-rust/">Adventures in Rust: A Basic 2D Game</a> [2018.02]</li></ul><h2 id="其他领域相关">其他领域相关</h2><ul><li><a href="https://rust-cli.github.io/book/index.html">The CLI Book</a></li><li><a href="https://rustwasm.github.io/docs/book/">The WebAssembly Book</a></li><li><a href="https://doc.rust-lang.org/stable/embedded-book/">The Embedded Book</a></li><li><a href="https://cs140e.sergio.bz/">An Experimental Course on Operating Systems</a></li><li><a href="https://www.lpalmieri.com/posts/2020-05-24-zero-to-production-0-foreword/">Zero to Production in Rust (Building Backend Services)</a></li></ul><h2 id="Rust-动态">Rust 动态</h2><ul><li><a href="https://this-week-in-rust.org/">This week in Rust Newsletter</a><ul><li>每周更新一次，把最新的 Rust 资源推到你的邮箱，这是跟踪 Rust 最新技术与事件的好方法。</li></ul></li><li>Discord<ul><li><a href="https://discord.gg/rust-lang-community">Official Rust Community Server</a></li><li><a href="https://discord.gg/rust-lang">Official Rust Server</a></li></ul></li><li>Telegram<ul><li><a href="https://t.me/rust_zh">Rust 众</a></li></ul></li><li>飞书<ul><li><a href="https://github.com/ZhangHanDong/rust-code-reading-club/issues/1">Rust 中文社群</a></li></ul></li><li><a href="https://www.reddit.com/r/rust/">The Rust Sub Reddit</a></li><li><a href="https://rustmagazine.github.io/rust_magazine_2021/index.html">Rust语言开源杂志（2021）月刊</a></li><li><a href="https://rustmagazine.github.io/rust_magazine_2022/index.html">Rust语言开源杂志（2022）季刊</a></li></ul><h2 id="各种汇总（Awesome-系列）">各种汇总（Awesome 系列）</h2><ul><li><a href="https://github.com/rust-unofficial/awesome-rust">Awesome Rust [A curated list of Rust code and resources]</a><ul><li>针对 Rust 语言的 awesome lists，这里面汇集了各种各样的 Rust 库和资源，去参与或学习开源项目是当你入门后最好的进阶方法。</li></ul></li><li><a href="https://github.com/ctjhoa/rust-learning">rust-learning</a><ul><li>一个由社区维护的关于学习 Rust 的资源的汇总。</li></ul></li><li><a href="https://github.com/EthanYuan/Rust-Study-Resource">EthanYuan/Rust-Study-Resource</a><ul><li>又是一个关于学习 Rust 的资源的汇总。</li></ul></li><li><a href="https://lborb.github.io/book/title-page.html">The Little Book of Rust Books</a><ul><li>Rust 相关书籍的汇总。</li></ul></li><li><a href="https://github.com/sger/RustBooks">sger/RustBooks</a><ul><li>Rust 相关书籍的汇总。</li></ul></li><li><a href="https://github.com/sunface/fancy-rust">sunface/fancy-rust</a><ul><li>Rust酷库推荐。使用我们精心挑选的开源代码，让你的Rust项目Fancy起来!</li></ul></li><li><a href="https://github.com/EvanLi/Github-Ranking/blob/master/Top100/Rust.md">EvanLi/Github-Ranking</a><ul><li>Github 中 Rust 库星星排名的 Top 100，每日刷新。</li></ul></li><li><a href="https://apollolabsblog.hashnode.dev/35-rust-learning-resources-every-beginner-should-know-in-2022">35 Rust Learning Resources Every Beginner Should Know in 2022</a><ul><li>一篇推荐新手资源的文章</li></ul></li></ul><h2 id="Podcast">Podcast</h2><ul><li><a href="https://rustacean-station.org/">Rustacean Station Podcast</a></li><li><a href="https://rusttalk.github.io/podcast/000/">RustTalk</a><ul><li>主播：写代码的西瓜</li><li><a href="https://rustcc.cn/">Rust 语言中文社区</a> 是一个相比干货分享的地方，偏文字，RustTalk 更侧重“湿货”，不仅仅会介绍到 Rust 的设计理念，更多的回去挖掘 Rust 背后的奇人轶事。</li></ul></li></ul><h2 id="博客">博客</h2><ul><li><a href="https://llever.com/">https://llever.com/</a><ul><li>包含很多 Rust 周报及相关博文的翻译，不过现在好像不更新了。</li></ul></li><li><a href="https://blog.budshome.com/">芽之家</a><ul><li>同样是包含很多 Rust 周报及相关博文的翻译，同样好像不更新了😓</li></ul></li></ul><h2 id="博客-RSS">博客 RSS</h2><table><thead><tr><th>名称</th><th>订阅链接</th></tr></thead><tbody><tr><td><a href="https://this-week-in-rust.org/">This Week in Rust</a></td><td><a href="https://this-week-in-rust.org/atom.xml">https://this-week-in-rust.org/atom.xml</a></td></tr><tr><td><a href="https://readrust.net/">Read Rust</a></td><td><a href="https://readrust.net/all/feed.rss">https://readrust.net/all/feed.rss</a></td></tr><tr><td><a href="https://www.reddit.com/r/rust/hot/">Rust Reddit Hot</a></td><td><a href="https://reddit.0qz.fun/r/rust/hot.json">https://reddit.0qz.fun/r/rust/hot.json</a></td></tr><tr><td><a href="https://rust.cc">Rust.cc</a></td><td><a href="https://rustcc.cn/rss">https://rustcc.cn/rss</a></td></tr><tr><td><a href="https://rust.libhunt.com/newsletter">Awesome Rust Weekly</a></td><td><a href="https://rust.libhunt.com/newsletter/feed">https://rust.libhunt.com/newsletter/feed</a></td></tr><tr><td><a href="https://rustmagazine.github.io/">Rust精选</a></td><td><a href="https://rustmagazine.github.io/rust_magazine_2021/rss.xml">https://rustmagazine.github.io/rust_magazine_2021/rss.xml</a></td></tr><tr><td><a href="https://medium.com/tag/rust/latest">Rust on Medium</a></td><td><a href="https://medium.com/feed/tag/rust">https://medium.com/feed/tag/rust</a></td></tr><tr><td><a href="https://gamedev.rs/">Rust GameDev WG</a></td><td><a href="https://gamedev.rs/rss.xml">https://gamedev.rs/rss.xml</a></td></tr><tr><td><a href="https://www.zhihu.com/column/time-and-spirit-hut">知乎专栏-时光与精神小屋</a></td><td><a href="https://rsshub.app/zhihu/zhuanlan/time-and-spirit-hut">https://rsshub.app/zhihu/zhuanlan/time-and-spirit-hut</a></td></tr><tr><td><a href="https://fasterthanli.me/">酷熊 Amos fasterthanli</a></td><td><a href="https://fasterthanli.me/index.xml">https://fasterthanli.me/index.xml</a></td></tr><tr><td><a href="https://www.ncameron.org/blog/">pretzelhammer/rust-blog</a></td><td><a href="https://www.ncameron.org/blog/rss/">https://www.ncameron.org/blog/rss/</a></td></tr><tr><td><a href="https://github.com/pretzelhammer/rust-blog">Nick Cameron</a></td><td><a href="https://github.com/pretzelhammer/rust-blog/releases.atom">https://github.com/pretzelhammer/rust-blog/releases.atom</a></td></tr><tr><td><a href="https://folyd.com/blog/">FOLYD</a></td><td><a href="https://folyd.com/blog/feed.xml">https://folyd.com/blog/feed.xml</a></td></tr><tr><td><a href="https://www.skyzh.dev/posts/">Alex Chi</a></td><td><a href="https://www.skyzh.dev/posts/index.xml">https://www.skyzh.dev/posts/index.xml</a></td></tr></tbody></table><h2 id="作为参考的学习路线">作为参考的学习路线</h2><h3 id="各种方法入门">各种方法入门</h3><p><a href="https://gist.github.com/noxasaxon/7bf5ebf930e281529161e51cd221cf8a">noxasaxon/learning_rust.md</a></p><p><a href="https://github.com/jondot/rust-how-do-i-start">jondot/rust-how-do-i-start</a></p><h3 id="路线1">路线1</h3><p><a href="https://zhuanlan.zhihu.com/p/146472398">Rust Study RoadMap</a></p><p>作者在文中提供了两种学习路线。</p><h3 id="路线2">路线2</h3><ol><li><p>通读 <a href="http://rustbyexample.com/">Rust by Example</a>，把其中的例子都自己运行一遍，特别是对其中指出的错误用法也调试一遍。</p></li><li><p>通读 <a href="http://rust-lang.github.io/book/">The Rust Programming Language</a>，在进行了第一步后，已经基本对 Rust 的常用概念有所了解了，这个时候再读这本官方教程，进一步理解某些细节。</p></li><li><p>行了，到这一步后你就可以尝试做一个项目了，然后在做项目的过程中你一定会需要各种各样的库，请到 <a href="https://crates.io/">Crates</a>上搜索，寻找适合你需求的 crate，了解它们的用法，必要时查阅它们的源码。一开始写实际代码时，你肯定会很痛苦，Rust 编译器一定会不断地折磨你，这个时候不要放弃，返回去再看 <a href="http://rustbyexample.com/">Rust by Example</a>和<a href="http://rust-lang.github.io/book/">The Rust Programming Language</a>，然后终有通过编译的那一刻，恭喜你，入坑了！</p></li></ol><h2 id="常用站点">常用站点</h2><ul><li><a href="https://crates.io/">Crates</a><ul><li>Rust 类库</li></ul></li><li><a href="https://docs.rs/">Docs.rs</a><ul><li>Rust 类库文档</li></ul></li><li><a href="http://arewegameyet.com/">Are we game yet</a><ul><li>关于游戏开发</li></ul></li><li><a href="http://www.arewewebyet.org/">Are we web yet</a><ul><li>关于 Web 开发</li></ul></li><li><a href="https://areweideyet.com/">Are we (I)DE yet</a><ul><li>关于 IDE</li></ul></li><li><a href="https://github.com/wtklbm/rust-library-i18n">rust-library-i18n</a><ul><li>Rust 中文文档，可以在 IDE 中使用</li></ul></li></ul><h2 id="其他资料">其他资料</h2><ul><li><a href="https://www.reddit.com/r/rust/comments/s3z7ek/the_10_books_that_helped_me_as_a_hobbyist_on_my/">The 10 books that helped me, as a hobbyist, on my journey to learn Rust to re-code a Django application</a></li><li><a href="https://www.rustnote.com/">Rustnote</a><ul><li>某个网友的个人笔记</li></ul></li></ul><h2 id="本文参考">本文参考</h2><ul><li><a href="https://rust-lang-cn.org/article/23">https://rust-lang-cn.org/article/23</a></li><li><a href="https://letsgetrusty.kartra.com/page/XDk8">https://letsgetrusty.kartra.com/page/XDk8</a></li><li><a href="https://rustcc.cn/">https://rustcc.cn/</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> Rust </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> Rust </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>用 Rust 实现简单的光线追踪</title>
      <link href="/2021/05/05/rust-raytrace/"/>
      <url>/2021/05/05/rust-raytrace/</url>
      
        <content type="html"><![CDATA[<p>学 Rust 十来天了，自己被这个语言惊艳到，就跟着教程 <a href="https://raytracing.github.io/books/RayTracingInOneWeekend.html">Ray Tracing in One Weekend</a> 写了个很简陋的光线追踪示例练习，项目在 <a href="https://github.com/Latias94/rust_raytracing">Latias94/rust_raytracing</a>。</p><p>学这门语言的时候，感觉就是上手容易遇到很多新概念，容易学不下去，跟编译器作斗争…不过作为一个还很新的系统编程语言，工具链如文章、包管理、格式化、编译器等很完善，官方教程很棒，社区也很活跃。</p><p>学 Rust 的契机其实是在 V2EX 上看到有人在纠结学 Go 还是 Rust，底下的帖子也有不少夸 Rust 语言的，因此自己也开始关注 Rust 语言。后来发现 Rust 的用武之地非常广，Github 上还能找到不少 Rust 做的游戏引擎，其中一部分主打 ECS 功能，例如：<a href="https://github.com/bevyengine/bevy">bevyengine/bevy</a> 、<a href="https://github.com/Ralith/hecs">Ralith/hecs</a> 等。</p><p>学习 Rust 语言，其实也是在了解一个现代化的语言该有的样子，了解 C++ 或其他语言部分设计上的不足，以及 Rust 是打算如何从根源解决这些问题的。这部分我作为一个初学者，不打算展开讲。大家有空可以了解一下 Rust 语言，看看官方的教程<a href="https://kaisery.github.io/trpl-zh-cn/title-page.html">《Rust 程序设计语言》</a>。</p><p>总而言之，我觉得光线追踪的教程可以作为学一门新语言后<strong>严肃学习</strong>的项目，做完成就感也满满！</p><span id="more"></span><p>顺便推荐一篇好文：<a href="https://juejin.cn/post/6898953413250252814">新技术学习不完全指北：以 Rust 为例</a>。</p><p>最后放下示例的渲染图：</p><p><img src="http://img.frankorz.com/rust-raytrace.png" alt="1200*800 渲染图"></p><p>五一劳动节快乐！</p>]]></content>
      
      
      <categories>
          
          <category> 图形学 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> Rust </tag>
            
            <tag> 3D 数学 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>24 天像素画从入门到放弃</title>
      <link href="/2021/04/18/learn-pixel-art/"/>
      <url>/2021/04/18/learn-pixel-art/</url>
      
        <content type="html"><![CDATA[<p>前不久十分厌学，想着是不是学废了，就想找到其他的东西学学，于是有一天周日尝试了同时入门 Blender 和 像素画。</p><p>建模其实和 Unity 用初始模型搭积木差不多，入门也还好。但是自己从小就是对美术绝缘，只有初中的时候会和其他同龄人一样照着漫画书瞎画。后来就直到现在，因此开始画像素画的时候还是十分不适应，感觉每一个点都是为无用艺术界添砖加瓦。</p><p>后来决定画像素画的契机是听<a href="https://gulugulufm.github.io/podcasts/2/">播客</a>介绍到一个码农姐姐为了做游戏<a href="https://www.douban.com/note/773573673/">坚持 100 天画像素画</a>，于是少年那颗不知天高地厚的心怦然地燃烧起来。</p><p>教练！我想学画画！</p><span id="more"></span><h2 id="兴起">兴起</h2><p>作为一个资深松鼠症患者，Steam 里早已经躺着不知道什么时候打折入手的像素画神器 “Asprite”，我原本以为一辈子再也不会下载它，这就是命运的安排吧。</p><p>很多软件能画像素画，毕竟像素画就只是由点构成的，不过好的工具能事半功倍。用习惯 PS 的美术朋友可能会选择继续用 PS 改笔触画像素画，对于我来说，功能太多的软件反而会打压我的一时兴起的兴趣，于是我跟着 <a href="https://www.bilibili.com/video/BV1Ax411x7Ss">Aseprite！超方便的像素画软件初学者教程</a> 花半小时大概清楚 Aseprite 有的功能。</p><p>Aseprite 是收费的，在 <a href="https://github.com/aseprite/aseprite">Github 开源</a> ，如果你想自己编译，可以参考 [<a href="https://www.reddit.com/r/PixelArt/comments/i387m1/guide_how_to_build_aseprite_from_source_aseprite/">GUIDE] How to build Aseprite from source. (Aseprite free &amp; legal)</a> 。我是在 Steam 上买的，现在看了看是 70 元，买了 Win、Mac、Linus 都能用，不需要自己分开编译，更新也会更方便些。</p><p>自己一开始跟着 Youtube 博主的视频教程入门，其中有前面 Aseprite 初学者教程的作者 <a href="https://www.youtube.com/channel/UCsn9MzwyPKeCE6MEGtMU4gg">MortMort</a>、开了 <a href="https://www.youtube.com/watch?v=51u9ZgrEThg&amp;list=PLmac3HPrav-9UWt-ahViIZxpyQxJ2wPSH">Pixel 101 系列教程</a>的 <a href="https://www.youtube.com/c/PeterMilko/featured">Pixel Pete</a> 等。</p><p>我喜欢看视频教程入门的原因有几点：</p><ol><li>我想看他们是怎么开始构思一幅画的（像素画的型）</li><li>画像素画的过程（先画什么后画什么，颜色如何选择）</li><li>对软件的使用，如何适当地使用软件的功能更快地画出你想要的画。</li><li>了解像素画独有的技巧，如：像素抖动等</li></ol><p>一开始我看的是 Pixel 101 的前几个教程，Pixel Pete 在像素极其有限的情况下很快就能画出不同的物品，让我知道精准控制地像素就能通过不同颜色影响画在人们脑海中的想象。</p><p>因此我对像素画的理解就是：易入门、精准控制少量颜色和少量像素、费时少易出货，需要的基础也比原画板绘少非常多。</p><p>由于我当前工作室的游戏就是像素风格，因此问了几位原画同事，都说像素画是最简单的，他们一开始有原画基础，临摹下像素画的画法，很快就都能上手画像素画了。</p><p>于是我决定开始 100 天像素画挑战，每天花一个小时左右鼠绘像素画。</p><h2 id="学习">学习</h2><p>第一天开始画之后，我发现很多问题：</p><ul><li>一个像素能表现的东西是有限的，我要决定这个像素应该表现成什么，从而决定是什么颜色。</li><li>像素画的分辨率十分低，阴影过渡和线条可能会非常生硬，画太多又会有色块现象。</li><li>像素画中颜色的数量要有限制，阴影可以用两到三个颜色过渡，太多颜色会很乱。</li></ul><p>于是找了本书 <a href="https://nostarch.com/pixelart">《Make Your Own Pixel Art》</a> 参考，这本书面对的是没绘画基础的新手，用的软件也是 Aseprite。这本书讲的非常详细，我印象最深的就是我需要先学会画出不同基本模型的光照，例如圆柱、正方体等，这些形状和光照会画之后，再将它们组合起来，创造出自己的物品或角色。</p><p>对于调色板的颜色，可以在 <a href="https://lospec.com/palette-list">Lospec</a> 中选一些经典的颜色，这样我们暂时不用考虑颜色的对比、饱和度挑选等，先限制自己在调色板中用色，后期学颜色理论（Color Theory）了再自己配色。</p><p>下面是第三天时做的书中的练习，给出一个角色的剪影，我来上色。</p><p><img src="http://img.frankorz.com/day3-shading-practice.png" alt="第三天"></p><p>第五天我尝试画自己的角色，画布是 $ 64 × 64 $ ，主题是太空。其中用到刚学的像素抖动，星星的表示，不过有些光影的地方还是错的。</p><p><img src="http://img.frankorz.com/day5-spaceman.png" alt=""></p><p>于是每天画画，时不时尝试不同风格的像素画，中途还尝试了下画动画帧。每天也会用 <a href="https://eagle.cool/">Eagle</a> 收集一下参考用的素材，这个软件看动画帧也非常方便。</p><p><img src="http://img.frankorz.com/20210418134913.png" alt="Eagle"></p><p>挑战途中，我还找了一些素材参考，其中十分有用的是风农翻译的蔚蓝主美 <a href="https://www.patreon.com/saint11">Pedra Medeiros</a> 的一系列像素画教程：<a href="https://space.bilibili.com/7647261/article">saint11像素宝典</a> 和 <a href="https://jkllin.zcool.com.cn/">JKLLIN</a> 的<a href="https://www.zcool.com.cn/work/ZNDM5MTM4MDA=.html">像素画学习系列</a>。像素宝典里面还教了很多游戏中很有用的动画帧画法，比如攻击时的准备帧、不同风格如光魔法和黑暗魔法的表现等。</p><p>如果还想深入，我十分推荐 <a href="https://michafrar.com/">Michael Azzi</a> 写的 <a href="https://pixellogicbook.com/">Pixel Logic</a> 这本书，B 站的物暗先生<a href="https://www.bilibili.com/read/cv8026953/">汉化</a>过。这本书里面介绍了很多像素画的专有概念，还引用了很多像素游戏作为参考，如果想做一个像素游戏，你能从中获益很多。</p><p>我还经常逛逛 Twitter 的像素画标签，如：<a href="https://twitter.com/hashtag/pixelart">pixelart</a>、<a href="https://twitter.com/hashtag/%E3%83%89%E3%83%83%E3%83%88%E7%B5%B5">ドット絵</a>等，Artstation 的 <a href="https://www.artstation.com/search?q=pixel%20art&amp;sort_by=relevance">Pixel Art 画</a>，还有 Deviant Art 的 <a href="https://www.deviantart.com/topic/pixel-art">Pixel Art 主题</a>。找到参考的同时，还能进一步找到自己<strong>喜欢的风格</strong>，这样可以跟着画的作者进一步了解这种风格的像素画画法。</p><h2 id="放弃">放弃</h2><p>直到第 24 天我决定放弃，其实还是时间的问题，回到家自己的时间也就两三个小时，有时候纠结一下颜色和参考，一堆时间就过去了，我希望把时间更多地重新放在技术上。</p><p>当然这 24 天我也是收获了不少，对像素画有了基本的了解，对颜色也开始有了那么点概念。工作中做游戏系统原型的时候都能不等美术资源，自己随便画图凑合用了 hhh。游戏开发者在学像素画的时候，也能请教下工作室中的美术，以后说不定还能靠着自己的像素画做独立开发，一箭双雕！</p><p>大家在一开始画的时候从小图画起，$ 16 × 16 $ 到 $ 64 × 64 $ 的画布就已经足够了，这样的画布也不需要绘画板，鼠绘就足够了，我相信画到一定程度，就会知道自己需不要一个绘画板了。</p><h2 id="最后">最后</h2><p>本文作为学习过程的记录，希望能给读者激起学像素画的兴趣，避免走一些弯路。博主学像素画也是为了点一下独立开发的技能，虽然画的不怎么样，至少不怕画画了！</p><p>如果你对我 24 天的像素画感兴趣，可以点击下方按钮，或者博客右上角的相对应的标签查看。</p><div class="gallery-group-main"><figure class="gallery-group">  <img class="gallery-group-img no-lightbox" src='http://img.frankorz.com/day23-dog-in-snow.png' alt="Group Image Gallery">  <figcaption>  <div class="gallery-group-name">像素画</div>  <p>像素画挑战</p>  <a href='/pixel-art/'></a>  </figcaption>  </figure>  </div>]]></content>
      
      
      <categories>
          
          <category> 游戏开发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 绘画 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Compute Shader 简介</title>
      <link href="/2021/04/17/compute-shader/"/>
      <url>/2021/04/17/compute-shader/</url>
      
        <content type="html"><![CDATA[<p>做游戏的时候，我们经常要面对各种优化问题。DOTS 技术栈的出现提供了一种 CPU 端的多线程方案，那么我们是否也能将一些计算转到 GPU 上面，从而平衡好对 CPU 和 GPU 的使用呢？对我而言，以前使用 GPU 无非是通过写 vert/frag shader、做好渲染相关的设置等操作，但实际上我们还能使用 GPU 的计算能力来帮我们解决问题。Compute Shader 就是我们跟 GPU 请求计算的一种手段。</p><p>本文将从并行架构开始，依次讲解一个最简单的 Compute Shader的编写、线程与线程组的概念、GPU 结构和其计算流水线，并讲解一个鸟群 Flocking 的实例，最后介绍 Compute Shader 的应用。全文较长，读者可以通过目录挑想看的看。</p><p>Compute Shader 也和传统着色器的写法十分不一样，写传统 Shader 写怕了的同学请放心~</p><span id="more"></span><h2 id="介绍">介绍</h2><p>当今的 GPU 已经针对单址或连续地址的大量内存处理（亦称为流式操作，streaming operation）进行了优化，这与 CPU 面向内存随机访问的设计理念则刚好背道而驰。再者，考虑到要对顶点与像素分别进行单独的处理，因此 GPU 现已经采用了大规模并行处理架构。例如，NVIDIA 公司开发的 “Fermi” 架构最多可支持 16 个流式多处理器（streaming multiprocessor, SM），而每个流式处理器又均含有 32 个 CUDA 核心，也就是共 512 个 CUDA 核心。</p><p>CUDA 与 OpenCL 其实就是通过访问 GPU 来编写通用计算程序的两组不同的 API。</p><p><img src="http://img.frankorz.com/cpu-gpu-compare.png" alt="CPU compare GPU"></p><p>现代的 CPU 有 4-8 个 Core，每个 Core 可以同时执行 4-8 个浮点操作，因此我们假设 CPU 有 64 个浮点执行单元，然而 GPU 却可以有上千个这样的执行单元。仅仅只是比较 GPU 和 CPU 的 Core 数量是不公平的，因为它们的职能不同，组织形式也不同。</p><p>显然，图形的绘制优势完全得益于 GPU 架构，因为这架构就是专为绘图而精心设计的。但是，一些非图形应用程序同样可以从 GPU 并行架构所提供的强大计算能力中受益。我们将 GPU 用于非图形应用程序的情况称为<strong>通用 GPU 程序设计</strong>（通用 GPU 编程。General Purpose GPU programming, GPGPU programming）。当然，并不是所有的算法都适合由 GPU 来执行，只有数据并行算法（data-parallel algorithm） 才能发挥出 GPU 并行架构的优势。也就是说，仅当拥有大量待执行相同操作的数据时，才最适宜采用并行处理。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>粒子系统是一个例子，我们可简化粒子之间的关系模型，使它们彼此毫无关联，不会相互影响，以此使每个粒子的物理特征都可以分别独立地计算出来。</p><p>对于 GPGPU 编程而言，用户通常需要将计算结果返回 CPU 供其访问。这就需将数据由显存复制到系统内存，虽说这个过程的速度较慢（见下图），但是 GPU 在运算时所缩短的时间相比却是微不足道的。 针对图形处理任务来说，我们一般将运算结果作为渲染流水线的输入，所以无须再由 GPU 向 CPU 传输数据。例如，我们可以用计算着色器（Compute Shader）对纹理进行模糊处理（blur），再将着色器资源视图（shader resource view，DirectX 的概念），与模糊处理后的纹理相绑定，以作为着色器的输入。</p><p><img src="http://img.frankorz.com/cpu-gpu-speed.png" alt="CPU 与 GPU 的数据传输"></p><p>计算着色器虽然是一种可编程的着色器，但 Direct3D 并没有将它直接归为渲染流水线中的一部分。虽然如此，但位于流水线之外的计算着色器却可以读写 GPU 资源。从本质上来说，计算着色器能够使我们访问 GPU 来实现数据并行算法，而不必渲染出任何图形。正如前文所说，这一点即为 GPGPU 编程中极为实用的功能。另外，计算着色器还能实现许多图形特效——因此对于图形程序员来说，它也是极具使用价值的。前面提到，由于计算着色器是 Direct3D 的组成部分，也可以读写 Direct3D 资源，由此我们就可以将其输出的数据直接绑定到渲染流水线上。</p><p><img src="http://img.frankorz.com/compute-shader-pipeline.png" alt="计算着色器并非渲染流水线的组成部分，但是却可以读写GPU 资源。而且计算着色器也可以参与图形的渲染或单独用于 GPGPU 编程"></p><h2 id="最简单的-Compute-Shader">最简单的 Compute Shader</h2><p>现在我们来看看一个最简单的 Compute Shader 的结构。</p><p>Unity 右键 → Create → Shader → Compute Shader 就可以创建一个最简单的 Compute Shader。</p><p>Compute Shader 文件扩展名为 .compute，它们是以 <a href="https://docs.unity3d.com/cn/current/Manual/SL-ShadingLanguage.html">DirectX 11 样式 HLSL 语言</a>编写的。</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#pragma kernel CSMain</span></span><br><span class="line"></span><br><span class="line">RWTexture2D&lt;float4&gt; Result;</span><br><span class="line"></span><br><span class="line">[numthreads(<span class="number">8</span>,<span class="number">8</span>,<span class="number">1</span>)]</span><br><span class="line"><span class="type">void</span> CSMain(uint3 id : SV_DispatchThreadID)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 为了演示，我把模板中下面这行改了</span></span><br><span class="line">    Result[id.xy] = float4(<span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1.0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第 1 行：一个计算着色器资源文件必须包含至少一个可以调用的 compute kernel，实际上这个 kernel 对应的就是一个函数，该函数由 <code>#pragma</code> 指示，名字要和函数名一致。一个 Shader 中可以有多个内核，只需定义多个 <code>#pragma kernel functionName</code> 和对应的函数即可，C# 脚本可以通过 kernel 的名字来找到对应要执行的函数（ <code>shader.FindKernel(functionName)</code>）。</p><p>第 3 行： RWTexture2D 是一种可供 Compute Shader 读写的纹理，C# 脚本可以通过 <code>SetTexture()</code> 设置一个可读写的 RenderTexture 供  Compute Shader 修改像素颜色。其中 RW 代表可读写。</p><p>第 5 行：<code>numthreads</code> 设置线程组中的线程数。组中的线程可以被设置为 1D、2D 或 3D 的网格布局。线程组和线程的概念下文会提到。</p><p>第 6 行：CSMain 为函数名，需要和 pragma 定义的 kernel 名一一对应。一个函数体代表一个线程要执行的语句，传进来的 <code>SV_DispatchThreadID</code> 是三维的线程 id，下文会提到。</p><p>第 9 行：根据当前线程 id 索引到可读写纹理对应的像素，并设置颜色。</p><p>C# 脚本这边</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">InitShader</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    _image = GetComponent&lt;Image&gt;();</span><br><span class="line">    _kernelIndex = computeShader.FindKernel(<span class="string">&quot;CSMain&quot;</span>);</span><br><span class="line">    <span class="built_in">int</span> width = <span class="number">1024</span>, height = <span class="number">1024</span>;</span><br><span class="line">    _rt = <span class="keyword">new</span> RenderTexture(width, height, <span class="number">0</span>) &#123;enableRandomWrite = <span class="literal">true</span>&#125;;</span><br><span class="line">    _rt.Create();</span><br><span class="line"></span><br><span class="line">    _image.material.SetTexture(<span class="string">&quot;_MainTex&quot;</span>, _rt);</span><br><span class="line">    computeShader.SetTexture(_kernelIndex, <span class="string">&quot;Result&quot;</span>, _rt);</span><br><span class="line">    computeShader.Dispatch(_kernelIndex, width / <span class="number">8</span>, height / <span class="number">8</span>, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第 4 行：一个 Compute Shader 可能有多个 Kernel，这里根据名字找到需要的 KernelIndex，这样脚本才知道要把数据送给哪一个函数运算。</p><p>第 6、7 行：创建一个支持随机读写的 <code>RenderTexture</code> 。</p><p>第 10 行：为 Compute Shader 设置要读写的纹理。</p><p>第 11 行：设置好要执行的线程组的数量，并开始执行 Compute Shader。线程组数量的设置下文会提到。</p><p>将 Compute Shader 在 Inspector 赋值给脚本，然后将脚本挂在一个有 Image 组件的 GameObject 下，就能看到蓝色的图片。</p><p><img src="http://img.frankorz.com/simple-compute-shader-demo.png" alt="简单的着色器示例"></p><p>到现在我们应该大概明白了：</p><ul><li>kernel 函数里面执行的是一个线程的要执行的逻辑。</li><li>我们需要设置线程组的数量（Dispatch）、和线程组内线程的数量（numthreads）。</li><li>我们可以为 Compute Shader 设置纹理等可读写资源。</li></ul><p>那么什么是线程组和线程呢？我们又该如何设置数量？</p><h2 id="如何划分工作：线程与线程组">如何划分工作：线程与线程组</h2><p>在 GPU 编程的过程中，根据程序具体的执行需求，可将 <strong>线程</strong> 划分为由 <strong>线程组（thread group）</strong> 构成 的网格（grid）。</p><p><code>numthread</code> 和 <code>Dispatch</code> 的三维 Grid 的设置方式只是方便逻辑上的划分，硬件执行的时候还会把所有线程当成一维的。因此 <code>numthread(8, 8, 1)</code> 和 <code>numthread(64, 1, 1)</code> 只是对我们来说索引线程的方式不一样而已，除外没区别。</p><h3 id="线程组构成的-3D-网格">线程组构成的 3D 网格</h3><p>下图是 <code>Dispatch(5,3,2)</code>， <code>numthreads(10,8,3)</code> 时的情况。</p><p>注意下图 Y 轴是 DirectX 的方向，向下递增，而 Compute Shader 中 Y 轴是相反的，向上递增，这里参考网格内的结构和线程组与线程的关系即可。</p><p><img src="http://img.frankorz.com/dx-grid-of-thread-group.png" alt="线程组 3D 网格"></p><p>上图中还显示了 <code>SV_DispatchThreadID</code> 是如何计算的。</p><p>不难看出，我们能够根据需求定义出不同的线程组布局。例如，可以定义一个具有 X 个线程的单行线程组 <code>[numthreads(X, 1, 1)]</code> 或内含 Y 个线程的单列线程组 <code>[numthreads(1, Y, 1)]</code>。</p><p>还可以通过将维度 z 设为 1 来定义规模为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mo>×</mo><mi>Y</mi></mrow><annotation encoding="application/x-tex">X × Y</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">Y</span></span></span></span> 的 2D 线程组，形如 <code>[numthreads(X, Y, 1)]</code>。我们应结合所遇到的具体问题来选择适当的线程组布局。</p><p>例如当我们处理 2D 图像时，需要让每一个线程单独处理一个像素，就可以定义 2D 的线程组。假设我们 <code>numthreads</code> 设置为 (8, 8, 1)，那么一个线程组就有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn><mo>×</mo><mn>8</mn></mrow><annotation encoding="application/x-tex">8×8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">8</span></span></span></span> 个线程，能处理 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn><mo>×</mo><mn>8</mn></mrow><annotation encoding="application/x-tex">8×8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">8</span></span></span></span> 的像素块（内含 64 个像素点）。</p><p>那么如果我们要处理一个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mi>e</mi><mi>x</mi><mi>R</mi><mi>e</mi><mi>s</mi><mi>o</mi><mi>l</mi><mi>u</mi><mi>t</mi><mi>i</mi><mi>o</mi><mi>n</mi><mo>×</mo><mi>t</mi><mi>e</mi><mi>x</mi><mi>R</mi><mi>e</mi><mi>s</mi><mi>o</mi><mi>l</mi><mi>u</mi><mi>t</mi><mi>i</mi><mi>o</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">texResolution × texResolution</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7778em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord mathnormal">eso</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord mathnormal">eso</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span></span></span></span> 分辨率的纹理，那么需要多少个线程组呢？</p><p>x 和 y 方向都需要 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mi>e</mi><mi>x</mi><mi>R</mi><mi>e</mi><mi>s</mi><mi>o</mi><mi>l</mi><mi>u</mi><mi>t</mi><mi>i</mi><mi>o</mi><mi>n</mi><mi mathvariant="normal">/</mi><mn>8</mn></mrow><annotation encoding="application/x-tex">texResolution / 8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord mathnormal">eso</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mord mathnormal">i</span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span><span class="mord">/8</span></span></span></span> 个线程组。</p><img class="inline-img" src="http://img.frankorz.com/thread-group-explain.png" style="height:250px" /><!-- ![Thread Group of image](http://img.frankorz.com/thread-group-explain.png) --><p>可以通过线程组来划分要处理哪些像素块（<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn><mo>×</mo><mn>8</mn></mrow><annotation encoding="application/x-tex">8×8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">8</span></span></span></span>）</p><p><img src="http://img.frankorz.com/thread-group-explain-1.png" alt="线程组解释"></p><p><img src="http://img.frankorz.com/thread-group-explain-2.png" alt="线程组解释"></p><p>numthreads 有最大线程限制，具体查阅不同平台的文档：<a href="https://docs.microsoft.com/zh-cn/windows/win32/direct3dhlsl/sm5-attributes-numthreads">numthreads</a> 。</p><p>前面介绍了如何设置线程组和线程的数量，现在介绍线程组和线程在硬件的运行形式。</p><h3 id="线程组的-GPU-之旅">线程组的 GPU 之旅</h3><p><a href="https://www.notion.so/Fermi-c97604af538148cf96805b7b14ce332e">Fermi 架构</a></p><p><a href="https://www.notion.so/Ampere-3e00af14b1fb455db5b0a928a26d65aa">Ampere 架构</a></p><p>我们知道 GPU 会有上千个“核心”，用 NVIDIA 的说法就是 CUDA Core。</p><ul><li><strong>SP</strong>：最基本的处理单元，streaming processor，也称为 CUDA core。最后具体的指令和任务都是在 SP 上处理的。GPU 进行并行计算，也就是很多个 SP 同时做处理。我们所说的几百核心的 GPU 值指的都是 SP 的数量；</li><li><strong>SM</strong>：多个 SP 加上其他的一些资源组成一个 streaming multiprocessor。也叫 GPU 大核，其他资源如：warp scheduler，register，shared memory 等。SM 可以看做 GPU 的心脏（对比 CPU 核心），register 和 shared memory 是 SM 的稀缺资源。CUDA 将这些资源分配给所有驻留在 SM 中的 threads。因此，这些有限的资源就使每个 SM 中 active warps 有非常严格的限制，也就限制了并行能力。</li></ul><p>这些核心被组织在流式多处理器（streaming multiprocessor, SM）中，一个线程组运行于一个多处理器（SM）之上。每一个核心同一时间可以运行一个线程。</p><p>流式多处理器（streaming multiprocessor, SM）是 Nvidia 的说法，AMD 对应的单元则是 Compute Unit。</p><p>因此，对于拥有 16 个 SM 的 GPU 来说，我们至少应将任务分解为 16 个线程组，来让每个多处理器都充分地运转起来。但是，要获得更佳的性能，我们还应当令每个多处理器至少拥有两个线程组，使它能够切换到不同的线程组进行处理，以连续不停地工作（线程组在运行的过程中可能会发生停顿，例如，着色器在继续执行下一个指令之前会等待纹理的处理结果，此时即可切换至另一个线程组）。</p><p>SM 会将它从 Gigathread 引擎（NVIDIA 技术，专门管理整个流水线）那收到的大线程块，拆分成许多更小的堆，每个堆包含 32 个线程，这样的堆也被称为：<strong>warp</strong> (AMD 则称为 <strong>wavefront</strong>)。多处理器会以 SIMD32 的方式（即 32 个线程同时执行相同的指令序列）来处理 warp，每个 CUDA 核心都可处理一个线程。</p><p>“Fermi” 架构中的每个多处理器都具有 32 个 CUDA 核心。</p><p>每一个线程组都会被划分到一个 Compute Unit 来计算，线程组中的线程由 Compute Unit 中的 SIMD 部分来执行。<br>如果我们定义 <code>numthreads(8, 2, 4)</code>，那么每个线程组就有 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn><mo>×</mo><mn>2</mn><mo>×</mo><mn>4</mn><mo>=</mo><mn>64</mn></mrow><annotation encoding="application/x-tex">8×2×4=64</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.7278em;vertical-align:-0.0833em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">4</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">64</span></span></span></span> 个线程，这一整个线程组会被分成两个 warp，调度到单个 SIMD 单元计算。</p><p><img src="https://img-blog.csdnimg.cn/20200126195846919.gif" alt="Memory Stall（内存延迟）"></p><p>单个 SM 处理逐个 warp，当一个 warp 暂时需要等待数据的时候，就可以先换其他 warp 继续执行。</p><h3 id="如何设置好线程组的大小">如何设置好线程组的大小</h3><p>我们应当总是将线程组的大小设置为 <strong>warp 尺寸的整数倍</strong>。让 SM 同时容纳多个 warp，能够以防一些情况。例如有时候为了等待某些数据就绪，你不得不停下来。比如说，我们需要通过法线纹理贴图来计算法线光照，即使该法线纹理已经在 Cache 中了，访问该资源仍然会有所耗时，而如果它不在 Cache 中，那就更加耗时了。用专业术语讲就是 <strong>Memory Stall（内存延迟）</strong>。与其什么事情也不做，不如将当前的 Warp 换成其它已经准备就绪的 Warp 继续执行。<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p><p><img src="http://img.frankorz.com/thread-group-size-talk.png" alt="Dispatch/Thread Group SIze Heuristics"></p><p>上图来自：<a href="https://channel9.msdn.com/Blogs/gclassy/DirectCompute-Lecture-Series-210-GPU-Optimizations-and-Performance">DirectCompute Lecture Series 210: GPU Optimizations and Performance</a></p><blockquote><p>NVIDIA 在 Maxwell 更改了 SM 的组织方式，即 SMM——全新的 SM 架构。每个 SM 分为四个独立的处理块，每个处理块具备自己的指令缓冲区、调度器以及 32 个 CUDA 核心。因此 Maxwell 中可以同时运行 4 个以上的 Warp，实际上，在 GTC2013 大会上的一个 CUDA 优化视频里讲到，在常用 case 中推荐使用 30 个以上的有效 Warp，这样才能确保 Pipeline 的满载利用率。<br>—— <a href="http://www.ece.rice.edu/~gw2/">Guohui Wang</a></p></blockquote><p>NVIDIA 公司生产的图形硬件所用的 warp 单位共有 32 个线程。而 ATI 公司采用的 “wavefront” 单位则具有 64 个线程，且建议为其分配的线程组大小应总为 wavefront 尺寸的整数倍。另外，值得一提的是，不管是 warp 还是 wavefront，它们的大小在未来几代中都有可能发生改变。</p><p>总之，每个 SM 的操作度是 warp，但是每个 SM 可以同时处理多个 warp。然后因为有内存等待（memory stall）的问题，同一个 thread block 有可能需要等待内存才做，因此可以使用多个线程组交叉运行。warp 对我们是不可见和不可编程的，我们可编程的只有线程组。<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup></p><p>还可以参考 GPU Open 中 <a href="https://gpuopen.com/performance/#shaders">Compute Shader 部分</a>。</p><h2 id="GPU-Compute-Unit">GPU Compute Unit</h2><p>接下来我们看一下 GPU 内部的结构，这里的内容来自 <a href="https://www.youtube.com/watch?v=0DLOJPSxJEg">Compute Shaders: Optimize your engine using compute / Lou Kramer, AMD</a>，<a href="https://twitter.com/lou_auroyup">Lou Kramer</a> 以 AMD 的 GCN 架构为例，介绍了 GPU 大体的结构。</p><p>这里 GCN 就是一个 Compute Unit，Vega 64 显卡有 64 个 Compute Unit。</p><p><img src="http://img.frankorz.com/gpu-talk-1.png" alt="gpu-talk-1"></p><p>GCN 有 4 个 SIMD-16 单元（即 16 个线程同时执行相同的指令序列）。</p><p><img src="http://img.frankorz.com/gpu-talk-2.png" alt="gpu-talk-2"></p><h3 id="线程间交流">线程间交流</h3><h4 id="多个线程组间的交流">多个线程组间的交流</h4><p>上面提到，线程并不能访问其他组中的共享内存。如果线程组需要互相交流，那么就需要 L2 cache 来支持。但是 L2 cache 性能肯定会有折扣，因此我们要保证组间的交流尽可能少。</p><p><img src="http://img.frankorz.com/gpu-talk-3.png" alt="gpu-talk-3"></p><h4 id="单个线程组内的交流">单个线程组内的交流</h4><p>如果单个线程组内线程需要互相交流，则需要 Local Data Share (LDS) 来完成。</p><p><img src="http://img.frankorz.com/gpu-talk-4.png" alt="gpu-talk-4"></p><p>LDS 会被其他着色阶段（shader stage）使用，例如像素着色器就需要 LDS 来插值。但是 Compute Shader 的用途和传统着色器不一样，不是必须要 LDS，因此我们可以随意地使用 LDS。</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">groupshared <span class="type">float</span> data[<span class="number">8</span>][<span class="number">8</span>];</span><br><span class="line"></span><br><span class="line">[numthreads(<span class="number">8</span>,<span class="number">8</span>,<span class="number">1</span>)]</span><br><span class="line"><span class="type">void</span> main(<span class="type">ivec3</span> <span class="keyword">index</span> : SV_GroupThreadID)</span><br><span class="line">&#123;</span><br><span class="line">  data[<span class="keyword">index</span>.x][<span class="keyword">index</span>.y] = <span class="number">0.0</span>;</span><br><span class="line">  GroupMemoryBarrierWithGroupSync();</span><br><span class="line">  data[<span class="keyword">index</span>.y][<span class="keyword">index</span>.x] += <span class="keyword">index</span>.x;</span><br><span class="line">  …</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要组内共享的变量前加 <code>groupshared</code> ，同时为了保证其他线程也能读到数据，我们也需要通过 Barrier 来保证他们读的时候 LDS 里面有需要的数据。</p><p>LDS 比 L1 cache 还快！</p><h3 id="Vector-Register-和-Scalar-Register">Vector Register 和 Scalar Register</h3><p>如果有些变量是线程独立的，我们称之为 “non-uniform” 变量。（如果一个线程组内有 64 个线程，就要存 64 份数据）</p><p>如果有些变量是线程间共享的，我们称之为 “uniform” 变量，例如线程组 id 是组内每个线程都一样的。（每个线程组内只存 1 份数据）</p><p>“non-uniform” 变量会被储存到 Vector Register（VGPR, vector general purpose register）中。</p><p>“uniform” 变量会被储存到 Scalar Register（SGPR, scalar general purpose register）中。</p><p><img src="http://img.frankorz.com/gpu-talk-5.png" alt="gpu-talk-5"></p><p>如果用了过多 “non-uniform” 变量导致 Vector Register 装不下，就会导致分配给 SIMD 的线程组数量降低。</p><h3 id="与传统着色器执行流程的异同">与传统着色器执行流程的异同</h3><h4 id="Vert-Frag-Shader">Vert-Frag Shader</h4><ol><li><p>首先 Command Processor 会收集并处理所有命令，发送到 GPU，并告知下一步要做什么。</p></li><li><p><code>Draw()</code> 命令发送后，Command Processor 告知 Graphics Processor 要做的事情。</p><p>我们可以将 Graphics Processor 看作是输入装配器（Input Assembler）的硬件对应的部分。</p></li><li><p>然后类似于顶点着色器这些就会被送到 Compute Unit 去计算，处理完会到 Rasterizer (光栅器)，并返回处理好的像素到 Compute Unit 执行像素着色（Pixel shader）。</p></li><li><p>最后才会输出到 RenderTarget 。</p></li></ol><p>下图中，AMD 显卡架构中的 Compute Unit 相当于 nVIDIA GPUs 中的流式多处理器（streaming multiprocessor, SM）。</p><p><img src="http://img.frankorz.com/gpu-talk-6.png" alt="gpu-talk-6"></p><h4 id="Compute-Shader">Compute Shader</h4><ol><li>首先 Command Processor 仍会收集并处理所有命令，发送到 GPU。</li><li>我们不需要传数据到 Graphics Processor，因为这不是一个 Graphics Command，而是直接传到 Compute Unit。</li><li>Compute Unit 开始处理 Compute Shader，输入可以有 constants 和 resources（对应 DirectX 的 Resource 可以绑定到渲染管线的资源，例如顶点数组等），输出可以有 writable resources（UAV, Unordered Access View 能被着色器写入的资源视图）。</li></ol><h3 id="总结">总结</h3><p>因此，如果我们用了 Compute Shader，<strong>可以不通过渲染管线，跳过 <a href="https://en.wikipedia.org/wiki/Render_output_unit">Render Output</a>，使用更少硬件资源</strong>，利用 GPU 来完成一些渲染不相关的工作。</p><p><img src="http://img.frankorz.com/gpu-talk-7.png" alt="gpu-talk-7"></p><p>此外，Compute Shader 的流水线<strong>需要的信息也更少</strong>。</p><p><img src="http://img.frankorz.com/gpu-talk-8.png" alt="gpu-talk-8"></p><h2 id="Boids-示例">Boids 示例</h2><p>讲完了理论，这里来看看我们在 Unity 中使用 Compute Shader 来做一个鸟群（Boids）的 demo。</p><p>群落算法可以参考：<a href="https://www.red3d.com/cwr/boids/">Boids (Flocks, Herds, and Schools: a Distributed Behavioral Model)</a></p><p>代码示例地址：<a href="https://github.com/Latias94/FlockingComputeShaderCompare">Latias94/FlockingComputeShaderCompare</a></p><p>群落算法简单来讲，就是模拟生物群落的自组织特性的移动。</p><p><a href="https://www.red3d.com/cwr/index.html">Craig Reynolds</a> 在 1986 年对诸如鱼群和鸟群的运动进行了建模，提出了三点特征来描述群落中个体的位置和速度：</p><ol><li>排斥（separation）：每个个体会避免离得太近。离得太近需要施加反方向的力使其分开。</li><li>对齐（Alignment）：每个个体的方向会倾向于附近群落的平均方向。</li><li>凝聚（Cohesion）：每个个体会倾向于移动到附近群落的平均位置。</li></ol><p>在这个示例中，我们可以将每一只鸟的位置和方向用一个线程来计算，Compute Shader 负责遍历这只鸟的周围鸟的信息，计算出这只鸟的平均方向和位置。C# 脚本则负责每一帧传入凝聚（Cohesion）的位置、经过的时间，再从 Compute Shader 获取每一只鸟的位置和朝向，设置到每一只鸟的 Transform 上。</p><h3 id="设置数据">设置数据</h3><p>文章开头的例子中，脚本给 Shader 设置了 <code>RWTexture2D&lt;float4&gt;</code> ，让 Compute Shader 能直接在 Render Tecture 设置颜色。</p><p>对于其他类型的数据，我们首先要定义一个结构（Struct），再通过 <code>ComputeBuffer</code>  与 Compute Shader 交流数据。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FlockingGPU.cs</span></span><br><span class="line"><span class="keyword">struct</span> Boid</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Vector3 position;</span><br><span class="line">    <span class="keyword">public</span> Vector3 direction;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">FlockingGPU</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> ComputeShader shader;</span><br><span class="line">    <span class="keyword">private</span> Boid[] _boidsArray;</span><br><span class="line">    <span class="keyword">private</span> GameObject[] _boids;</span><br><span class="line">    <span class="keyword">private</span> ComputeBuffer _boidsBuffer;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        _kernelHandle = shader.FindKernel(<span class="string">&quot;CSMain&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="built_in">uint</span> x;</span><br><span class="line">        <span class="comment">// 获取 Compute Shader 中定义的 numthreads</span></span><br><span class="line">        shader.GetKernelThreadGroupSizes(_kernelHandle, <span class="keyword">out</span> x, <span class="keyword">out</span> _, <span class="keyword">out</span> _);</span><br><span class="line">        _groupSizeX = Mathf.CeilToInt(boidsCount / (<span class="built_in">float</span>) x);</span><br><span class="line">        <span class="comment">// 塞满每个线程组，免得 Compute Shader 中有线程读不到数据，造成读取数据越界</span></span><br><span class="line">        _numOfBoids = _groupSizeX * (<span class="built_in">int</span>) x;</span><br><span class="line"></span><br><span class="line">        InitBoids();</span><br><span class="line">        InitShader();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">InitBoids</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 初始化 _Boids GameObject[]、_boidsArray Boid[]</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">InitShader</span>()</span></span><br><span class="line">    &#123;   <span class="comment">// 定义大小，鸟的数量和每个鸟结构的大小，一个 Vector3 就是 3 * sizeof(float)</span></span><br><span class="line">        <span class="comment">// 10000 只鸟，每只占6 * 4 bytes，总共也就占 0.234mib GPU 显存 </span></span><br><span class="line">        _boidsBuffer = <span class="keyword">new</span> ComputeBuffer(_numOfBoids, <span class="number">6</span> * <span class="keyword">sizeof</span>(<span class="built_in">float</span>));</span><br><span class="line">        _boidsBuffer.SetData(_boidsArray); <span class="comment">// 设置结构数组到 Compute Buffer 中</span></span><br><span class="line">        <span class="comment">// 设置 buffer 到 Compute Shader，同时设置要调用的计算的函数 Kernel</span></span><br><span class="line">        shader.SetBuffer(_kernelHandle, <span class="string">&quot;boidsBuffer&quot;</span>, _boidsBuffer);</span><br><span class="line">        shader.SetFloat(<span class="string">&quot;boidSpeed&quot;</span>, boidSpeed); <span class="comment">// 设置其他常量</span></span><br><span class="line">        shader.SetVector(<span class="string">&quot;flockPosition&quot;</span>, target.transform.position);</span><br><span class="line">        shader.SetFloat(<span class="string">&quot;neighbourDistance&quot;</span>, neighbourDistance);</span><br><span class="line">        shader.SetInt(<span class="string">&quot;boidsCount&quot;</span>, boidsCount);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">OnDestroy</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (_boidsBuffer != <span class="literal">null</span>)</span><br><span class="line">        &#123;    <span class="comment">// 用完主动释放 buffer</span></span><br><span class="line">            _boidsBuffer.Dispose();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="获取数据">获取数据</h3><p>在开头最简单的 Compute Shader 一节中，我介绍了需要 <code>Dispatch</code> 去执行 Compute Shader 的 Kernel。</p><p>下面的 <code>Update</code>，设置了每一帧会变的参数，Dispatch 之后，再通过 <code>GetData</code> 阻塞等待 Compute Shader kernel 的计算结果，最后对每一个 Boid 结构赋值。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// FlockingGPU.cs</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">FlockingGPU</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">    &#123;   <span class="comment">// 设置每一帧会变的变量</span></span><br><span class="line">        shader.SetFloat(<span class="string">&quot;deltaTime&quot;</span>, Time.deltaTime);</span><br><span class="line">        shader.SetVector(<span class="string">&quot;flockPosition&quot;</span>, target.transform.position);</span><br><span class="line">        shader.Dispatch(_kernelHandle, _groupSizeX, <span class="number">1</span>, <span class="number">1</span>); <span class="comment">// 调用 Compute Shader Kernel 来计算</span></span><br><span class="line">        <span class="comment">// 阻塞等待 Compute Shader 计算结果从 GPU 传回来</span></span><br><span class="line">        _boidsBuffer.GetData(_boidsArray);</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; _boidsArray.Length; i++)</span><br><span class="line">        &#123;   <span class="comment">// 设置鸟的 position 和 rotation</span></span><br><span class="line">            _boids[i].transform.localPosition = _boidsArray[i].position;</span><br><span class="line">            <span class="keyword">if</span> (!_boidsArray[i].direction.Equals(Vector3.zero))</span><br><span class="line">            &#123;</span><br><span class="line">                _boids[i].transform.rotation = Quaternion.LookRotation(_boidsArray[i].direction);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 Compute Shader 中，也要定义一个 Boid 结构和相对应的 <code>RWStructuredBuffer&lt;Boid&gt;</code> 来用脚本传来的 Compute Buffer。Shader 主要就是对一只鸟遍历一定范围内的鸟群的信息，计算出结果返回给脚本。</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SimpleFlocking.compute</span></span><br><span class="line"><span class="meta">#pragma kernel CSMain</span></span><br><span class="line"><span class="meta">#define GROUP_SIZE 256</span></span><br><span class="line"></span><br><span class="line">struct Boid</span><br><span class="line">&#123;   <span class="comment">// Compute Shader 也定义好相关的结构</span></span><br><span class="line">    float3 position;</span><br><span class="line">    float3 direction;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">RWStructuredBuffer&lt;Boid&gt; boidsBuffer; <span class="comment">// 允许读写的数据 buffer</span></span><br><span class="line"><span class="type">float</span> deltaTime;</span><br><span class="line">float3 flockPosition;</span><br><span class="line"></span><br><span class="line">[numthreads(GROUP_SIZE,<span class="number">1</span>,<span class="number">1</span>)]</span><br><span class="line"><span class="type">void</span> CSMain(uint3 id : SV_DispatchThreadID)</span><br><span class="line">&#123;</span><br><span class="line">    Boid boid = boidsBuffer[id.x];</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; boidsCount; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (i == id.x)</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        Boid tempBoid = boidsBuffer[i];</span><br><span class="line">        <span class="comment">// 通过周围的鸟的信息，计算经过三个特性后，这一只鸟的方向和位置。</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    boid.direction = lerp(direction, <span class="built_in">normalize</span>(boid.direction), <span class="number">0.94</span>);</span><br><span class="line">    boid.position += boid.direction * boidSpeed * deltaTime;</span><br><span class="line">    <span class="comment">// 设置数据到 Buffer，等待 CPU 读取</span></span><br><span class="line">    boidsBuffer[id.x] = boid;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Dispatch</code> 之后 <code>GetData</code> 是阻塞的，如果想异步地获取数据，Unity 2019 新引入一个 API：<a href="https://docs.unity3d.com/ScriptReference/Rendering.AsyncGPUReadbackRequest.html">AsyncGPUReadbackRequest</a> ，可以让我们先发送一个获取数据的请求，再每一帧去查询数据是否计算完。也有同学用了测出第一次调用耗时较多等问题，具体可以参考：<a href="https://blog.csdn.net/h5502637/article/details/85637872">Compute Shader 功能测试（二）</a>。</p><p>下面是 100 只鸟的结果：</p><p><img src="http://img.frankorz.com/boids-run-demo.gif" alt="100只鸟的结果"></p><p>通过 Compute Shader，我们可以通过 Compute Shader 在 GPU 直接计算好需要计算的东西（例如位置、mesh 顶点等），并与传统着色器共享一个 <code>ComputeBuffer</code> ，直接在 GPU 渲染，这样就省去渲染时 CPU 再次传数据给 GPU 的耗时。我们也可以将 Compute Shader 计算后的数据返回给 CPU 再做额外的计算。总而言之，Compute Shader 十分灵活。</p><h3 id="CPU-端计算-vs-GPU-端计算">CPU 端计算 vs GPU 端计算</h3><p>假设我们在 CPU 端不用任何 DOTS，直接在每个 Update 中 for 每个鸟计算朝向和位置，这样性能是非常差的。</p><p>下图是把计算都放到 C# Update 中的 Profile：</p><p><img src="http://img.frankorz.com/boid-profile-1.png" alt="C# 每个 Update 中直接计算"></p><p>如果放到 Compute Shader 计算，每个 Update 更新数据，这样 CPU 消耗小了很多。</p><p><img src="http://img.frankorz.com/boid-profile-2.png" alt="Compute Shader 计算，每个 Update 更新数据"></p><p>感兴趣的朋友可以对比下 FlockingCPU.cs 和 FlockingGPU.cs 的代码，会发现两者的代码其实十分相似，只不过前者把 for loop 放到脚本，后者放到了 Compute Shader 中而已，因此如果大家觉得有一些地方十分适合并行计算，就可以考虑把这部分计算放到 GPU 计算。</p><h3 id="Profile-Compute-Shader">Profile Compute Shader</h3><p>我们可以通过 Profiler 来看 GPU 利用情况，通常这个面板是隐藏的，需要手动打开。</p><p>也可以通过 RenderDoc 来看，这里不展示。</p><p><img src="http://img.frankorz.com/boid-profile-3.png" alt="boid-profile-3"></p><h3 id="优化：DrawMeshInstanced">优化：DrawMeshInstanced</h3><p>前面我们用 <code>Instantiate</code> 来初始化鸟群，其实我们也能通过 <a href="https://docs.unity3d.com/Manual/GPUInstancing.html">GPU instancing</a> 来优化，用 <a href="https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstanced.html">Graphics.DrawMeshInstanced</a> 来实例化 prefab。这个优化未包含在 Github 例子中，这里提供思路。</p><p><img src="http://img.frankorz.com/boid-profile-4.png" alt="boid-profile-4"></p><p>这么做的话，位置和旋转都要在传统 shader 中计算成变换矩阵应用在顶点上，因此为了防止 Compute Shader 数据传回 CPU 再传到 GPU 的传统 shader 的开销，需要两个 Shader 共享一个 <code>StructuredBuffer</code> 。</p><p>这样如果要给模型加动画的话，还得提前烘焙动画，将每一帧动画的顶点和当前帧数提前传到 vertex shader(or surface shader) 里做插值，这样做的话还能根据鸟的速度去控制动画的速率。</p><h2 id="应用">应用</h2><ul><li>遮挡剔除（<em>Occlusion Culling</em>）</li><li>环境光遮蔽（<em>Ambient Occlusion</em>）<ul><li><a href="https://www.anandtech.com/show/2848/2">AMD’s Radeon HD 5850: The Other Shoe Drops</a></li></ul></li><li>程序化生成：<ul><li>terrain heightmap evaluation with noise, erosion, and voxel algorithms</li></ul></li><li>AI 寻路<ul><li>Compute Shader 做寻路有点不太好的就是往往游戏（CPU）需要知道计算结果，因此还要考虑 GPU 返回结果给 CPU 的延时。可以考虑做 CPU 端并行的方案，例如用 Job System。</li></ul></li><li>GPU 光线追踪<ul><li><a href="http://blog.three-eyed-games.com/2018/05/03/gpu-ray-tracing-in-unity-part-1/">GPU Ray Tracing in Unity – Part 1</a></li></ul></li><li>图像处理，例如模糊化等。</li><li>其他你想放到 GPU，但是传统着色器干不了的并行的解决方案。</li></ul><h3 id="原神">原神</h3><p><a href="https://www.bilibili.com/video/BV1Za4y1s7VL">Unity线上技术大会-游戏专场｜从手机走向主机 -《原神》主机版渲染技术分享</a></p><p><img src="http://img.frankorz.com/genshin-render-compute-shader-1.png" alt="Genshin 主机渲染管线简介"></p><h4 id="解压预烘焙的-Shadow-Texture">解压预烘焙的 Shadow Texture</h4><p>在离线制作的时候，对于烘焙好的 shadow texture 做一个压缩，尽量地去保持精度，运行的时候解压的速度也非常快，用 Compute Shader 去解压的情况，1K×1K 的 shadow texture，解压只需要 0.05 毫秒。</p><p><img src="http://img.frankorz.com/genshin-render-compute-shader-2.png" alt="解压预烘焙的 Shadow Texture"></p><h4 id="做模糊处理">做模糊处理</h4><p>在进行模糊处理的时候，每个像素需要采取周边多个像素的数值进行混合，可以看到，如果使用传统的 PS，每个像素都会需要多次贴图采样，且这些采样结果实际上是可以在相邻其他像素的计算中进行重用的，因此为了进一步提升计算性能，《原神》这里的做法是将模糊处理放到 Compute Shader 中来完成。</p><p>具体的做法是，将相邻像素的采样结果存储在 <strong>局部存储空间（Local Data Share）</strong> 中，之后再模糊的时候取用，一次性完成四个像素的模糊计算，并将结果输出。<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup></p><h3 id="天涯明月刀">天涯明月刀</h3><p><a href="http://youxiputao.com/articles/21061">《天涯明月刀》手游引擎技术负责人：如何应用GPU Driven优化渲染效果？| TGDC 2020</a></p><p><img src="http://img.frankorz.com/gpu-driven-compute-1.png" alt="gpu-driven-compute-1"></p><p>做遮挡剔除（Occlusion Culling）时，CPU 只能做到 Object Level，而 GPU 可以通过切分 Mesh 做进一步的剔除。</p><p><img src="http://img.frankorz.com/gpu-driven-compute-2.png" alt="gpu-driven-compute-2"></p><p>知乎上也有人尝试了实现：<a href="https://zhuanlan.zhihu.com/p/352850047">Unity实现GPUDriven地形</a>。</p><h3 id="斗罗大陆">斗罗大陆</h3><p><a href="https://mp.weixin.qq.com/s/y0QZ3aaJg8S7u1c5sCaZgg">三七研发，这款被称作 “目前最原汁原味的”《斗罗大陆》3D 手游都用到了哪些 Unity 技术？</a></p><p>利用 Compute Shader 对所有美术贴图逐像素对比，筛选出大量的重复、相似、屯余、大透明的贴图。</p><h3 id="Clay-Book">Clay Book</h3><p>基于3D SDF 体渲染的黏土游戏：<a href="https://www.claybookgame.com/">Claybook Game</a>。</p><p>演讲：<a href="https://youtu.be/Xpf7Ua3UqOA?t=405">DD2018: Sebastian Aaltonen - GPU based clay simulation and ray tracing tech in Claybook</a></p><p>动图：<a href="https://gfycat.com/gaseousterriblechupacabra">https://gfycat.com/gaseousterriblechupacabra</a></p><h3 id="Jelly-in-the-sky">Jelly in the sky</h3><p><a href="https://www.reddit.com/r/Unity3D/comments/7j4hay/finished_my_compute_shader_based_game/">Finished my compute shader based game</a> 这帖子的哥们写了六千多行 HLSL 代码做了一个完全在 GPU 执行的基于物理模拟的游戏。</p><p>Steam：<a href="https://store.steampowered.com/app/593530/Jelly_in_the_sky/">Jelly in the sky on Steam</a></p><p>动图：<a href="https://gfycat.com/validsolidcanine">https://gfycat.com/validsolidcanine</a></p><h3 id="开源项目">开源项目</h3><ul><li><a href="https://github.com/cinight/MinimalCompute">cinight/MinimalCompute</a></li><li><a href="https://github.com/krylov-na/Compute-shader-particles">krylov-na/Compute-shader-particles</a></li><li><a href="https://github.com/keijiro/Swarm">keijiro/Swarm</a></li><li><a href="https://github.com/ellioman/Indirect-Rendering-With-Compute-Shaders">ellioman/Indirect-Rendering-With-Compute-Shaders</a></li></ul><h2 id="缺点">缺点</h2><p>虽然 Unity 帮我们做了跨平台的工作，但是我们仍然需要面对一些平台差异。</p><p>本小节内容大部分来自 <a href="https://zhuanlan.zhihu.com/p/53785954">Compute Shader : Optimize your game using compute</a>。</p><ul><li>难 Debug</li><li><strong>数组越界</strong>，DX 上会返回 0，其它平台会出错。</li><li>变量名与关键字/内置库函数<strong>重名</strong>，DX 无影响，其他平台会出错。</li><li>如果 SBuffer 内结构的显存布局与<strong>内存布局不一致</strong>，DX 可能会转换，其他平台会出错。</li><li><strong>未初始化</strong>的 SBuffer 或 Texture，在某些平台上会全部是 0，但是另外一些可能是任意值，甚至是NaN。</li><li>Metal 不支持<strong>对纹理的原子操作</strong>，不支持对 SBuffer 调用 <code>GetDimensions</code>。</li><li>ES 3.1 在一个 CS 里<strong>至少支持 4 个 SBuffer</strong>（所以，我们需要将相关联的数据定义为 struct）。</li><li>ES 从 3.1 开始支持 CS，也就是说，在手机上的支持率并不是很高。部分号称支持 es 3.1+ 的 Android 手机只支持在片元着色器内访问 StructuredBuffer。<ul><li>使用 <code>SystemInfo.supportsComputeShaders</code> 来判断支不支持<sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup></li></ul></li></ul><h2 id="最后">最后</h2><p>我相信 Compute Shader 这个词不少读者应该都会在其他地方见过，但是大都觉得这个技术离我们还很远。我身边的朋友问了问也没怎么了解过，更不要说在项目上用了，这也是这篇文章诞生的原因之一。</p><p>当我们面临使用 DOTS 还是 Compute Shader 的抉择时，更应该从项目本身出发，考虑计算应该放在 CPU 还是 GPU，Compute Shader 中跟 GPU 沟通的开销是否能够接受。读者也可以参考下 Unity Forum 中相关的讨论：<a href="https://forum.unity.com/threads/unity-dots-vs-compute-shader.1093228/">Unity DOTS vs Compute Shader</a>。</p><p>开始碎碎念，去年的年终总结也没写，今年到现在就憋出一篇文章，十分不应该。其实也是自己没什么好分享的，自己还需要多学习。当然也很高兴通过博客认识到不同朋友，这是我写作的动力，谢谢你们。</p><h2 id="参考">参考</h2><ul><li><a href="https://github.com/chenjd/Unity-Boids-Behavior-on-GPGPU">chenjd/Unity-Boids-Behavior-on-GPGPU</a></li><li><a href="https://zhuanlan.zhihu.com/p/74418914">关于Compute Shader的一些基础知识记录</a></li><li><a href="https://www.youtube.com/watch?v=0DLOJPSxJEg&amp;t=167s">Compute Shaders: Optimize your engine using compute / Lou Kramer, AMD</a></li></ul><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p><a href="https://book.douban.com/subject/30426701/">《DirectX 12 3D 游戏开发实战》第13章 计算着色器</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p><a href="https://blog.csdn.net/hexiaolong2009/article/details/104088308">Render Hell —— 史上最通俗易懂的GPU入门教程（二）</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p><a href="https://www.zhihu.com/question/445590537/answer/1742513246#comment-1265841070">知乎 - “问个CUDA并行上的小白问题，既然SM只能同时处理一个WARP，那是不是有的SP处于闲置？”的评论</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p></li><li id="fn4" class="footnote-item"><p><a href="http://youxiputao.com/articles/20948">米哈游技术总监分享：《原神》主机版渲染技术要点和解决方案</a> <a href="#fnref4" class="footnote-backref">↩︎</a></p></li><li id="fn5" class="footnote-item"><p><a href="https://zhuanlan.zhihu.com/p/68886986">ComputeShader 手机兼容性报告</a> <a href="#fnref5" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
      
      
      <categories>
          
          <category> 游戏开发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> Shader </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>博客新增公开笔记部分</title>
      <link href="/2020/10/03/add-note-section-to-blog/"/>
      <url>/2020/10/03/add-note-section-to-blog/</url>
      
        <content type="html"><![CDATA[<p>我认为博客应该放一些经过思考的、实践的、适合读者阅读的文章，自己有时候也会在看其他视频教程或文章时记一些笔记。有些笔记本身不太适合分享出来，因为做笔记不可避免的会按照自己的思路和现有知识来定制，可能和别人注意重点不太一样。</p><p>因此我打算将一些比较成文的、有结构性的、有参考价值的笔记分享出来，这也能锻炼我把笔记组织成文的能力。</p><p>这些公开的笔记我放在独立的一个 Notion 页面中，这个页面可以点击博客上方的<strong>公开笔记</strong>，或者这个链接找到：<a href="https://www.notion.so/frankorz/e9a64743c07743f08605f5df7cb57eaf">公开笔记</a>。</p><p>Notion 本身对公式、排版都比较友好，但是打开可能要科学上网，我是懒得把这些文章往博客搬了…不过用 Notion 有个好处就是，我对公开笔记的编辑都能实时更新到。</p><span id="more"></span><p>正文太空了也不好，就放个我笔记的主页图吧~</p><p><img src="http://img.frankorz.com/notion-page.gif" alt=""></p>]]></content>
      
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> 3D数学 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>图形学常见的变换推导</title>
      <link href="/2020/07/26/transformation/"/>
      <url>/2020/07/26/transformation/</url>
      
        <content type="html"><![CDATA[<p>注意：由于这个博客主题对 MathJax 支持不好，部分推导转用图片代替，或者可以移步我的 Notion 笔记：<a href="https://www.notion.so/frankorz/ce300124a4444c2cacc90a5ea0a5a19b">Transformation</a>。</p><p>本文是 Games101-现代计算机图形学入门 第三和第四节课的笔记，文中对二维变换、三维变换、视图变换、正交投影和透视投影做了推导，相关视频在下方。</p><p><a href="https://www.bilibili.com/video/BV1X7411F744?p=3">GAMES101-Lecture03 Transformation</a></p><p><a href="https://www.bilibili.com/video/BV1X7411F744?p=4">GAMES101-Lecture04 Transformation Cont.</a></p><p>本文同时参考了《Unity Shader 入门精要》的第四章，作者公开了第四章的 PDF，可以在下面下载到。</p><p><a href="https://github.com/candycat1992/Unity_Shaders_Book#%E7%AC%AC%E5%9B%9B%E7%AB%A0%E5%8B%98%E8%AF%AF">candycat1992/Unity_Shaders_Book</a></p><p>闫老师的推导十分简洁易懂，我也尽量把过程补充到文章中，读者看了我相信肯定也能跟着思路把变换公式推导出来。</p><p>在读本文的过程中，也推荐参考上面提到的视频和 pdf 互相参考，本文是视频中推导的详细笔记，冯乐乐的 pdf 中虽然没有投影变换的推导，但是在很多地方都把理论讲的十分清晰，例如必要的数学基础和各种图形学概念的讲解。</p><span id="more"></span><h2 id="线性变换">线性变换</h2><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>a</mi><mi>x</mi><mo>+</mo><mi>b</mi><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>c</mi><mi>x</mi><mo>+</mo><mi>d</mi><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l}x^{\prime}=a x+b y \\\ y^{\prime}=c x+d y\end{array} </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal">a</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">b</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal">c</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p>如果我们可以把变换写成这样一种形式，矩阵乘以输入坐标等于输出坐标，这样可以叫做线性变换。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>c</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{ll}a &amp; b \\\ c &amp; d\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi mathvariant="bold">x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi mathvariant="bold">M</mi><mi mathvariant="bold">x</mi></mrow><annotation encoding="application/x-tex">\mathbf{x}^{\prime}=\mathbf{M} \mathbf{x}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8019em;"></span><span class="mord"><span class="mord mathbf">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6861em;"></span><span class="mord mathbf">Mx</span></span></span></span></span></p><h3 id="Scale-Matrix">Scale Matrix</h3><p><img src="http://img.frankorz.com/2d-scale-2.png" alt="Transformation%206c54d524cd134bc0943ed5335afa2508/Untitled.png"></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>s</mi><mi>x</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>s</mi><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l}x^{\prime}=s x \\\ y^{\prime}=s y\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">sy</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p>其变换矩阵：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>s</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>s</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{ll}s &amp; 0 \\\ 0 &amp; s\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">s</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><h3 id="Scale-Non-Uniform">Scale (Non-Uniform)</h3><p>x y 可以不均匀地缩放</p><p><img src="http://img.frankorz.com/2d-scale.png" alt="201.png"></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>x</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>y</mi></msub></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{ll}s_{x} &amp; 0 \\\ 0 &amp; s_{y}\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><h3 id="Reflection-Matrix">Reflection Matrix</h3><p><img src="http://img.frankorz.com/2d-reflection.png" alt="202.png"></p><p>Horizontal reflection:</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mo>−</mo><mi>x</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l}x^{\prime}=-x \\\ y^{\prime}=y\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">−</span><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mn>1</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{cc}-1 &amp; 0 \\\ 0 &amp; 1\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><h3 id="Shear-Matrix">Shear Matrix</h3><p><img src="http://img.frankorz.com/2d-shear.png" alt="203.png"></p><h3 id="2D-Rotation-Matrix">2D Rotation Matrix</h3><p><img src="http://img.frankorz.com/2d-rotation-deduce.png" alt="204.png"></p><p><img src="http://img.frankorz.com/2d-rotation.png" alt="205.png"></p><h2 id="齐次坐标">齐次坐标</h2><h3 id="Translation">Translation</h3><p>平移变换非常特殊。</p><p><img src="http://img.frankorz.com/2d-translation.png" alt="206.png"></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>x</mi><mo>+</mo><msub><mi>t</mi><mi>x</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>y</mi><mo>+</mo><msub><mi>t</mi><mi>y</mi></msub></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l}x^{\prime}=x+t_{x} \\\ y^{\prime}=y+t_{y}\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p>写出来简单，但是两个式子不能写成线性变换的形式。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>c</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{ll}a &amp; b \\\ c &amp; d\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><p>只能写成：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>c</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>+</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>t</mi><mi>y</mi></msub></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right]=\left[\begin{array}{ll}a &amp; b \\\ c &amp; d\end{array}\right]\left[\begin{array}{l}x \\\ y\end{array}\right]+\left[\begin{array}{l}t_{x} \\\ t_{y}\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><p>因此平移变换并不是线性变换。</p><p>但是我们不希望将平移变换看作一个特殊的例子，那么有没有办法将缩放、错切、平移等变换用一种统一的方式来表示？</p><p>在计算机科学，永远要考虑 “Trade-Off”。数据结构中不同降低时间复杂度的办法都会引入空间复杂度。如果两者都能低就很好，但更多时候是非此即彼的事情。“No Free Lunch Theory”。</p><p><img src="http://img.frankorz.com/2d-translation-ppt.png" alt="207.png"></p><p>引入齐次坐标，可以通过增加一个维度来将平移变换也写成矩阵乘一个点的形式。</p><p>向量具有平移不变性，因此后面是 (x, y, 0)，平移变换后也不变。</p><p>我们也可以通过 w 分量来推出我们操作的结果：</p><p>Valid operation if w-coordinate of result is 1 or 0</p><ul><li>vector + vector = vector</li><li>point – point = vector</li><li>point + vector = point</li><li>point + point = ??</li></ul><h3 id="Affine-Transformations-仿射变换">Affine Transformations 仿射变换</h3><p>Affine map = linear map + translation</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>c</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>⋅</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>+</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>t</mi><mi>y</mi></msub></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\left(\begin{array}{l}x^{\prime} \\\ y^{\prime}\end{array}\right)=\left(\begin{array}{ll}a &amp; b \\\ c &amp; d\end{array}\right) \cdot\left(\begin{array}{l}x \\\ y\end{array}\right)+\left(\begin{array}{l}t_{x} \\\ t_{y}\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">)</span></span></span></span></span></span></span></p><p>Using homogenous coordinates:</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>c</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>⋅</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\left(\begin{array}{l}x^{\prime} \\\ y^{\prime} \\\ 1\end{array}\right)=\left(\begin{array}{ccc}a &amp; b &amp; t_{x} \\\ c &amp; d &amp; t_{y} \\\ 0 &amp; 0 &amp; 1\end{array}\right) \cdot\left(\begin{array}{l}x \\\ y \\\ 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">c</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="2D-Transformations">2D Transformations</h3><h3 id="Scale">Scale</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">S</mi><mrow><mo fence="true">(</mo><msub><mi>s</mi><mi>x</mi></msub><mo separator="true">,</mo><msub><mi>s</mi><mi>y</mi></msub><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>x</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>y</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext> 1 </mtext></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{S}\left(s_{x}, s_{y}\right)=\left(\begin{array}{ccc}s_{x} &amp; 0 &amp; 0 \\\ 0 &amp; s_{y} &amp; 0 \\\ 0 &amp; 0 &amp; \text { 1 }\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord mathbf">S</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord"> 1 </span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="Rotation">Rotation</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">R</mi><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{R}(\alpha)=\left(\begin{array}{ccc}\cos \alpha &amp; -\sin \alpha &amp; 0 \\\ \sin \alpha &amp; \cos \alpha &amp; 0 \\\ 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathbf">R</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="Translation-2">Translation</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">T</mi><mrow><mo fence="true">(</mo><msub><mi>t</mi><mi>x</mi></msub><mo separator="true">,</mo><msub><mi>t</mi><mi>y</mi></msub><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{T}\left(t_{x}, t_{y}\right)=\left(\begin{array}{ccc}1 &amp; 0 &amp; t_{x} \\\ 0 &amp; 1 &amp; t_{y} \\\ 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord mathbf">T</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="逆变换">逆变换</h3><p><img src="http://img.frankorz.com/inverse-transform.png" alt="209.png"></p><p><img src="http://img.frankorz.com/translate-then-rotate.png" alt="2010.png"></p><p><img src="http://img.frankorz.com/rotate-then-translate.png" alt="2011.png"></p><p>因此变换顺序是很重要的，不满足交换律。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>R</mi><mn>45</mn></msub><mo>⋅</mo><msub><mi>T</mi><mrow><mo stretchy="false">(</mo><mn>1</mn><mo separator="true">,</mo><mn>0</mn><mo stretchy="false">)</mo></mrow></msub><mo mathvariant="normal">≠</mo><msub><mi>T</mi><mrow><mo stretchy="false">(</mo><mn>1</mn><mo separator="true">,</mo><mn>0</mn><mo stretchy="false">)</mo></mrow></msub><mo>⋅</mo><msub><mi>R</mi><mn>45</mn></msub></mrow><annotation encoding="application/x-tex">R_{45} \cdot T_{(1,0)} \neq T_{(1,0)} \cdot R_{45}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">45</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.0496em;vertical-align:-0.3552em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.5198em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mopen mtight">(</span><span class="mord mtight">1</span><span class="mpunct mtight">,</span><span class="mord mtight">0</span><span class="mclose mtight">)</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3552em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel"><span class="mrel"><span class="mord vbox"><span class="thinbox"><span class="rlap"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="inner"><span class="mord"><span class="mrel"></span></span></span><span class="fix"></span></span></span></span></span><span class="mrel">=</span></span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.0385em;vertical-align:-0.3552em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.5198em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mopen mtight">(</span><span class="mord mtight">1</span><span class="mpunct mtight">,</span><span class="mord mtight">0</span><span class="mclose mtight">)</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3552em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">45</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></span></p><p>矩阵是从右到左运算的：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mo stretchy="false">(</mo><mn>1</mn><mo separator="true">,</mo><mn>0</mn><mo stretchy="false">)</mo></mrow></msub><mo>⋅</mo><msub><mi>R</mi><mn>45</mn></msub><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mn>4</mn><msup><mn>5</mn><mo lspace="0em" rspace="0em">∘</mo></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>sin</mi><mo>⁡</mo><mn>4</mn><msup><mn>5</mn><mo lspace="0em" rspace="0em">∘</mo></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>sin</mi><mo>⁡</mo><mn>4</mn><msup><mn>5</mn><mo lspace="0em" rspace="0em">∘</mo></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mn>4</mn><msup><mn>5</mn><mo lspace="0em" rspace="0em">∘</mo></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T_{(1,0)} \cdot R_{45}\left[\begin{array}{l}x \\\ y \\\ 1\end{array}\right]=\left[\begin{array}{ccc}1 &amp; 0 &amp; 1 \\\ 0 &amp; 1 &amp; 0 \\\ 0 &amp; 0 &amp; 1\end{array}\right]\left[\begin{array}{ccc}\cos 45^{\circ} &amp; -\sin 45^{\circ} &amp; 0 \\\ \sin 45^{\circ} &amp; \cos 45^{\circ} &amp; 0 \\\ 0 &amp; 0 &amp; 1\end{array}\right]\left[\begin{array}{l}x \\\ y \\\ 1\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0385em;vertical-align:-0.3552em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.5198em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mopen mtight">(</span><span class="mord mtight">1</span><span class="mpunct mtight">,</span><span class="mord mtight">0</span><span class="mclose mtight">)</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3552em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">45</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">4</span><span class="mord"><span class="mord">5</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6741em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∘</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">4</span><span class="mord"><span class="mord">5</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6741em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∘</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">4</span><span class="mord"><span class="mord">5</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6741em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∘</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">4</span><span class="mord"><span class="mord">5</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6741em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∘</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>矩阵没有交换律，但有结合律。</p><!-- ![](http://img.frankorz.com/MommyTalk1596544330598.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544330598.svg"/></div><h2 id="三维变换">三维变换</h2><p>齐次坐标系下的三维变换可以写成下面的形式</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msup><mi>z</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>c</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>d</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>e</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>f</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>g</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>h</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>i</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>⋅</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>z</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\left(\begin{array}{l}x^{\prime} \\\ y^{\prime} \\\ z^{\prime} \\\ 1\end{array}\right)=\left(\begin{array}{llll}a &amp; b &amp; c &amp; t_{x} \\\ d &amp; e &amp; f &amp; t_{y} \\\ g &amp; h &amp; i &amp; t_{z} \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right) \cdot\left(\begin{array}{l}x \\\ y \\\ z \\\ 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">d</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">e</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">h</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">c</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">i</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="Scale-2">Scale</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">S</mi><mrow><mo fence="true">(</mo><msub><mi>s</mi><mi>x</mi></msub><mo separator="true">,</mo><msub><mi>s</mi><mi>y</mi></msub><mo separator="true">,</mo><msub><mi>s</mi><mi>z</mi></msub><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>x</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>y</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>s</mi><mi>z</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{S}\left(s_{x}, s_{y}, s_{z}\right)=\left(\begin{array}{cccc}s_{x} &amp; 0 &amp; 0 &amp; 0 \\\ 0 &amp; s_{y} &amp; 0 &amp; 0 \\\ 0 &amp; 0 &amp; s_{z} &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord mathbf">S</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">s</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="Translation-3">Translation</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">T</mi><mrow><mo fence="true">(</mo><msub><mi>t</mi><mi>x</mi></msub><mo separator="true">,</mo><msub><mi>t</mi><mi>y</mi></msub><mo separator="true">,</mo><msub><mi>t</mi><mi>z</mi></msub><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>t</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{T}\left(t_{x}, t_{y}, t_{z}\right)=\left(\begin{array}{cccc}1 &amp; 0 &amp; 0 &amp; t_{x} \\\ 0 &amp; 1 &amp; 0 &amp; t_{y} \\\ 0 &amp; 0 &amp; 1 &amp; t_{z} \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord mathbf">T</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">t</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><h3 id="Rotation-2">Rotation</h3><h4 id="绕轴旋转">绕轴旋转</h4><p>Rotation around x-, y-, or z-axis</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi mathvariant="bold">R</mi><mi>x</mi></msub><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{R}_{x}(\alpha)=\left(\begin{array}{cccc}1 &amp; 0 &amp; 0 &amp; 0 \\\ 0 &amp; \cos \alpha &amp; -\sin \alpha &amp; 0 \\\ 0 &amp; \sin \alpha &amp; \cos \alpha &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathbf">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi mathvariant="bold">R</mi><mi>y</mi></msub><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mo>−</mo><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{R}_{y}(\alpha)=\left(\begin{array}{cccc}\cos \alpha &amp; 0 &amp; \sin \alpha &amp; 0 \\\ 0 &amp; 1 &amp; 0 &amp; 0 \\\ -\sin \alpha &amp; 0 &amp; \cos \alpha &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathbf">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi mathvariant="bold">R</mi><mi>z</mi></msub><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>sin</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>cos</mi><mo>⁡</mo><mi>α</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{R}_{z}(\alpha)=\left(\begin{array}{cccc}\cos \alpha &amp; -\sin \alpha &amp; 0 &amp; 0 \\\ \sin \alpha &amp; \cos \alpha &amp; 0 &amp; 0 \\\ 0 &amp; 0 &amp; 1 &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathbf">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p><img src="http://img.frankorz.com/rotate-around-x-axis.png" alt="2012.png"></p><p>绕着 x 轴旋转，说明 y 和 z 都是在进行旋转的，但 x 不变。因此绕 x 轴的旋转矩阵相比二维的旋转矩阵，第一行是不变的。中间部分和二维旋转矩阵一样。</p><p>绕 y 轴旋转不一样，这里涉及到我们要如何思考轴的相互顺序。</p><p>根据右手螺旋定则，x 叉乘 y 得到 z，y 叉乘 z 得到 x。但 z 叉乘 x 才能得到 y，是反的，因此 Ry 部分不一样。</p><h4 id="Rodrigues’-Rotation-Formula">Rodrigues’ Rotation Formula</h4><p>我们能够解决一些简单的问题，复杂的问题可以转化成一些简单问题的组合。</p><p>给定根据三个轴的旋转，能否将某一个方向旋转到任意一个方向上去？</p><p><img src="http://img.frankorz.com/compose-any-3d-rotation.png" alt="2013.png"></p><p>Rotation by angle α round axis n</p><p>有人将任意一个旋转分解成通过 x y z 轴分别做旋转。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">R</mi><mo stretchy="false">(</mo><mi mathvariant="bold">n</mi><mo separator="true">,</mo><mi>α</mi><mo stretchy="false">)</mo><mo>=</mo><mi>cos</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mi mathvariant="bold">I</mi><mo>+</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>cos</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mi mathvariant="bold">n</mi><msup><mi mathvariant="bold">n</mi><mi>T</mi></msup><mo>+</mo><mi>sin</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>α</mi><mo stretchy="false">)</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><msub><mi>n</mi><mi>z</mi></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>n</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>n</mi><mi>z</mi></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><msub><mi>n</mi><mi>x</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mo>−</mo><msub><mi>n</mi><mi>y</mi></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>n</mi><mi>x</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\mathbf{R}(\mathbf{n}, \alpha)=\cos (\alpha) \mathbf{I}+(1-\cos (\alpha)) \mathbf{n} \mathbf{n}^{T}+\sin (\alpha)\left(\begin{array}{ccc}0 &amp; -n_{z} &amp; n_{y} \\\ n_{z} &amp; 0 &amp; -n_{x} \\\ -n_{y} &amp; n_{x} &amp; 0\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathbf">R</span><span class="mopen">(</span><span class="mord mathbf">n</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">cos</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mord mathbf">I</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.1413em;vertical-align:-0.25em;"></span><span class="mop">cos</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">))</span><span class="mord mathbf">n</span><span class="mord"><span class="mord mathbf">n</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="mop">sin</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.0037em;">α</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,84c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-92c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">−</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.875em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='3.600em' viewBox='0 0 875 3600'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,9c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-144c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>证明过程可以参考闫令琪老师的证明：</p><p><a href="https://sites.cs.ucsb.edu/~lingqi/teaching/resources/GAMES101_Lecture_04_supp.pdf">GAMES101_Lecture_04_supp.pdf</a></p><p>公式给了我们一个旋转矩阵，定义中给了我们一个旋转轴 n 和旋转角度 α。旋转角度好理解，但旋转轴似乎不能这么简单地定义。因为一个旋转轴首先跟起点有关系，然后跟方向有关系，只给一个向量是不是不太合适？</p><p>假如说沿着 y 轴旋转，跟沿着 x 和 n 各等于 1 并且也是沿着 y 方向的向量。方向一样，但起点不一样，结果肯定也是不一样的。因此我们说沿着某个轴的方向旋转，就默认了是过原点的，这样起点就在原点上，方向就是 n 方向。</p><p>那么如果轴 n 可以平移怎么办？那么我们可以将其进行变换的分解。如果我们要沿着任意轴旋转且轴的起点不在原点，我们可以将所有的东西移到起点为原点的条件下，再旋转，再移回去。</p><h2 id="四元数相关">四元数相关</h2><p>我们上面所用到的旋转矩阵是不太适合做插值的，例如二维旋转 10 度的旋转矩阵加二维旋转 20 度的旋转矩阵求平均，不能得到二维旋转 15 度的旋转矩阵。四元数在这方面方便很多。</p><h2 id="View-Camera-Transformation-视图变换">View/Camera Transformation 视图变换</h2><h3 id="定义相机">定义相机</h3><p><img src="http://img.frankorz.com/perform-view-transformation.png" alt="2014.png"></p><p>定义一个相机需要三个变量，位置，朝向，和一个向上的方向。</p><h3 id="视图变换">视图变换</h3><p><img src="http://img.frankorz.com/perform-view-transformation-2.png" alt="2015.png"></p><p>当相机和要拍的东西一起移动的时候，那拍出来的相片是一样的。也就是说，当我们移动物体时，只要同时以相同的方式移动相机，没有相对位置，那么得出来的结果就是一样的。</p><p>如果我们将相机放在一个固定的位置上，那么所有东西在移动时，都可以认为是其他东西在移动，而相机一直在原点不动。相机永远往 -z 方向看，以 y 轴为向上方向（右手坐标系，符合 OpenGL 传统）。这是约定俗成的。相机放在原点有很多好处，能简化计算。</p><p>从坐标空间的角度来看，就是将物体和相机从世界空间转到观察空间（摄像机空间）。</p><p><img src="http://img.frankorz.com/perform-view-transformation-3.png" alt="2016.png"></p><p>我们要将相机移到原点，就需要先把相机中心 e 平移到原点，还得把观察的方向 g 移到 -z 上，再把向上方向 t 旋转到 y 方向上，把 g X t 的方向移到 x 方向上。</p><p>下面将这系列操作转为矩阵操作。</p><h3 id="求视图变换矩阵">求视图变换矩阵</h3><ol><li><strong>先把相机中心 e 平移到原点</strong></li></ol><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>T</mi><mrow><mi>v</mi><mi>i</mi><mi>e</mi><mi>w</mi></mrow></msub><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><msub><mi>x</mi><mi>e</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><msub><mi>y</mi><mi>e</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><msub><mi>z</mi><mi>e</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T_{v i e w}=\left[\begin{array}{cccc}1 &amp; 0 &amp; 0 &amp; -x_{e} \\\ 0 &amp; 1 &amp; 0 &amp; -y_{e} \\\ 0 &amp; 0 &amp; 1 &amp; -z_{e} \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02691em;">w</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>平移矩阵写好后，接下来写旋转矩阵。</p><ol start="2"><li><strong>把观察的方向 g 旋转到 -z 上，把向上方向 t 旋转到 y 方向上，g X t (g 叉乘 t)的方向旋转到 x 方向上</strong></li></ol><ul><li>Rotate g to -z , t to y,  g X t To x （世界空间到观察空间）</li><li>Consider its inverse rotation: x to  g X t , y to t, z to -g （观察空间到世界空间）</li></ul><p>我们可以反过来写，例如把 x 轴 (1,0,0 ) 旋转到 g X t 方向上的旋转矩阵，就比 g X t 移到 x 轴的旋转矩阵要好写很多，而这两个旋转矩阵是互逆的。写出 x 轴旋转到 g X t 方向的旋转矩阵后，再求其逆变换就是我们所需要的 g X t 移到 x 轴的旋转矩阵。</p><p>x to g X t , y to t, z to -g 的旋转矩阵就是：</p><p>这里 z to -g 是因为我们定义相机的坐标空间为右手坐标系。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msubsup><mi>R</mi><mrow><mi>v</mi><mi>i</mi><mi>e</mi><mi>w</mi></mrow><mrow><mo>−</mo><mn>1</mn></mrow></msubsup><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>x</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>x</mi><mi>t</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>x</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>y</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>y</mi><mi>t</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>y</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>z</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>z</mi><mi>t</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>z</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">R_{v i e w}^{-1}=\left[\begin{array}{cccc}x_{\hat{g} \times \hat{t}} &amp; x_{t} &amp; x_{-g} &amp; 0 \\\ y_{\hat{g} \times \hat{t}} &amp; y_{t} &amp; y_{-g} &amp; 0 \\\ z_{\hat{g} \times \hat{t}} &amp; z_{t} &amp; z_{-g} &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1311em;vertical-align:-0.267em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-2.433em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02691em;">w</span></span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.267em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.9388em;vertical-align:-2.2194em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.7194em;"><span style="top:-4.8794em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.6331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1406em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.2194em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.7194em;"><span style="top:-4.8794em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.6331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1406em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.2194em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.7194em;"><span style="top:-4.8794em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.6331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1406em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.2194em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.7194em;"><span style="top:-4.8794em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.6331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.1406em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.2194em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>要验证也很简单，用该旋转矩阵变换 x 轴就能得到 g X t 的方向。</p><!-- ![](http://img.frankorz.com/MommyTalk1596544303744.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544303744.svg"/></div><p>那么我们的旋转矩阵就能通过对上面的矩阵求逆得出：</p><p>因为旋转矩阵是正交矩阵，因此要求逆矩阵，对其转置即可。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>R</mi><mrow><mi>v</mi><mi>i</mi><mi>e</mi><mi>w</mi></mrow></msub><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>x</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>y</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>z</mi><mrow><mover accent="true"><mi>g</mi><mo>^</mo></mover><mo>×</mo><mover accent="true"><mi>t</mi><mo>^</mo></mover></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>x</mi><mi>t</mi></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>y</mi><mi>t</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>z</mi><mi>t</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><msub><mi>x</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>y</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>z</mi><mrow><mo>−</mo><mi>g</mi></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">R_{v i e w}=\left[\begin{array}{cccc}x_{\hat{g} \times \hat{t}} &amp; y_{\hat{g} \times \hat{t}} &amp; z_{\hat{g} \times \hat{t}} &amp; 0 \\\ x_{t} &amp; y_{t} &amp; z_{t} &amp; 0 \\\ x_{-g} &amp; y_{-g} &amp; z_{-g} &amp; 0 \\\ 0 &amp; 0 &amp; 0 &amp; 1\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight" style="margin-right:0.02691em;">w</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8463em;vertical-align:-2.1731em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6731em;"><span style="top:-4.8331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.5869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.1731em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6731em;"><span style="top:-4.8331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.5869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.1731em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6731em;"><span style="top:-4.8331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3448em;"><span style="top:-2.4298em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord accent mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.6944em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.2222em;"><span class="mord mtight">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1944em;"><span></span></span></span></span></span><span class="mbin mtight">×</span><span class="mord accent mtight"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8785em;"><span style="top:-2.7em;"><span class="pstrut" style="height:2.7em;"></span><span class="mord mathnormal mtight">t</span></span><span style="top:-2.8841em;"><span class="pstrut" style="height:2.7em;"></span><span class="accent-body" style="left:-0.1667em;"><span class="mord mtight">^</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4063em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.5869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2583em;"><span style="top:-2.55em;margin-left:-0.044em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.1869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.1731em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6731em;"><span style="top:-4.8331em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.5869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.3869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.1869em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.1731em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>这样我们世界空间到观察空间的变换矩阵就能得出来了：M_view=R_view·T_view</p><!-- ![](http://img.frankorz.com/MommyTalk1596544270087.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544270087.svg"/></div><p>其中 V_g×t 为 g×t 的向量，V_e 为相机原点。</p><p>相机需要进行这种变换，变换到约定俗成的位置（原点）上去，那么其他所有物体也需要做这样的变换，这样相对运动不变。这个就是<strong>视图变换</strong>。</p><p>模型变换和视图变换经常一起被称为<strong>模型视图变换（ModelView Transformation）</strong>。</p><h2 id="Projection-Transformation-投影变换">Projection Transformation 投影变换</h2><p>Projection in Computer Graphics</p><ul><li>3D to 2D</li><li>Orthographic projection</li><li>Perspective projection</li></ul><p><img src="http://img.frankorz.com/projection-transformation.png" alt="2017.png"></p><h3 id="Perspective-projection-vs-orthographic-projection">Perspective projection vs. orthographic projection</h3><p><img src="http://img.frankorz.com/vs-two-projection.png" alt="2018.png"></p><h3 id="Orthographic-Projection-正交投影">Orthographic Projection 正交投影</h3><h4 id="方法一">方法一</h4><p>A simple way of understanding</p><ul><li>Camera located at origin, looking at -Z, up at Y (looks familiar?)</li><li>Drop Z coordinate</li><li>Translate and scale the resulting rectangle to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mo>−</mo><mn>1</mn><mo separator="true">,</mo><mn>1</mn><msup><mo stretchy="false">]</mo><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">[-1,1]^{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0641em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mord">−</span><span class="mord">1</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">]</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></li></ul><p><img src="http://img.frankorz.com/remove-z.png" alt="2019.png"></p><p>将坐标中的 z 扔掉，如何区分物体的前和后？</p><p>感兴趣可以参考 <a href="https://catlikecoding.com/unity/tutorials/rendering/part-1/">Catlikecoding Render 1</a> 中 Orthographic Camera 部分。</p><h4 id="方法二">方法二</h4><p>In general, we want to map a cuboid [l, r] x [b, t] x [<strong>f, n</strong>] to the “canonical (正则、规范、标准)” cube [-1,1]^3</p><p>我们在 x 轴上定义左和右 [l, r] （左比右小），y 轴上定义下和上 [b, t]（下比上小），z 轴上定义远和近 [f, n]（远比近小）。</p><p>不管 x, y 多大，都将其映射到 [-1, 1] 之间。这也是个约定俗成的事情，能方便计算。这样任何空间中的长方体，都可以映射成一个标准的立方体。</p><p>这也是**标准化设备坐标（NDC）**的定义。</p><p>上面的左比右小是相对于 x 轴来说的，下比上小是相对于 y 轴说的，但 z 轴上不太直观，因为我们推导的 NDC 是右手坐标系，（相机）看的是 -z 方向，因此一个面离我们远，说明 z 值更小。离我们近，说明 z 值更大。</p><p><img src="http://img.frankorz.com/ndc.png" alt="2020.png"></p><p>在标准化设备坐标系中 OpenGL 使用的是左手坐标系，因为左手系在这一点上会比较方便。但也会造成别的问题：x × y ≠ z。</p><p>这里可以参考：LearnOpenGL 进入3D 的 <a href="https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/#3d">右手坐标系</a> 部分。</p><p>Slightly different orders (to the “simple way”)</p><ul><li>Center cuboid by translating 移到原点</li><li>Scale into “canonical” cube 映射到 [-1, 1]，也就是缩放</li></ul><p>Translate (<strong>center</strong> to origin) first, then scale (length/width/height to <strong>2</strong>) 因为 -1 到 1 的长度就是 2。</p><p>因此我们可以用一个平移矩阵和缩放矩阵来求出正交投影矩阵，先平移，再缩放：</p><!-- ![](http://img.frankorz.com/MommyTalk1596544157453.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544157453.svg"/></div><p>如果把长方体范围缩成立方体，物体不会被拉伸吗？<br>会，这就涉及到另外一个变换。在所有变换做完之后，还要做一个视口变换，还要做一次拉伸。</p><h3 id="Perspective-Projection-透视投影">Perspective Projection 透视投影</h3><ul><li>Most common in Computer Graphics, art, visual system</li><li>Further objects are smaller</li><li>Parallel lines not parallel; converge to single point</li></ul><p><img src="http://img.frankorz.com/image-plane.png" alt="2021.png"></p><p><img src="http://img.frankorz.com/projection-wiki.png" alt="2022.png"></p><p>平行线就是永不相交的两条线，但照片上铁轨是平行的，却交于一点。透视投影的情况下，一个平面相当于被投影到了另外一个平面上，这种情况下就不是平行线了。</p><h4 id="Recall">Recall</h4><ul><li>Before we move on</li><li>Recall: property of homogeneous coordinates<ul><li>(x, y, z, 1),(k x, k y, k z, k !=0), (x z, y z, z^2, z !=0) all represent the same point (x, y, z) in 3D<ul><li>只要一个点乘于一个不为零的 k，那么它们还是一个点。那么我们还可以将其乘以 z，其表示的点还是空间中同样的点。下面我们会用到。</li></ul></li><li>e.g. (1, 0, 0, 1) and (2, 0, 0, 2) both represent (1, 0, 0)</li></ul></li><li>Simple, but useful</li></ul><h4 id="怎么做透视投影">怎么做透视投影</h4><p>How to do perspective projection</p><ul><li>First “squish” the frustum into a cuboid (n→n, f→f) (M_persp→ortho)</li><li>Do orthographic projection ( M_ortho, already known!)</li></ul><p><img src="http://img.frankorz.com/frustum-and-cuboid.png" alt="2023.png"></p><p>透视投影的视锥体中，远的平面比近的平面要大。</p><p>我们可以把远的平面往里“挤”，“挤”到同一高度且同近平面大小，“挤”成空间中的长方体，再做正交投影就解决了。</p><p>我们已经知道正交投影怎么做了，因此剩下的就是“挤”这个操作。</p><p>在这个过程中，需要规定：</p><ul><li>近平面上任何一个点不变。</li><li>Z 值不变</li><li>远平面的中心也不会发生变化</li></ul><h4 id="求出任何一个点挤压后的-x’-y’-值">求出任何一个点挤压后的 x’, y’ 值</h4><p>要做“挤”的操作，首先要知道任何一个点的 x, y 值是怎么变化的。因为我们任何一个面都要挤成近平面大小，我们也可以将 (x,y,z) 投影到近平面上求出变换后的 x’, y’ 值。对于 x, y 值来说，这种变换是线性的。</p><p>因此，在视锥体的上面一部分中，我们可以通过相似三角形求出变换后的 x’, y’ 值。（z’ 值不是线性变化的，后面会提到）</p><p><img src="http://img.frankorz.com/projection-similar-triangle.png" alt="2024.png"></p><p>上图中，n 为近平面的 z 值，z 为任何一个点(x,y,z)中的 z 值。</p><p>挤压后的 y’ 值，我们可以通过相似三角形原理得出：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mfrac><mi>n</mi><mi>z</mi></mfrac><mi>y</mi></mrow><annotation encoding="application/x-tex">y^{\prime}=\frac{n}{z} y</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9963em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.7936em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span></span></p><p>同理可得挤压后的 x’ 值：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mfrac><mi>n</mi><mi>z</mi></mfrac><mi>x</mi></mrow><annotation encoding="application/x-tex">x^{\prime}=\frac{n}{z} x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8019em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.7936em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord mathnormal">x</span></span></span></span></span></p><p>在齐次坐标系中，对于变换后的 (x’, y’, z’)  我们只剩下 z’ 未知。</p><p>这里给矩阵乘了 z，其表示的点还是空间中同样的点。</p><!-- ![](http://img.frankorz.com/MommyTalk1596544132635.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544132635.svg"/></div><p>也就是说 (x,y,z,1) 经过 Mpersp→ortho 矩阵“挤压”后，会被映射到 (nx,ny,??,z)：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msubsup><mi>M</mi><mrow><mi>p</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>p</mi><mo>→</mo><mi>o</mi><mi>r</mi><mi>t</mi><mi>h</mi><mi>o</mi></mrow><mrow><mo stretchy="false">(</mo><mn>4</mn><mo>×</mo><mn>4</mn><mo stretchy="false">)</mo></mrow></msubsup><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>z</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>1</mn></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>n</mi><mi>x</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>n</mi><mi>y</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>  unknown </mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mi>z</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">M_{p e r s p \rightarrow o r t h o}^{(4 \times 4)}\left(\begin{array}{c}x \\\ y \\\ z \\\ 1\end{array}\right)=\left(\begin{array}{c}n x \\\ n y \\\ \text { unknown } \\\ z\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.0448em;"><span style="top:-2.3987em;margin-left:-0.109em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">ers</span><span class="mord mathnormal mtight">p</span><span class="mrel mtight">→</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">or</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">h</span><span class="mord mathnormal mtight">o</span></span></span></span><span style="top:-3.2198em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mopen mtight">(</span><span class="mord mtight">4</span><span class="mbin mtight">×</span><span class="mord mtight">4</span><span class="mclose mtight">)</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.4374em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord text"><span class="mord"> unknown </span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>根据上式，我们可以得出部分的 Mpersp→ortho 矩阵：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>M</mi><mrow><mi>p</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>p</mi><mo>→</mo><mi>o</mi><mi>r</mi><mi>t</mi><mi>h</mi><mi>o</mi></mrow></msub><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="left left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>n</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>n</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mo stretchy="false">?</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo stretchy="false" lspace="0em" rspace="0em">?</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo stretchy="false" lspace="0em" rspace="0em">?</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo stretchy="false" lspace="0em" rspace="0em">?</mo></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext> </mtext><mn>0</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">M_{p e r s p \rightarrow o r t h o}=\left(\begin{array}{llll}n &amp; 0 &amp; 0 &amp; 0 \\\ 0 &amp; n &amp; 0 &amp; 0 \\\ ? &amp; ? &amp; ? &amp; ? \\\ 0 &amp; 0 &amp; 1 &amp; 0\end{array}\right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9694em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.109em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">p</span><span class="mord mathnormal mtight">ers</span><span class="mord mathnormal mtight">p</span><span class="mrel mtight">→</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">or</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">h</span><span class="mord mathnormal mtight">o</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,-36,557 l0,1284c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189l0,-1292c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mclose">?</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mspace"> </span><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mclose">?</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mclose">?</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mclose">?</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.875em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.875em' height='4.800em' viewBox='0 0 875 4800'><path d='M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,1209c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558l0,-1344c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>对于 z，我们不知道 z 会怎么变，我们只规定了近的平面上和远的平面上 z 不变。</p><p>Observation: the third row is responsible for z’</p><ul><li>Any point on the near plane will not change<ul><li>近平面的点不变，对于任何 (x,y,n,1) 运算完了一定还是 (x,y,n,1)</li></ul></li><li>Any point’s z on the far plane will not change<ul><li>远平面的点，虽然 x, y 会变化，但是 z 没有变。</li></ul></li></ul><h4 id="求出任何一个点挤压后的-z’-值">求出任何一个点挤压后的 z’ 值</h4><p>由“近平面的点不变，对于任何 (x,y,n,1) 运算完了一定还是 (x,y,n,1)”可得：</p><p>这里给矩阵乘了 n，其表示的点还是空间中同样的点。</p><!-- ![](http://img.frankorz.com/MommyTalk1596544077215.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544077215.svg"/></div><p>因此 Mpersp→ortho 第三行一定是 (0,0,A,B) 的形式，因为：</p><!-- ![](http://img.frankorz.com/MommyTalk1596544091556.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544091556.svg"/></div><p>由上式可得：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>A</mi><mi>n</mi><mo>+</mo><mi>B</mi><mo>=</mo><msup><mi>n</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">A n+B=n^{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></span></p><p>前面我们已经知道第三行前两个数是 0。</p><p>我们前面已经规定了远平面的中心经过 Mpersp→ortho 变换后也不会发生变化。</p><p>另外一个等式可以用远平面可以用其特殊的中心点得出，给中心点再乘个 f 可得：</p><!-- ![](http://img.frankorz.com/MommyTalk1596544046146.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544046146.svg"/></div><p>平截头体（Frustum）被压缩成长方体以后，内部的点的 z 值是更偏向于近平面还是更偏向于远平面？</p><p>可以参考 ScratchAPixel 的 <a href="https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix">The Perspective and Orthographic Projection Matrix</a></p><p><img src="http://img.frankorz.com/z-fighting.png" alt="2025.png"></p><p><a href="https://developer.nvidia.com/content/depth-precision-visualized">Depth Precision Visualized</a></p><h3 id="定义视锥">定义视锥</h3><p>前面提到了长方体近平面的 l, r, b, t，有没有更好的方法去定义这些呢？</p><p>vertical <strong>field-of-view (fovY)</strong> and <strong>aspect ratio</strong></p><p>我们现实中相机有视角的定义，也就是可以看到的角度的范围，也就是 field of view。广角相机就是可视角度比较大，对于视锥体来说，就是张的比较开。</p><p>垂直的可视角度就是 fovY。而相机的长宽比就是 aspect ratio。</p><p>我们也可以通过 fovY 和 aspect ratio，来推出水平的可视角度。</p><p><img src="http://img.frankorz.com/fovY.png" alt="2026.png"></p><p>How to convert from fovY and aspect to l, r, b, t?</p><p><img src="http://img.frankorz.com/fovY-2.png" alt="2027.png"></p><!-- ![](http://img.frankorz.com/MommyTalk1596544012560.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596544012560.svg"/></div><h3 id="完成推导正交投影矩阵">完成推导正交投影矩阵</h3><p><img src="http://img.frankorz.com/unity-camera-property.png" alt="2028.png"></p><p>正交投影没有 fovY，在 Unity 中，正交投影的参数由 Camera 组件中的参数 Size, Near, Far（Viewport Rect 暂时忽略）和 Game 视图的横纵比（aspect ratio）共同决定。</p><p>这里的 Near 是近裁面的距离，也就是 -n，Far 同理，等于 -f。</p><p>Size 属性用来更改视锥体竖直方向上高度的一半，也就是前面近平面的高度 t。</p><p>由此可得正交投影近远平面的高度 t-b 为：2·Size=2·t</p><p>正交投影近远平面的宽度 r-l 为：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>A</mi><mi>s</mi><mi>p</mi><mi>e</mi><mi>c</mi><mi>t</mi><mo>⋅</mo><mtext>近远平面的高度</mtext><mo>=</mo><mn>2</mn><mo>⋅</mo><mi>A</mi><mi>s</mi><mi>p</mi><mi>e</mi><mi>c</mi><mi>t</mi><mo>⋅</mo><mi>S</mi><mi>i</mi><mi>z</mi><mi>e</mi><mo>=</mo><mn>2</mn><mo>⋅</mo><mi>A</mi><mi>s</mi><mi>p</mi><mi>e</mi><mi>c</mi><mi>t</mi><mo>⋅</mo><mi>t</mi></mrow><annotation encoding="application/x-tex">Aspect\cdot \text{近远平面的高度}=2\cdot Aspect\cdot Size=2\cdot Aspect\cdot t</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">s</span><span class="mord mathnormal">p</span><span class="mord mathnormal">ec</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord text"><span class="mord cjk_fallback">近远平面的高度</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">s</span><span class="mord mathnormal">p</span><span class="mord mathnormal">ec</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal">i</span><span class="mord mathnormal">ze</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">A</span><span class="mord mathnormal">s</span><span class="mord mathnormal">p</span><span class="mord mathnormal">ec</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span></span></span></span></span></p><p><img src="http://img.frankorz.com/projection-property.png" alt="2029.png"></p><p><img src="http://img.frankorz.com/MommyTalk1596543978233.svg" alt=""></p><p>注意：这里的 n 和 f 是 -z 轴上的，代表近裁面和远裁面的 z 值，值为负数。</p><h3 id="完成推导透视投影矩阵">完成推导透视投影矩阵</h3><p>前面已经得出：</p><!-- ![](http://img.frankorz.com/1596543733403.svg) --><div align="center"><img src="http://img.frankorz.com/1596543733403.svg"/></div><!-- ![](http://img.frankorz.com/MommyTalk1596543871566.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596543871566.svg"/></div><p>注意：这里的 n 和 f 是 -z 轴上的，代表近裁面和远裁面的 z 值，值为负数。</p><p>通常我们透视投影的参数除了近裁面远裁面的距离外，还会有 fov 和 Aspect，且 r+l=0，因此整理公式可得：</p><!-- ![](http://img.frankorz.com/MommyTalk1596543953726.svg) --><div align="center"><img src="http://img.frankorz.com/MommyTalk1596543953726.svg"/></div><h2 id="后记">后记</h2><p>在长文的最后，我强烈推荐大家也手推一下各种变换，n, f 取 -z 轴上的 z 值或绝对值（也就是距离）得出来的变换矩阵也不一样，都推导一遍可以理解更深刻。</p><p>此外，我们也可以开始实现一个简单的 CPU 软光栅渲染器，我近期也在准备写一个软光栅，把必要的过程都推导一遍，到时候再写博文分享一下。</p>]]></content>
      
      
      <categories>
          
          <category> 图形学 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> 3D数学 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2020年5月技术导读</title>
      <link href="/2020/05/23/2020-05-tech-reading/"/>
      <url>/2020/05/23/2020-05-tech-reading/</url>
      
        <content type="html"><![CDATA[<p>最近一直在思考自己在技术学习上是否过于低效的问题。出现这个问题的缘由是，自己工作下班之后没多少精力和时间去留给自己学习。本来就有限的时间，如果还没有合适的学习计划，而是一昧凭兴趣去学一些离自己目标方向比较远的东西，可能会事倍功半。</p><p>就我而言，自己目前的水平更像是“磨”出来的，东学学西学学，什么都有点印象，但是专业方向的知识之间的连接却非常离散。读研的时候有大量时间给我“磨”，总归能学会点什么，但工作则不一样。</p><span id="more"></span><p>例如一本《Shader 入门精要》目前看了两次，但仍然害怕离开书来写 Shader。再者，一门技术很久不用，再拿起来也需要时间。为此，我总结了三点原因：</p><ol><li>工作中很少用到，游戏有特效师，自己也不会主动地思考怎样的 Shader 能为游戏带来更好的效果。</li><li>学的时候只光看了，跟着书敲了，但一直没有走出书本，去自己实现自己想要的功能。</li><li>觉得 Shader 像 CSS 一样，要记各种东西，凭经验实现效果，而这种知识自己最容易不用就忘。（当然这是错的，这也是为什么我在上期导读选择跟 Games101 图形学课程打基础）</li></ol><p>基于上面的原因，导读也是偏向自己打基础的方面。</p><p>上一期的博文在：<a href="http://frankorz.com/2020/04/13/2020-04-tech-reading/">《2020年4月技术导读》</a></p><h1>书籍</h1><h2 id="《软技能2：软件开发者职业生涯指南》">《软技能2：软件开发者职业生涯指南》</h2><p><a href="https://book.douban.com/subject/35043940/">豆瓣地址</a></p><p>其实正如上文所说，我学东西靠的是“磨”，但这并不是什么好的学习方式。而且除去编程，作为一个开发者，我们仍然有很多“软技能”需要注意，例如自己的职业规划、如何发现技能短板、如何推进职业生涯等等。</p><p>我相信不止是我，很多开发者也在不断地摸索如何成为一个目标中资深且专业的开发者。而本月出版的这本书就是为这方面而量身定做的，是著名的《软技能》的第二版。这本书目前看到的翻译都十分优秀，每次开卷都有益。如果你也想专注于自己的职业发展，我十分推荐这本书。</p><p>下面是书中介绍的十步学习法，自己根据它来调整了下学习计划：</p><p><img src="http://img.frankorz.com/study-method.png" alt="十步学习法"></p><h1>公开课</h1><h2 id="Games101-图形学入门">Games101 图形学入门</h2><p>这门公开课我已经在上期导读中提到：<a href="http://frankorz.com/2020/04/13/2020-04-tech-reading/#%E5%9B%BE%E5%BD%A2%E5%AD%A6">《2020年4月技术导读》</a>，再次提到是因为经过学习，我认为课程中理论部分讲的深入浅出，而且也能了解到当下流行的图形技术和它们是怎么跟我们所学的联系在一起。</p><p>闫老师提到他的学习方式是：Why, What then How，课程也是按照这种思路来设计的。先问为什么要学这个东西，然后问学的是什么，最后了解这东西具体是怎么运作的、怎么用。How 部分是最不重要的，也是最容易忘的部分，我们只需把握好 Why 和 What 部分即可。</p><p>那课程是怎么把知识点联系起来的呢，就凭借一个个 Why 来连接。拿光线追踪来举例，下面是我做的笔记：</p><p><img src="http://img.frankorz.com/ray-tracing-note.jpg" alt="光线追踪笔记截图"></p><p>如果你对笔记感兴趣，我也将其分享了出来：<a href="https://www.notion.so/frankorz/2cbfd6f8e62f40b5abb0b236b045d6b4">《光线追踪》</a>。</p><h2 id="The-Cherno-的游戏引擎教程">The Cherno 的游戏引擎教程</h2><p><a href="https://www.youtube.com/playlist?list=PLlrATfBNZ98dC-V-N3m0Go4deliWHPFwT">油管地址</a></p><p><a href="https://www.bilibili.com/video/BV1KE41117BD">B站搬运地址</a></p><p><a href="https://github.com/TheCherno/Hazel">教学版引擎仓库地址</a></p><p>Up 主 The Cherno 在自己的 CPP 和 OpenGL 教程之后，再基于两者推出了游戏引擎教程。引擎的仓库是跟着视频教程的进度而推进的，有兴趣的可以了解下。</p><h2 id="The-Missing-Semester-of-Your-CS-Education">The Missing Semester of Your CS Education</h2><p><a href="https://missing.csail.mit.edu/">课程地址</a></p><p><a href="https://www.bilibili.com/video/BV1x7411H7wa">B站搬运地址</a></p><p>这门公开课是通过知乎专栏<a href="https://zhuanlan.zhihu.com/p/139361685">《6.NULL：恨不相逢“未嫁时”》</a>了解到的。</p><blockquote><p>Classes teach you all about advanced topics within CS, from operating systems to machine learning, but there’s one critical subject that’s rarely covered, and is instead left to students to figure out on their own: proficiency with their tools. We’ll teach you how to master the command-line, use a powerful text editor, use fancy features of version control systems, and much more!</p></blockquote><p>简而言之，这门课不是教计算机的理论，而是介绍一些常用的工具，让你在面对问题的时候有更多的手段。自己可以看看一些不熟悉的主题查漏补缺。</p><h2 id="MIT-教授-Gilbert-Strang-「线性代数」课程">MIT 教授 Gilbert Strang 「线性代数」课程</h2><p><a href="https://ocw.mit.edu/resources/res-18-010-a-2020-vision-of-linear-algebra-spring-2020/index.htm">课程地址</a></p><p>这门公开课是通过知乎专栏<a href="https://zhuanlan.zhihu.com/p/139045147">《86岁还在录网课：MIT教授Gilbert Strang最新「线性代数」课程上线》</a>了解到的。</p><p>这门课程在今年录制，目前有 6 个视频讲座，对于之前录制的课程也可以在 B 站找录播，例如：<a href="https://www.bilibili.com/video/av6951511">麻省理工公开课：线性代数</a>。</p><p>具体介绍可以看知乎专栏，自己对于这门课程优先级比较低，线代遇坎儿了再学。</p><h2 id="其他计算机公开课">其他计算机公开课</h2><p>有网友总结了国外的计算机课程，还附上了视频链接，可以参考<a href="https://www.yuque.com/guigumentor/guigu/computer">《电子工程和计算机科学系》</a>。</p><h2 id="文章">文章</h2><ul><li><a href="https://zhuanlan.zhihu.com/p/142992185">图形程序学习经历</a><ul><li>文章作者认为从图形 API 开始从底向上了解硬件做的事情到摸透一整个流程是个好的学习过程，其中也给出了很多有价值的参考资料。作者声称能以较快速度在两个月满足工作要求，完成图形程序入门的知识体系。</li></ul></li><li><a href="https://www.raywenderlich.com/7630142-entity-component-system-for-unity-getting-started">Entity Component System for Unity: Getting Started</a><ul><li>Ray Wenderlich 的教程通常会提供初始项目，手把手带你写代码，加上独特风格的配图，很适合用来入门某个技术。这次带来的是 Unity ECS 的入门，用 Entities 包实现一个坦克射击游戏。</li></ul></li><li><a href="https://learn.unity.com/tutorial/fixing-performance-problems-2019-3">Fixing Performance Problems - 2019.3</a><ul><li>Unity 发表的关于解决性能问题的教程，里面有很多小知识点，对初学者可能有帮助。虽然之前好像看过，但这是 2019.3 版本的。</li></ul></li><li><a href="https://zhuanlan.zhihu.com/p/102293437">Cache的基本原理</a><ul><li>作者图文并茂地讲解了缓存内部是如何运作的，Visio 手绘风格的图画也很清晰明了。对缓存感兴趣的还可以进一步关注作者的专栏。</li></ul></li><li><a href="https://refactoringguru.cn/design-patterns">Refactoring.Guru</a><ul><li>这网站也图文并茂地介绍了重构、 设计模式、 SOLID 原则 （单一职责、 开闭原则、 里氏替换、 接口隔离以及依赖反转） 以及其他和智能编程主题相关的内容。</li></ul></li><li><a href="https://jason2506.gitbooks.io/cpumemory/content/">每位程式設計師都該知道的記憶體知識</a><ul><li>这系列文章是 Ulrich Drepper 於 2007 年写的论文<a href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">《What Every Programmer Should Know About Memory》</a>的中文繁体翻译，这篇论文十分有名，游戏开发者都应该看看了解内存的细节。</li></ul></li><li><a href="http://www.sebaslab.com/zero-allocation-code-in-unity/">Zero allocation code in C# and Unity</a><ul><li>文章作者开源过叫 Svelto 的 ECS 框架，这篇文章是他对 Zero allocation 代码（也就是自己管理内存）的理解，也介绍了相关的概念、书籍和很多查看内存分配的工具，对想了解 DOTS 的同学可以阅读一下。</li></ul></li></ul><h2 id="最后">最后</h2><p>其实本篇导读相当一部分在讲的是我个人怎么样看待自己目前学习情况，每次导读中所有的内容我其实只能看完一部分，怎么样更高效地学和怎么样更好地平衡生活是我最近不断思考的问题。当然了，我不会放弃。</p><p>这次导读分享的多是游戏内存、缓存、图形学相关的内容，自己没分享过某种具体实现的 Shader 的文章，是因为我认为自己基础要先打好，这也是我对其他 Shader 课程的态度。希望这次分享能对读者有帮助。</p>]]></content>
      
      
      <categories>
          
          <category> 导读 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2020年4月技术导读</title>
      <link href="/2020/04/13/2020-04-tech-reading/"/>
      <url>/2020/04/13/2020-04-tech-reading/</url>
      
        <content type="html"><![CDATA[<p>首先，感谢所有将自己时间贡献在知识分享的人们。</p><p>本文将简单地介绍下最近我看到的想学的、有意思的教程、视频等，内容不仅限于游戏开发。大家可以做个参考，说不定其中就能找到适合自己的教程，进一步打算接下来的学习。</p><p>这个文章类型和之前写的都不一样，一方面有自己时间少、沉淀不够、不能持续输出的原因，另一方面是因为这些东西都比较碎，也不好在记录生活的微博上发布一些专业的东西。至于这个导读会不会成为一个系列来更新，还要看看我有没有足够东西来分享啦。</p><span id="more"></span><h1>书籍</h1><h2 id="《程序员修炼之道（第2版）》">《程序员修炼之道（第2版）》</h2><p><a href="https://book.douban.com/subject/35006892/">豆瓣地址</a></p><p>在我经过了入门阶段之后，发现很多以前学的很多教程，都很难在工作中进行参考。恰巧云风翻译的第二版已经出版了，里面的内容让我对编程“修炼”有了更深的理解。</p><blockquote><p>投资知识，收益最佳。<br>——本杰明·富兰克林</p></blockquote><p><img src="http://img.frankorz.com/invest-knowledge.png" alt="http://img.frankorz.com/invest-knowledge.png"></p><p>学习学习再学习！</p><h2 id="游戏开发：世嘉新人培训教材">游戏开发：世嘉新人培训教材</h2><p><a href="https://book.douban.com/subject/34996720/">豆瓣地址</a></p><p>这本书我从 17 年等到现在，三年了，拿到手之后觉得所有的等待都是值得的。</p><p>从第一章就能看出作者踏实的编程功底和力求和对优化的不倦追求。一个命令行推箱子游戏从过程式的实现，到运用 C++风格的面向对象的实现，到章节末为了讲解底层内存存储结构而把项目中所有变量都存在一个内存数组中的实现。从中作者讲解基础的 C++ 语法、位运算、指针和引用的异同等概念。</p><p>接下来的章节，作者还讲解 2D 图形处理、状态、碰撞、数学等等游戏密不可分的章节，由于作者都为章节提供了类库，因此不必担心书是否会过时，读者只需踏踏实实跟着作者打基础即可。</p><p>虽然我很少写 C++，也清楚学 C++ 在游戏开发中是逃不开的，干脆就啃这本书的同时把 C++ 也给上手了，一箭双雕！</p><h2 id="Data-Oriented-Design">Data-Oriented Design</h2><p>一本深度讲解面向数据设计的书籍，当你熟悉了面向对象编程，可以尝试看看这本书，用一种不同的思维方式看待你的数据。Unity 开发者在 ECS 正式版公布之前，也可以先深入了解一下面向数据设计的模式。</p><p>作者在其网站公开了书籍内容，地址为：<a href="https://www.dataorienteddesign.com/dodbook/">Data-Oriented Design</a></p><p>同时也可以参考这篇导读：<a href="http://dataorienteddesign.com/dodbook">Highly recommended read : dataorienteddesign.com/dodbook</a></p><h1>公开课</h1><h2 id="图形学">图形学</h2><p><a href="https://www.zhihu.com/question/41468803/answer/1040420856">零基础如何学习计算机图形学？——闫令琪的回答</a></p><blockquote><p>游戏编程类书籍涉及的内容太杂，不适合用作专门学习图形学的参考资料。OpenGL / Shader 确实是应该学习的内容，但是以我的理解来看，更适合先入门图形学基础之后再去进一步学习，这样会简单很多。这也是我个人的理念：我们要学的是图形学，而不是图形学 API，这两者应该完全分开看待。另外 OpenGL / Shader 这块学多了会容易让人产生理解偏差，觉得图形学就等于实时渲染（当然是错的，但是很多人真的这么认为！）。</p></blockquote><p>于是闫老师发布了一门公开课：“GAMES101: 现代计算机图形学入门”。</p><blockquote><p>本课程将全面而系统地介绍现代计算机图形学的四大组成部分：（1）光栅化成像，（2）几何表示，（3）光的传播理论，以及（4）动画与模拟。每个方面都会从基础原理出发讲解到实际应用，并介绍前沿的理论研究。通过本课程，你可以学习到计算机图形学背后的数学和物理知识，并锻炼实际的编程能力。</p></blockquote><p>官网：<a href="https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html">GAMES101: 现代计算机图形学入门</a></p><p>B站录播：<a href="https://www.bilibili.com/video/av90798049">GAMES101-现代计算机图形学入门-闫令琪</a></p><p>课程作业系统：<a href="http://www.smartchair.org/GAMES2020Course-YLQ/">GAMES2020在线课程：计算机图形学（闫令琪）课程作业系统</a></p><h2 id="国外-MIT-CMU-部分课程翻译">国外 MIT CMU 部分课程翻译</h2><p><a href="https://www.simtoco.com/#/home">Simviso</a> 是一个翻译国外课程的民间组织，目前已经翻译了：<a href="https://www.bilibili.com/video/av75384920">MIT 6.004 计算机组成原理</a>、<a href="https://www.bilibili.com/video/av91748150">MIT 6.824 分布式系统 2020</a>、<a href="https://www.bilibili.com/video/BV1f7411z7dw">CMU数据库15-445/645</a>、<a href="https://www.bilibili.com/video/BV1cE411f78c">斯坦福编译原理</a> 等顶级教程。他们接下来的翻译计划也在 <a href="https://zhuanlan.zhihu.com/p/122550750">simviso 国外MIT CMU 斯坦福计算机科班顶级课程翻译系列</a> 中提到。（我在想这一段字的知识浓度得有多高 Orz）</p><p>原理性的东西参考这些教程最好不过了，我们还可以跟着课程手写一个数据库、CPU 等等。</p><h1>教程</h1><h2 id="Custom-SRP">Custom SRP</h2><p><a href="https://catlikecoding.com/unity/tutorials/custom-srp/">Catlike Coding &gt; Custom SRP</a></p><p>Catlike Coding 的教程一向是从基础讲到进阶应用，在 SRP 的主题中，他写了不少基于 Unity 2019 版本的自定义渲染流水线的教程，目前还在持续更新。</p><h2 id="Ray-Tracing-In-One-Week">Ray Tracing In One Week</h2><p>跟着 <a href="https://raytracing.github.io/books/RayTracingInOneWeekend.html">Ray Tracing In One Week</a> 系列教程一步步来做个光线追踪器，也可以在学完上面图形学公开课中的光线追踪部分后来看这个教程作为补充。这个教程也可以用 Unity 来做，例如我的 <a href="https://github.com/Latias94/RayTracingInOneWeekWithUnity">Latias94/RayTracingInOneWeekWithUnity</a>，虽然只跟到第八章 = =。</p><p>跟完这个教程之后，如果对进一步的 DOTS、优化方面学习感兴趣的话，我还提供了下面四篇文章/项目作为参考。</p><ul><li><a href="https://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/">Daily Pathtracer</a><ul><li>跟着 aras-p 做一个 C# + burst 路径追踪器，还可以参考一下陈嘉栋老师的介绍：<a href="https://zhuanlan.zhihu.com/p/37462611">《Daily Pathtracer！安利下不错的Pathtracer学习资料》</a></li></ul></li><li><a href="http://theinstructionlimit.com/unity-toy-path-tracer">Unity Toy Path Tracer</a><ul><li>路径追踪器的另一个实现，该作者想进一步探索 Unity 的 DOTS 技术栈，尤其是 Burst + Job System，对这方面感兴趣的不要错过。</li></ul></li><li><a href="https://github.com/stella3d/weekend-tracer">weekend-tracer</a><ul><li>又一个 C# + burst 的路径跟踪器实现。</li></ul></li><li><a href="https://medium.com/@jcowles/gpu-ray-tracing-in-one-weekend-3e7d874b3b0f">GPU Ray Tracing in One Weekend</a><ul><li>用 Compute Shader 实现光追的基于 GPU 的实现。</li></ul></li></ul><h2 id="Go-语言探索万物原理">Go 语言探索万物原理</h2><p>了解了当前使用的技术，可以扩宽领域，学习一些和项目不相关的东西，说不定会带来灵感。</p><h3 id="老司机带你飞系列">老司机带你飞系列</h3><p>Github 地址：<a href="https://github.com/happyer/distributed-computing">happyer/distributed-computing</a></p><ol><li><a href="https://github.com/happyer/distributed-computing/blob/master/src/mapreduce">《老司机带你用 Go 语言实现 MapReduce 框架》</a></li><li><a href="https://github.com/happyer/distributed-computing/blob/master/src/raft">《老司机带你用 Go 语言实现 Raft 分布式一致性协议》</a></li><li><a href="https://github.com/happyer/distributed-computing/blob/master/src/paxos">《老司机带你用 Go 语言实现 Paxos 算法》</a></li><li><a href="https://github.com/happyer/distributed-computing/blob/master/src/shardkv">《老司机带你用 Go 语言实现分布式数据库》</a></li></ol><h3 id="7-天用-Go-动手写-从零实现系列">7 天用 Go 动手写/从零实现系列</h3><p>Github 地址：<a href="https://github.com/geektutu/7days-golang">geektutu/7days-golang</a></p><ol><li>7天用Go从零实现Web框架 - Gee</li><li>7天用Go从零实现分布式缓存 GeeCache</li><li>7天用Go从零实现ORM框架 GeeORM</li></ol><h1>视频</h1><ul><li><a href="https://www.bilibili.com/video/av77692640">【双语】纪念碑谷的关卡创作挑战 | Monument Valley’s Level Design | Mix and Jam</a><ul><li>Mix and Jam 用六分钟清晰地讲解了纪念碑谷制作思路，并提供了<a href="https://github.com/mixandjam/MonumentValley-LevelDesign">项目</a>参考。</li></ul></li></ul><h1>文章</h1><ul><li><a href="https://www.raywenderlich.com/5671826-introduction-to-shaders-in-unity">Ray Wenderlich - Introduction to Shaders in Unity</a><ul><li>Ray Wenderlich 的教程通常会提供初始项目，手把手带你写代码，加上独特风格的配图，很适合用来入门某个技术。这次带来的是如何写 Unity Shaders，实现一个简单的水流 Shader。</li></ul></li></ul><h1>最后</h1><p>看了看内容，这次分享的视频和文章都偏少了，其实主要是想分享前面的书籍和公开课，有机会的话有价值的视频和文章我也会多关注，并且分享到导读中。如果你喜欢这系列，或者想对这个系列有其他的想法，欢迎在评论区告诉我。</p>]]></content>
      
      
      <categories>
          
          <category> 导读 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>《图解 C# 教程 第5版》与性能优化</title>
      <link href="/2020/01/31/illustrated-csharp-and-performance/"/>
      <url>/2020/01/31/illustrated-csharp-and-performance/</url>
      
        <content type="html"><![CDATA[<p>这本书仍然是入门 C# 最好的一本书。</p><p>这本书新版出来的时候我十分关注，于是英子姐送了一本给我，本文也是答应英子姐所写的一篇文章。她一开始还问我“你现在还需要看这本入门书吗？”，我认为是的。工作了遇到了不少问题，大都跟自己基础不牢有关系。</p><p>这本书以图形为载体，生动地介绍了 C# 语言本身。其中图形对我们了解 C# 语法在内存中的本质十分有帮助，异步、异常等章节中的处理流程图也很清晰明了，这也是我看重的一点。</p><p>本文会从本书出发，简单讲讲代码优化的一些点。</p><span id="more"></span><h2 id="在内存中的形态">在内存中的形态</h2><p>为什么要了解 C# 在内存中的形态呢？</p><p>书中第四章介绍内存区域的栈中，有一句话说的很好：</p><blockquote><p>作为程序员，你不需要显式地对它做任何事情。但了解栈的基本功能可以更好地了解程序在运行时在做什么，并能更好地了解 C# 文档和著作。</p></blockquote><p>游戏开发中，除了业务逻辑，我们还会更关注游戏的性能本身。我们需要保证游戏能流畅运行在大部分机型上，保证每一帧能流畅地播放，例如 CPU 需要处理渲染代码、物理模拟、动画回调等等，其中我们的代码也有可能引起性能问题。我们需要更了解执行代码的代价，例如：</p><ul><li>这些代码产生了多少 GC</li><li>GC 只会产生一次还是每帧都会产生</li><li>在极端情况下代码的性能如何</li><li>是否使用了正确的数据结构</li><li>Unity API 或者一些库 API 的背后到底做了什么</li><li>…</li></ul><p>这本书相对上一版多了 .Net Core, C# 7.0 语法的讲解，对于我而言，重温的是第 4、7、11、13、15、17、19 和 27 章节，这些内容是我工作中经常要接触、着手优化的地方。书中对于异步编程也介绍地很好，但对于我来说，反射、异步编程、新增语法等到以后有需要再看也不迟。</p><p>脚本的性能优化，无非是用更合适的代码去实现需求，不必要的内存都给我吐出来！（注：作者在生活中并没有这么吝啬）</p><p>下面会列举一些代码写法的性能对比。</p><h2 id="结构和类">结构和类</h2><p>这其实也是用栈还是用堆的考量。</p><h3 id="垃圾回收">垃圾回收</h3><p>Unity 用的是 mono 虚拟机，其堆的内存是通过垃圾回收算法 Boehm GC 来管理的，其不分代（Non-generational）和非压缩式（Non-compacting）的特性，导致了我们平常要注意避免加载过多的小内存，从而内存碎片化（Memory fragmentation）。</p><ul><li><strong>分代</strong>：大块内存、小内存、超小内存分在不同内存区域来进行管理。此外还有长久内存，当有一个内存很久没动的时候会移到长久内存区域中，从而省出内存给更频繁分配的内存。</li><li><strong>压缩式</strong>：当有内存被回收的时候，压缩内存会把下图空的地方重新排布。<br><img src="http://img.frankorz.com/compacting.png" alt="compacting.png"></li><li><strong>内存碎片化</strong>：内存过多小内存，导致大内存不能有效地被使用。<br><img src="http://img.frankorz.com/memory-fragmentation.png" alt="memory fragmentation"></li></ul><p>具体可以参考 Unity 文档 <a href="https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html">Understanding the managed heap</a></p><p>同时也推荐高川老师的演讲：<a href="https://www.bilibili.com/video/av79798486/">浅谈 Unity 内存管理</a>，和我看视频时的笔记：<a href="https://www.notion.so/Unity-f79bb1d4ccfc483fbd8f8eb859ae55fe">笔记</a>。</p><h3 id="用结构还是类">用结构还是类</h3><p>这里推一篇微软官方文档：<a href="https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct">Choosing Between Class and Struct</a></p><p>引用类型被分配在堆上并被垃圾回收算法管理，值类型则分配在栈上，栈会按需 <a href="http://en.wikipedia.org/wiki/Call_stack#Unwinding">unwind</a> 来释放他们。因此，值类型的释放比引用类型的释放开销要小。</p><p>书中 11.9 小节还提到：</p><blockquote><p>对结构进行分配的开销比创建类实例小，所以使用结构代替类有时可以提高性能，但要注意装箱和拆箱的高昂代价。</p></blockquote><p><strong>值类型数组</strong>的分配和释放比<strong>引用类型数组</strong>的分配和释放开销也更小。</p><p>除了最基本的修改值类型和引用类型的区别外，要注意的是传递参数或者返回返回值的时候，值类型都会隐性地被创建，这可能也会产生没想到的内存开销。</p><p>从 .Net 内存分配成本的角度来说，类的对象储存的内存首先需要分配 4 个字节作为<strong>对象头字节</strong>（object header word），跟着再分配 4 个字节作为<strong>方法表指针</strong>（method table pointer），这些字段是服务于 JIT 和 CIL 的，是隐藏的分配成本。</p><p>保留在堆中所需的内存还会根据操作系统位数来决定：</p><ul><li>32 位系统中，堆上的对象会对齐到最近 4 字节的倍数，因此如果一个对象只有一个 byte 成员，也需要对齐占 4 个字节，因此这个对象总共占堆上 12 个字节。</li><li>64 位系统中，堆上的对象会对齐到最近 8 字节的倍数，方法表指针和对象头字节也会分别占 8 字节的内存。</li></ul><p>（注：平常开发我们不需要这么抠门，上面只是一个小知识点。）</p><p>大多时候我们都会用类型来实现设计模式、框架的设计，那什么时候使用结构体呢？我们可以遵循微软爸爸的建议：</p><ul><li>逻辑上表示单个值</li><li>大小小于 16 个字节</li><li>不会改变值</li><li>不需要经常装箱拆箱</li></ul><p>对于一些特定的场景下，我们也可以享受值类型数组在内存中线性排布的福利，例如内存连续、SIMD 等。Unity 的 DOTS 技术栈就是一个很好的例子。</p><p>推荐阅读：</p><ul><li><a href="https://zhuanlan.zhihu.com/p/100162855">在C#中使用Struct代替Class</a></li><li>作者用 DOTS（结构体、Job System、Burst）实现 A 星寻路实现性能飞跃：y2b 搜 “Pathfinding in Unity DOTS!”</li></ul><h2 id="装箱">装箱</h2><p>第 17 章介绍了转换，其中提到了装箱拆箱。那么装箱的代价有多大呢？我们可以做个测试：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">const</span> <span class="built_in">int</span> Iterations = <span class="number">100</span>_000;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 其他地方初始化了 Iterations 大小的随机数组</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">int</span>[] numberArr = <span class="literal">null</span>; </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="built_in">bool</span> <span class="title">MeasureTestA</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 设定大小以避免自动扩容带来的性能消耗</span></span><br><span class="line">    Stack stack = <span class="keyword">new</span> Stack(Iterations); </span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; Iterations; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        stack.Push(numberArr[i]); <span class="comment">// int -&gt; object 装箱</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="built_in">bool</span> <span class="title">MeasureTestB</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    Stack&lt;<span class="built_in">int</span>&gt; genericStack = <span class="keyword">new</span> Stack&lt;<span class="built_in">int</span>&gt;(Iterations);</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; Iterations; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        genericStack.Push(numberArr[i]);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/profiler.png" alt="profiler"></p><p>查看 Profiler 可以看到，调用十次 TestA 产生了 26.7MB GC，用了 267.22 毫秒；调用十次 TestB 只产生了 3.8MB GC，用了 20.92 毫秒。因此大量的装箱拆箱会导致不必要的性能消耗，而有些消耗则是完全可以避免的。</p><p>我的 Rider 插件 <a href="https://plugins.jetbrains.com/plugin/9223-heap-allocations-viewer">Heap Allocations Viewer</a> 也会提示我 TestA 中存在装箱的情况。</p><p><img src="http://img.frankorz.com/rider-boxing-plugin.png" alt="rider-boxing-plugin"></p><h2 id="最后">最后</h2><p>写到这里，发现还很多东西没讲完就已经这么多篇幅了…</p><p>大家对于平常代码的不同写法也可以测试下性能，例如：</p><ul><li>foreach 和 for</li><li>装箱和拆箱</li><li>一维数组、多维数组（矩形数组）和交错数组（Jagged Arrays）<ul><li>这里还是强调下尽量使用一维数组，实在需要用多维数组的话，可以改用交错数组</li></ul></li><li>通过 for 循环复制数组和 Array.CopyTo 方法</li><li>字符串拼接，string 和 Stringbuilder</li><li>反射和 DynamicMethod</li><li>…</li></ul><p>上面装箱的截图中的测试项目我也上传了 Github：<a href="https://github.com/Latias94/UnityCsharpPerformanceTest">Latias94/UnityCsharpPerformanceTest</a>，不用 Unity 的同学也可以参考下实际的测试代码：<a href="https://github.com/Latias94/UnityCsharpPerformanceTest/tree/master/Assets/Scripts">UnityCsharpPerformanceTest/Assets/Scripts</a>，自己写个命令行项目来跑下对比。</p><p>当然我们开发中还是要以需求的变化为主，不能过早优化从而破坏代码的扩展性。</p>]]></content>
      
      
      <categories>
          
          <category> Unity </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2019 年的收获与成长</title>
      <link href="/2019/12/04/2019-year-end-summary/"/>
      <url>/2019/12/04/2019-year-end-summary/</url>
      
        <content type="html"><![CDATA[<p>今年发生了很多事情，博客也因此从七月停更到了现在，实在惭愧…现在趁着年终，赶紧抓住 2019 年的尾巴了，来总结下我的这一年。</p><p>本文真的会很啰嗦，但是希望能帮到希望用 Unity 恰饭或者其他技术恰饭的同学。</p><span id="more"></span><h1>毕业</h1><p>今年的上半年完成了研究生的学业，结束了留学生涯。</p><p>我的专业课程比较松，总共两年要修 16 节课的学分，但是必修课只有四节，因此我可以尽可能的选择有实战的课程。这边课程的大作业大多需要团队协作，但是有些 IT 研究生同学是跨专业过来的，不是很能写代码，所以有时候挺考验自身能力的（笑。 我组完队一般都希望能把大作业的规模做大些，一方面是作业能拿比较好的分数，另一方面是求职的时候可以拿出能往简历上放的项目经验。</p><p>毕业后我发现这种选择是对的，我的队友在毕业后还问我们的线上项目怎么开不起来了，他们也到了找工作的时间，还让我帮忙看了看简历。澳洲工作是很休闲的，大部分下午五六点就能下班。身边同学也在努力地留下来，考 PTE、CCL 考试凑分拿 PR。自己因为还是想做游戏，澳洲环境不太好，就回了国。</p><h1>从面试到工作</h1><p>由于课程结束三个月后才能参加毕业典礼拿到毕业证，当时我对求职还不太上心，还想等着春招。但是又不想在家里混吃混喝，就开始每天刷刷面试题，学学感兴趣的，同时也开始在某直聘找工作，打算每周面试一次，接触下当前的就业形势，同时查漏补缺。</p><h2 id="第一周">第一周</h2><p>第一周面试了家一百多人的游戏公司，一上来要求三十分钟解一道 Leetcode hard 的题…好不容易解出来了，又要求递归改迭代，又问有没有能优化的点。之后还问了些逻辑题，这时候挺庆幸自己复习了<a href="https://book.douban.com/subject/25753386/">《程序员面试经典（第5版）》</a>（<a href="https://www.ituring.com.cn/book/1876">第六版</a>刚出噢），基本还能 hold 住场面，但是后来分析算法的时间复杂度分析的十分糟糕，于是就没有然后了…</p><p>当天十分沮丧，恰巧面试的地方和一个主美朋友工作的地方很接近，就约了个饭。他开导我说：”拿美术来说，不同公司也会需要不同的美术：古风游戏自然需求专精画古风的，科幻游戏需求的美术风格很明显也不一样。再面试几家就好，今天面试只代表公司不适合你。“我听了很有道理！于是继续不务正业学了喜欢的东西，简单复习复习算法，刷了刷题。</p><h2 id="第二周">第二周</h2><p>第二周又接到另一个游戏公司 HR 的面试邀请，面试时直接来了三个面试官，两个程序大佬一个制作人。很明显的，面试风格都不一样。他们事先看了我的简历，看了我的博客。刚好第一周的时候更新了一篇 <a href="http://frankorz.com/2019/07/14/clarify-ecs-basic-concept/">DOTS 的博文</a>，于是他们一开始就让我介绍下 Unity 的 DOTS 技术栈是什么，还有一些概念细节。后来的其他问题很明显能感觉他们在考察我知识的广度，例如图形学，我简历上提到的 C# 热更新等。</p><p>刚好那段时间”不务正业“地跟着<a href="https://book.douban.com/subject/30348061/">《自己动手实现Lua》</a>写了一半的 Lua 虚拟机，于是问到对 Lua 是否熟悉的时候，我就提了一嘴最近在学的东西，接着又展开新的问答。整个过程中，我觉得面试官的风格和第一周公司的面试风格完全不一样，但是有些地方还是答得不够好，于是又在家瞎学。</p><p>一周后，我拿到第二家公司的 Offer，成为了公司工具人。</p><p>我觉得从面试就能看出公司关注的是开发人员的哪些方面，如主美朋友所说，如果不愿意改变自己学习的风格，那就找到需求这种风格的公司，接下来的工作也印证了这一点。</p><h2 id="上面提到的内容">上面提到的内容</h2><ul><li><a href="https://book.douban.com/subject/34813624/">《程序员面试经典（第6版）》</a>（<a href="https://book.douban.com/subject/25753386/">第5版</a>）</li><li>DOTS 相关博文<a href="http://frankorz.com/2019/07/14/clarify-ecs-basic-concept/">《洞明 Unity ECS 基础概念》</a></li><li><a href="https://book.douban.com/subject/30348061/">《自己动手实现Lua：虚拟机、编译器和标准库》</a>，用Go语言实现 Lua 虚拟机、编译器和标准库。</li></ul><h1>了解代码的另一面</h1><p>入职后，才发现公司写了一套自己的 C# 热更新，这种热更新是和 xLua 一样的注入式热更，跟 <a href="https://github.com/egametang/ET">ET 框架</a>分两个项目跑的还不一样（下文会解释）。有意思的是，在我入职过了几个月后，xLua 作者也开源了C# 注入式的热更新项目：<a href="https://github.com/Tencent/InjectFix">InjectFix</a>，作者还配套写了一套 IL 的运行时，听说性能还比 ILRuntime 更好些。</p><p>感兴趣的可以先看看 xLua 作者的讲解：</p><ul><li><a href="https://www.zhihu.com/question/54344452/answer/139413144">如何评价腾讯在Unity下的xLua（开源）热更方案？</a></li><li><a href="https://www.zhihu.com/question/345639690/answer/825301379">作为游戏开发者，如何看待腾讯最新发布的Unity热修复工具InjectFix？</a></li></ul><p>先前基于 ILRuntime 的热更新，例如 ET 框架，大多是分两个项目，主项目和热更项目，热更项目放一些需要经常修改的战斗逻辑、UI 界面等。这样可以直接把热更项目都放在 ILRuntime 上跑，整个项目都能热更，十分方便，但是这样十分依赖 ILRuntime 的性能。</p><p>那么注入式的热更有什么区别呢？我们给每个函数前加 if 判断，如果 ILRuntime 上跑的（能热更的）DLL里面有对应的方法，就执行热更的方法，这样 ILRuntime 的性能问题也能避免开来，因为我们可能只有需要热更的函数在 ILRuntime 上面跑，而不是整个项目。</p><blockquote><p>那么，古尔丹，代价是什么呢？<br>——格罗玛什·地狱咆哮</p></blockquote><p>代价就是能热更的东西极其局限，只能热更函数、和新增的类等。</p><p>在了解原因之前，我们先来看看例子，假设我们游戏就这么多代码：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Unity 2019.2 之前，Scripting Runtime Version: .Net 3.5 Equivalent(Deprecated)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">TestIL</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">int</span>[] arr = &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;;</span><br><span class="line">        Action action = () =&gt; Debug.Log(<span class="string">&quot;Hello IL&quot;</span>);</span><br><span class="line">        action();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面是看上去如古天乐平平无奇的代码，当我们用 <a href="https://github.com/0xd4d/dnSpy">dnSpy</a> 反编译 <code>Library\ScriptAssemblies\Assembly-CSharp.dll</code> 后会发现：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">TestIL</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span>[] array = <span class="keyword">new</span> <span class="built_in">int</span>[]</span><br><span class="line">&#123;</span><br><span class="line"><span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span></span><br><span class="line">&#125;;</span><br><span class="line">Action action = <span class="built_in">delegate</span>()</span><br><span class="line">&#123;</span><br><span class="line">Debug.Log(<span class="string">&quot;Hello IL&quot;</span>);</span><br><span class="line">&#125;;</span><br><span class="line">action();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译器生成的匿名函数</span></span><br><span class="line">[<span class="meta">CompilerGenerated</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> &lt;Start&gt;m__0()</span><br><span class="line">&#123;</span><br><span class="line">Debug.Log(<span class="string">&quot;Hello IL&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[<span class="meta">CompilerGenerated</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Action &lt;&gt;f__am$cache0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译器为我们的 Action 生成了匿名函数，那也就是说如果我需要更改 Debug.Log 中打印的字符串，我只需在热更 DLL 中提供：<strong>修改后的函数</strong> + <strong>编译器生成的匿名函数</strong>就 okay 了？实际上没那么简单，因为编译器又作妖了。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="built_in">int</span>[] arr = &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;;</span><br><span class="line">    Action action = () =&gt; Debug.Log(<span class="string">&quot;Hello &quot;</span> + arr[<span class="number">0</span>]); <span class="comment">// 修改打印</span></span><br><span class="line">    action();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再次查看反编译后的 DLL：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span>[] arr = <span class="keyword">new</span> <span class="built_in">int</span>[]</span><br><span class="line">&#123;</span><br><span class="line"><span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span></span><br><span class="line">&#125;;</span><br><span class="line">Action action = <span class="built_in">delegate</span>()</span><br><span class="line">&#123;</span><br><span class="line">Debug.Log(<span class="string">&quot;Hello &quot;</span> + arr[<span class="number">0</span>]);</span><br><span class="line">&#125;;</span><br><span class="line">action();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">[<span class="meta">CompilerGenerated</span>]</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">sealed</span> <span class="keyword">class</span> &lt;<span class="title">Start</span>&gt;<span class="title">c__AnonStorey0</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span> &lt;Start&gt;c__AnonStorey0()</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">void</span> &lt;&gt;m__0()</span><br><span class="line">&#123;</span><br><span class="line">Debug.Log(<span class="string">&quot;Hello &quot;</span> + <span class="keyword">this</span>.arr[<span class="number">0</span>]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="built_in">int</span>[] arr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于 action 中引用了局部变量，mono 编译器将本该生成的匿名方法生成了匿名类，并在调用的时候传入 arr int 数组。</p><p>现在我们调整下我们的热更新策略：如果我们检测到编译器生成的匿名函数，将其转换成匿名类，再把这个新增的类复制到热更 DLL 中。</p><p>还是有问题！</p><p>这时候就需要认识 C# 的中间语言—— MSIL（又称 IL），每句 C# 代码都可以转换成<strong>可读性较好</strong>的<strong>类似于机器代码</strong>的 IL 代码。</p><p>当我们查看 Start 函数的 IL 代码时：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">.<span class="function">method <span class="keyword">private</span> hidebysig </span></span><br><span class="line"><span class="function">instance <span class="keyword">void</span> <span class="title">Start</span> () cil managed</span> </span><br><span class="line">&#123;</span><br><span class="line">.maxstack <span class="number">4</span></span><br><span class="line">.<span class="function">locals <span class="title">init</span> (<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">[<span class="number">0</span>] <span class="keyword">class</span> TestIL/<span class="string">&#x27;&lt;Start&gt;c__AnonStorey0&#x27;</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">[<span class="number">1</span>] <span class="keyword">class</span> [System.Core]System.Action</span></span></span><br><span class="line"><span class="params"><span class="function"></span>)</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function">IL_0000: newobj    instance <span class="keyword">void</span> TestIL/&#x27;&lt;Start&gt;c__AnonStorey0&#x27;::.<span class="title">ctor</span>()</span></span><br><span class="line"><span class="function">IL_0005: stloc.0</span></span><br><span class="line"><span class="function">IL_0006: nop</span></span><br><span class="line"><span class="function">IL_0007: ldloc.0</span></span><br><span class="line"><span class="function">IL_0008: ldc.i4.4</span></span><br><span class="line"><span class="function">IL_0009: newarr    [mscorlib]System.Int32</span></span><br><span class="line"><span class="function">IL_000E: dup       <span class="comment">// 下面这串是什么？怎么又引用了另外一个类？</span></span></span><br><span class="line"><span class="function">IL_000F: ldtoken   field valuetype &#x27;&lt;PrivateImplementationDetails&gt;&#x27;/&#x27;$ArrayType</span>=<span class="number">16&#x27;</span> <span class="string">&#x27;&lt;PrivateImplementationDetails&gt;&#x27;</span>::<span class="string">&#x27;$field-1456763F890A84558F99AFA687C36B9037697848&#x27;</span></span><br><span class="line">IL_0014: call      <span class="keyword">void</span> [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(<span class="keyword">class</span> [<span class="title">mscorlib</span>]<span class="title">System.Array</span>, <span class="title">valuetype</span> [<span class="title">mscorlib</span>]<span class="title">System.RuntimeFieldHandle</span>)</span><br><span class="line"><span class="title">IL_0019</span>: <span class="title">stfld</span>     <span class="title">int32</span>[] <span class="title">TestIL</span>/&#x27;&lt;<span class="title">Start</span>&gt;<span class="title">c__AnonStorey0</span>&#x27;::<span class="title">arr</span></span><br><span class="line"><span class="title">IL_001E</span>: <span class="title">ldloc.0</span></span><br><span class="line"><span class="title">IL_001F</span>: <span class="title">ldftn</span>     <span class="title">instance</span> <span class="title">void</span> <span class="title">TestIL</span>/&#x27;&lt;<span class="title">Start</span>&gt;<span class="title">c__AnonStorey0</span>&#x27;::&#x27;&lt;&gt;<span class="title">m__0</span>&#x27;()</span><br><span class="line"><span class="title">IL_0025</span>: <span class="title">newobj</span>    <span class="title">instance</span> <span class="title">void</span> [<span class="title">System.Core</span>]<span class="title">System.Action</span>::.<span class="title">ctor</span>(<span class="title">object</span>, <span class="title">native</span> <span class="title">int</span>)</span><br><span class="line"><span class="title">IL_002A</span>: <span class="title">stloc.1</span></span><br><span class="line"><span class="title">IL_002B</span>: <span class="title">ldloc.1</span></span><br><span class="line"><span class="title">IL_002C</span>: <span class="title">callvirt</span>  <span class="title">instance</span> <span class="title">void</span> [<span class="title">System.Core</span>]<span class="title">System.Action</span>::<span class="title">Invoke</span>()</span><br><span class="line"><span class="title">IL_0031</span>: <span class="title">ret</span></span><br><span class="line">&#125; <span class="comment">// end of method TestIL::Start</span></span><br></pre></td></tr></table></figure><p>在 dnSpy 中找了找，发现了 <code>PrivateImplementationDetails</code> 类：</p><p><img src="http://img.frankorz.com/dnspy.png" alt="dnspy"></p><p>这看起来应该是这个数组被存到了某个地方，这个类只是提供了 id 告诉 IL 这个数组应该在哪找？通过查询 <a href="https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/CodeGen/PrivateImplementationDetails.cs">Roslyn 编译器的文档</a>，发现了这个类的注释：”The main purpose of this class so far is to contain mapped fields and their types.“ 所以我们要热更的话，还需要将 <code>PrivateImplementationDetails</code> 类复制到热更 DLL 中。</p><p>我们怎么分析代码是否是匿名函数呢？<a href="https://github.com/jbevain/cecil">Mono.Cecil</a> 就是一套基于 IL 分析程序集的库，我们可以通过这个库来判断哪些方法不能热更等，这又是另外一个话题，略过不提。</p><p>以上只是注入热更的一个小插曲，但是涉及的东西就已经与一开始 Start 方法中的三行代码相去甚远了。如果我们还要支持重载函数的热更，泛型类中函数的热更，就更是让人掉头发的话题，涉及的 IL 十分复杂。</p><p>现代的高级语言为我们封装了太多东西，提供了方便编程的语法糖，但也为我们知根知底的学习方式设立了门槛。</p><p>但是当我们了解了 IL 中间语言的话，我们以后面对”在匿名函数引用 for 循环中变量的行为会诡异“等问题的时候，我们可以直接反编译 DLL 来看代码真正的面目是怎么样的。</p><p>不小心写了足以充当一篇文章的内容，但是我想表达的是：</p><ul><li>对于游戏开发者，我们有必要对自己的代码做了什么有充分的了解，谨慎运用语法糖，这样才能充分掌握游戏的性能。</li><li>虽说我们远远没达到造 InjectFix 轮子的程度，但是了解该技术的根基——IL，再尝试根据其他文档来分析，能让我们更好的了解这个框架的背后，了解这种热更新的优缺点。</li></ul><h2 id="上面提到的内容-2">上面提到的内容</h2><ul><li><a href="https://github.com/Tencent/InjectFix">InjectFix</a> C# 热更新</li><li><a href="https://github.com/0xd4d/dnSpy">dnSpy</a> .Net 反编译编辑器</li><li><a href="https://github.com/jbevain/cecil">Mono.Cecil</a> 基于 IL 分析程序集的库，dnSpy 也是基于这个库来分析的。</li></ul><h1>自顶向下，再自底向上</h1><p>游戏开发很多技术都倚重于硬件的能力，因此我们有必要对这些硬件的实现和原理有所了解。但这方面也是我的弱点，我个人喜欢按照兴趣来学，因此我的总结的方法是：<strong>自顶向下，再自底向上</strong>。</p><p>就如上面热更新的例子一般，<strong>自顶向下</strong>就是揭开抽象的面纱，从感兴趣的框架或库的应用入手，逐步通过各种方式来了解底层的原理。</p><p>拿 DOTS 技术栈做例子，ECS 的编程模式保证数据线性地排列在内存中，恰好能内存 cache 缓存命中率，从而得到更高效的数据读取。更多细枝末节可以先放一旁，例如 Shared components 这种与理念不相符的共享组件是怎么实现的。</p><p>知道了内存 cache 缓存命中率在其中发挥巨大作用后，我刷<a href="https://www.zhihu.com/question/352847684/answer/889567006">知乎</a>还发现，Disruptor 库为了对齐 128 个字节方便 CPU cache line 用，选择浪费空间提高缓冲命中率。</p><p>到底内存的结构是怎么样的？缓存命中率又是什么？字节对齐又是什么？为什么这样的做法能提高性能？带着种种疑问，我们开始<strong>自底向上</strong>地了解内存结构。</p><p>从感兴趣的应用入手，了解硬件底层，这样不至于太枯燥。学到一定程度，当我们把知识网中重要的几个概念都了解之后，我们可以再阅读相关操作系统的书，将周边的知识点与之前比较大的概念相连接，互相印证，从而结成结实的知识图谱。</p><p>一开始我想重新捡起<a href="https://book.douban.com/subject/20260928/">《编码：隐匿在计算机软硬件背后的语言》</a>这本书，这本书从手电筒聊天开始，依次讲了摩斯电码、二进制、再到继电器电路、组合成 CPU 等等，能解答我的疑惑，但是后来发现节奏偏慢。于是又看了 <a href="https://www.bilibili.com/video/av21376839?p=9">Crash Course Computer Science（计算机科学速成课）</a> 中讲 CPU 的一集，觉得节奏非常好，通过 12 分钟视频讲解除法电路、缓存、流水线设计、并行等概念，揭开计算机一层一层的抽象，我认为这是自底向上最好的教材。另外在知乎刷到一篇文章：<a href="https://zhuanlan.zhihu.com/p/83449008">《带你深入理解内存对齐最底层原理》</a>也是很好的佐料。</p><p>了解了硬件原理之后，这种优化能带到实践中吗？一维数组的顺序访问比逆序访问要快，那么二维数组呢？</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">TestCache</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="built_in">int</span> LINE = <span class="number">10240</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">const</span> <span class="built_in">int</span> COLUMN = <span class="number">10240</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span>[,] array = <span class="keyword">new</span> <span class="built_in">int</span>[LINE, COLUMN];</span><br><span class="line">Stopwatch stopwatch = <span class="keyword">new</span> Stopwatch();</span><br><span class="line">stopwatch.Start();</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; LINE; i++)  <span class="comment">// 344 ms</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">0</span>; j &lt; COLUMN; j++)</span><br><span class="line">&#123;</span><br><span class="line">array[i, j] = i + j; <span class="comment">// 一行一行访问</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">stopwatch.Stop();</span><br><span class="line">Debug.Log(<span class="string">&quot;用时1：&quot;</span> + stopwatch.ElapsedMilliseconds + <span class="string">&quot; ms&quot;</span>); </span><br><span class="line">stopwatch.Restart();</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; LINE; i++) <span class="comment">// 1274 ms</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">0</span>; j &lt; COLUMN; j++)</span><br><span class="line">&#123;</span><br><span class="line">array[j, i] = i + j; <span class="comment">// 一列一列访问</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">stopwatch.Stop();</span><br><span class="line">Debug.Log(<span class="string">&quot;用时2：&quot;</span> + stopwatch.ElapsedMilliseconds + <span class="string">&quot; ms&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一行一行访问和一列一列访问的效率很明显不一样，自此，我们自底向上地把对硬件的了解反映到了实际编码中。</p><p>由于自己就是兴趣导向的学习，所以学东西难免有所偏科，但是我认为只要感兴趣的够多，就不怕偏科（笑。每个人有自己学习的节奏，在这里只是提供一种思路。</p><h2 id="上面提到的内容-3">上面提到的内容</h2><ul><li><a href="http://frankorz.com/2019/05/07/simple-talk-unity-dots/">《Unity DOTS 走马观花》</a></li><li><a href="https://www.zhihu.com/question/352847684/answer/889567006">你在开源项目里看到过哪些精髓的代码片段？</a></li><li><a href="https://book.douban.com/subject/20260928/">《编码：隐匿在计算机软硬件背后的语言》</a></li><li><a href="https://www.bilibili.com/video/av21376839?p=9">Crash Course Computer Science（计算机科学速成课）</a>第九集：高级 CPU 设计<ul><li>建议空闲的同学从第一集开始看起，内容十分详实有趣。</li></ul></li><li><a href="https://zhuanlan.zhihu.com/p/83449008">《带你深入理解内存对齐最底层原理》</a>，其专栏也是个宝藏之地。</li></ul><h1>找到适合自己的学习方式</h1><p>说起来容易，但是要运用之前，我首先需要知道学习这种知识的途径有哪些，才能从中选择最适合自己的学习方式。</p><p>拿 Unity 中的渲染来说，我能通过相关官方文档来学，能通过看博文来了解，也能通过 Direct3D、OpenGL 相关教程来学，或者重新拿起图形学的书本，因为都会涉及到渲染流水线的知识。</p><p>那么怎么样的才算是适合自己的学习方式呢？有的同学可能喜欢先把理论吃一遍再来实战，我喜欢通过动手来了解。我尝试跟着 LearnOpenGL 教程和一些 Shader 教程来学习渲染管线的知识，也初识 Batch（批次）、GPU Instancing 等名词，但是不同教程都有不同侧重：Shader 教程可能着重教你如何实现各种效果，而图形学课程可能对这些名词的背后实现语焉不详。</p><p>之后一直压着疑惑使用着这些技术，我后来又发现了一些选择：要不自己写一套软渲染器？又或者我可以通过 Unity 新出的 Scriptable Render Pipeline（可编程渲染管线）来自定义一套自己的渲染管线？这似乎已经很靠近我想学的东西了，再考虑自己的时间和兴趣，我决定跟着 catlikecoding 的 SRP 教程写一个可编程渲染管线，尝试以一种较底层的角度来了解渲染管线相关的实现。</p><h2 id="上面提到的内容-4">上面提到的内容</h2><ul><li>LearnOpenGL <a href="https://learnopengl.com/">英文版</a> <a href="https://learnopengl-cn.github.io/">中文版</a></li><li><a href="https://book.douban.com/subject/26821639/">《Unity Shader 入门精要》</a></li><li>图形学：<a href="https://www.scratchapixel.com/">Scratchapixel</a></li><li>Scriptable Render Pipeline 可编程渲染管线（目前在学 catlikecoding 的教程）：<ul><li><a href="https://catlikecoding.com/unity/tutorials/custom-srp/">Custom SRP</a> （Unity 2019 and later）</li><li><a href="https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/">Scriptable Render Pipeline</a>（Unity 2018）</li></ul></li></ul><p>啰嗦了这么多，其实只是想分享下我学习的经验：正所谓曲高和寡，越是进阶的知识点，教程风格越五花八门，当然也会越少人写。每个人有自己的学习风格，尽量多拓展自己的知识来源，不要将自己限制在国内教程和书籍中。</p><p>如何扩展知识来源呢？</p><ul><li>国内外出版社书籍（此处推荐英子姐写的<a href="https://mp.weixin.qq.com/s?__biz=MzI4NTE4MzMwOA==&amp;mid=2651741950&amp;idx=1&amp;sn=1c6401735cfd47a9bb2d59ee2ae5bb2b&amp;chksm=f00ae2ddc77d6bcbc2d36dac9de7bcf4b192a4838369cfd5270451620f056b7ffda4f50f9031&amp;mpshare=1&amp;scene=1&amp;srcid=&amp;sharer_sharetime=1575337559586&amp;sharer_shareid=8d96f389de807cb2e9daef47a949d4f8&amp;key=7ded8ce4ce1a7d9e0f5711b5b430edae80ad959ba6b208a010948a08d253c4532e8231c2af04f5331ec4b2ff80034f7f9bb1969dcf727b8823b887f74fa9ef9dac7bdf29681b69880a482c3a30cd8983&amp;ascene=1&amp;uin=MTg1NzQzNDYyMQ%3D%3D&amp;devicetype=Windows+10&amp;version=62070158&amp;lang=zh_CN&amp;pass_ticket=fdAgvNNteCyTRuP%2BDBSnsadU1Thl59Kp5fPRiLirDhI1Tu0z3v40XkHlORu3UAYk">《程序员最喜欢的技术书大都出自这 20 家出版社》</a>）<ul><li><a href="https://learning.oreilly.com/home/">O’Relly Learning</a> 的书籍分类做的比较好，可以拿来关注新书的出版。</li><li>关注国内出版社的书讯</li></ul></li><li>关注 Youtube 博主、博客博主、知乎专栏等（我通过 RSS 订阅）</li><li>Github 看有没有人上传自己的 Demo，例如一个<a href="https://github.com/zhing2006/GPU-Ray-Tracing-in-One-Weekend-by-Unity-2019.3">小型的光线追逐实现</a>、或者一个<a href="https://github.com/CelestialAmber/Pokemon-Red-Unity">神奇宝贝 Unity 复刻版</a>等。</li><li>线下 Meetup 技术分享。</li></ul><p>有了选择，就可以根据自己兴趣爱好来跟着学习，想办法把他们学到脑子里！</p><h1>先写，再优化</h1><p>工作后的这段时间我一直在思考，我距离独立造轮子还差多少能力。先写框架划分模块？还是先实现功能？如何写出高内聚低耦合的代码？抛去代码可读性和模块的划分不谈，写出一个轮子最基本的功能对于我可能都是一个难题。</p><p>我的工位在写出热更新的大佬的旁边，一开始大佬说招我的原因，是希望我能接受他的热更新框架，继续维护。可惜自己能力不够，看懂了实现，但是却无从下手，之后也就不了了之。不过有段时间，我问大佬问题问到什么程度呢？我一转头瞄下他，他就会划着电脑椅过来等我提问…</p><p>大佬在工作室中负责战斗以外的技术攻坚，有需求的话就会主动去学，去实现，C# 热更新框架就是他的作品。后来团队觉得可能要通过帧同步来防外挂，他又开始看相关的论文，文章来从头写，虽然到现在团队还没用上。耳濡目染之下，我也会跟着大佬去看帧同步相关的内容，有时候当当小黄鸭，帮他查漏补缺。</p><p>**对于造轮子，有需求，有思路，就先开始写！**这是我这段时间从大佬身上学到的一点。例如他认为我们 UI 的数据不好管理，于是参考了<a href="https://book.douban.com/subject/27074806/">《Flux 架构》</a>和其他文章，准备将这种理念与 Unity 结合，于是他又开新坑了。</p><p>他给我的评价是：知识面较广，好学，就是<strong>不肯开始写</strong>。</p><p>说的没错，太多考虑反而会束手束脚，适得其反。先把功能写出来，设计模式按照经验来划分，能用了再优化，这是功利的做法，但也是能让人顾虑少地造轮子的做法。</p><h1>新的开始</h1><p>工作的节奏和上学完全不一样，虽说是 9.5 6.5 5，但是两个小时的通勤仍然让我措手不及。公司下班吃完饭，回到家九点，随便看点啥就十一点了，早上还得八点多起…</p><p><img src="http://img.frankorz.com/daxiong.jpg" alt="daxiong"></p><p>最后尝试更换了出行方式，从地铁改成坐巴士上班，这样有位置坐，才能静下心看看书。尽量把阅读安排在通勤时间上，这样才能勉强保持学的进度。</p><p>工作方面压力也还行，平常有时间的话能关注下业务逻辑以外的东西。例如有一次运营拿来一个我们游戏的脚本样本，我花了点时间解包，学了学反编译，再让负责战斗的大佬针对性地做了点预防。这个过程对我而言也是新的经验，其中应对 iOS 外挂时借鉴了图灵的<a href="https://book.douban.com/subject/34658682/">《九阴真经：iOS黑客攻防秘籍》</a>的思路。</p><p>前不久转了正，算是给学生时代交了份答卷。随着见识的增长，自己对博文和代码又有了更高的要求。见识了“好”的文章后，自己怎么写才能组织好文章，才能更好地讲述一些知识点？</p><blockquote><p>我们需要放下书本，去实践，去体验，去观察，去琢磨，去尝试，去创造，去设计，去stay hungry, stay foolish。<br><a href="https://www.zhihu.com/question/20576786/answer/29048655">号称终极快速学习法的费曼技巧，究竟是什么样的学习方法？</a></p></blockquote><p>学习、实践、总结、再清楚地解说一件事，并将其写成博文，这是我对我下一阶段的要求。其中学习总结的速度也得跟上，我已经看到有读者抱怨我博客断更了……</p><p>今年读过的书：</p><ul><li><a href="https://book.douban.com/subject/30432848/">《Unity3D网络游戏实战（第2版）》</a></li><li><a href="https://book.douban.com/subject/30280467/">《游戏架构：核心技术与面试精粹》</a></li><li><a href="https://book.douban.com/subject/25843328/">《深入理解C#（第3版）》</a></li><li><a href="https://book.douban.com/subject/30283996/">《利用Python进行数据分析 原书第2版》</a></li><li><a href="https://book.douban.com/subject/30442187/">《白话机器学习算法》</a></li><li><a href="https://book.douban.com/subject/26806943/">《游戏安全——手游安全技术入门》</a></li><li><a href="https://book.douban.com/subject/34658682/">《九阴真经：iOS黑客攻防秘籍》</a></li><li><a href="https://book.douban.com/subject/27135506/">《网络多人游戏架构与编程》</a></li><li><a href="https://book.douban.com/subject/30348061/">《自己动手实现Lua : 虚拟机、编译器和标准库》</a></li><li><a href="https://book.douban.com/subject/30304759/">《C#从现象到本质》</a></li></ul><p>明年想更深入多线程、内存布局、和渲染相关的话题。</p><p>加油吧，感谢 2019 年帮助过我的所有人。</p>]]></content>
      
      
      <categories>
          
          <category> Unity </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>洞明 Unity ECS 基础概念</title>
      <link href="/2019/07/14/clarify-ecs-basic-concept/"/>
      <url>/2019/07/14/clarify-ecs-basic-concept/</url>
      
        <content type="html"><![CDATA[<p>虽然网络上已经有不少 ECS 文档的汉化，但自己读官方文档的时候也会产生不少疑问，为此通过查询各种资料，写下本文。</p><p>本文从 ECS 官方文档出发，加之内存布局结构的讲解，力求读者能够和博主一起吃透 ECS 中的基本概念。同时建议读者可以先读读我的上一篇博文<a href="http://frankorz.com/2019/05/07/simple-talk-unity-dots/#Entity-Component-System">《Unity DOTS 走马观花》</a>的 ECS 部分，本文不再复述前文已经提到过的相关概念。</p><h1>ECS 与 Job System</h1><p>我认为有必要重申其两者的关系。</p><ul><li>Job System 能帮我们方便地写出线程安全的多线程代码，其中每个任务单元称为 Job。</li><li>ECS，又称实体组件系统。与传统的面向对象编程相比，ECS 是一种基于数据设计的编程模式。<a href="http://frankorz.com/2019/05/07/simple-talk-unity-dots/#Entity-Component-System">前文</a>从内存结构分析了 OOP 模式的缺点，也提到了 ECS 是怎么样基于数据的设计内存结构的。</li></ul><p>Job System 是 Unity 自带的库，而要使用 ECS 我们需要从 Package Manager 中安装 “Entities” 预览包。这两者虽说完全是两种东西，但是他们能很好地相辅相成：ECS 保证数据线性地排列在内存中，这样通过更高效的数据读取，能有效提升 Job 的执行速度，同时也给了 Burst 编译器更多优化的机会。</p><span id="more"></span><h1>Entities（实体）</h1><p>在 <code>World</code>中， <code>EntityManager</code> 管理所有实体和组件。</p><p>当你需要创建实体和为其添加组件的时候， <code>EntityManager</code>会一直跟踪所有独立的组件组合（也就是原型 Archetype）。</p><h2 id="创建实体">创建实体</h2><p>最简单的方法就是在编辑器直接挂一个 <code>ConvertToEntity</code> 脚本，在运行时中把 GameObject 转成实体。</p><p><img src="http://img.frankorz.com/5d2afb77ee64e53003.png" alt="在编辑器中挂脚本，GameObject 会在运行时中转成实体"></p><p>在编辑器中挂脚本，GameObject 会在运行时中转成实体</p><p>脚本中，你也可以创建系统（System）并在一个 Job 中创建多个实体，也可以通过 <code>EntityManager.CreateEntity</code> 方法来一次生成大量 Entity。</p><p>我们可以通过下面四种方法来创建一个实体：</p><ul><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.ComponentType.html">ComponentType</a> 数组创建一个带组件的实体</li><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.EntityArchetype.html">EntityArchetype</a> 创建一个带组件的实体</li><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.EntityManager.html#Unity_Entities_EntityManager_Instantiate_Unity_Entities_Entity_">Instantiate</a> 复制一个已存在的实体和其当前的数据，</li><li>创建一个空的实体然后再为其添加组件</li></ul><p>也可以通过下面的方法一次性创建多个实体：</p><ul><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.EntityManager.html#Unity_Entities_EntityManager_CreateEntity">CreateEntity</a> 来创建相同原型（archetype）的实体并填满一个 NativeArray （要多少实体就提前设定好 NativeArray 的长度）</li><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.EntityManager.html#Unity_Entities_EntityManager_Instantiate_Unity_Entities_Entity_">Instantiate</a> 来复制一个已存在的实体并填满一个 NativeArray</li><li>用 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.EntityManager.html#Unity_Entities_EntityManager_CreateChunk_">CreateChunk</a> 来显式创建内存块（Chunks），并且填入自定数量的给定原型的实体</li></ul><h2 id="增加和移除组件">增加和移除组件</h2><p>实体被创建之后，我们可以增加和移除其组件。当我们这样做的时候，相关联的原型（Archetype）将会被改变， <code>EntityManager</code> 也需要改变内存布局，将受影响的数据移到新的内存块（new Chunk of memory），同时也会压缩原来内存块中的组件数组。</p><p>对实体的修改会带来内存结构的改变。</p><p>实体的修改包括：</p><ul><li>增加和移除组件</li><li>改变 <code>SharedComponentData</code>的值</li><li>增加和删除实体</li></ul><p>这些操作都不能放到 Job 中执行，因为这些都会改变内存中的数据结构。因此我们需要用到命令（Commands）来保存这些操作，将这些操作存到 <code>EntityCommandBuffer</code> 中，然后在 Job 完成后再依次执行 <code>EntityCommandBuffer</code> 中储存的操作。</p><h1>World（世界）</h1><p>每一个 <code>World</code> 包含一个 <code>EntityManager</code> 和一系列的 <code>ComponentSystem</code>。一个世界中的实体、原型、系统等都不能被另外一个世界访问到。你可以创建很多 <code>World</code> ，例如通常我们会使用或创建一个负责主要逻辑运算的 simulation <code>World</code> 和负责图形渲染的 rendering <code>World</code> 或 presentation <code>World</code>。</p><p>当我们点击运行按钮进入 <strong>Play Mode</strong> 时，Unity 会默认创建一个 <code>World</code>，并且增加项目中所有可用的 <code>ComponentSystem</code>。我们也可以关闭默认的 <code>World</code> 从而自己创建一个。</p><ul><li><strong>Default World creation code</strong> (see file: <em>Packages/com.unity.entities/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs</em>)</li><li><strong>Automatic bootstrap entry point</strong> (see file:<em>Packages/com.unity.entities/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs</em>)</li></ul><h1>Components（组件）</h1><p>ECS 中的组件是一种结构，可以通过实现下列接口来实现：</p><ul><li>IComponentData</li><li>ISharedComponentData</li><li>ISystemStateComponentData</li><li>ISharedSystemStateComponentData</li></ul><p><code>EntityManager</code> 会组织所有实体中独立的组件组合成不同的<strong>原型（Archetypes）</strong>，还会将拥有同样原型的所有实体的组件（数据）储存到一起，都放到同一个**内存块（Chunks）**中。</p><p>如果你为一个实体新增了一个组件，那么其原型就改变了，实体的数据也需要从原来的内存块移到新的内存块，因为只有相同原型的实体数据才会放到相同的内存块中。</p><p>一个原型由很多个内存块组成，这些内存块中存的都是拥有相同原型的实体。</p><p><img src="http://img.frankorz.com/5cd16af8d2324.png" alt=""></p><h2 id="General-Purpose-Component（普通用途组件）">General Purpose Component（普通用途组件）</h2><p>这里指的是最普通的组件，可以通过实现 <code>IComponentData</code> 接口来创建。</p><p><code>IComponentData</code> 不存储行为，只储存数据。<code>IComponentData</code> 还是一个结构体（Struct）而不是一个类（Class），这意味着被复制时默认是通过值而不是通过引用。</p><p>通常我们会用下面的<strong>模式</strong>来修改组件数据：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> transform = <span class="keyword">group</span>.transform[index]; <span class="comment">// Read</span></span><br><span class="line">    </span><br><span class="line">transform.heading = playerInput.move; <span class="comment">// Modify</span></span><br><span class="line">transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;</span><br><span class="line"></span><br><span class="line"><span class="keyword">group</span>.transform[index] = transform; <span class="comment">// Write</span></span><br></pre></td></tr></table></figure><p><code>IComponentData</code> 结构不包含托管对象（managed objects）的引用，所有<code>IComponentData</code> 被存在无垃圾回收的<a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/chunk_iteration.html">块内存（chunk memory）</a>中。</p><p>你可能还听过一种组件是不包含数据、只用来标记的“Tag”组件（Tag component），其用途也很广，例如我们可以轻易地给实体加标记来区分玩家和敌人，这样系统中能更容易通过组件的类型来筛选我们想要的实体。如果我们给一个内存块（Chunk）中的所有实体都添加&quot;Tag“组件的话，只有内存块中对应的原型会修改，不添加数据，因此官方也推荐利用好”Tag“组件。</p><p>See file: /Packages/com.unity.entities/Unity.Entities/IComponentData.cs.</p><h2 id="Shared-components（共享组件）">Shared components（共享组件）</h2><p>Shared components 是一种特殊的组件，你可以把某些特殊的需要共享的值放到 shared component 中，从而在实体中与其他组件划分开。例如有时候我们的实体需要共享一套材质，我们可以为需要共享的材质创建 <code>Rendering.RenderMesh</code>，再放到 shared components 中。原型中也可以定义 shared components，这一点和其他组件是一样的。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">System.Serializable</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">struct</span> RenderMesh : ISharedComponentData</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> Mesh                 mesh;</span><br><span class="line">    <span class="keyword">public</span> Material             material;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> ShadowCastingMode    castShadows;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">bool</span>                 receiveShadows;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当你为一个实体添加一个 shared components 时， <code>EntityManager</code> 会把所有带有同样 shared components 的实体放到一个同样的内存块中（Chunks）。shared components 允许我们的系统去一并处理相似的（有同样 shared components 的）实体。</p><h3 id="内存结构">内存结构</h3><p><img src="http://img.frankorz.com/5d295d398f62598876.png" alt=""></p><p>每个内存块（Chunk）会有一个存放 shared components 索引的数组。这句话包含了几个要点：</p><ol><li>对于实体来说，有同样 <code>SharedComponentData</code> 的实体会被一起放到同样的内存块（Chunk）中。</li><li>如果我们有两个存储在同样的内存块中的两个实体，它们有同样的 <code>SharedComponentData</code> 类型和值。我们修改其中一个实体的 <code>SharedComponentData</code> 的值，这样会导致这个实体会被移动到一个新的内存块中，因为一个内存块共享同一个数组的 <code>SharedComponentData</code> 索引。事实上，从一个实体中增加或者移除一个组件，或者改变 shared components 的值都会导致这种操作的发生。</li><li>其索引存储在内存块而非实体中，因此 <code>SharedComponentData</code> 对实体来说是低开销的。</li><li>因为内存块只需要存其索引，<code>SharedComponentData</code> 的内存消耗几乎可以忽略不计。</li></ol><p>因为上面的第二个要点，我们不能滥用 shared components。滥用 shared components 将让 Unity 不能利用好内存块（Chunk），因此我们要避免添加不必要的数据或修改数据到 shared components 中。我们可以通过 Entity Debugger 来监测内存块的利用。</p><p><img src="http://img.frankorz.com/5d295b6d78b7f51832.png" alt=""></p><p>拿上一段 RenderMesh 的例子来说，共享材质会更有效率，因为 shared components 有其自己的 <code>manager</code> 和哈希表。其中 <code>manager</code> 带有一个存储 shared components 数据的自由列表（<a href="https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E8%A1%A8">freelist</a>），哈希表可以快速地找到相应的值。内存块里面存的是索引数组，需要找数据的时候就会从 Shared Component Manager 中找。</p><h3 id="其他要点">其他要点</h3><ul><li><code>EntityQuery</code> 可以迭代所有拥有相同 <code>SharedComponentData</code> 的实体</li><li>我们可以用 <code>EntityQuery.SetFilter()</code> 来迭代所有拥有某个特定 <code>SharedComponentData</code> 的实体。这种操作开销十分低，因为 <code>SetFilter</code> 内部筛选的只是 int 的索引。前面说了每个内存块都有一个<code>SharedComponentData</code> 索引数组，因此对于每个内存块来说，筛选（filtering）的消耗都是可以忽略不计的。</li><li>怎么样获取 <code>SharedComponentData</code> 的值呢？<code>EntityManager.GetAllUniqueSharedComponentData&lt;T&gt;</code> 可以得到在存活的实体中（alive entities）的所有的泛型 T 类型的<code>SharedComponentData</code> 值，结果以参数中的列表返回，你也可以通过其重载的方法获得所有值的索引。其他获取值的方法可以参考 /Packages/com.unity.entities/Unity.Entities/EntityManagerAccessComponentData.cs。</li><li><code>SharedComponentData</code> 是自动引用计数的，例如在没有任何内存块拥有某个<code>SharedComponentData</code> 索引的时候，引用计数会置零，从而知道要删除<code>SharedComponentData</code> 的数据 。这一点就能看出其在 ECS 的世界中是非常独特的存在，想要深入了解可以看这篇文章<a href="https://gametorrahod.com/everything-about-isharedcomponentdata/">《Everything about ISharedComponentData》</a>。</li><li><code>SharedComponentData</code> 应该尽量不去更改，因为更改 <code>SharedComponentData</code> 会导致实体的组件数据需要复制到其他的内存块中。</li></ul><p>你也可以读读这篇更深入的文章<a href="https://gametorrahod.com/everything-about-isharedcomponentdata/">《Everything about ISharedComponentData》</a>。</p><h2 id="System-state-components（系统状态组件）">System state components（系统状态组件）</h2><p><code>SystemStateComponentData</code> 允许你跟踪系统（System）的资源，并允许你合适地创建和删除某些资源，这些过程中不依赖独立的回调（individual callback）。</p><blockquote><p>假设有一个网络同步 System State，其监控一个 Component A 的同步，则我只需要定义一个 SystemStateComponent SA。当 Entity [有 A，无 SA] 时，表示 A 刚添加，此时添加 SA。等到 Entity [无 A，有 SA] 时,表示 A 被删除（尝试销毁Entity 时也会删除 A）。<br><a href="https://zhuanlan.zhihu.com/p/51289405">《浅入浅出Unity ECS》</a> BenzzZX</p></blockquote><p><code>SystemStateComponentData</code>  和 <code>SystemStateSharedComponentData</code> 这两个类型与 <code>ComponentData</code> 和 <code>SharedComponentData</code> 十分相似，不同的是前者两个类型都是系统级别的，不会在实体删除的时候被删除。</p><h3 id="Motivation（诱因）">Motivation（诱因）</h3><p>System state components 有这样特殊的行为，是因为：</p><ul><li>系统可能需要保持一个基于 <code>ComponentData</code> 的内部状态。例如已经被分配的资源。</li><li>系统需要通过值来管理这些状态，也需要管理其他系统所造成的状态改变。例如在组件中的值改变的时候，或者在相关组件被添加或者被删除的时候。</li><li>“没有回调”是 ECS 设计规则的重要元素。</li></ul><h3 id="Concept（概念）">Concept（概念）</h3><p><code>SystemStateComponentData</code> 普遍用法是镜像一个用户组件，并提供内部状态。</p><p>上面引用的网络同步的例子中，A 就是用户分配的 <code>ComponentData</code>，SA 就是系统分配的 <code>SystemComponentData</code>。</p><p>下面以 FooComponent （<code>ComponentData</code>）和 FooStateComponent（<code>SystemComponentData</code>）做主要用途的示例。前两个用途已经在前面的网络同步例子中呈现过。</p><h4 id="检测组件的添加">检测组件的添加</h4><p>如果用户添加 FooComponent 时，FooStateComponent 还不存在。FooSystem 会在 update 中查询，如果实体只有 FooComponent 而没有 FooStateComponent,，则可以判断这个实体是新添加的。这时候 FooSystem 会加上 FooStateComponent 组件和其他需要的内部状态。</p><h4 id="检测组件的删除">检测组件的删除</h4><p>如果用户删除 FooComponent 后，FooStateComponent 仍然存在。FooSystem 会在 update 中查询，如果实体没有 FooComponent 而有 FooStateComponent,，则可以判断 FooComponent 已经被删除了。这时候 FooSystem 会给删除 FooStateComponent 组件和修改其他需要的内部状态。</p><h4 id="监测实体的删除">监测实体的删除</h4><p>通常 <code>DestroyEntity</code> 这个方法可以用来：</p><ol><li>找到所有由某个实体 ID 标记的所有组件</li><li>删除那些组件</li><li>回收实体 ID 以作重用</li></ol><p>然而，<code>DestroyEntity</code> 无法删除 <code>SystemStateComponentData</code> 。</p><p>在你删除实体时，<code>EntityManager</code> <strong>不会</strong>移除任何 system state components，在它们没被删除的时候，<code>EntityManager</code> 也不会回收其实体的 ID 。这样允许系统（System）在一个实体被删除的时候，去整理内部的状态（internal state），也能清理关联着实体 ID 的相关的资源和状态。实体 ID 只会在所有 <code>SystemStateComponentData</code> 被删除的时候才被重用。</p><h2 id="Dynamic-Buffers（动态缓冲）">Dynamic Buffers（动态缓冲）</h2><p><code>DynamicBuffer</code> 也是组件的一种类型，它能把一个变量内存空间大小的弹性的缓冲（variable-sized, “stretchy” buffer）和一个实体关联起来。它内部存储着一定数量的元素，但如果内部所占内存空间太大，会额外划分一个堆内存（heap memory）来存储。</p><p>动态缓冲的内存管理是全自动的。与 <code>DynamicBuffer</code> 关联的内存由 <code>EntityManager</code> 来管理，这样当<code>DynamicBuffer</code> 组件被删除的时候，所关联的堆内存空间也会自动释放掉。</p><p>上面的解释可能略显苍白，实际上 <code>DynamicBuffer</code> 可以看成一个有默认大小的数组，其行为和性能都和 <code>NativeArray</code>（在 ECS 中常用的无 GC 容器类型）差不多，但是存储数据超过默认大小也没关系，上文提到了会创建一个堆内存来存储多的数据。<code>DynamicBuffer</code> 可以通过 <code>ToNativeArray</code> 转成 <code>NativeArray</code> 类型，其中只是把指针重新指向缓冲，不会复制数据。</p><p><a href="http://tsubakit1.hateblo.jp/entry/2018/11/07/234502">【Unity】ECSで配列を格納する Dynamic Buffers</a> 这篇文章中，作者用<code>DynamicBuffer</code> 来储存临近的圆柱体实体，从而更方便地与这些实体交互。</p><h3 id="定义缓冲">定义缓冲</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 8 指的是缓冲中默认元素的数量，例如这例子中存的是 Integer 类型</span></span><br><span class="line"><span class="comment">// 那么 8 integers （32 bytes）就是缓冲的默认大小</span></span><br><span class="line"><span class="comment">// 64 位机器中则占 16 bytes</span></span><br><span class="line">[<span class="meta">InternalBufferCapacity(8)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">struct</span> MyBufferElement : IBufferElementData</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 下面的隐式转换是可选的，这样可以少写些代码</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">implicit</span> <span class="keyword">operator</span> <span class="title">int</span>(<span class="params">MyBufferElement e</span>)</span> &#123; <span class="keyword">return</span> e.Value; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">implicit</span> <span class="keyword">operator</span> <span class="title">MyBufferElement</span>(<span class="params"><span class="built_in">int</span> e</span>)</span> &#123; <span class="keyword">return</span> <span class="keyword">new</span> MyBufferElement &#123; Value = e &#125;; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 每个缓冲元素要存储的值</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> Value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可能有点奇怪，我们要定义缓冲中元素的结构而不是 <code>Buffer</code> 缓冲本身，其实这样在 ECS 中有两个好处：</p><ol><li>对于 <code>float3</code> 或者其他常见的值类型来说，这样能支持多种 <code>DynamicBuffer</code> 。我们可以重用已有的缓冲元素的结构，来定义其他的 <code>Buffers</code>。</li><li>我们可以将 <code>Buffer</code> 的元素类型包含在 <code>EntityArchetypes</code> 中，这样它会表现得像拥有一个组件一样。例如用 <code>AddBuffer()</code> 方法，可以通过 <code>entityManager.AddBuffer&lt;MyBufferElement&gt;(entity);</code> 来添加缓冲。</li></ol><h1>Systems（系统）</h1><p>系统负责将组件数据从一个状态（state）通过逻辑处理到下一个状态。例如系统可以根据帧间隔和实体的速度，在当前帧更新所有移动实体的位置。</p><p>世界初始化后提供了三个系统组（system groups），分别是 initialization、simulation 和 presentation，它们会按顺序在每帧中执行。</p><p>系统组的概念会在下文提到。</p><h2 id="ComponentSystem（组件系统）">ComponentSystem（组件系统）</h2><p><code>ComponentSystem</code> 通常指 ECS 实体组件系统中最基本的概念 System，它提供要执行的操作给实体。</p><p><code>ComponentSystem</code> 不能包含实体的数据。从传统的开发模式来看，它与旧的 Component 类有点相似，不过 <code>ComponentSystem</code>  <strong>只包含方法</strong>。</p><p>一个 <code>ComponentSystem</code>  负责更新所有<strong>匹配组件类型</strong>的实体。例如：系统可以通过条件过滤来获得所有拥有 Player 标记（Tag）和位置（Translation）的实体，再对获得的一系列 Player 实体进行处理。其中这种条件过滤由 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/component_group.html">EntityQuery</a> 结构定义。</p><p><img src="http://img.frankorz.com/5d29bcddd863174576.png" alt=""></p><p>要注意的是，<code>ComponentSystem</code> 只在<strong>主线程</strong>中执行。</p><p>我们可以通过继承 <code>ComponentSystem</code>  抽象类来定义我们的系统。</p><p>See file: /Packages/com.unity.entities/Unity.Entities/ComponentSystem.cs.</p><h2 id="JobComponentSystem（任务组件系统）">JobComponentSystem（任务组件系统）</h2><p>前文提到了 ECS 能很好的和 JobSystem 一起合作，那么这个类型就是一个很好的例子。<code>ComponentSystem</code> 只在<strong>主线程</strong>中执行，而 <code>JobComponentSystem</code>  则能在<strong>多线程</strong>中执行，更能利用多核的优势。</p><p><img src="http://img.frankorz.com/5d29bd129f0d058419.png" alt=""></p><h3 id="自动化的-Job-依赖管理">自动化的 Job 依赖管理</h3><p><code>JobComponentSystem</code> 能帮我们自动管理依赖。原理很简单，来自不同系统的 Job 可以并行地读取相同类型的 <code>IComponentData</code>。如果其中一个 Job 正在写（write）数据，那么所有的 Job 就不能并行地执行，而是设定它们的依赖来安排执行顺序。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">RotationSpeedSystem</span> : <span class="title">JobComponentSystem</span></span><br><span class="line">&#123;</span><br><span class="line">    [<span class="meta">BurstCompile</span>]</span><br><span class="line">    <span class="keyword">struct</span> RotationSpeedRotation : IJobForEach&lt;Rotation, RotationSpeed&gt;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="built_in">float</span> dt;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="keyword">ref</span> Rotation rotation, [ReadOnly]<span class="keyword">ref</span> RotationSpeed speed</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            rotation.<span class="keyword">value</span> = math.mul(math.normalize(rotation.<span class="keyword">value</span>), quaternion.axisAngle(math.up(), speed.speed * dt));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 所有对 Rotation 读/写的和对 RotationSpeed 进行写操作的</span></span><br><span class="line">        <span class="comment">// 已经排程的 Job 会自动放到 JobHandle 类型的依赖句柄 inputDeps 中</span></span><br><span class="line">        <span class="comment">// 在方法中，我们也需要把自己的 Job 依赖加进句柄中，并在方法末尾返回回来。</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> JobHandle <span class="title">OnUpdate</span>(<span class="params">JobHandle inputDeps</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">var</span> job = <span class="keyword">new</span> RotationSpeedRotation() &#123; dt = Time.deltaTime &#125;;</span><br><span class="line">        <span class="keyword">return</span> job.Schedule(<span class="keyword">this</span>, inputDeps);</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="怎么运行的？">怎么运行的？</h3><p>所有 Jobs 和系统会声明它们会读/写哪些组件类型（ComponentTypes）。JobComponentSystem 返回的 <a href="https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.html">JobHandle</a> 依赖句柄会自动注册到 <code>EntityManager</code> 中，以及所有包含读或写（reading or writing）信息的类型中。</p><p>这样如果一个系统对 Component A 进行写操作而之后另一个系统会对其进行读操作， <code>JobComponentSystem</code> 会查询读取（reading）的类型列表，然后传给你一个依赖。依赖包含第一个系统返回的 JobHandle，也就是包含“一个系统对 Component A 进行写操作”这个依赖，并将其作为第二个系统的参数传入。</p><p><code>JobComponentSystem</code> 简单地按照需求维护一个依赖链，这样不会对主线程造成影响。但是如果一个非 Job 的 <code>ComponentSystem</code> 要存取（access）相同的数据会怎么样呢？因为所有的存取都是声明好的，因此对于所有 <code>ComponentSystem</code> 需要进行存取的组件类型（component type）相关联的 Jobs，<code>ComponentSystem</code> 都会先自动完成这些相关的 Jobs，再在 <code>OnUpdate</code> 中调用依赖。</p><h3 id="依赖管理是保守的（conservative）和确定性的（deterministic）">依赖管理是保守的（conservative）和确定性的（deterministic）</h3><p>依赖管理是保守的。 <code>ComponentSystem</code> 只是简单的跟踪所有使用的 <code>EntityQuery</code>，然后基于 <code>EntityQuery</code> 存储需要读或写的类型。</p><p>当在一个系统中分发多个 Jobs 的时候，依赖必须被发送到所有 Jobs 中，即使不同的 Jobs 可能需要更少的依赖。如果这里被证明有性能问题，那最好的解决方法是将系统一分为二。</p><p>依赖管理的手段也是保守的。它通过提供一个非常简单的 API 来允许确定性和正确的行为。</p><h3 id="Sync-points（同步点）">Sync points（同步点）</h3><p><img src="http://img.frankorz.com/5d29bd3e4103143937.png" alt=""></p><p>所有结构性的变化都有确切的同步点（hard sync points）。 <code>CreateEntity</code>、<code>Instantiate</code>、 <code>Destroy</code>、 <code>AddComponent</code>、 <code>RemoveComponent</code>、<code>SetSharedComponentData</code> 都有一个确切的同步点。这代表所有通过 <code>JobComponentSystem</code> 排期的 Jobs 都会在创建实体之前自动完成。</p><p>例如，在一帧中间的 <code>EntityManager.CreateEntity</code> 可能带来较大的<strong>停滞</strong>，因为所有在世界中的提前排期好的 Jobs 都需要完成。</p><p>如果要在游戏中避免上面提到的停滞，可以使用 <code>EntityCommandBuffer</code>。</p><h3 id="Multiple-Worlds（多个世界）">Multiple Worlds（多个世界）</h3><p>所有世界（World）都有自己的 <code>EntityManager</code> ，因此 <code>JobHandle</code> 依赖句柄的集合都是分开的。一个世界中的确切的同步点（hard sync points）不会影响另外一个世界。因此，对于流式传输和程序化生成的场景，最后在一个世界中创建实体然后移到另一个世界作为一个事务（transaction）并在帧的开始执行。</p><p>对于上面的问题可以参考 <a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/exclusive_entity_transaction.html">ExclusiveEntityTransaction</a> 和 System update order。</p><h2 id="Entity-Command-Buffer（实体命令缓冲）">Entity Command Buffer（实体命令缓冲）</h2><p><code>EntityCommandBuffer</code> 解决了两个重要问题：</p><ol><li>在 Job 中无法访问 <code>EntityManager</code>，因此不能通过它来管理实体。</li><li>当你使用 <code>EntityManager</code> 时（例如创建一个实体），你会使所有已被注入的数组和 <code>EntityQuery</code> 无效。（这里注入的概念大概是指：系统中可以设定某个过滤条件，给过滤条件加上 <code>[inject]</code> 后，系统会在启动时为这个属性根据条件注入数据，这样就能得到我们想要的数据。会无效是因为你修改了实体数据，那么结果可能会发生改变。）</li></ol><p><code>EntityCommandBuffer</code> 的抽象允许我们去把需要对数据的更改（changes）排好队，这个更改可以来自主线程或者 Jobs，这样数据可以晚一点在主线程接受更改，从而将其和获取数据分离开来。</p><p>我们有两种方法来使用 <code>EntityCommandBuffer</code> ：</p><ul><li>在主线程 update 的 <code>ComponentSystem</code> 子类有一个 <code>PostUpdateCommands</code>（其本身是一个<code>EntityCommandBuffer</code> ） 可以用，我们只要简单地把变化按顺序放进去即可。在系统的 <code>Update</code> 调用之后，它会立刻自动在世界（World）中进行所有数据更改。这样可以防止数组数据无效，API 也和 <code>EntityManager</code> 很相似。</li></ul><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">PostUpdateCommands.CreateEntity(TwoStickBootstrap.BasicEnemyArchetype);</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="keyword">new</span> Position2D &#123; Value = spawnPosition &#125;);</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="keyword">new</span> Heading2D &#123; Value = <span class="keyword">new</span> float2(<span class="number">0.0f</span>, <span class="number">-1.0f</span>) &#125;);</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="literal">default</span>(Enemy));</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="keyword">new</span> Health &#123; Value = TwoStickBootstrap.Settings.enemyInitialHealth &#125;);</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="keyword">new</span> EnemyShootState &#123; Cooldown = <span class="number">0.5f</span> &#125;);</span><br><span class="line">PostUpdateCommands.SetComponent(<span class="keyword">new</span> MoveSpeed &#123; speed = TwoStickBootstrap.Settings.enemySpeed &#125;);</span><br><span class="line">PostUpdateCommands.AddSharedComponent(TwoStickBootstrap.EnemyLook);</span><br></pre></td></tr></table></figure><ul><li>对于 Jobs 来说，我们必须从主线程的 <code>EntityCommandBufferSystem</code> 中请求一个 <code>EntityCommandBuffer</code>，再传到 Job 里面让其调用。 每当 <code>EntityCommandBufferSystem</code>  进行 update，命令缓冲都会在主线程中重新把更改按创建的顺序执行一遍。这样允许我们集中进行内存管理，也保证了创建的实体和组件的确定性。</li></ul><h3 id="Entity-Command-Buffer-Systems（实体命令缓冲系统）">Entity Command Buffer Systems（实体命令缓冲系统）</h3><p>在一个系统组中，有一个 Entity Command Buffer Systems 运行在所有系统组之前，还有一个运行在所有系统组之后。比较建议的是我们可以用已存在的命令缓存系统（command buffer system）之一，而不用创建自己的，这样可以最小化同步点（sync point）。</p><h3 id="在-ParallelFor-jobs-中使用-EntityCommandBuffers">在 ParallelFor jobs 中使用 EntityCommandBuffers</h3><p>在 <a href="https://docs.unity3d.com/Manual/JobSystemParallelForJobs.html">ParallelFor jobs</a> 使用 <code>EntityCommandBuffer</code> 存 <code>EntityManager</code> 的命令（command）时， <code>EntityCommandBuffer.Concurrent</code> 接口能保证线程安全和确定性的回放（deterministic playback）。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// See file: /Packages/com.unity.entities/Unity.Entities/EntityCommandBuffer.cs.</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Entity <span class="title">CreateEntity</span>(<span class="params"><span class="built_in">int</span> jobIndex, EntityArchetype archetype = <span class="keyword">new</span> EntityArchetype(</span>))</span></span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line">    m_Data-&gt;AddCreateCommand(chain, jobIndex, ECBCommand.CreateEntity,  index, archetype, kBatchableCommand);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> Entity &#123;Index = index&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>EntityCommandBuffer.Concurrent</code> 的公共方法都会接受一个 <code>jobIndex</code> 参数，这样能回放（playback）已经按顺序保存好的命令。 <code>jobIndex</code> 作为 ID 必须在每个 Job 中唯一。从性能考虑，<code>jobIndex</code> 必须是传进 <code>IJobParallelFor.Execute()</code> 的不断增长的 <code>index</code>。除非你真的知道你传的是啥，否则最安全的做法就是把参数中的 <code>index</code> 作为 <code>jobIndex</code> 传进去。用其他 <code>jobIndex</code> 可能会产生正确的结果，但是可能在某些情况下会有严重的性能影响。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Unity.Jobs</span></span><br><span class="line">&#123;</span><br><span class="line">    [<span class="meta">JobProducerType(typeof (IJobParallelForExtensions.ParallelForJobStruct&lt;&gt;))</span>]</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IJobParallelFor</span></span><br><span class="line">    &#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span>   <span class="doctag">&lt;para&gt;</span>Implement this method to perform work against a specific iteration index.<span class="doctag">&lt;/para&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;index&quot;&gt;</span>The index of the Parallel for loop at which to perform work.<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Execute</span>(<span class="params"><span class="built_in">int</span> index</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="System-Update-Order（系统更新顺序）">System Update Order（系统更新顺序）</h2><p>组件系统组（Component System Groups）其实是为了解决世界（World）中各种 update 的顺序问题。一个系统组中包含了很多需要按照顺序一起 update 的组件系统（component systems），可以来指定它成员系统（member system）的 update 顺序。</p><p>和其他系统一样， <code>ComponentSystemGroup</code> 也继承自 <code>ComponentSystemBase</code> ，因此系统组可以当成一个大的“系统”，里面也用 <code>OnUpdate()</code> 函数来更新系统。它也可以被指定更新的顺序（在某个系统的之前或之后更新等，下文会讲），并且也可以嵌入到其他系统组中。</p><p>默认情况下， <code>ComponentSystemGroup</code> 的 <code>OnUpdate()</code> 方法会按照成员系统（member system）的顺序来调用他们的 <code>Update()</code>，如果成员系统也是一个系统组，那么这个系统组也会递归地更新它的成员系统。总体的系统遵循树的深度优先遍历。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// See file: /Packages/com.unity.entities/Unity.Entities/ComponentSystemGroup.cs.</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">OnUpdate</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (m_systemSortDirty)</span><br><span class="line">        SortSystemUpdateList();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">foreach</span> (<span class="keyword">var</span> sys <span class="keyword">in</span> m_systemsToUpdate)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">try</span></span><br><span class="line">        &#123;</span><br><span class="line">            sys.Update();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> (Exception e)</span><br><span class="line">        &#123;</span><br><span class="line">            Debug.LogException(e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (World.QuitUpdate)</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="System-Ordering-Attributes（系统顺序属性）">System Ordering Attributes（系统顺序属性）</h3><ul><li><code>[UpdateInGroup]</code> 指定某个系统成为一个 <code>ComponentSystemGroup</code> 中的成员系统。如果没有用这个属性，这个系统会自动被添加到默认世界（default World）的 SimulationSystemGroup 中。</li><li><code>[UpdateBefore]</code> 和 <code>[UpdateAfter]</code> 指定系统相对于其他系统的更新顺序。这两个系统必须在同一个系统组（system group）中，前文说到系统组也可以嵌套，因此只要两个系统身处同一个根系统组即可。<ul><li>例子：如果 System A 在 Group A 中、System B 在 Group B 中，而且 Group A 和 Group B 都是 Group C 的成员系统，那么 Group A 和 Group B 的相对顺序也决定着 System A 和 System B 的相对顺序，这时候就不需要明确地用属性标明顺序了。</li></ul></li><li><code>[DisableAutoCreation]</code> 阻止系统从默认的世界初始化中创建或添加到世界中。这时候我们需要显式地创建和更新系统。然而我们也可以把这个系统和它的标记（tag）加到 <code>ComponentSystemGroup</code> 的更新列表中（update list），这样这个系统会正常地自动更新。</li></ul><h3 id="Default-System-Groups（默认系统组）">Default System Groups（默认系统组）</h3><p>默认世界（default World）包含 <code>ComponentSystemGroup</code> 实例的层次结构（hierarchy）。在 Unity Player Loop 中会添加三个根层次（root-level）的系统组。</p><p>下图中打开 Entity Debugger，也能看到这三个系统组和其顺序。</p><p><img src="http://img.frankorz.com/5d2ab0dd94fe244311.png" alt=""></p><p>这三个系统组各司其职， <code>InitializationSystemGroup</code> 做初始化工作， <code>SimulationSystemGroup</code> 在 Update 中做主要的逻辑运算， <code>PresentationSystemGroup</code> 做图形渲染工作。</p><p>如果勾选 “Show Full Player Loop” 项，还能看到完整的游戏主循环，以及系统组执行的顺序。</p><p><img src="http://img.frankorz.com/5d2acd6b5ea8681591.png" alt=""></p><p>下面列表也展示了预定义的系统组和其成员系统：</p><ul><li>InitializationSystemGroup （在游戏循环（Player Loop）的 <code>Initialization</code> 层最后 update）<ul><li>BeginInitializationEntityCommandBufferSystem</li><li>CopyInitialTransformFromGameObjectSystem</li><li>SubSceneLiveLinkSystem</li><li>SubSceneStreamingSystem</li><li>EndInitializationEntityCommandBufferSystem</li></ul></li><li>SimulationSystemGroup（在游戏循环的 <code>Update</code> 层最后 update）<ul><li>BeginSimulationEntityCommandBufferSystem</li><li>TransformSystemGroup<ul><li>EndFrameParentSystem</li><li>CopyTransformFromGameObjectSystem</li><li>EndFrameTRSToLocalToWorldSystem</li><li>EndFrameTRSToLocalToParentSystem</li><li>EndFrameLocalToParentSystem</li><li>CopyTransformToGameObjectSystem</li></ul></li><li>LateSimulationSystemGroup</li><li>EndSimulationEntityCommandBufferSystem</li></ul></li><li>PresentationSystemGroup（在游戏循环的 <code>PreLateUpdate</code> 层最后 update）<ul><li>BeginPresentationEntityCommandBufferSystem</li><li>CreateMissingRenderBoundsFromMeshRenderer</li><li>RenderingSystemBootstrap</li><li>RenderBoundsUpdateSystem</li><li>RenderMeshSystem</li><li>LODGroupSystemV1</li><li>LodRequirementsUpdateSystem</li><li>EndPresentationEntityCommandBufferSystem</li></ul></li></ul><p><strong>P.S. 内容可能在未来有更改</strong></p><p>Multiple Worlds（多个世界）</p><p>前文多处提到默认的世界，实际上我们可以创建多个世界。同样的组件系统（component system）的类可以在不同的世界中初始化，而且每个实例都可以处于不同的同步点以不同的速度进行update。</p><p>当前没有方法手动更新一个世界中的所有系统，但是我们可以控制哪些系统被哪个世界控制，和它们要被加到哪个现存的世界中。自定义的世界可以通过实现 <code>ICustomBootstrap</code> 接口来创建。</p><h3 id="Tips-and-Best-Practices（提示与最佳实践）">Tips and Best Practices（提示与最佳实践）</h3><ul><li>**用 <code>[UpdateInGroup]</code> 为你的系统指定一个 <code>ComponentSystemGroup</code> 系统组。**如果没有用这个属性，这个系统会自动被添加到默认世界（default World）的 SimulationSystemGroup 中。</li><li>**用手动更新循环（manually-ticked）的 ComponentSystemGroups 来 update 在主循环中的系统。**添加 <code>[DisableAutoCreation]</code>  阻止系统从默认的世界初始化中创建或添加到世界中。这时候我们可以在主线程中调用 <code>World.GetOrCreateSystem()</code> 来创建系统，调用 <code>MySystem.Update()</code> 来 update 系统。如果你有一个系统要在帧中早点或者晚点运行，这种做法能更简单地把系统插到主循环中。</li><li>**尽量使用已存在的 <code>EntityCommandBufferSystem</code> 而不是重新添加一个新的。**因为一个 <code>EntityCommandBufferSystem</code> 代表一个主线程等待子线程完成的同步点（sync point），如果重用一个在每个根系统组（root-level system group）中预定义的 Begin/End 系统，就能节省多个同步点所带来的额外时间间隔（可以回去看同步点小节的示意图，同步点的位置是由最晚执行完的子线程所决定的）。</li><li>**避免放自定义的逻辑到 <code>ComponentSystemGroup.OnUpdate()</code> 中。**虽然 <code>ComponentSystemGroup</code> 功能上和一个组件系统（component system）一样，但是我们应该避免这么做。因为它作为一个系统组，在外面不能马上知道成员系统是否已经执行了 update，因此推荐的做法是只让系统组当一个组（group）来用，而把逻辑放到与其分离的组件系统中，再定好该系统与系统组的相对顺序。</li></ul><h1>最后</h1><p>自己才刚考完试，所以计划的文章一直拖到现在。ECS 对我而言充满着吸引力，可能有些程序员也会对性能特别执着吧，它就像魔法一样，完全不同的开发模式，还需要我们深入了解内存的结构。尽管 ECS 可能在工作中对我是一种屠龙技，但有些知识啊，学了就已经很开心了~</p><p>我的毕业季也到来了，有空的话可能会写写 Demo 把剩下的实践部分补完，当然计划也可能搁浅。不管怎么样，希望本文对 ECS 同好有所帮助，有问题也欢迎在评论指出。</p><h1>参考</h1><ul><li><a href="https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html">《Entity Component System Manual》</a></li><li><a href="https://github.com/BrianWill/LearnUnity/tree/master/ecs-jobs">BrianWill LearnUnity/ecs-jobs</a></li><li><a href="https://forum.unity.com/threads/ecs-memory-layout.532028/">《ECS Memory Layout》</a></li><li><a href="https://gametorrahod.com/world-system-groups-update-order-and-the-player-loop/">《World, system groups, update order, and the player loop》</a></li><li><a href="https://docs.google.com/presentation/d/1vxE61D_N79cvgUI3eIocF2n4rn04wjgRRq00ugyoB1M/edit?usp=sharing">UniteLA 2018 - ECS deep dive</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> Unity </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> ECS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Unity DOTS 走马观花</title>
      <link href="/2019/05/07/simple-talk-unity-dots/"/>
      <url>/2019/05/07/simple-talk-unity-dots/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/5cd168d7974ba.png" alt="The Big Picture"></p><p>简单介绍 Data-Oriented Technology Stack (DOTS, 数据导向型技术栈) ，其包含了 C# Job System、the Entity Component System (ECS) 和 Burst。</p><span id="more"></span><h1>特点</h1><p>DOTS 要实现的特点有：</p><ul><li>**性能的准确性。**我们希望的效果是：如果循环因为某些原因无法向量化，它应该会出现编译器错误，而不是使代码运行速度慢 8 倍，并得到正确结果，完全不报错。</li><li><strong>跨平台架构特性</strong>。我们编写的输入代码无论是面向 iOS 系统还是 Xbox，都应该是相同的。</li><li>我们应该<strong>有不错的迭代循环</strong>。在修改代码时，可以轻松查看为所有架构生成的机器代码。机器代码“查看器”应该很好地说明或解释所有机器指令的行为。</li><li><strong>安全性</strong>。大多数游戏开发者不把安全性放在很高的优先级，但我们认为，解决 Unity 出现内存损坏问题是关键特性之一。在运行代码时应该有一个特别模式，如果读取或写入到内存界限外或取消引用 Null 时，它能够提供我们明确的错误信息。</li></ul><p>其中向量化指的是 Vectorization。</p><p>向量化的相关介绍：</p><ul><li><a href="https://stackoverflow.com/questions/1422149/what-is-vectorization">https://stackoverflow.com/questions/1422149/what-is-vectorization</a></li><li><a href="https://www.wikiwand.com/en/Array_programming">https://www.wikiwand.com/en/Array_programming</a></li></ul><h1>Burst</h1><p>Unity 构建了名为 Burst 的代码生成器和编译器。</p><p>当使用 C# 时，我们对整个流程有完整的控制，包括从源代码编译到机器代码生成，如果有我们不想要的部分，我们会找到并修复它。我们会逐渐把 C++ 语言的性能敏感代码移植为 HPC# （高性能 C#，下文会提到）代码，这样会更容易得到想要的性能，更难出现 Bug，更容易进行处理。</p><p><img src="http://img.frankorz.com/5cd1692330824.png" alt=""></p><p>如果 Asset Store 资源插件的开发者在资源中使用 HPC# 代码，资源插件在运行时代码会运行得更快。除此之外，高级用户也会通过使用 HPC# 编写出自定义高性能代码而受益。</p><p><a href="https://www.youtube.com/watch?v=QkM6zEGFhDY">ECS Track: Deep Dive into the Burst Compiler - Unite LA</a></p><p>Burst 对于 HPC# 更详细的支持可以在下面找到：</p><p><a href="https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#cnet-language-support">Burst User Guide</a></p><h2 id="深入栈">深入栈</h2><p>向量化（Vectorization）无法进行的常见情况是，编译器无法确保二个指针不指向相同的内存，即混淆情况（Alias）。Alias 的问题在 Unity GDC 中也有一个演讲提到过：<a href="https://www.youtube.com/watch?v=NF6kcNS6U80">Unity at GDC - C# to Machine Code</a>。</p><p><a href="https://docs.unity3d.com/Packages/com.unity.collections@0.0/manual/index.html">Collections 类</a>就是为了解决这个问题而诞生的，里面包含 NativeList<T>、NativeHashMap&lt;TKey, TValue&gt;、NativeMultiHashMap&lt;TKey, TValue&gt; 和 NativeQueue<T> 四种额外的数据结构。</p><p>两个 NativeArray 之间从不会发生混淆这种情况，这也是为什么我们将会经常使用这些数据结构。我们可以在 Burst 中运用这个知识，使它不会由于害怕两个数组指针指向相同内存而放弃优化。</p><p>Unity 还编写了 <a href="https://github.com/Unity-Technologies/Unity.Mathematics">Unity.Mathemetics</a> 数学库，提供了很多像 Shader 代码的数据结构。Burst 也能和这数学库很好的工作，未来 Burst 将能够为 <code>math.sin()</code> 等计算作出牺牲精度的优化。</p><p>对于 Burst 而言，<code>math.sin()</code> 不仅是要编译的 C# 方法，Burst 还能理解出 <code>sin()</code> 的三角函数属性，同时知道 x 值较小时会出现 <code>sin(x)</code> 等于 x 的情况，并了解它能替换为泰勒级数展开，以便牺牲特定精度。</p><p><strong>跨平台和架构的浮点准确性是 Burst 未来的目标。</strong></p><h1>传统模式的问题</h1><p>传统模式指的是什么呢？</p><ul><li>跟 MonoBehaviours 打交道</li><li>数据和其处理过程耦合在一起</li><li>高度依赖引用类型</li></ul><p><img src="http://img.frankorz.com/5cd16b478f8af.png" alt=""></p><h2 id="问题一：数据分布在内存的各个角落">问题一：数据分布在内存的各个角落</h2><p><img src="http://img.frankorz.com/5cd557e05fa56.jpg" alt=""></p><p>离散的数据导致搜索效率十分低下，还有 Cache Miss 的问题，这个问题可以参考下面的链接：</p><p><a href="https://zhuanlan.zhihu.com/p/41652478">ECS 的泛泛之谈</a></p><h2 id="问题二：很多不必要的数据也被提供了">问题二：很多不必要的数据也被提供了</h2><p><img src="http://img.frankorz.com/5cd56a9b06095.png" alt=""></p><p>例如当我们要调用 Transform 时，可能实际上我们只需要 position 和 rotation 两个属性来移动 gameObject，但是其他不需要的数据也被提供给了 gameObject。</p><h2 id="问题三：低效的单线程数据处理">问题三：低效的单线程数据处理</h2><p>传统模式只使用单线程来按顺序一个一个地处理数据和操作，这样十分低效。</p><h1>高性能 C＃（HPC#）</h1><p>当我们使用 C# 语言时，仍然无法控制数据在内存中如何进行分布，但这是我们提升性能的关键点。</p><p>除此之外，标准库面向的是“堆上的对象”和“具有其它对象指针引用的对象”。</p><p>也就是意味着，当处理性能敏感代码时，我们可以放弃使用大部分标准库，例如：Linq、StringFormatter、List、Dictionary。禁止内存分配，即不使用类，只使用结构、映射、垃圾回收器和虚拟调用，并添加可使用的部分新容器，例如：NativeArray 和其他集合类型。</p><p>我们可以在越界访问时得到错误和错误信息，以及使用 C++ 代码时的调试器支持和编译速度。我们通常把该子集称为高性能 C# 或 HPC#。</p><p>它可以被总结为：</p><ul><li>大部分的原始类型（float、int、uint、short、bool…），enums，structs 和其他类型的指针</li><li>集合：用 <code>NavtiveArray&lt;T&gt;</code> 代替 <code>T[]</code></li><li>所有的控制流语句（除了 try、finally、foreach、using）</li><li>对 <code>throw new XXXException(...)</code> 给予基础支持</li></ul><h1>Job System</h1><p>Job System 是针对上述传统模式问题的一种解决方式。例如下图可以把发射子弹看成一个 Job，从而用多线程来并行地处理发射操作。</p><p><img src="http://img.frankorz.com/5cd549475461e.jpg" alt=""></p><p>目前主流的 CPU 有 4-6 个物理核心，8-12 个逻辑核心，多线程处理将能够更好地发挥 CPU 的性能。</p><p>传统的多线程问题也有很多：</p><ul><li>线程安全的代码十分难写</li><li>竞态条件，也就是计算结果依赖于两个或更多进程被调度的顺序</li><li>低效的上下文切换，切换线程的时候十分耗时</li></ul><p>而 Job System 就是专注解决上面问题的一个方案，这样我们就能享受着多线程的好处来开发游戏。当然了，我们也要写出正确的 ECS 代码，熟悉新的开发模式。</p><h2 id="解决的多线程问题">解决的多线程问题</h2><p>**C++ 和 C# 都无法为开发者编写线程安全代码提供太多帮助。**即使在今天，拥有多个核心游戏消费级硬件发展至今已经过去了十年，但依旧很难有效处理使用多个核心的程序。</p><p>数据冲突，不确定性和死锁是使多线程代码难以编写的挑战。Unity 想要的特性是“确保代码调用的函数和所有内容不会在全局状态下读取或写入”。Unity 希望应该让编译器抛出错误来提醒，而不是属于“程序员应遵守的准则”，Burst 则会提供编译器错误。</p><p>Unity 鼓励 Unity 用户编写 “Jobified” 代码：将「所有需要发生的数据转换」划分为 Job。</p><p>Job 会明确指定使用的只读缓冲区和读写缓冲区，尝试访问其它数据会得到编译器错误。Job 调度程序会确保在 Job 运行时，任何程序都不会写入只读缓冲区。Unity 也会确保在 Job 运行时，任何程序都不会读取读写缓冲区。</p><p>如果调度的 Job 违反了这些规则，我们会得到运行时错误（通常这种错误会在竞态条件出现时得到）。错误信息会说明，你正在尝试调度的 Job 想要读取缓冲区 A，但你之前已经调度了会写入缓冲区 A 的 Job ，所以如果想要执行该操作，需要把之前的 Job 指定为依赖。</p><h1>Entity Component System</h1><p>Unity 一直以组件的概念为中心，例如：我们可以添加 Rigidbody 组件到游戏对象上，使对象能够向下掉落。我们也可以添加 Light 组件到游戏对象上，使它可以发射光线。我们添加 AudioEmitter 组件，可以使游戏对象发出声音。</p><p>我们实现组件系统的方法并没有很好地演变。过去我们<strong>使用面向对象的思维编写组件系统</strong>，导致组件和游戏对象都是“大量使用 C++ 代码”的对象，创建或销毁它们需要使用互斥锁修改“id 到对象指针”的全局列表。</p><p>通过使用面向数据的思维方式，我们可以更好地处理这种情况。我们可以保留用户眼中的优良特性，即<strong>只需添加组件就可以实现功能，而同时通过新组件系统取得出色的性能和并行效果。</strong></p><p>这个全新的组件系统就是实体组件系统 ECS。简单来说，如今我们对游戏对象进行的操作可用于处理新系统的实体，组件仍称作组件。那么区别是什么？区别在于数据布局。</p><h2 id="ECS-数据布局">ECS 数据布局</h2><p>ECS 使用的数据布局会把这些情况看作一种非常常见的模式，并<strong>优化内存布局</strong>，使类似操作更加快捷。</p><h3 id="组件（Component）">组件（Component）</h3><p>首先要明确的是这里的“组件”与上文提到的 Rigidbody “组件”是不一样的概念。ECS 中的组件只会存单纯的数据，不参与任何逻辑运算，逻辑运算会交由系统（System）来处理。</p><h3 id="原型（Archetype）">原型（Archetype）</h3><p><strong>ECS 会在内存中对带有相同组件（Component）集的所有实体（Entity）进行组合</strong>。ECS 把这类组件集称为原型（Archetype）。</p><p>下图的原型就是由 Position 组件、Velocity 组件、Rigidbody 组件和 Renderer 组件组成的。</p><p>如果一个实体只有三个组件（不同于前面提到的原型），那么那三个组件就组成了一个新的原型。</p><p>下面的图来自 Unite LA 的一次演讲的讲义， 很遗憾那次演讲没有录制下来。讲义可以在<a href="https://docs.google.com/presentation/d/1vxE61D_N79cvgUI3eIocF2n4rn04wjgRRq00ugyoB1M/edit?usp=sharing">这里</a>找到。</p><p><img src="http://img.frankorz.com/5cd16aea80286.png" alt=""></p><p>ECS 以 16k 大小的块（Chunk）来分配内存，每个块仅包含单个原型中所有<strong>实体</strong>的<strong>组件</strong>数据。</p><p><img src="assets/undefined" alt=""></p><p>一个<a href="https://forum.unity.com/threads/memory-layout-for-ecs-components.590731/">帖子</a>中有人提供了更加形象的内存布局图，例如上半部分的原型由 Position 组件和 Rock 组件组成，其中整个原型占了一个块（Chunk），两个组件的数据分别存在两个数组中，里面还带着组件数据对应的实体的信息。</p><p><img src="http://img.frankorz.com/5cd583e06c6ef.jpg" alt=""></p><p>每个原型都有一个 Chunks 块列表，用来保存原型的实体。我们会循环所有块，并在每个块中，对紧凑的内存进行线性循环处理，以读取或写入组件数据。该线性循环会对每个实体运行相同的代码，同时为 Burst 创造向量化（Vectorization，可以参考 <a href="https://stackoverflow.com/questions/1422149/what-is-vectorization">StackOverflow 的问题</a>）处理的机会。</p><p>每个块会被安排好内存中的位置，以便于快速从内存得到想要的数据，详情可以参考下面的文章。</p><p><a href="https://blog.csdn.net/yudianxia/article/details/80498015">Unity2018 ECS 框架 Entities 源码解析（二）组件与 Chunk 的内存布局 - 大鹏的专栏</a></p><h3 id="实体（Entity）">实体（Entity）</h3><p>实体是什么？实体只是一个 32 位的整数 key （和一些额外的数据例如 index 和 version 实体版本，不过在这里不重要），所以除了实体的组件数据外，不必为实体保存或分配太多内存。实体可以实现游戏对象的所有功能，甚至更多功能，因为实体非常轻量。</p><p>实体的性能消耗很低，所以我们可以把实体用在不适合游戏对象的情况，例如：为粒子系统内的每个单独粒子使用一个实体。</p><p>实体本身不是对象，也不是一个容器，它的作用是把其组件的数据关联到一起。</p><p><img src="http://img.frankorz.com/5cd16b1878af0.png" alt=""></p><h3 id="系统（System）">系统（System）</h3><p><img src="http://img.frankorz.com/5cd16b28a3b79.png" alt=""></p><p>我们不必使用用户的 Update 方法搜索组件，然后在运行时对每个实例进行操作，使用 ECS 时我们只需静态地声明：我想对同时附带 Velocity 组件和 Rigidbody 组件的所有实体进行操作。为了找到所有实体，我们只需找到所有符合<strong>特定“组件搜索查询”的原型</strong>即可，而这个过程就是由系统（System）来完成的。</p><p>很多情况下，这个过程会分成多个 Job ，使处理 ECS 组件的代码达到几乎 100% 的核心利用率。ECS 会完成所有工作，我们只需要提供对每个实体运行的代码即可。我们也可以手动处理块迭代过程（<a href="https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Samples/Assets/HelloECS/HelloCube_03_IJobChunk/README.md">IJobChunk</a>）。</p><p>当我们从实体添加或移除组件时，ECS 会切换原型。我们会把它从当前块移动到新原型的块，然后交换之前块的最后实体来“填补空缺”。</p><p>在 ECS 中，我们还要静态声明要对组件数据进行什么处理，是 ReadOnly 只读还是 ReadWrite 读写（Job System 一小节提到过的两种缓冲区）。通过确定仅对 Position 组件进行读取，ECS 可以更高效地调度 Job ，其它需要读取 Position 组件的 Job 不必进行等待。</p><p>大体上，实体提供纯粹的数据给系统，系统根据自己所需要的组件来获得相应的满足条件的实体，最后系统再通过多线程来基于 Job System 来处理数据。</p><p><img src="http://img.frankorz.com/5cd547a7c0260.jpg" alt=""></p><p>这种数据布局也解决了 Unity 长期以来的困扰，即：加载时间和序列化的性能。现在从大型场景加载或流式处理 ECS 数据的时间，不会比从硬盘加载和使用原始字节多多少。</p><h2 id="优点">优点</h2><p>总的来说，ECS 有以下好处：</p><ul><li>为性能而生</li><li>更容易写出高度优化和可重用的代码</li><li>更能充分利用硬件的性能</li><li>原型的数据被紧密地排列在内存中</li><li>享受 Burst 编译器带来的魔法</li></ul><h2 id="缺点">缺点</h2><p>对 ECS 的常见观点是：ECS 需要编写很多代码。因此，实现想要的功能需要处理很多样板代码。现在针对移除多数样板代码需求的大量改进即将推出，这些改进会使开发者更简单地表达自己的目的。</p><p>Unity 暂时没有实现太多这类改进，因为 Unity 现在正专注于处理基础性能。</p><blockquote><p>太多样板代码对 ECS 游戏代码没有好处，我们不能让编写 ECS 代码比编写 MonoBehaviour 更麻烦。<br>——Unity</p></blockquote><p>而为网页游戏而生的基于 ECS 的 Project Tiny 已经实现了部分改进，例如：基于 lambda 函数的迭代 API。</p><h1>最后</h1><p>由于自己空闲时间不多，只能囫囵吞枣地拼凑出这样一篇笔记。上面大部分文字都是来自 Unity 的博文介绍，自己加了其他的内容帮助理解。本文从内存布局介绍了 ECS 的概念，也介绍了 Job System 和 Burst。我相信走过一遍文章之后，能清楚 Unity 对数据驱动的未来开发趋势的布局，也能更加容易从 <a href="https://github.com/Unity-Technologies/EntityComponentSystemSamples">Unity ECS Sample</a> 中理解如何实践 ECS。</p><h1>参考</h1><ul><li><a href="https://indienova.com/indie-game-development/unity-dod-all-in-one/">Unity DOD (ECS) 基础概念与资料汇总</a><ul><li>这篇文章总结得很好，但很多视频链接都错了，我提供给了一个改好的版本：<a href="https://www.notion.so/frankorz/DOTS-3562fce36b2a44909e5910609ddfd6a7">DOD 相关文章</a></li></ul></li><li><a href="https://zhuanlan.zhihu.com/p/36649462">Unity ECS 编程官方文档选译–Getting Started</a></li><li><a href="https://mp.weixin.qq.com/s/GsrX6o-sHLrqVgffp1svqg">面向数据技术栈 DOTS 之 ECS 实体组件系统</a></li><li><a href="https://blogs.unity3d.com/2019/03/08/on-dots-entity-component-system/">On DOTS: Entity Component System - Unity Blog</a></li><li><a href="https://blogs.unity3d.com/2019/02/26/on-dots-c-c/">On DOTS: C++ &amp; C# - Unity Blog</a></li><li><a href="https://rams3s.github.io/blog/2019-01-09-ecs-deep-dive/">ECS Deep Dive</a></li><li><a href="https://docs.google.com/presentation/d/1vxE61D_N79cvgUI3eIocF2n4rn04wjgRRq00ugyoB1M/edit?usp=sharing">UniteLA 2018 - ECS deep dive</a></li><li><a href="https://www.youtube.com/playlist?list=PLX2vGYjWbI0S4yHZwjDI1boIrYStpBCdN">Intro To The Entity Component System And C# Job System</a><ul><li>视频中代码部分已经过时，建议参考 <a href="https://github.com/Unity-Technologies/EntityComponentSystemSamples">Unity ECS Sample</a> 官方 Demo 来学习 ECS</li></ul></li></ul>]]></content>
      
      
      <categories>
          
          <category> Unity </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> ECS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>属于 Unity 的 Flutter——UIWidgets</title>
      <link href="/2019/04/01/uiwidgets-practice/"/>
      <url>/2019/04/01/uiwidgets-practice/</url>
      
        <content type="html"><![CDATA[<h2 id="介绍">介绍</h2><p><a href="https://github.com/UnityTech/UIWidgets/">UIWidgets</a> 是 Unity 的一个插件包，是一个从 Google 的移动 UI 框架 Flutter 演变过来的 UI 框架。</p><p>相对于原生开发的高开发成本（不同平台都需要不同的一套代码），Flutter、React-Native 等这种跨平台 UI 框架应运而生。</p><p>Flutter 自 2018 年 3 月发布以来，社区不断壮大。由于 Flutter 自身设计理念的出色，Unity 中国已经着手将其移植过来。当然了，也因为这两个东西都非常的年轻，因此开发的时候都像开荒一样。</p><span id="more"></span><h2 id="框架图">框架图</h2><p>Flutter 有自己的一套渲染系统，那么 Unity 作为一个游戏引擎，底层的图形 API 用自己的一套东西就行了，因此移植过来更方便了。</p><h3 id="Flutter-框架结构">Flutter 框架结构</h3><p><img src="http://img.frankorz.com/flutter-architecture.png" alt=""></p><h3 id="UIWidgets-框架结构">UIWidgets 框架结构</h3><p><img src="http://img.frankorz.com/5ca0a9930c276.png" alt=""></p><h2 id="执行效率">执行效率</h2><p><img src="http://img.frankorz.com/5ca0b27a35401.gif" alt=""></p><p>这里提一些基础的知识：</p><p>Batch 就是 DrawCall 的另一种说法，了解渲染流水线的同学会知道流水线在 CPU 与 GPU 之间通信时，一般有三个步骤：</p><ol><li>把数据加载到显存中。</li><li>设置渲染状态。</li><li>调用 Draw Call</li></ol><p>Draw Call 就是一个调用命令，让 CPU 告诉 GPU 要怎么样用给定的渲染状态和输入的顶点信息来计算。Batch 里面装着顶点信息，也就是 DrawCall 中 GPU 需要的顶点信息。</p><p>DrawCall 可以在 Profiler 中看，Batches 可以在 Stats 窗口看，大家可以仔细看看上面动图（右键在新标签页打开图片）里面的数据变化。</p><p><img src="http://img.frankorz.com/5ca0ac9d4fd8c.png" alt=""></p><p>在我随便写的一个例子中间，可以看到 Batches 数只有 1 。即使在有动画的时候 Batches 会多一点，但动画停止后 DrawCall 和 Batches 都马上下来了。这也有我这个应用写的太简单的原因，但是这种效率还是非常值得期待的。</p><h3 id="组件树">组件树</h3><p>学过前端的同学应该熟悉组件树，这里就不介绍了。</p><p>为了更高的渲染效率，Unity 采用了 Render Object Compositiing 的技术。</p><p><img src="http://img.frankorz.com/5ca0b42e394a0.png" alt=""></p><p>如果一个子树没有发生改变，Unity 就会将其渲染到一个离屏的 Render Texture 上缓存下来，需要的时候再将其贴到屏幕上。</p><p>相比之下，以前的做法是，Canvas 只要有 UI element 改动了，整个 Canvas 都需要重新绘制。即使也有一种优化做法是准备两个 Canvas 分别绘制动态 UI element 和 静态 UI element，但这样也存在很多手动管理的地方。</p><p>另外一方面，你可能也意识到了，我们不需要再管什么用同一个材质等等来优化图的合批，UIWidgets 会自动来管理这些事情。这方面也跟 <a href="http://fairygui.com">FairyGUI</a> 非常像，开发者能专注在生产效率上，让插件来管理麻烦的事情。</p><h2 id="优点">优点</h2><ul><li>能开发游戏以外的 APP</li><li>游戏中的 UI</li><li>新的用户体验</li><li>不用管渲染过程，提升效率</li><li>因为是 Unity 的插件，可以轻松加各种粒子效果和其他骚操作。</li><li>一套代码能跑在游戏中、APP 中、网页中和 Unity 的 Editor 窗口中。（开发者还用其做了一个 <a href="https://github.com/UnityTech/DocCN">Unity 中文文档的网站</a>，一套代码能用在网页上和 APP 端）</li><li>在静态页面进行降帧的优化，有动画效果再把帧率提上来。</li><li>和 Flutter 的 API 几乎一样，可以参考 Flutter 教程来用 UIWidgets 搭应用。</li></ul><h2 id="缺点">缺点</h2><ul><li>无论是 Flutter 还是 UIWidgets 都还很年轻，有很多组件 UIWidgets 还没移植过来（GridView、Circle Avatar 等等）</li><li>官方示例、文档还没完善</li><li>开发时是开荒模式，所以可能忍不住直接转用 Flutter 去了…</li></ul><h2 id="我的示例">我的示例</h2><p><img src="http://img.frankorz.com/5ca082d35a884.gif" alt=""></p><p>这里借用了 <a href="https://github.com/Miraikomachi/MiraikomachiUnity">ミライ小町</a> 的模型，所以代码窗口大小会比较大。（项目里面还有ミライ小町的跳舞动画 animation！）</p><p>项目仓库：<a href="https://github.com/Latias94/UIWidget-Practice">Latias94/UIWidget-Practice</a></p><p>UIWidgets：<a href="https://github.com/UnityTech/UIWidgets/">UnityTech/UIWidgets</a></p><p>官方讲解录播：<a href="https://www.bilibili.com/video/av47558897">[官方直播] UIWidgets - 不止游戏！如何使用 Unity 开发跨平台应用</a></p>]]></content>
      
      
      <categories>
          
          <category> Unity </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>不越狱在 iOS 12.113.3 设备安装 Kodi</title>
      <link href="/2019/01/13/install-kodi-with-tweakbox/"/>
      <url>/2019/01/13/install-kodi-with-tweakbox/</url>
      
        <content type="html"><![CDATA[<p><strong>2020 年 2 月 11 日更新另外一种方法</strong></p><p>今天一不小心发现 <a href="https://kodi.tv/">Kodi</a> 这个播放神器居然还有 iPad 版！</p><p>但是苹果 App Store 不允许 Kodi 应用商家，于是自己在网上找了些方法：</p><ol><li>越狱（手动再见）</li><li>下载官方提供的安装包用 Xcode 打包进去（太麻烦）</li><li>国内同步助手等提供的“VIP 服务”，购买服务后，用它们提供企业证书来下载（吃相难看）</li><li>…</li></ol><span id="more"></span><h2 id="2020-年-2-月-11-日更新">2020 年 2 月 11 日更新</h2><p>最近又想装回 Kodi，但是 Tweakbox 自己用不了了，于是找了找发现了 AppCake。</p><p>AppCake 声称能在越狱和非越狱的手机上使用，直接在设备上给应用签名。</p><ol><li>手机 Safari 打开 <a href="https://iphonecake.com/app/">https://iphonecake.com/app/</a>，点 Install。</li><li>安装描述文件</li><li>打开应用后应该还要再要你安装另一个描述文件用来给应用签名</li><li>Tweaked Tab 标签页中下载安装 Kodi!</li></ol><p><img src="http://img.frankorz.com/img_0342.png" alt=""></p><h2 id="介绍-Tweakbox">介绍 Tweakbox</h2><p>最终发现了国外一个不错的免费服务：<a href="https://www.tweakboxapp.com/">Tweakbox</a>，官方介绍也很令人振奋：</p><blockquote><p>TweakBox is an app store where you can download apps for iOS devices that are not available in the official Apple app store. TweakBox App is completely free and it has tons of features that make it a very popular choice among the other third party app stores.</p></blockquote><p>简而言之，Tweakbox 允许你：</p><ul><li>不需要越狱，苹果 ID</li><li>下载部分 Appstore 上没有或者付费的软件和游戏</li><li>下载部分主流的<strong>修改增强版</strong>软件和游戏</li></ul><h2 id="安装">安装</h2><ol><li>iPhone 和 iPad 设备上在 Safari 浏览器中进入网页 <a href="https://www.tweakboxapp.com/">Download</a></li><li>点 “Install Now” 按钮安装描述文件</li><li>然后就会下载 Tweakbox 应用</li><li>打开设置 -&gt; 通用 -&gt; 描述文件与设备管理 -&gt; TweakBox 点击信任</li><li>之后就能打开 Tweakbox 应用来安装应用了！</li><li>打开 Tweakbox，点击屏幕上方的 Apps 选项，点击 Tweakbox Apps 一列，Kodi 应用就在其中。</li></ol><p><img src="http://img.frankorz.com/5c3b083aede5c.png" alt="发现 Kodi!"></p><p>点进去 install 就能下载应用了，下载完还需要再次去信任描述文件才能使用。</p><p>唯一有点缺陷的是 Kodi 有时会弹出插屏广告，断网使用即可。</p><p>汉化参考：<a href="https://jingyan.baidu.com/article/6dad50750da52ca122e36e5c.html">KODI 播放器 V17 设置中文</a></p><h2 id="更多">更多</h2><p>前面说到 Tweakbox 还能安装其他实用的应用：</p><p><img src="http://img.frankorz.com/5c3b0ade16e14.png" alt=""></p><p><img src="http://img.frankorz.com/5c3b0b00308dd.png" alt="增强版 Spotify"></p><p><img src="http://img.frankorz.com/5c3b0b1c42964.png" alt=""></p><p><img src="http://img.frankorz.com/5c3b0b6874005.png" alt=""></p><h2 id="碎碎念">碎碎念</h2><p>周更博客真的难 = =，有些主题觉得没比别人写的好，于是就不想写了…</p><p>这周先水过去，再次赞下 Tweakbox！</p>]]></content>
      
      
      <categories>
          
          <category> 工具癖 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> iPad </tag>
            
            <tag> APP </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Unity 开源双端框架 ET 中初尝热更新技术</title>
      <link href="/2019/01/06/hotfix-introduction-of-unity-et-framework/"/>
      <url>/2019/01/06/hotfix-introduction-of-unity-et-framework/</url>
      
        <content type="html"><![CDATA[<h2 id="ET-框架简介">ET 框架简介</h2><p>正所谓时势造英雄，在 Web 开发领域或者传统软件开发领域中，人们把经过千锤百炼的代码总结出一套开发框架，从而提高开发效率，让开发者能更专注于业务本身。对于游戏领域而言，不同游戏需求的东西也不一样：有的游戏对性能有着苛刻要求，有的游戏需要快速地迭代出来，有的游戏需要联网热更新等等。因此不同的游戏框架应运而生。</p><p>例如：</p><ul><li><a href="https://github.com/EllanJiang/GameFramework">Game Framework</a> 是一个基于 Unity 引擎的游戏框架，主要对游戏开发过程中常用模块进行了封装，很大程度地规范开发过程、加快开发速度并保证产品质量。</li><li><a href="https://github.com/liangxiegame/QFramework">QFramework</a> 一套渐进式的快速开发框架。框架内部积累了多个项目的在各个技术方向的解决方案。</li><li><a href="https://github.com/sschmid/Entitas-CSharp">Entitas</a> 一套基于 C# 和 Unity 的实体组件系统。</li><li><a href="https://github.com/Unity-Technologies/EntityComponentSystemSamples">Entities</a> Unity 官方的实体组件系统实现，不过还是 Beta 版本，详细介绍可以查看<a href="https://unity.com/unity/features/job-system-ECS">官网</a>。</li><li><a href="https://github.com/strangeioc/strangeioc">StrangeIoC</a> 一套基于 C# 和 Unity 的控制反转 (Inversion-of-Control) 框架。</li></ul><p>今天介绍的是 ET 框架。</p><blockquote><p>ET是一个开源的游戏客户端（基于unity3d）服务端双端框架，服务端是使用C# .net core开发的分布式游戏服务端，其特点是开发效率高，性能强，双端共享逻辑代码，客户端服务端热更机制完善，同时支持可靠udp tcp websocket协议，支持服务端3D recast寻路等等</p></blockquote><p>ET 框架能让我们只用 C# 就能搞定前后端，热更新方面也采用了基于 C# 的 IL 运行时——<a href="https://ourpalm.github.io/ILRuntime/">ILRuntime</a>， 贯彻了 “珍爱生命，远离 Lua” 这句话。目前自己接触的大多是客户端部分，因此服务器方面不做介绍。</p><span id="more"></span><h2 id="框架文件结构">框架文件结构</h2><p><a href="https://github.com/egametang/ET">ET 官网</a> 本身给了很多介绍，我们可以克隆 Git 仓库到本地。</p><p>下面来看看每个文件夹的作用：</p><p><img src="http://img.frankorz.com/5c30b35ce39f5.png" alt=""></p><h2 id="客户端文件结构">客户端文件结构</h2><p>本文主要来介绍客户端，因此进入到 Unity 文件夹，文件夹结构如下：</p><p><img src="http://img.frankorz.com/5c30b35088c36.png" alt=""></p><p>当前 Master 分支目前需要 Unity 2018.3 以上版本。使用之前需要参考下官方的 <a href="https://github.com/egametang/ET/blob/master/Doc/%E8%BF%90%E8%A1%8C%E6%8C%87%E5%8D%97.md">运行指南</a>。</p><p>在 VS 中重新编译，或者 Rider Rebuild 一下项目。Scene 选择 Scenes\Init.unity，点 Play 按钮应该就能成功运行，看到登陆界面。</p><p><img src="http://img.frankorz.com/5c30b2ce0c2f1.png" alt=""></p><h2 id="组件设计">组件设计</h2><p>ET 框架使用了组件的设计，一切都是实体（Entity）和组件（Component），官方文档 <a href="https://github.com/egametang/ET/blob/master/Doc/%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1.md">组件设计</a> 介绍的很详细。</p><p>看完文档，我们来看看项目代码的启动入口。</p><p><img src="http://img.frankorz.com/5c30b502d774a.png" alt=""></p><p>这个 Init.cs 文件，在 Model 文件夹下。可能有同学注意到 Hotfix 文件夹下也有一个 Init.cs 文件，而且这两个文件夹的结构大同小异，两边都有一些相同的文件，而它们只是命名空间不一样。这是因为我们用到 ILRuntime，而 ILRuntime 最好不要跨域继承。</p><p><code>Model/Init.cs</code> 文件中</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">async</span> ETVoid <span class="title">StartAsync</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">try</span></span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 添加了组件，就赋予了各种功能。</span></span><br><span class="line"><span class="comment">// 例如加了 Timer 组件，就有了计时功能</span></span><br><span class="line">Game.Scene.AddComponent&lt;TimerComponent&gt;();</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下载热更用的 AssetBundle 包</span></span><br><span class="line"><span class="keyword">await</span> BundleHelper.DownloadBundle();</span><br><span class="line"><span class="comment">// 加载热更用的dll等文件，调用 Hotfix/Init.cs</span></span><br><span class="line">Game.Hotfix.LoadHotfixAssembly();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载配置</span></span><br><span class="line">Game.Scene.GetComponent&lt;ResourcesComponent&gt;().LoadBundle(<span class="string">&quot;config.unity3d&quot;</span>);</span><br><span class="line">Game.Scene.AddComponent&lt;ConfigComponent&gt;();</span><br><span class="line"><span class="comment">// 加载后卸载相应的 AB 包</span></span><br><span class="line">Game.Scene.GetComponent&lt;ResourcesComponent&gt;().UnloadBundle(<span class="string">&quot;config.unity3d&quot;</span>);</span><br><span class="line">Game.Scene.AddComponent&lt;OpcodeTypeComponent&gt;();</span><br><span class="line">Game.Scene.AddComponent&lt;MessageDispatcherComponent&gt;();</span><br><span class="line"></span><br><span class="line">Game.Hotfix.GotoHotfix();</span><br><span class="line"></span><br><span class="line">Game.EventSystem.Run(EventIdType.TestHotfixSubscribMonoEvent, <span class="string">&quot;TestHotfixSubscribMonoEvent&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过组件设计，可以轻易地加载组件和卸载组件，例如我可以写一个心跳包组件来每隔30秒发送一个心跳包到服务器，当我需要这个组件的时候，可以直接 <code>AddComponent</code>，不需要的时候可以 <code>RemoveComponent</code> 移除组件。</p><h2 id="登陆界面的热更新启动过程">登陆界面的热更新启动过程</h2><p>接下来看到 <code>Hotfix/Init.cs</code> 文件中</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Start</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">try</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 注册热更层回调</span></span><br><span class="line">ETModel.Game.Hotfix.Update = () =&gt; &#123; Update(); &#125;;</span><br><span class="line">ETModel.Game.Hotfix.LateUpdate = () =&gt; &#123; LateUpdate(); &#125;;</span><br><span class="line">ETModel.Game.Hotfix.OnApplicationQuit = () =&gt; &#123; OnApplicationQuit(); &#125;;</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 加载热更配置</span></span><br><span class="line">ETModel.Game.Scene.GetComponent&lt;ResourcesComponent&gt;().LoadBundle(<span class="string">&quot;config.unity3d&quot;</span>);</span><br><span class="line">Game.Scene.AddComponent&lt;ConfigComponent&gt;();</span><br><span class="line">ETModel.Game.Scene.GetComponent&lt;ResourcesComponent&gt;().UnloadBundle(<span class="string">&quot;config.unity3d&quot;</span>);</span><br><span class="line"></span><br><span class="line">UnitConfig unitConfig = (UnitConfig)Game.Scene.GetComponent&lt;ConfigComponent&gt;().Get(<span class="keyword">typeof</span>(UnitConfig), <span class="number">1001</span>);</span><br><span class="line">Log.Debug(<span class="string">$&quot;config <span class="subst">&#123;JsonHelper.ToJson(unitConfig)&#125;</span>&quot;</span>);</span><br><span class="line"><span class="comment">// 发送事件来启动界面</span></span><br><span class="line">Game.EventSystem.Run(EventIdType.InitSceneStart);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>来看看发送的事件，代码在<br><code>Hotfix\Module\Demo\UI\UILogin\System\InitSceneStart_CreateLoginUI.cs</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">ETHotfix</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 用 Attribute 来注册事件</span></span><br><span class="line">[<span class="meta">Event(EventIdType.InitSceneStart)</span>]</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">InitSceneStart_CreateLoginUI</span>: <span class="title">AEvent</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Run</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">UI ui = UILoginFactory.Create();</span><br><span class="line"><span class="comment">// 这里就是启动登陆界面的地方，界面可以直接 add 或者 remove</span></span><br><span class="line">Game.Scene.GetComponent&lt;UIComponent&gt;().Add(ui);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再来看看一个界面是怎么生成的，代码在 <code>Hotfix\Module\Demo\UI\UILogin\System\UILoginFactory.cs</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> UI <span class="title">Create</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"> ...</span><br><span class="line">    ResourcesComponent resourcesComponent = ETModel.Game.Scene.GetComponent&lt;ResourcesComponent&gt;();</span><br><span class="line"><span class="comment">// 让资源组件读取登陆界面的 AB 包</span></span><br><span class="line">    resourcesComponent.LoadBundle(UIType.UILogin.StringToAB());</span><br><span class="line"><span class="comment">// 从 AB 包拿到登陆界面的 GameObject</span></span><br><span class="line">    GameObject bundleGameObject = (GameObject) resourcesComponent.GetAsset(UIType.UILogin.StringToAB(), UIType.UILogin);</span><br><span class="line">    GameObject gameObject = UnityEngine.Object.Instantiate(bundleGameObject);</span><br><span class="line"></span><br><span class="line">    UI ui = ComponentFactory.Create&lt;UI, <span class="built_in">string</span>, GameObject&gt;(UIType.UILogin, gameObject, <span class="literal">false</span>);</span><br><span class="line"><span class="comment">// 添加登陆界面组件</span></span><br><span class="line">    ui.AddComponent&lt;UILoginComponent&gt;();</span><br><span class="line">    <span class="keyword">return</span> ui;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>来看看登陆界面组件，代码在 <code>Hotfix\Module\Demo\UI\UILogin\Component\UILoginComponent.cs</code></p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">UILoginComponent</span>: <span class="title">Component</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span> GameObject account;</span><br><span class="line"><span class="keyword">private</span> GameObject loginBtn;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Awake</span>()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 通过引用来获取 UI 组件，再为其添加点击事件</span></span><br><span class="line">ReferenceCollector rc = <span class="keyword">this</span>.GetParent&lt;UI&gt;().GameObject.GetComponent&lt;ReferenceCollector&gt;();</span><br><span class="line">loginBtn = rc.Get&lt;GameObject&gt;(<span class="string">&quot;LoginBtn&quot;</span>);</span><br><span class="line">loginBtn.GetComponent&lt;Button&gt;().onClick.Add(OnLogin);</span><br><span class="line"><span class="keyword">this</span>.account = rc.Get&lt;GameObject&gt;(<span class="string">&quot;Account&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">OnLogin</span>()</span></span><br><span class="line">&#123; <span class="comment">// 有兴趣可以再进去看看 OnLoginAsync，其中登陆的 Session 连接了服务器地址 127.0.0.1:10002</span></span><br><span class="line">LoginHelper.OnLoginAsync(<span class="keyword">this</span>.account.GetComponent&lt;InputField&gt;().text).Coroutine();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>服务器地址存在了 Tools 菜单中的全局配置，上面的资源路径则是热更新服务器的地址。</p><p><img src="http://img.frankorz.com/5c30c3d30a5a8.png" alt=""></p><p>游戏运行后，在 Hierarchy 界面中也可以看到组件的结构，其中 <code>uilogin.unity3d</code> 就是登陆界面的 AB 包引用：</p><p><img src="http://img.frankorz.com/5c30bd561727f.png" alt=""></p><p>这就是通过热更新逻辑生成的界面，也就是说，上面的代码让我们可以通过热更新来给应用加载各种界面和改写页面跳转逻辑，当然还可以通过热更来增加修改游戏逻辑和功能。</p><p>如果不喜欢这种页面加载方式，可以考虑不使用 <code>Hotfix/Init.cs</code> 中的 <code>Game.Scene.AddComponent&lt;UIComponent&gt;();</code> 这个 UIComponent，而使用其他 UI 组件，例如主仓库中的 FairyGUI 分支，让 FairyGUI 来单独负责 UI 界面。这里也可以看出基于组件的框架的灵活性，我以后也会出文章单独介绍 FairyGUI。</p><h2 id="热更新切换">热更新切换</h2><p>首先看看作者的介绍：</p><blockquote><p>7.客户端热更新一键切换<br>因为ios的限制，之前unity热更新一般使用lua，导致unity3d开发人员要写两种代码，麻烦的要死。之后幸好出了ILRuntime库，利用ILRuntime库，unity3d可以利用C#语言加载热更新dll进行热更新。ILRuntime一个缺陷就是开发时候不支持VS debug，这有点不爽。ET框架使用了一个预编译指令ILRuntime，可以无缝切换。平常开发的时候不使用ILRuntime，而是使用Assembly.Load加载热更新动态库，这样可以方便用VS单步调试。在发布的时候，定义预编译指令ILRuntime就可以无缝切换成使用ILRuntime加载热更新动态库。这样开发起来及其方便，再也不用使用狗屎lua了<br>8.客户端全热更新<br>客户端可以实现所有逻辑热更新，包括协议，config，ui等等</p></blockquote><p><img src="http://img.frankorz.com/5c30c69760435.png" alt=""></p><p>预编译指令指的就是在 Player Setting 中，上图右下角箭头指着的地方。当前有两个预编译指令，通常在开发中，可以只填写 <code>NET452</code>，这样可以得到完整的堆栈信息来调试程序。还有一个预编译指令 <code>ASYNC</code>，加上后，应用就会从前面填写的热更新服务器下载热更包，该指令在后文会提到。</p><p>在国内环境下，手机游戏热更新的需求较强烈。市场上手机系统普遍分成 Android 和 iOS 阵营，其中 iOS 不支持 JIT 热更，因此 ET 框架给了两种选择：ILRuntime 热更新和 Mono 热更新。</p><p>两者概念可以参考文末的参考链接，在这里不多说。</p><h2 id="体验热更新">体验热更新</h2><p>体验热更新之前，先把项目切到 Android 平台。</p><p>按照下图配置 Mono 热更新：</p><p><img src="http://img.frankorz.com/5c31edd4669a9.png" alt=""></p><p>确保 Scripting Backend 为 Mono，下面预编译宏去掉 ILRuntime，加上 ASYNC，按下<strong>回车键</strong>执行变更。ASYNC 说明我们现在的热更新资源从资源服务器中获取，这里的热更新资源包括 Res 文件夹、Bundles 文件夹、Hotfix 文件夹中的代码等。在这个例子中，登陆界面的代码就已经写在热更新文件夹中了，我们将尝试通过热更新来展示登陆界面。</p><p>点击 Play 按钮，会有两个报错：</p><p><img src="http://img.frankorz.com/5c31eefc3359a.png" alt=""></p><p>第二个 Log 信息展示了应用想要获取资源的热更资源服务器地址，这个地址可以在 Tools 菜单的全局配置中找到。报错信息提示找不到终端主机。报错是理所当然的，因为我们还没有启动本地服务器。</p><p>首先要生成热更新文件，在 Tools 菜单中点击打包工具，如下图所示：</p><p><img src="http://img.frankorz.com/5c31f02a12e8c.png" alt=""></p><p>平台选择当前的 Android 平台，目前不需要打包应用，所以无视第一个单选按钮。</p><p>前面的思维导图提到了 ET 根目录的 Release 文件夹存的就是热更新资源文件。打包工具也会把打包后的资源放在 Release 文件夹下。而第二个按钮指的是是否把打包的热更新资源也放在应用中，目前也不需要选择。开启热更新后，应用会比较服务器和本地应用的 Version 文件，计算文件差异后才会下载相关的热更新资源文件。</p><p>如果勾选了第二个按钮，打包工具将会把资源也复制到 Assets/StreamingAssets 文件夹下，同时更新 Version 文件，这样我们将不能测试下载热更包的过程。</p><p><img src="http://img.frankorz.com/5c31f17b71319.png" alt=""></p><p>点击开始打包后，热更文件就生成了：</p><p><img src="http://img.frankorz.com/5c31f2f8dbf1d.png" alt=""></p><p>再点击 Tools 菜单中的 web 资源服务器开启映射了 Release 文件夹的本地文件服务器。</p><p>点击 Play 按钮，应用通过下载热更新资源，生成了登陆界面，也把热更资源下载到了应用中，也就是 Assets/StreamingAssets 文件夹。</p><p><img src="http://img.frankorz.com/5c31f3b4ad69f.png" alt=""></p><p>重新启动 web 资源服务器清除 log 信息，再次运行应用，会发现没有再次下载热更新资源。因为对比了 Version 文件后，应用本地的文件已经不需要更新了。</p><p><img src="http://img.frankorz.com/5c31f4aa1c424.png" alt=""></p><p>至此，我们完成了一次完整的热更新。</p><h2 id="总结">总结</h2><p>ET 框架给了我们一种统一的开发体验，提供了方便的热更新切换和调试方案，这足以支撑起一些小游戏的开发需求，有需要的同学可以了解下 <a href="https://github.com/egametang/ET">ET 框架</a>~</p><p>2019 年立了个 Flag：周更技术博客，欢迎督促和交流，也欢迎常来我博客 <a href="http://frankorz.com">萤火之森</a> 逛！</p><h2 id="参考">参考</h2><ul><li><a href="https://zhuanlan.zhihu.com/p/34551169">一些新潮的Unity热更方案</a></li><li><a href="https://answer.uwa4d.com/question/5abdea21425802635474fbb4">Mono和IL2CPP选哪个更合适？</a></li><li><a href="https://zhuanlan.zhihu.com/p/37291067">Unity实现c#热更新方案探究（一）</a></li><li><a href="https://answer.uwa4d.com/question/5a9fc420d35eb22c10a0a365">关于热更新，大家现在都是怎么实现的？</a> 中网友 gx 的回答</li></ul>]]></content>
      
      
      <categories>
          
          <category> 游戏开发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Unity </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Unity 中用有限状态机来实现一个 AI</title>
      <link href="/2018/06/22/finite-state-machine-in-unity/"/>
      <url>/2018/06/22/finite-state-machine-in-unity/</url>
      
        <content type="html"><![CDATA[<p>最近在阅读《游戏人工智能编程案例精粹（修订版）》，本文是书中第二章的一篇笔记。</p><p>有限状态机（英语：Finite-state machine, 缩写：FSM），是一个被数学家用来解决问题的严格形式化的设备，在游戏业中也常见有限状态机的身影。</p><p>对于游戏程序员来说，可以用下面这个定义来了解：</p><blockquote><p>一个有限状态机是一个设备（device），或是一个设备模型（a model of a device）。具有有限数量的状态，它可以在任何给定的时间根据输入进行操作，是的从一个状态变换到另一个状态，或者是促使一个输出或者一种行为的发生。一个有限状态机在任何瞬间只能处在一种状态。<br>——《游戏人工智能编程案例精粹（修订版）》 Mat Buckland</p></blockquote><p>有限状态机就是要把一个对象的行为分解成易于处理的“块”或者状态。拿某个开关来说，我们可以把它分成两个状态：开或关。其中开开关这个操作，就是一次<strong>状态转移</strong>，使开关的状态从“关”变换到“开”，反之亦然。</p><span id="more"></span><p>拿游戏来举例，一个 FPS 游戏中的敌人 AI 状态可以分成：巡逻、侦查（听到了玩家）、追逐（玩家出现在 AI 视野）、攻击（玩家进入 AI 攻击范围）、死亡等，这些<strong>有限的</strong>状态都<strong>互相独立</strong>，且要<strong>满足某种条件</strong>才能从一个状态转移到另外一个状态。</p><p>有限状态机由三部分组成：</p><ul><li>存储任务信息的一些<strong>状态（states）</strong>，例如一个 AI 可以有探索状态、追踪状态、攻击状态等等。</li><li>状态之间的一些<strong>变换（transitions）</strong>，转移代表状态的转移，并且描述着状态转移的条件。例如听到了主角的脚步声，就转移到追踪状态。</li><li>需要跟随每个状态的一系列<strong>行为（actions）</strong>。例如在探索状态，要随机移动和找东西。</li></ul><p>下图是只有三种状态的 AI 的有限状态机图示：<br><img src="http://img.frankorz.com/5b2cc09f49f70.png" alt=""></p><h2 id="优缺点">优缺点</h2><p>实现有限状态机之前，要先了解它的优点：</p><ol><li><strong>编程快速简单</strong>：很多有限状态机的实现都较简单，本文会列出三种实现方法。</li><li><strong>易于调试</strong>：因为行为被分成单一的状态块，因此要调试的时候，可以只跟踪某个异常状态的代码。</li><li><strong>很少的计算开销</strong>：几乎不占用珍贵的处理器时间，因为除了 if-this-then-that 这种思考处理之外，是不存在真正的“思考”的。</li><li><strong>直觉性</strong>：人们总是自然地把事物思考为处在一种或另一种状态。人类并不是像有限状态机一样工作，但我们发现这种方式下考虑行为是很有用的，或者说我们能更好更容易地进行 AI 状态的分解和创建操作 AI 的规则，容易理解的概念也让程序员之间能更好地交流其设计。</li><li><strong>灵活性</strong>：游戏 AI 的有限状态机能很容易地由程序员进行调整，增添新的状态和规则也很容易扩展一个 AI 的行为。</li></ol><p>有限状态机的缺点是：</p><ol><li>当状态过多时，难以维护代码。</li><li>《AI Game Development》的作者 Alex J. Champandard 发表过一篇文章<a href="http://aigamedev.com/open/article/fsm-age-is-over/">《10 Reasons the Age of Finite State Machines is Over》</a></li></ol><h2 id="if-then-实现">if-then 实现</h2><p>这是第一种实现有限状态机的方法，用一系列 if-then 语句或者 switch 语句来表达状态。</p><p>下面拿那个只有三个状态的僵尸 AI 举例：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="built_in">enum</span> ZombieState</span><br><span class="line">&#123;</span><br><span class="line">    Chase, Attack, Die</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Zombie</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> ZombieState currentState;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">switch</span> (currentState)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">case</span> ZombieState.Chase:</span><br><span class="line">                <span class="keyword">if</span> (currentHealth &lt;= <span class="number">0</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    ChangeState(ZombieState.Die);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 玩家在攻击范围内则进入攻击状态</span></span><br><span class="line">                <span class="keyword">if</span> (PlayerInAttackRange())</span><br><span class="line">                &#123;</span><br><span class="line">                    ChangeState(ZombieState.Attack);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ZombieState.Attack:</span><br><span class="line">                <span class="keyword">if</span> (currentHealth &lt;= <span class="number">0</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    ChangeState(ZombieState.Die);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (!PlayerInAttackRange())</span><br><span class="line">                &#123;</span><br><span class="line">                    ChangeState(ZombieState.Chase);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ZombieState.Die:</span><br><span class="line">                Debug.Log(<span class="string">&quot;僵尸死亡&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种写法能实现有限状态机，但当游戏对象复杂到一定程度时，case 就会变得特别多，使程序难以理解、调试。另外这种写法也不灵活，难以扩展超出它原始设定的范围。</p><p>此外，我们常需要在<strong>进入状态</strong>和<strong>退出状态</strong>时做些什么，例如僵尸在开始攻击时像猩猩一样锤几下胸口，玩家跑出攻击范围的时候，僵尸要“摇摇头”让自己清醒，好让自己打起精神继续追踪玩家。</p><h3 id="状态变换表">状态变换表</h3><p>一个用于组织状态和影响状态变换的更好的机制是一个<strong>状态变换表</strong>。</p><table><thead><tr><th>当前状态</th><th style="text-align:center">条件</th><th style="text-align:right">状态转移</th></tr></thead><tbody><tr><td>追踪</td><td style="text-align:center">玩家进入攻击范围</td><td style="text-align:right">攻击</td></tr><tr><td>追踪</td><td style="text-align:center">僵尸生命值小于或等于0</td><td style="text-align:right">死亡</td></tr><tr><td>攻击</td><td style="text-align:center">玩家脱离攻击范围</td><td style="text-align:right">追踪</td></tr><tr><td>攻击</td><td style="text-align:center">僵尸生命值小于或等于0</td><td style="text-align:right">死亡</td></tr></tbody></table><p>这表格可以被僵尸 AI 不间断地查询。使得它能基于从游戏环境的变化来进行状态变换。每个状态可以模型化为一个分离的对象或者存在于 AI 外的函数。提供了一个清楚且灵活的结构。</p><p>我们只用告诉僵尸它有多少个状态，僵尸则会根据自己获得的信息（例如玩家是否在它的攻击范围内）来处理规则（转移状态）。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Zombie</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> ZombieState currentState;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 生命值小于等于0，进入死亡状态</span></span><br><span class="line">        <span class="keyword">if</span> (currentHealth &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            ChangeState(ZombieState.Die);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 玩家在攻击范围内则进入攻击状态，反之进入追踪状态</span></span><br><span class="line">        <span class="keyword">if</span> (PlayerInAttackRange())</span><br><span class="line">        &#123;</span><br><span class="line">            ChangeState(ZombieState.Attack);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            ChangeState(ZombieState.Chase);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="内置规则">内置规则</h3><p>另一种方法就是将状态转移规则<strong>内置到状态内部</strong>。</p><p>在这里，每一个状态都是一个小模块，虽然每个模块都可以意识到其他模块的存在，但是每个模块都是一个独立的单位，而且不依赖任何外部的逻辑来决定自己是否要进行状态转移。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Zombie</span> : <span class="title">MonoBehaviour</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> State currentState;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> CurrentHealth &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">Update</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        currentState.Execute(<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ChangeState</span>(<span class="params">State state</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        currentState = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">PlayerInAttackRange</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// ...游戏逻辑</span></span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Zombie zombie</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">ChaseState</span> : <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Zombie zombie</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (zombie.CurrentHealth &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            zombie.ChangeState(<span class="keyword">new</span> DieState());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (zombie.PlayerInAttackRange())</span><br><span class="line">        &#123;</span><br><span class="line">            zombie.ChangeState(<span class="keyword">new</span> AttackState());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">AttackState</span> : <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Zombie zombie</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (zombie.CurrentHealth &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            zombie.ChangeState(<span class="keyword">new</span> DieState());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!zombie.PlayerInAttackRange())</span><br><span class="line">        &#123;</span><br><span class="line">            zombie.ChangeState(<span class="keyword">new</span> ChaseState());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DieState</span> : <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Zombie zombie</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Debug.Log(<span class="string">&quot;僵尸死亡&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Update()</code> 函数只需要根据 <code>currentState</code> 来执行代码，当 <code>currentState</code> 改变时，下一次 <code>Update()</code> 的调用也会进行状态转移。这三个状态都作为对象封装，并且都给出了影响状态转移的规则（条件）。</p><p>这个结构被称为<strong>状态设计模式（state design pattern）</strong>，它提供了一种优雅的方式来实现状态驱动行为。这种实现编码简单，容易扩展，也可以容易地为状态增加<strong>进入</strong>和<strong>退出</strong>的动作。下文会给出更完整的实现。</p><h2 id="West-World-项目">West World 项目</h2><p>这项目是关于使用有限状态机创建一个 AI 的实际例子。游戏环境是一个古老西部风格的开采金矿的小镇，称作 West World。一开始只有一个挖金矿工 Bob，后期会加入他的妻子。任何的状态改变或者输出都会出现在控制台窗口中。West World 中有四个位置：金矿，可以存金块的银行，可以解除干渴的酒吧，还有家。矿工 Bob 会挖矿、睡觉、喝酒等，但这些都由 Bob 的当前状态决定。</p><p>项目在这里：<a href="https://github.com/Latias94/programming-game-ai-by-example-in-unity/tree/master/WestWorld">programming-game-ai-by-example-in-unity/WestWorld/</a><br><img src="http://img.frankorz.com/5b2cd71f9ebdb.gif" alt="West World"></p><p>当你看到矿工改变了位置时，就代表矿工改变了状态，其他的事情都是状态中发生的事情。</p><h3 id="Base-Game-Entity-类">Base Game Entity 类</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">BaseGameEntity</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 每个实体具有一个唯一的识别数字</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> m_ID;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 这是下一个有效的ID，每次 BaseGameEntity 被实例化这个值就被更新</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 这项目居民较少，采用预定义 id 的方式，可以忽视</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span> m_iNextValidID &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">BaseGameEntity</span>(<span class="params"><span class="built_in">int</span> id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_ID = id;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> ID</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> m_ID; &#125;</span><br><span class="line">        <span class="keyword">set</span></span><br><span class="line">        &#123;</span><br><span class="line">            m_ID = <span class="keyword">value</span>;</span><br><span class="line">            m_iNextValidID = m_ID + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 在 GameManager 的 Update() 函数中调用，相当于实体自己的 Update 函数</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">EntityUpdate</span>()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Miner-类">Miner 类</h3><p>MIner 类是从 BaseGameEntity 类中继承的，包含很多成员变量，代码如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Miner</span> : <span class="title">BaseGameEntity</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 指向一个状态实例的指针</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> State m_pCurrentState;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 旷工当前所处的位置</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> LocationType m_Location;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 旷工的包中装了多少金块</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> m_iGoldCarried;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 旷工在银行存了多少金块</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> m_iMoneyInBank;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 口渴程度，值越高，旷工越口渴</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> m_iThirst;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 疲倦程度,值越高，旷工越疲倦</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> <span class="built_in">int</span> m_iFatigue;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Miner</span>(<span class="params"><span class="built_in">int</span> id</span>) : <span class="title">base</span>(<span class="params">id</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_Location = LocationType.Shack;</span><br><span class="line">        m_iGoldCarried = <span class="number">0</span>;</span><br><span class="line">        m_iMoneyInBank = <span class="number">0</span>;</span><br><span class="line">        m_iThirst = <span class="number">0</span>;</span><br><span class="line">        m_iFatigue = <span class="number">0</span>;</span><br><span class="line">        m_pCurrentState = GoHomeAndSleepTilRested.Instance;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 等于 Update 函数，在 GameManager 内被调用，每调用一次就变得越口渴</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">EntityUpdate</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_iThirst += <span class="number">1</span>;</span><br><span class="line">        m_pCurrentState.Execute(<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...其他的代码看 Github 项目</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Miner-状态">Miner 状态</h3><p>金矿工人有四种状态：</p><ul><li><strong>EnterMineAndDigForNugget</strong>：如果矿工没在金矿，则改变位置。在金矿里了，就挖掘金块。</li><li><strong>VisitBankAndDepositGold</strong>：矿工会走到银行并且存储他携带的所有天然金矿。</li><li><strong>GoHomeAndSleepTilRested</strong>：矿工会回到他的小木屋睡觉知道他的疲劳值下降到可接受的程度。醒来继续去挖矿。</li><li><strong>QuenchThirst</strong>：去酒吧买一杯威士忌，不口渴了继续挖矿。</li></ul><table><thead><tr><th>当前状态</th><th style="text-align:center">条件</th><th style="text-align:right">状态转移</th></tr></thead><tbody><tr><td>EnterMineAndDigForNugget</td><td style="text-align:center">挖矿挖到口袋装不下</td><td style="text-align:right">VisitBankAndDepositGold</td></tr><tr><td>EnterMineAndDigForNugget</td><td style="text-align:center">口渴</td><td style="text-align:right">QuenchThirst</td></tr><tr><td>VisitBankAndDepositGold</td><td style="text-align:center">觉得自己存够钱能安心了</td><td style="text-align:right">GoHomeAndSleepTilRested</td></tr><tr><td>VisitBankAndDepositGold</td><td style="text-align:center">没存够钱</td><td style="text-align:right">EnterMineAndDigForNugget</td></tr><tr><td>GoHomeAndSleepTilRested</td><td style="text-align:center">疲劳值下降到一定程度</td><td style="text-align:right">EnterMineAndDigForNugget</td></tr><tr><td>QuenchThirst</td><td style="text-align:center">不口渴了</td><td style="text-align:right">EnterMineAndDigForNugget</td></tr></tbody></table><h3 id="再谈状态设计模式">再谈状态设计模式</h3><p>之前提到要为状态实现<strong>进入</strong>和<strong>退出</strong>这两个一个状态只执行一次的逻辑，这样可以增加有限状态机的灵活性。下面是威力加强版：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 当状态被进入时执行这个函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Enter</span>(<span class="params">Miner miner</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 旷工更新状态函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Miner miner</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 当状态退出时执行这个函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Exit</span>(<span class="params">Miner miner</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这两个增加的方法只有在矿工改变状态时才会被调用。我们也需要修改 <code>ChangeState</code> 方法的代码如下：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ChangeState</span>(<span class="params">State state</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 执行上一个状态的退出方法</span></span><br><span class="line">    m_pCurrentState.Exit(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">// 更新状态</span></span><br><span class="line">    m_pCurrentState = state;</span><br><span class="line">    <span class="comment">// 执行当前状态的进入方法</span></span><br><span class="line">    m_pCurrentState.Enter(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，每个具体的状态都添加了<strong>单例模式</strong>，这样可以节省内存资源，不必重复分配和释放内存给改变的状态。以其中一个状态为例子：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">EnterMineAndDigForNugget</span> : <span class="title">State</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> EnterMineAndDigForNugget Instance &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="title">EnterMineAndDigForNugget</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        Instance = <span class="keyword">new</span> EnterMineAndDigForNugget();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Enter</span>(<span class="params">Miner miner</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (miner.Location() != LocationType.Goldmine)</span><br><span class="line">        &#123;</span><br><span class="line">            Debug.Log(<span class="string">&quot;矿工：走去金矿&quot;</span>);</span><br><span class="line">            miner.ChangeLocation(LocationType.Goldmine);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">Miner miner</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        miner.AddToGoldCarried(<span class="number">1</span>);</span><br><span class="line">        miner.IncreaseFatigue();</span><br><span class="line">        Debug.Log(<span class="string">&quot;矿工：采到一个金块 | 身上有 &quot;</span> + miner.GoldCarried() + <span class="string">&quot; 个金块&quot;</span>);</span><br><span class="line">        <span class="comment">// 口袋里金块满了就去银行存</span></span><br><span class="line">        <span class="keyword">if</span> (miner.PocketsFull())</span><br><span class="line">        &#123;</span><br><span class="line">            miner.ChangeState(VisitBankAndDepositGold.Instance);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 口渴了就去酒吧喝威士忌</span></span><br><span class="line">        <span class="keyword">if</span> (miner.Thirsty())</span><br><span class="line">        &#123;</span><br><span class="line">            miner.ChangeState(QuenchThirst.Instance);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> <span class="title">Exit</span>(<span class="params">Miner miner</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        Debug.Log(<span class="string">&quot;矿工：离开金矿&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到这里，大家应该都会很熟悉。这不就是 Unity 中动画控制器 Animator 的功能吗！</p><p>没错，Animator 也是一个状态机，有和我们之前实现十分相似的功能，例如：添加状态转移的条件，每个状态都有进入、执行、退出三个回调方法供使用。</p><p><img src="http://img.frankorz.com/5b2ce778774fa.png" alt=""></p><p>我们可以创建 Behaviour 脚本，对 Animator 中每一个状态的进入、执行、退出等方法进行自定义，所以有些人直接拿 Animator 当状态机来使用，不过我们在下文还会为我们的状态机实现扩展更多的功能。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">NewState</span> : <span class="title">StateMachineBehaviour</span> &#123;</span><br><span class="line">    <span class="comment">// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state</span></span><br><span class="line">    <span class="comment">//override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) &#123;</span></span><br><span class="line">    <span class="comment">//</span></span><br><span class="line">    <span class="comment">//&#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks</span></span><br><span class="line">    <span class="comment">//override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) &#123;</span></span><br><span class="line">    <span class="comment">//</span></span><br><span class="line">    <span class="comment">//&#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// OnStateExit is called when a transition ends and the state machine finishes evaluating this state</span></span><br><span class="line">    <span class="comment">//override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) &#123;</span></span><br><span class="line">    <span class="comment">//</span></span><br><span class="line">    <span class="comment">//&#125;</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使-State-基类可重用">使 State 基类可重用</h2><p>由于上面四个状态是矿工独有的状态，如果要新建不同功能的角色，就有必要创建一个分离的 State 基类，这里用泛型实现。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">State</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 当状态被进入时执行这个函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Enter</span>(<span class="params">T entity</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 旷工更新状态函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Execute</span>(<span class="params">T entity</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 当状态退出时执行这个函数</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">Exit</span>(<span class="params">T entity</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="状态翻转（State-Blip）">状态翻转（State Blip）</h2><p>这个项目其实有点像模拟人生这个游戏，其中有一点有意思的是，当模拟人生的主角做某件事时忽然要上厕所，去完之后会继续做之前停止的事情。这种返回前一个状态的行为就是<strong>状态翻转（State Blip）</strong>。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> State&lt;T&gt; m_pCurrentState;</span><br><span class="line"><span class="keyword">private</span> State&lt;T&gt; m_pPreviousState;</span><br><span class="line"><span class="keyword">private</span> State&lt;T&gt; m_pGlobalState;</span><br></pre></td></tr></table></figure><p><code>m_pGlobalState</code> 是一个全局状态，也会在 <code>Update()</code> 函数中和 <code>m_pCurrentState</code> 一起调用。如果有紧急的行为中断状态，就把这行为（例如上厕所）放到全局状态中，等到全局状态为空再进入当前状态。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">StateUpdate</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 如果有一个全局状态存在，调用它的执行方法</span></span><br><span class="line">    <span class="keyword">if</span> (m_pGlobalState != <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        m_pGlobalState.Execute(m_pOwner);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (m_pCurrentState != <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        m_pCurrentState.Execute(m_pOwner);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="StateMachine-类">StateMachine 类</h2><p>通过把所有与状态相关的数据和方法封装到一个 StateMachine 类中，可以使得设计更为简洁。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">StateMachine</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">private</span> T m_pOwner;</span><br><span class="line">    <span class="keyword">private</span> State&lt;T&gt; m_pCurrentState;</span><br><span class="line">    <span class="keyword">private</span> State&lt;T&gt; m_pPreviousState;</span><br><span class="line">    <span class="keyword">private</span> State&lt;T&gt; m_pGlobalState;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">StateMachine</span>(<span class="params">T owner</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_pOwner = owner;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SetCurrentState</span>(<span class="params">State&lt;T&gt; state</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_pCurrentState = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SetPreviousState</span>(<span class="params">State&lt;T&gt; state</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_pPreviousState = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">SetGlobalState</span>(<span class="params">State&lt;T&gt; state</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_pGlobalState = state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">StateMachineUpdate</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 如果有一个全局状态存在，调用它的执行方法</span></span><br><span class="line">        <span class="keyword">if</span> (m_pGlobalState != <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            m_pGlobalState.Execute(m_pOwner);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (m_pCurrentState != <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            m_pCurrentState.Execute(m_pOwner);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">ChangeState</span>(<span class="params">State&lt;T&gt; newState</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        m_pPreviousState = m_pCurrentState;</span><br><span class="line">        m_pCurrentState.Exit(m_pOwner);</span><br><span class="line">        m_pCurrentState = newState;</span><br><span class="line">        m_pCurrentState.Enter(m_pOwner);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 返回之前的状态</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">RevertToPreviousState</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        ChangeState(m_pPreviousState);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> State&lt;T&gt; <span class="title">CurrentState</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> m_pCurrentState;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> State&lt;T&gt; <span class="title">PreviousState</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> m_pPreviousState;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> State&lt;T&gt; <span class="title">GlobalState</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> m_pGlobalState;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">IsInState</span>(<span class="params">State&lt;T&gt; state</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> m_pCurrentState == state;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="新人物-Elsa">新人物 Elsa</h2><p>第二个项目会演示之前的改进。Elsa 是矿工 Bob 的妻子，她会清理小木屋和上厕所（老喝咖啡）。其中 VisitBathroom 状态是用状态翻转实现的，即上完厕所要回到之前的状态。</p><p>项目地址：<a href="https://github.com/Latias94/programming-game-ai-by-example-in-unity/tree/master/WestWorldWithWoman">programming-game-ai-by-example-in-unity/WestWorldWithWoman/</a></p><p><img src="http://img.frankorz.com/5b2ce58c91280.gif" alt="West World With Woman"></p><h2 id="消息功能">消息功能</h2><p>好的游戏实现趋向于事件驱动。即当一件事情发生了（发射了武器，主角发出了声音等等），事件会被广播给游戏中相关的对象。</p><p>整合事件（观察者模式）的状态机可以实现更灵活的需求，例如：一个足球运动员从队友旁边通过时，传球者可以发送一个（延时）消息，通知队友应该什么时候到相应位置来接球；一个士兵正在开枪攻击敌人，忽然一个队友中了流弹，这时候队友可以发送一个（即时）消息，通知士兵立刻救援队友。</p><h3 id="Telegram-结构">Telegram 结构</h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">struct</span> Telegram</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> BaseGameEntity Sender &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> BaseGameEntity Receiver &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> MessageType Message &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">float</span> DispatchTime &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line">    <span class="keyword">public</span> Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt; ExtraInfo &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Telegram</span>(<span class="params"><span class="built_in">float</span> time, BaseGameEntity sender, BaseGameEntity receiver, MessageType message,</span></span></span><br><span class="line"><span class="params"><span class="function">        Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt; extraInfo = <span class="literal">null</span></span>) : <span class="title">this</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        Sender = sender;</span><br><span class="line">        Receiver = receiver;</span><br><span class="line">        DispatchTime = time;</span><br><span class="line">        Message = message;</span><br><span class="line">        ExtraInfo = extraInfo;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里用结构体来实现消息。要发送的消息可以作为枚举加在 <code>MessageType</code> 中，DispatchTime 是决定立刻发送还是延时发送的时间戳，ExtraInfo 能携带额外的信息。这里只用两种消息做例子。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="built_in">enum</span> MessageType</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 矿工让妻子知道他已经回到小屋了</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    HiHoneyImHome,    </span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 妻子通知矿工自己什么时候要将晚饭从烤箱中拿出来</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 以及通知矿工食物已经放在桌子上了</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    StewReady,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="发送消息">发送消息</h3><p>下面是 MessageDispatcher 类，用来管理消息的发送。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 管理消息发送的类</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> 处理立刻被发送的消息，和打上时间戳的消息</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">MessageDispatcher</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> MessageDispatcher Instance &#123; <span class="keyword">get</span>; <span class="keyword">private</span> <span class="keyword">set</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="title">MessageDispatcher</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        Instance = <span class="keyword">new</span> MessageDispatcher();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">MessageDispatcher</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        priorityQueue = <span class="keyword">new</span> HashSet&lt;Telegram&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 根据时间排序的优先级队列</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">private</span> HashSet&lt;Telegram&gt; priorityQueue;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 该方法被 DispatchMessage 或者 DispatchDelayedMessages 利用。</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 该方法用最新创建的 telegram 调用接受实体的消息处理成员函数 receiver</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Discharge</span>(<span class="params">BaseGameEntity receiver, Telegram telegram</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (!receiver.HandleMessage(telegram))</span><br><span class="line">        &#123;</span><br><span class="line">            Debug.LogWarning(<span class="string">&quot;消息未处理&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 创建和管理消息</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;delay&quot;&gt;</span>时间的延迟（要立刻发送就用零或负值）<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;senderId&quot;&gt;</span>发送者 ID<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;receiverId&quot;&gt;</span>接受者 ID<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;message&quot;&gt;</span>消息本身<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;extraInfo&quot;&gt;</span>附加消息<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DispatchMessage</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">float</span> delay,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">int</span> senderId,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="built_in">int</span> receiverId,</span></span></span><br><span class="line"><span class="params"><span class="function">        MessageType message,</span></span></span><br><span class="line"><span class="params"><span class="function">        Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">string</span>&gt; extraInfo</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">// 获得消息发送者</span></span><br><span class="line">        BaseGameEntity sender = EntityManager.Instance.GetEntityFromId(senderId);</span><br><span class="line">        <span class="comment">// 获得消息接受者</span></span><br><span class="line">        BaseGameEntity receiver = EntityManager.Instance.GetEntityFromId(receiverId);</span><br><span class="line">        <span class="keyword">if</span> (receiver == <span class="literal">null</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            Debug.LogWarning(<span class="string">&quot;[MessageDispatcher] 找不到消息接收者&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">float</span> currentTime = Time.time;</span><br><span class="line">        <span class="keyword">if</span> (delay &lt;= <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            Telegram telegram = <span class="keyword">new</span> Telegram(<span class="number">0</span>, sender, receiver, message, extraInfo);</span><br><span class="line"></span><br><span class="line">            Debug.Log(<span class="built_in">string</span>.Format(</span><br><span class="line">                <span class="string">&quot;消息发送时间: &#123;0&#125; ，发送者是：&#123;1&#125;，接收者是：&#123;2&#125;。消息是 &#123;3&#125;&quot;</span>,</span><br><span class="line">                currentTime,</span><br><span class="line">                sender.Name,</span><br><span class="line">                receiver.Name,</span><br><span class="line">                message.ToString()));</span><br><span class="line">            Discharge(receiver, telegram);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">        &#123;</span><br><span class="line">            Telegram delayedTelegram = <span class="keyword">new</span> Telegram(currentTime + delay, sender, receiver, message, extraInfo);</span><br><span class="line">            priorityQueue.Add(delayedTelegram);</span><br><span class="line"></span><br><span class="line">            Debug.Log(<span class="built_in">string</span>.Format(</span><br><span class="line">                <span class="string">&quot;延时消息发送时间: &#123;0&#125; ，发送者是：&#123;1&#125;，接收者是：&#123;2&#125;。消息是 &#123;3&#125;&quot;</span>,</span><br><span class="line">                currentTime,</span><br><span class="line">                sender.Name,</span><br><span class="line">                receiver.Name,</span><br><span class="line">                message.ToString()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 发送延时消息</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 这个方法会放在游戏的主循环中，以正确地和及时地发送任何定时的消息</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">DisplayDelayedMessages</span>()</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">float</span> currentTime = Time.time;</span><br><span class="line">        <span class="keyword">while</span> (priorityQueue.Count &gt; <span class="number">0</span> &amp;&amp;</span><br><span class="line">               priorityQueue.First().DispatchTime &lt; currentTime &amp;&amp;</span><br><span class="line">               priorityQueue.First().DispatchTime &gt; <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            Telegram telegram = priorityQueue.First();</span><br><span class="line">            BaseGameEntity receiver = telegram.Receiver;</span><br><span class="line"></span><br><span class="line">            Debug.Log(<span class="built_in">string</span>.Format(<span class="string">&quot;延时消息开始准备分发，接收者是 &#123;0&#125;，消息是 &#123;1&#125;&quot;</span>,</span><br><span class="line">                receiver.Name,</span><br><span class="line">                telegram.Message.ToString()));</span><br><span class="line">            <span class="comment">// 开始分发消息</span></span><br><span class="line">            Discharge(receiver, telegram);</span><br><span class="line">            priorityQueue.Remove(telegram);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>DispatchMessage</code> 函数会管理消息的发送，即时消息会直接由 <code>Discharge</code> 函数发送到接收者，延时消息会进入队列，通过 GameManager 游戏主循环，每一帧调用 <code>DisplayDelayedMessages()</code> 函数来轮询要发送的消息，当发现当前时间超过了消息的发送时间，就把消息发送给接收者。</p><h3 id="处理消息">处理消息</h3><p>处理消息的话修改 BaseGameEntity 来增加处理消息的功能。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">BaseGameEntity</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// ... 省略无关代码</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">bool</span> <span class="title">HandleMessage</span>(<span class="params">Telegram message</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Miner</span> : <span class="title">BaseGameEntity</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">bool</span> <span class="title">HandleMessage</span>(<span class="params">Telegram message</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> m_stateMachine.HandleMessage(message);</span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>StateMachine 代码也要改：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">StateMachine</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">HandleMessage</span>(<span class="params">Telegram message</span>)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (m_pCurrentState != <span class="literal">null</span> &amp;&amp; m_pCurrentState.OnMessage(m_pOwner, message))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">        <span class="comment">// 如果当前状态没有代码适当的处理消息</span></span><br><span class="line">        <span class="comment">// 它会发送到实体的全局状态的消息处理者</span></span><br><span class="line">        <span class="keyword">if</span> (m_pCurrentState != <span class="literal">null</span> &amp;&amp; m_pGlobalState.OnMessage(m_pOwner, message))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>State 基类也要修改：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">State</span>&lt;<span class="title">T</span>&gt;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> 处理消息</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;entity&quot;&gt;</span>接受者<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;message&quot;&gt;</span>要处理的消息<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>消息是否成功被处理<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">bool</span> <span class="title">OnMessage</span>(<span class="params">T entity, Telegram message</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Discharge</code> 函数发送消息给接收者，接收者将消息给他 StateMachine 的 <code>HandleMessage</code> 函数处理，消息最后通过 StateMachine 到达各种状态的 <code>OnMessage</code> 函数，开始根据消息的类型来做出处理（例如进行状态转移）。</p><p>具体实现请看项目代码：<a href="https://github.com/Latias94/programming-game-ai-by-example-in-unity/tree/master/WestWorldWithMessaging">programming-game-ai-by-example-in-unity/WestWorldWithMessaging/</a></p><p><img src="http://img.frankorz.com/5b2cf105c1153.png" alt="West World With Messaging"></p><p>这里实现的场景是：</p><ol><li>矿工 Bob 回家后发送 HiHoneyImHome <strong>即时消息</strong>给他的妻子 Elsa，提醒她做饭。</li><li>Elsa 收到消息后，停止手上的活儿，开始进入 CookStew 状态做饭。</li><li>Elsa 进入 CookStew 状态后，把肉放到烤炉里面，并且发送 StewReady <strong>延时消息</strong>提醒自己在一段时间后拿出烤炉中的肉。</li><li>Elsa 收到 StewReady 消息后，发送一个 StewReady <strong>即时消息</strong>给 Bob 提醒他饭已经做好了。如果 Bob 这时不在家，命令行将显示 Discharge 函数中的 Warning “消息未处理”。Bob 在家，就会开心地去吃饭。</li><li>Bob 收到 StewReady 的消息，状态转移到 EatStew，开始吃饭。</li></ol><h2 id="总结">总结</h2><p>有时候我们可能会用到多个状态机来并行工作，例如一个 AI 有多个状态，其中包括攻击状态，而攻击状态又有不同攻击类型（瞄准和射击），像一个状态机包含另一个状态机这种<strong>层次化的状态机</strong>。当然也有其他不同的使用场景，我们不能受限于自己的想象力。</p><p>本文根据《游戏人工智能编程案例精粹（修订版）》进行了 Unity 版本的实现，我对有限状态机也有了更清晰的认识。阅读这本书的同时也会把 Unity 实现放到下面的仓库地址中，下篇文章可能会总结行为树的知识，如果没看到请督促我~</p><p>项目地址：<a href="https://github.com/Latias94/programming-game-ai-by-example-in-unity">programming-game-ai-by-example-in-unity</a></p><h2 id="引用">引用</h2><ul><li><a href="https://book.douban.com/subject/19930152/">《游戏人工智能编程案例精粹（修订版）》</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> 游戏开发 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Unity </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>2D 像素风平台游戏 Aretha’s Journey</title>
      <link href="/2018/06/17/arethas-journey/"/>
      <url>/2018/06/17/arethas-journey/</url>
      
        <content type="html"><![CDATA[<p>继<a href="https://frankorz.com/2018/04/13/first-board-game/">《我设计的第一个桌游》</a>后，这次带来的是电子游戏的作业—— 2D像素平台游戏 Aretha’s Journey。</p><h2 id="游戏背景">游戏背景</h2><p>来自潘达尼亚的女孩 Aretha 已经离乡别井几年了，回乡之际，发现族人们都被神所诅咒而变成了石头，原因是他们的族人曾尝试反抗神。于是 Aretha 便踏上拯救家园之路…</p><span id="more"></span><h2 id="游戏截图">游戏截图</h2><p>美术风格采用了像素风，游戏一共有五关，第一关为教学关，第五关为 Boss 战，截图如下：</p><p><img src="http://img.frankorz.com/5b2624189ebf8.png" alt="Stage01"></p><p><img src="http://img.frankorz.com/5b2624185d904.png" alt="Stage02"></p><p><img src="http://img.frankorz.com/5b2624187816a.png" alt="Stage03"></p><p><img src="http://img.frankorz.com/5b262418800b1.png" alt="Stage04"></p><p><img src="http://img.frankorz.com/5b2624f413674.png" alt="Stage05"></p><h2 id="游戏介绍">游戏介绍</h2><p>游戏类型是常见的平台游戏。主角可以收集金币，去商人那购买&quot;遗物&quot;（Relic），遗物可以看做是一种装备，穿上能提供一些特殊增益，例如增加血上限、打怪吸血等。本来想设计成 Roguelike 中众多能提供不同能力的道具，但苦于时间不足，因此在游戏中只设计了几个能购买的遗物，有些遗物能在宝箱里面随机开出来。</p><p>游戏地图是用 Unity 2017 的新特性 TileMap 做的，镜头使用 2D Cinemachine。现在回看作品，其实这一类型的游戏并不难，但是我们团队除了我都是第一次接触 Unity，踩过不少坑，也导致到 Due 的时候很多东西都没做完。</p><p>我负责了游戏中除战斗系统外的编程任务，例如：Game Loop、Item Respawn、角色状态、背包系统等功能。可以说很多代码都是重用性很高的，但自己以前没有积累，所以做这游戏的时候也只能从零开始做，在编程中逐渐模块化系统代码。</p><h2 id="Showcase">Showcase</h2><p>不过很开心的是，游戏被入选到学校的 Student Games Showcase 里面，这个 Showcase 里还有其他同学期其他同学所做的优秀的游戏作品。大家有兴趣的话可以在这个网站查看2018年的入选游戏：<a href="http://www.gamesstudio.org/games">Student Games</a> 。我们游戏在 Showcase 的评委投票和公开投票中都获得第二名的成绩，自己和小组都是很开心的~</p><p><img src="http://img.frankorz.com/5b262a27c0eb6.jpg" alt="猜猜我们组在哪~"></p><p><img src="http://img.frankorz.com/5b262a292f38c.jpg" alt="评委投票"></p><h2 id="结语">结语</h2><p>游戏下载：<a href="https://github.com/Latias94/Aretha-s-Journey">Aretha-s-Journey</a></p><p>总而言之，游戏设计课认识不少新朋友，也收获了很多，自己在游戏开发的路上又往前走了一步。</p><p>Flag: 以后博客会更新得勤一些，学了啥就写啥，不能等到吃透了一个东西再放上来= =。</p><blockquote><p>Thank you for playing my game.</p></blockquote>]]></content>
      
      
      
        <tags>
            
            <tag> 游戏设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>我设计的第一个桌游</title>
      <link href="/2018/04/13/first-board-game/"/>
      <url>/2018/04/13/first-board-game/</url>
      
        <content type="html"><![CDATA[<p>很久没写文章了，这次带来的是游戏设计课中的桌游大作业。</p><p>小组成员在第一节课就随机分好了，我们一开始选择每人准备一个桌游原型，然后共同选择一个原型来深入设计，最后一起选择了我的设计原型。</p><span id="more"></span><h2 id="第一版设计">第一版设计</h2><p><img src="http://img.frankorz.com/5ad027ce886e5.png" alt="第一版地图"></p><p>我的设计是：每个玩家初始有 4 HP，四个玩家从左下角通过掷骰子的方式在地图的点上往终点走，先到终点者胜，HP为零时需要从起点重新开始。地图中有四种不同功能的点，玩家到达某个点时会触发相应效果：</p><ul><li>黑点：无特殊效果</li><li>绿点：休息点，回 1 HP</li><li>红点：陷阱点，拿一张陷阱卡，立即使用来接受惩罚，如： 扣血、禁一回合等效果</li><li>黄点：宝物点，拿一张宝物卡，可以在当前回合使用，也可以藏起来等以后的回合使用，如：回血、其他玩家倒退三步、禁某玩家一回合等效果</li></ul><p>原型的灵感主要来自于最近常玩的 <a href="http://store.steampowered.com/app/646570/Slay_the_Spire/">“Slay the Spire”</a> 中的地图，玩家通过选择不同的道路抉择，目标是活着打败关底 BOSS。在第一版设计中，整个的PVE设计下，也考虑了 PVP 因素：两个玩家走到同一个点时可以进行决斗，每人 -1 HP。由于步数主要依赖于随机的骰子得来的数字，所以道路不能设计太多，尽量增加玩家相遇的机会。同时在较长的支路中，宝物点较多，陷阱点也较多，机遇与风险并存。</p><p>这样设计的话，我们主要考虑地图设计的平衡和去扩展卡牌的效果即可。</p><p>但是问题也来了：</p><ul><li>总有运气出奇好的玩家一路趋吉避凶，什么挫折都没遇到就到终点了</li><li>玩家死了之后就要回到起点重新开始，没干劲继续玩了</li><li>游戏性不足，现有的游戏元素还是太少，主要靠骰子和抓牌时的运气，玩家要考虑的实际上不多</li></ul><h2 id="第二版设计">第二版设计</h2><p>针对前面的第一点问题，新增了“地形”的设计：</p><p><img src="http://img.frankorz.com/5ad02e2f1a8be.png" alt=""></p><p>地形不是一个能走的点，效果只有在玩家走过（在点上走跨越地形）时触发。相对于点的设计，地形是一个“必须触发”的设计。每个玩家在跨过山脉的时候必须 -1 HP，遇到龙窟的时候也能必得一张宝物卡，这样降低了运气的成分，提高了功能卡的利用率。</p><p>对于上面的第二个问题，我们通过设计“旅馆”的一种地形，玩家走过 +1 HP，同时可以当做玩家的重生点，玩家死后可以从路过的旅馆重生。</p><p>针对第三个问题，我们新增了英雄卡的设计。每个英雄有不同能力，例如坦克天生带 5 HP，一些特殊的英雄还能根据现有元素设计能力。我们也新增了 AP（攻击力）和装备卡的设计，注入更多的 RPG 元素。</p><h2 id="成品">成品</h2><h3 id="地图">地图</h3><p><img src="http://img.frankorz.com/5ad030cf4f485.jpg" alt=""></p><h3 id="英雄卡">英雄卡</h3><p><img src="http://img.frankorz.com/5ad031273fd55.jpg" alt=""></p><h3 id="装备卡及卡背">装备卡及卡背</h3><p><img src="http://img.frankorz.com/5ad031592c916.jpg" alt=""></p><h3 id="陷阱卡">陷阱卡</h3><p>陷阱卡太多就不放上来了，效果主要是退几步、禁一回合、丢到所有的卡牌回到最近的起点等等。</p><h3 id="宝物卡">宝物卡</h3><p>效果主要有：</p><ul><li>回血</li><li>偷别人一张卡（宝物卡或者装备卡）</li><li>其他玩家退三步</li><li>多一次掷骰子行动的机会</li><li>+1 HP 并再拿一张宝物卡</li><li>每个玩家拿一张宝物卡（增加卡牌利用率）</li><li>和某玩家决斗！（增加 PVP 的存在感）</li><li>…</li></ul><h3 id="说明书中部分内容">说明书中部分内容</h3><p><img src="http://img.frankorz.com/5ad033beeed3b.png" alt=""></p><p><img src="http://img.frankorz.com/5ad034060f860.png" alt=""></p><h2 id="结语">结语</h2><p>在昨天晚上的游戏设计课里，每个小组都要玩别的小组做的桌游并打分，在五分满分的前提下，参与的小组给我们游戏大多打了四分以上，主要进行游戏设计部分的我还是很开心的！！！在文尾再次感谢小组成员所付出的努力，特别是主要进行美术设计的韩国小哥。</p><p>下一个大作业做电子游戏，准备做 Roguelike，有什么好的建议也可以在评论区提出~</p>]]></content>
      
      
      
        <tags>
            
            <tag> 游戏设计 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>寻路算法-贪婪最佳优先算法</title>
      <link href="/2017/12/16/greedy-best-find-search/"/>
      <url>/2017/12/16/greedy-best-find-search/</url>
      
        <content type="html"><![CDATA[<p>最近开始接触寻路算法，对此不太了解的话建议读者先看这篇文章<a href="http://www.gameres.com/777251.html">《如何快速找到最优路线？深入理解游戏中寻路算法》</a> 。</p><blockquote><p>所有寻路算法都需要一种方法以数学的方式估算某个节点是否应该被选择。大多数游戏都会使用<strong>启发式</strong>（heuristic） ，以 h(x) 表示，就是估算从某个位置到目标位置的开销。理想情况下，启发式结果越接近真实越好。</p><p>——《游戏编程算法与技巧》</p></blockquote><p>今天主要说的是贪婪最佳优先搜索（Greedy Best-First Search），贪心算法的含义是：求解问题时，总是做出在当前来说最好的选择。通俗点说就是，这是一个“短视”的算法。</p><span id="more"></span><p>为什么说是“短视”呢？首先要明白一个概念：<a href="https://baike.baidu.com/item/%E6%9B%BC%E5%93%88%E9%A1%BF%E8%B7%9D%E7%A6%BB">曼哈顿距离</a>。</p><h3 id="曼哈顿距离">曼哈顿距离</h3><p>曼哈顿距离被认为不能沿着对角线移动，如下图中，红、蓝、黄线都代表等距离的曼哈顿距离。绿线代表<a href="https://baike.baidu.com/item/%E6%AC%A7%E6%B0%8F%E8%B7%9D%E7%A6%BB">欧氏距离</a>，如果地图允许对角线移动的话，曼哈顿距离会经常比欧式距离高。</p><p><img src="assets/u=591525660,2345357852&amp;fm=27&amp;gp=0.jpg" alt="img"></p><p>在 2D 地图中，曼哈顿距离的计算如下：</p><p><img src="http://img.frankorz.com/5a353e6bc0c97.png" alt="img"></p><h3 id="贪婪最佳优先搜索的简介">贪婪最佳优先搜索的简介</h3><p>贪婪最佳优先搜索的每一步，都会查找相邻的节点，计算它们距离终点的曼哈顿距离，即最低开销的启发式。</p><p>贪婪最佳优先搜索在障碍物少的时候足够的快，但最佳优先搜索得到的都是次优的路径。例如下图，算法不断地寻找当前 h（启发式）最小的值，但这条路径很明显不是最优的。</p><p><img src="http://img.frankorz.com/5a352478a9196.png" alt="img"></p><p>贪婪最佳优先搜索“未能远谋”，大多数游戏都要比贪婪最佳优先算法所能提供的更好的寻路，但大多数寻路算法都是基于贪婪算法，所以了解该算法很有必要。</p><p>首先是节点类，每个节点需要存储上一个节点的引用和 h 值，其他信息是为了方便算法的实现。存储上一个节点的引用是为了像一个链表一样，最后能通过引用得到路径中所有的节点。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Node</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 上一个节点</span></span><br><span class="line"><span class="keyword">public</span> Node parent;</span><br><span class="line"><span class="comment">// 节点的 h(x) 值</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">float</span> h;</span><br><span class="line"><span class="comment">// 与当前节点相邻的节点</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Node&gt; adjecent = <span class="keyword">new</span> List&lt;Node&gt;();</span><br><span class="line"><span class="comment">// 节点所在的行</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">int</span> row;</span><br><span class="line"><span class="comment">// 节点所在的列</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">int</span> col;</span><br><span class="line"><span class="comment">// 清除节点信息</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Clear</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">parent = <span class="literal">null</span>;</span><br><span class="line">h = <span class="number">0.0f</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面是图类，图类最主要的任务就是根据提供的二维数组初始化所有的节点，包括寻找他们的相邻节点。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 图类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Graph</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">int</span> rows = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">int</span> cols = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span> Node[] nodes;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">Graph</span>(<span class="params"><span class="built_in">int</span>[, ] grid</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">rows = grid.GetLength(<span class="number">0</span>);</span><br><span class="line">cols = grid.GetLength(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">nodes = <span class="keyword">new</span> Node[grid.Length];</span><br><span class="line"><span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; nodes.Length; i++)</span><br><span class="line">&#123;</span><br><span class="line">Node node = <span class="keyword">new</span> Node();</span><br><span class="line">node.row = i / cols;</span><br><span class="line">node.col = i - (node.row * cols);</span><br><span class="line">nodes[i] = node;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 找到每一个节点的相邻节点</span></span><br><span class="line"><span class="keyword">foreach</span> (Node node <span class="keyword">in</span> nodes)</span><br><span class="line">&#123;</span><br><span class="line"><span class="built_in">int</span> row = node.row;</span><br><span class="line"><span class="built_in">int</span> col = node.col;</span><br><span class="line"><span class="comment">// 墙，即节点不能通过的格子 </span></span><br><span class="line"><span class="comment">// 1 为墙，0 为可通过的格子</span></span><br><span class="line"><span class="keyword">if</span> (grid[row, col] != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">// 上方的节点</span></span><br><span class="line"><span class="keyword">if</span> (row &gt; <span class="number">0</span> &amp;&amp; grid[row - <span class="number">1</span>, col] != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line">node.adjecent.Add(nodes[cols * (row - <span class="number">1</span>) + col]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 右边的节点</span></span><br><span class="line"><span class="keyword">if</span> (col &lt; cols - <span class="number">1</span> &amp;&amp; grid[row, col + <span class="number">1</span>] != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line">node.adjecent.Add(nodes[cols * row + col + <span class="number">1</span>]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下方的节点</span></span><br><span class="line"><span class="keyword">if</span> (row &lt; rows - <span class="number">1</span> &amp;&amp; grid[row + <span class="number">1</span>, col] != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line">node.adjecent.Add(nodes[cols * (row + <span class="number">1</span>) + col]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 左边的节点</span></span><br><span class="line"><span class="keyword">if</span> (col &gt; <span class="number">0</span> &amp;&amp; grid[row, col - <span class="number">1</span>] != <span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line">node.adjecent.Add(nodes[cols * row + col - <span class="number">1</span>]);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在算法类中，我们需要记录开放集合和封闭集合。开放集合指的是当前步骤我们需要考虑的节点，例如算法开始时就要考虑初始节点的相邻节点，并从其找到最低的 h(x) 值开销的节点。封闭集合存放已经计算过的节点。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 开放集合</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Node&gt; reachable;</span><br><span class="line"><span class="comment">// 封闭集合，存放已经被算法估值的节点</span></span><br><span class="line"><span class="keyword">public</span> List&lt;Node&gt; explored;</span><br></pre></td></tr></table></figure><p>下面是算法主要的逻辑，额外的函数可以查看项目源码。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Stack&lt;Node&gt; <span class="title">Finding</span>()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 存放查找路径的栈</span></span><br><span class="line">    Stack&lt;Node&gt; path;</span><br><span class="line">    Node currentNode = reachable[<span class="number">0</span>];</span><br><span class="line">  <span class="comment">// 迭代查找，直至找到终点节点</span></span><br><span class="line">    <span class="keyword">while</span> (currentNode != destination)</span><br><span class="line">    &#123;</span><br><span class="line">        explored.Add(currentNode);</span><br><span class="line">        reachable.Remove(currentNode);</span><br><span class="line">      <span class="comment">// 将当前节点的相邻节点加入开放集合</span></span><br><span class="line">        AddAjacent(currentNode);</span><br><span class="line"><span class="comment">// 查找了相邻节点后依然没有可以考虑的节点，查找失败。</span></span><br><span class="line">        <span class="keyword">if</span> (reachable.Count == <span class="number">0</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      <span class="comment">// 将开放集合中h值最小的节点当做当前节点</span></span><br><span class="line">        currentNode = FindLowestH();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 查找成功，则根据节点parent找到查找到的路径</span></span><br><span class="line">    path = <span class="keyword">new</span> Stack&lt;Node&gt;();</span><br><span class="line">    Node node = destination;</span><br><span class="line">    <span class="comment">// 先将终点压入栈，再迭代地把node的前一个节点压入栈</span></span><br><span class="line">    path.Push(node);</span><br><span class="line">    <span class="keyword">while</span> (node.parent != <span class="literal">null</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        path.Push(node.parent);</span><br><span class="line">        node = node.parent;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> path;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除此以外还有些展示算法的类，代码不在这里展出。下面是算法执行的截图，其中白色格子为可走的格子，灰色格子是不可穿越的，红色格子为查找到的路径，左上角格子为查找起点，右上角格子为查找终点。</p><p><img src="http://img.frankorz.com/5a352df416fd6.png" alt="small"></p><p><img src="http://img.frankorz.com/5a352f076fc3c.png" alt="big"></p><p>后一个实例也展现了其&quot;短视&quot;的缺点，红线走了共65个格子，但蓝箭头方向只走了45个格子。</p><p><img src="http://img.frankorz.com/5a35305e2845e.png" alt=""></p><h3 id="最后">最后</h3><p>还有一种方案就是直接计算起点到终点的路径，这样可以节省一点计算开销。如下方右图，左图为广度优先算法。</p><p><img src="http://img.frankorz.com/5d441ebc50ff524704.gif" alt="img"></p><p>本项目源码在 <a href="https://github.com/Latias94/PathFindingDemo">Github-PathFindingDemo</a> 。<br>了解了贪婪最佳优先算法后，下一篇文章会在本文基础上讲A* 寻路。</p><h3 id="参考资料">参考资料</h3><ul><li><a href="http://www.gameres.com/777251.html">如何快速找到最优路线？深入理解游戏中寻路算法</a></li><li><a href="http://blog.jobbole.com/71044/">关于寻路算法的一些思考（1）：A*算法介绍</a></li><li>《游戏编程算法与技巧》</li></ul>]]></content>
      
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>3D数学基础-矩阵变换（二）</title>
      <link href="/2017/09/24/matrix-transformation-2/"/>
      <url>/2017/09/24/matrix-transformation-2/</url>
      
        <content type="html"><![CDATA[<p><strong>2020年7月26日更新：</strong><br>最近对变换重新学习整理了，文章在：<a href="http://frankorz.com/2020/07/26/transformation/">图形学常见的变换推导</a>。</p><p>上一篇笔记<a href="http://frankorz.com/2017/09/22/basic-of-vector-and-matrix-transformation/">3D 数学基础-向量运算基础和矩阵变换</a>记录了一些向量和矩阵运算的基础，和一些矩阵基本的变换。这篇笔记主要介绍了平移变换、齐次坐标、平移和旋转变换的组合、法线变换和改变坐标系。</p><span id="more"></span><h2 id="平移（Translation）">平移（Translation）</h2><p>前一篇文章总结了旋转、缩放等变换，这里介绍一下平移变换。</p><p>假如我们需要把 X 坐标从 x 变换到 x + 5，我们需要构造什么样的变换矩阵来实现平移呢？</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>z</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mo stretchy="false" lspace="0em" rspace="0em">?</mo></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo>+</mo><mn>5</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\begin{bmatrix}x&#x27; \\ y&#x27; \\ z&#x27; \end{bmatrix}=\begin{bmatrix} &amp; &amp; \\ &amp; ? &amp; \\ &amp; &amp; \end{bmatrix}\begin{bmatrix}x \\ y \\ z \end{bmatrix}=\begin{bmatrix}x+5 \\ y \\ z \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span><span style="top:-2.85em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span><span style="top:-1.65em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mclose">?</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span><span style="top:-2.85em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span><span style="top:-1.65em;"><span class="pstrut" style="height:2.84em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">5</span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>要注意的是，变换矩阵不能包含 x、y、z 等坐标变量。例如要得到 x + 5，那么矩阵的第一个行向量不能是 $$(1, 0, 5/z)$$，因为变换的一个重要性质是<strong>变换矩阵保持不变</strong>。</p><p>我们可以给矩阵多加一个维度 w，令其等于 1，这样就不需要用到 x、y、z 这些坐标变量了。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>y</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>z</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msup><mi>w</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>5</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo>+</mo><mn>5</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\begin{bmatrix}x&#x27; \\ y&#x27; \\ z&#x27; \\ w&#x27; \end{bmatrix}=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 5\\ 0 &amp; 1 &amp; 0 &amp; 0\\ 0 &amp; 0 &amp; 1 &amp; 0\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}\begin{bmatrix}x \\ y \\ z \\ 1 \end{bmatrix}=\begin{bmatrix}x+5 \\ y \\ z \\ 1 \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">5</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">5</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>这里引用到了<a href="https://www.wikiwand.com/zh/%E9%BD%90%E6%AC%A1%E5%9D%90%E6%A0%87">齐次坐标</a>的概念。</p><h2 id="齐次坐标">齐次坐标</h2><blockquote><p>齐次坐标表示是计算机图形学的重要手段之一，它既能够用来明确区分向量和点，同时也更易用于进行仿射几何变换。<br>—— F.S. Hill, JR 《计算机图形学(OpenGL 版)》作者</p></blockquote><p>想要了解三维坐标是怎么样扩展到四维坐标，可以先了解下二维空间中的齐次坐标 $$(x, y, w)$$。</p><p>下图的三维坐标中在 $$w=1$$ 处有一个二维平面，则该平面上的二维平面坐标可以表示为 $$(x, y, 1)$$，图中的 $$(1.0, 0.8, 1.0)$$ 就是一个在该二维平面上的点。对于不在二维平面上的点，我们则将其投影到二维平面上，所以齐次坐标 $$(x, y, w)$$ 映射到实际二维平面上的点为 $$(x/w, y/w)$$，例如另外一个点 $$(2.5, 2.0, 2.5)$$ 在 $$w=1$$ 二维平面投影得到的点为 $$(1.0, 0.8)$$。</p><p><img src="http://img.frankorz.com/59c657c43c5dd.png" alt=""></p><p>同理，三维空间的点可以认为是在四维空间中 $$w=1$$ 的“平面”上，所以齐次坐标 $$(x, y, z, w)$$ 映射到三维空间上的点为 $$(x/w, y/w, z/w)$$，这也就是点的非齐次坐标。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>w</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>y</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>z</mi><mi mathvariant="normal">/</mi><mi>w</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\begin{bmatrix}x \\ y \\ z \\ w \end{bmatrix}=\begin{bmatrix}x/w \\ y/w \\ z/w \\ 1 \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p>四维齐次坐标的任何常量缩放会得到相同的结果，也就是说，它包含了点的非齐次坐标所有可能的缩放（还记得上面图中的两个点吗？），w 可以等于任何数值：</p><ul><li>如果 w &gt; 0，这表示一个真实物理世界的点，我们可以用 x，y，z 三个坐标除以 w 得到这个真实的点。</li><li>如果 w = 0，这表示一个无穷远处的点。在实际应用中，通常表示一个向量。</li></ul><p>齐次坐标的优点：</p><ul><li>让我们可以同时考虑所有的变换，如平移、观察、旋转、透视投影等。（如果我们要做缩放变换，可以直接变换 x、y、z 的值并且保持 w 不变。）</li><li>整个渲染管线都以 4 * 4 的齐次坐标矩阵以及对应的四维向量为基础，只有在最后要表示真实点的位置的时候，才需要把齐次坐标变为非齐次。（计算机里除法是个非常复杂的操作，需要花费更多的时间周期，但齐次坐标只需在渲染管线最后一步做除法。）</li><li>不会出现特殊情况。有时考虑判断直线是否相交，当它们平行的时候，有些公式会出错，但无穷远点已经在齐次坐标中约定了 w=0。</li><li>四阶矩阵和齐次坐标是计算机图形软件和硬件中普遍使用的标准。</li></ul><p>下面表示的是一个点向量加上一个平移变量：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>P</mi><msup><mrow></mrow><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mi>T</mi><mi>P</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>x</mi><mo>+</mo><msub><mi>T</mi><mi>x</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>y</mi><mo>+</mo><msub><mi>T</mi><mi>y</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>z</mi><mo>+</mo><msub><mi>T</mi><mi>z</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mi>P</mi><mo>+</mo><mi>T</mi></mrow><annotation encoding="application/x-tex">P{}&#x27;=TP=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; T_{x}\\ 0 &amp; 1 &amp; 0 &amp; T_{y}\\ 0 &amp; 0 &amp; 1 &amp; T_{z}\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}\begin{bmatrix}x \\ y \\ z \\ 1 \end{bmatrix}=\begin{bmatrix}x+T_{x} \\ y+T_{y} \\ z+T_{z} \\ 1 \end{bmatrix}=P+T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8019em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord"><span class="mord"></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">TP</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span></span></p><p>有时候平移矩阵 T 内的左上角部分会直接用单位矩阵来表示，其中 $$I_{3}$$ 表示 3 * 3 的单位矩阵。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>I</mi><mn>3</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>T</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">T=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; T_{x}\\ 0 &amp; 1 &amp; 0 &amp; T_{y}\\ 0 &amp; 0 &amp; 1 &amp; T_{z}\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} I_{3} &amp; T\\ 0 &amp; 1 \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0785em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><h2 id="组合平移和旋转变换">组合平移和旋转变换</h2><p>当要对一个点进行两种变换的时候，就会涉及到变换顺序的问题，是先平移再旋转，还是先旋转再平移？</p><h3 id="先旋转再平移">先旋转再平移</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>P</mi><msup><mrow></mrow><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mrow><mo fence="true">(</mo><mi>T</mi><mi>R</mi><mo fence="true">)</mo></mrow><mi>P</mi><mo>=</mo><mi>M</mi><mi>P</mi><mo>=</mo><mi>R</mi><mi>P</mi><mo>+</mo><mi>T</mi></mrow><annotation encoding="application/x-tex">P{}&#x27;=\left ( TR \right )P=MP=RP+T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8019em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord"><span class="mord"></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord mathnormal" style="margin-right:0.00773em;">TR</span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">MP</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span></span></p><p>其中 T 为平移矩阵，R 为旋转矩阵，矩阵 P 通过平移再旋转后得到矩阵 P’。注意，在上面的公式中做的是标准的向量计算，在计算时才用齐次坐标计算。</p><p>由于我们使用的是列矩阵，矩阵的阅读顺序应该从右到左。也就是说对于 TR 这个旋转矩阵而言，（TR）P 这个操作，就是对矩阵 P 先 R 旋转，再进行 T 平移。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>11</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>12</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>13</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>21</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>22</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>23</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>31</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>32</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>33</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>11</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>12</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>13</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>21</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>22</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>23</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>y</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>31</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>32</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>33</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>z</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>R</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>T</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">M=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; T_{x}\\ 0 &amp; 1 &amp; 0 &amp; T_{y}\\ 0 &amp; 0 &amp; 1 &amp; T_{z}\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}\begin{bmatrix} R_{11} &amp; R_{12} &amp; R_{13} &amp; 0\\ R_{21} &amp; R_{22} &amp; R_{23} &amp; 0\\ R_{31} &amp; R_{32} &amp; R_{33} &amp; 0\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} R_{11} &amp; R_{12} &amp; R_{13} &amp; T_{x}\\ R_{21} &amp; R_{22} &amp; R_{23} &amp; T_{y}\\ R_{31} &amp; R_{32} &amp; R_{33} &amp; T_{z}\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} R &amp; T \\ 0 &amp; 1 \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">11</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">21</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">31</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">22</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">32</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">13</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">23</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">33</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">11</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">21</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">31</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">22</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">32</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">13</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">23</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">33</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mi>P</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>R</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>T</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>P</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>R</mi><mi>P</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>T</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mi>R</mi><mi>P</mi><mo>+</mo><mi>T</mi></mrow><annotation encoding="application/x-tex">MP=\begin{bmatrix} R &amp; T \\ 0 &amp; 1 \end{bmatrix}\begin{bmatrix} P &amp; 0 \\ 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} RP &amp; T \\ 0 &amp; 1 \end{bmatrix}=RP+T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">MP</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span></span></span></p><h3 id="先平移再旋转">先平移再旋转</h3><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>P</mi><msup><mrow></mrow><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo>=</mo><mo stretchy="false">(</mo><mi>R</mi><mi>T</mi><mo stretchy="false">)</mo><mi>P</mi><mo>=</mo><mi>M</mi><mi>P</mi><mo>=</mo><mi>R</mi><mo stretchy="false">(</mo><mi>P</mi><mo>+</mo><mi>T</mi><mo stretchy="false">)</mo><mo>=</mo><mi>R</mi><mi>P</mi><mo>+</mo><mi>R</mi><mi>T</mi></mrow><annotation encoding="application/x-tex">P{}&#x27;=(RT)P=MP=R(P+T)=RP+RT</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8019em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mord"><span class="mord"></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8019em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.13889em;">RT</span><span class="mclose">)</span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">MP</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.13889em;">P</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RT</span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>11</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>12</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>13</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>21</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>22</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>23</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>31</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>32</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mn>33</mn></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>T</mi><mi>x</mi></msub></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msub><mi>R</mi><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow></msub><msub><mi>T</mi><mrow><mn>3</mn><mo>×</mo><mn>1</mn></mrow></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mn>0</mn><mrow><mn>1</mn><mo>×</mo><mn>3</mn></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">M=\begin{bmatrix} R_{11} &amp; R_{12} &amp; R_{13} &amp; 0\\ R_{21} &amp; R_{22} &amp; R_{23} &amp; 0\\ R_{31} &amp; R_{32} &amp; R_{33} &amp; 0\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; T_{x}\\ 0 &amp; 1 &amp; 0 &amp; T_{x}\\ 0 &amp; 0 &amp; 1 &amp; T_{x}\\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} R_{3\times 3} &amp; R_{3\times 3}T_{3\times 1}\\ 0_{1\times 3} &amp; 1 \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:4.8em;vertical-align:-2.15em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">11</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">21</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">31</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">22</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">32</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">13</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">23</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">33</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>M</mi><mi>P</mi><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mi>R</mi><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><msub><mi>R</mi><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow></msub><msub><mi>T</mi><mrow><mn>3</mn><mo>×</mo><mn>1</mn></mrow></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><msub><mn>0</mn><mrow><mn>1</mn><mo>×</mo><mn>3</mn></mrow></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>P</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>R</mi><mi>P</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>R</mi><mi>T</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mi>R</mi><mi>P</mi><mo>+</mo><mi>R</mi><mi>T</mi></mrow><annotation encoding="application/x-tex">MP=\begin{bmatrix} R_{3\times 3} &amp; R_{3\times 3}T_{3\times 1}\\ 0_{1\times 3} &amp; 1 \end{bmatrix}\begin{bmatrix} P &amp; 0\\ 0 &amp; 1 \end{bmatrix}=\begin{bmatrix} RP &amp; RT\\ 0 &amp; 1 \end{bmatrix}=RP+RT</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">MP</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.1389em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3</span><span class="mbin mtight">×</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">P</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.13889em;">RT</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RP</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">RT</span></span></span></span></span></p><p>对比先平移再旋转的结果，可以看到不同的地方是 T 变成了 RT，也就是说旋转也施加在了平移的方向上。在下图中可看到小人在 Y 轴方向发生了平移，所以<strong>先旋转再平移</strong>可能会更容易得到理想的结果，但两种变换组合都是可行的。（在 gluLookAt 的推导中需要先做平移，gluLookAt 是 OpenGL 中观察变换的一个关键函数。）</p><p><img src="http://img.frankorz.com/59c771e4d39f0.png" alt=""></p><h2 id="法线变换">法线变换</h2><p>在图形学中曲面法线的方向也很重要。</p><h3 id="法线（Normal）">法线（Normal）</h3><blockquote><p>法线是始终垂直于某平面的虚线。在数学几何中法线指平面上垂直于曲线在某点的切线的一条线。<br>——百度百科</p></blockquote><p><img src="http://img.frankorz.com/59c6f8e40b986.png" alt=""></p><p>切线的位置实际上就是曲面上的几何位置，因此它们的变换矩阵和曲面的变换矩阵保持一致。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>→</mo><mi>M</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">t\rightarrow Mt</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">Mt</span></span></span></span></span></p><p>而法线变换是另外一个矩阵，我们称之为 Q。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>n</mi><mo>→</mo><mi>Q</mi><mi>n</mi><mtext>  </mtext><mi>Q</mi><mo>=</mo><mo stretchy="false">?</mo></mrow><annotation encoding="application/x-tex">n\rightarrow Qn~~Q=?</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">Q</span><span class="mord mathnormal">n</span><span class="mspace nobreak"> </span><span class="mspace nobreak"> </span><span class="mord mathnormal">Q</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mclose">?</span></span></span></span></span></p><p>法线必须垂直于这些切线，因此法线和切线的点积等于 0。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>n</mi><mi>T</mi></msup><mi>t</mi><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">n^{T}t=0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8913em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span></span></p><p>变换后法线和切点依然互相垂直，可得：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mi>Q</mi><mi>n</mi><msup><mo stretchy="false">)</mo><mi>T</mi></msup><mo>=</mo><mo stretchy="false">(</mo><msup><mi>n</mi><mi>T</mi></msup><msup><mi>Q</mi><mi>T</mi></msup><mo stretchy="false">)</mo><mo stretchy="false">(</mo><mi>M</mi><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">(Qn)^{T}=(n^{T}Q^{T})(Mt)=0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1413em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">Q</span><span class="mord mathnormal">n</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1413em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">Q</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mopen">(</span><span class="mord mathnormal">Mt</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span></span></p><p>在该等式中，只有当 $$Q^{T}M=I$$ 时，矩阵才容易求解。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>n</mi><mi>T</mi></msup><msup><mi>Q</mi><mi>T</mi></msup><mi>M</mi><mi>t</mi><mo>=</mo><mn>0</mn><mo>⇒</mo><msup><mi>Q</mi><mi>T</mi></msup><mi>M</mi><mo>=</mo><mi>I</mi></mrow><annotation encoding="application/x-tex">n^{T} Q^{T} M t=0 \Rightarrow Q^{T} M=I</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0858em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mord mathnormal">Q</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mord mathnormal">Mt</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">⇒</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.0858em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">Q</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span></span></span></span></span></p><p>最后可得法线变换公式，其中 $$M^{-1}$$ 只对左上角的 3 * 3 矩阵求逆和转置：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Q</mi><mo>=</mo><mo stretchy="false">(</mo><msup><mi>M</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><msup><mo stretchy="false">)</mo><mi>T</mi></msup></mrow><annotation encoding="application/x-tex">Q=(M^{-1})^{T}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8778em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">Q</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1413em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10903em;">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span></span></span></span></span></p><p>要进行法线变换，这个公式要施加在曲面上所有的法线上。</p><p>另外要注意的是法线是一个向量，不会随着平移而改变，因此平移变换对法线没影响。</p><h2 id="改变坐标系">改变坐标系</h2><p><img src="http://img.frankorz.com/59c70f36b057b.jpg" alt=""></p><p>在图中，点 (2, 1) 要平移到 (1, 1) 处，可以直接把点向左平移，也可以看成是坐标系的改变，将坐标系向右平移。</p><p>引出坐标系的概念是因为，在很多情形下，我们需要一个特定的物理位置在不同的坐标系间变换，例如上一篇文章<a href="http://frankorz.com/2017/09/22/basic-of-vector-and-matrix-transformation/#%E6%AD%A3%E4%BA%A4%E5%9D%90%E6%A0%87%E7%B3%BB">正交坐标系</a>中所举的例子。下图中，有世界坐标系、相机坐标系和点 P。</p><p><img src="http://img.frankorz.com/59c72df0ddea8.jpg" alt=""></p><p>点 P 在两个坐标系中的坐标是不同的，而在图形学中，我们经常要做这样的变换。</p><p><img src="http://img.frankorz.com/59c72e3657a2a.jpg" alt=""></p><p>如果我们在世界坐标系中有一个点，要计算出其在相机坐标系中的位置，则要同时考虑相机旋转的坐标系、相机的位置和观察的位置（视点）。</p><p>在<a href="http://frankorz.com/2017/09/22/basic-of-vector-and-matrix-transformation/#%E4%BA%8C%E7%BB%B4%E7%A9%BA%E9%97%B4%E4%B8%8B%E7%9A%84%E6%97%8B%E8%BD%AC">二维空间下的旋转</a>中能看到二维旋转矩阵的推导过程，在此基础上，可以看作坐标系向右旋转了θ度，这样点 P 就在新坐标系中到达目标位置 P’ 了。</p><p><img src="http://img.frankorz.com/59c73178738e1.jpg" alt=""></p><p>这样我们就能通过旋转矩阵来得到新的 uv 坐标系了！</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>u</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>v</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>c</mi><mi>o</mi><mi>s</mi><mi>θ</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>s</mi><mi>i</mi><mi>n</mi><mi>θ</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>s</mi><mi>i</mi><mi>n</mi><mi>θ</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>c</mi><mi>o</mi><mi>s</mi><mi>θ</mi></mrow></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\begin{bmatrix} u\\v \end{bmatrix}=\begin{bmatrix} cos\theta &amp; -sin\theta \\ sin\theta &amp; cos\theta \end{bmatrix}\begin{bmatrix} x\\y \end{bmatrix}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">u</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">v</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">cos</span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">s</span><span class="mord mathnormal">in</span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">−</span><span class="mord mathnormal">s</span><span class="mord mathnormal">in</span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">cos</span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size3">[</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size3">]</span></span></span></span></span></span></span></p><hr><p>之前讨论的都是三维的变换，下篇笔记会介绍图形学中的观察（Viewing）。3D 数学基础知识有点枯燥，但这是入门图形魔法的必经之路。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://book.douban.com/subject/1400419/">3D 数学基础：图形与游戏开发</a></li><li><a href="https://www.edx.org/course/computer-graphics-uc-san-diegox-cse167x-3">edx-Computer Graphics</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> 图形学 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> 3D数学 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>3D数学基础-向量运算基础和矩阵变换</title>
      <link href="/2017/09/22/basic-of-vector-and-matrix-transformation/"/>
      <url>/2017/09/22/basic-of-vector-and-matrix-transformation/</url>
      
        <content type="html"><![CDATA[<p><strong>2020年7月26日更新：</strong><br>最近对变换重新学习整理了，文章在：<a href="http://frankorz.com/2020/07/26/transformation/">图形学常见的变换推导</a>。</p><p>最近在跟公开课 edx 的 <a href="https://www.edx.org/course/computer-graphics-uc-san-diegox-cse167x-3">Computer Graphics</a>（想一起学的告诉我！），这篇笔记主要介绍了图形学中会用到的比较基础的3D数学，重拾大学线性代数知识。</p><span id="more"></span><h2 id="基础运算">基础运算</h2><h3 id="点积（Dot-Product）">点积（Dot Product）</h3><p>将向量相乘得到一个标量<br>可以通过两个向量除以它们的模得到两个向量之间的夹角</p><p>用途：</p><ul><li>求两个向量之间的夹角（光源和表面之间夹角的余弦值对于投影来说非常重要）</li><li>找到一个向量在另一向量上的投影也非常重要（比如我们想知道一个点在新的坐标系下的坐标）。</li><li>点积在笛卡尔坐标系下很有用</li></ul><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>a</mi><mo>⃗</mo></mover><mo>⋅</mo><mover accent="true"><mi>b</mi><mo>⃗</mo></mover><mo>=</mo><mi mathvariant="normal">∥</mi><mi>a</mi><mi mathvariant="normal">∥</mi><mi mathvariant="normal">∥</mi><mi>b</mi><mi mathvariant="normal">∥</mi><mi>cos</mi><mo>⁡</mo><mi>θ</mi></mrow><annotation encoding="application/x-tex">\vec{a} \cdot \vec{b}=\|a\|\|b\| \cos \theta</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.714em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">a</span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'><path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 53.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 1110.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359c-16-25.333-24-45-24-59z'/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.9774em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.9774em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal">b</span></span><span style="top:-3.2634em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'><path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 53.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 1110.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359c-16-25.333-24-45-24-59z'/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">∥</span><span class="mord mathnormal">a</span><span class="mord">∥∥</span><span class="mord mathnormal">b</span><span class="mord">∥</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">cos</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span></span></span></p><p><img src="http://img.frankorz.com/59c275ef5ad97.png" alt=""></p><h3 id="叉积（Cross-Product）">叉积（Cross Product）</h3><p>将向量相乘得到一个正交向量（垂直于向量a和向量b）</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>×</mo><mi>b</mi><mo>=</mo><mo>−</mo><mi>b</mi><mo>×</mo><mi>a</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">∥</mi><mi>a</mi><mo>×</mo><mi>b</mi><mi mathvariant="normal">∥</mi><mo>=</mo><mi mathvariant="normal">∥</mi><mi>a</mi><mi mathvariant="normal">∥</mi><mi mathvariant="normal">∥</mi><mi>b</mi><mi mathvariant="normal">∥</mi><mi>sin</mi><mo>⁡</mo><mi>θ</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l}{a \times b=-b \times a} \\ {\|a \times b\|=\|a\|\|b\| \sin \theta}\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.4em;vertical-align:-0.95em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.45em;"><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">−</span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">a</span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">∥</span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">×</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">b</span><span class="mord">∥</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">∥</span><span class="mord mathnormal">a</span><span class="mord">∥∥</span><span class="mord mathnormal">b</span><span class="mord">∥</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">sin</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.95em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p><img src="http://img.frankorz.com/59c2f0c604b26.png" alt=""></p><p>叉积所得到的向量的方向可以通过右手坐标系来得到：</p><p><img src="http://img.frankorz.com/59c2f0ae5357d.png" alt=""></p><p>用右手食指代表叉积中前一个向量，用中指代表叉积中后一个向量，则大拇指的方向就是叉积得到的向量方向。这简单的方法能提醒你：将进行叉积的两个向量顺序调换的话，得到的向量方向会相反。</p><p><img src="http://img.frankorz.com/59c2f15017b3b.png" alt=""><br><img src="http://img.frankorz.com/59c2f2e8931f9.png" alt=""></p><p>叉积可以用向量 a 的对偶矩阵来完成，所以可以将它表示成A星(A*)乘以b，其中A*是向量a的对偶矩阵（<a href="https://zh.wikipedia.org/zh-hans/%E5%85%B1%E8%BD%AD%E8%BD%AC%E7%BD%AE">共轭转置</a>）。</p><h3 id="正交坐标系">正交坐标系</h3><p>正交基和坐标系对于表示点的位置非常重要，因为在图形学中，我们通常需要很多不同的坐标系，来表示点在不同参照物下的位置。例如：要表示自己身前一台电脑的位置，可以在自己的位置建立一个坐标系来表示电脑的位置；要表示北极的位置时，用地球的坐标系表示北极的位置会更容易。</p><p><img src="http://img.frankorz.com/59c2f8a2d4ed7.png" alt=""><br><img src="http://img.frankorz.com/59c2f9bd606b9.png" alt=""></p><h3 id="矩阵">矩阵</h3><p>图形学中矩阵很重要，因为大多数变换都涉及一个矩阵乘以一个向量，矩阵可以用来变换点。下面简单的列一下矩阵的性质，更多的性质可以参考下维基页面<a href="https://www.wikiwand.com/zh-hans/%E7%9F%A9%E9%98%B5">矩阵</a>。</p><h4 id="矩阵相乘">矩阵相乘</h4><p><img src="http://img.frankorz.com/59c45933d1131.png" alt=""></p><p>矩阵相乘时，乘积处(i,j)处的元素是：第一个矩阵的 i 行和第二个矩阵 j 列的点积，这也是为什么要求第一个矩阵的列数等于第二个矩阵的行数。</p><h4 id="矩阵转置">矩阵转置</h4><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>3</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>4</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>6</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>7</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>8</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>9</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>10</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>11</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>12</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mi>T</mi></msup><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>1</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>4</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>7</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>10</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>2</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>5</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>8</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>11</mn></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>3</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>6</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>9</mn></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>12</mn></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{ccc}{1} &amp; {2} &amp; {3} \\ {4} &amp; {5} &amp; {6} \\ {7} &amp; {8} &amp; {9} \\ {10} &amp; {11} &amp; {12}\end{array}\right]^{T}=\left[\begin{array}{cccc}{1} &amp; {4} &amp; {7} &amp; {10} \\ {2} &amp; {5} &amp; {8} &amp; {11} \\ {3} &amp; {6} &amp; {9} &amp; {12}\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.0313em;vertical-align:-2.15em;"></span><span class="minner"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M403 1759 V84 H666 V0 H319 V1759 v1200 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">1</span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">4</span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">7</span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">10</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">2</span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">5</span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">8</span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">11</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">3</span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">6</span></span></span></span><span style="top:-2.41em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">9</span></span></span></span><span style="top:-1.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65em;"><span style="top:-4.65em;"><span class="pstrut" style="height:6.8em;"></span><span style="width:0.667em;height:4.800em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='4.800em' viewBox='0 0 667 4800'><path d='M347 1759 V0 H0 V84 H263 V1759 v1200 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v1200 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.15em;"><span></span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:2.8812em;"><span style="top:-5.1029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">1</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">2</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">3</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">4</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">5</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">6</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">7</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">8</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">9</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">10</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">11</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>c</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>e</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>f</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>g</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>h</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>i</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mi>T</mi></msup><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>d</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>g</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>e</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>h</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>c</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>f</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>i</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{lll}{a} &amp; {b} &amp; {c} \\ {d} &amp; {e} &amp; {f} \\ {g} &amp; {h} &amp; {i}\end{array}\right]^{T}=\left[\begin{array}{lll}{a} &amp; {d} &amp; {g} \\ {b} &amp; {e} &amp; {h} \\ {c} &amp; {f} &amp; {i}\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.8313em;vertical-align:-1.55em;"></span><span class="minner"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">a</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">b</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">e</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">h</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">c</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">i</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:2.2812em;"><span style="top:-4.5029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.6em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">a</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">b</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">c</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">e</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">h</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">i</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mi>T</mi></msup><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mspace width="1em"/><msup><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow><mi>T</mi></msup><mo>=</mo><mrow><mo fence="true">[</mo><mtable rowspacing="0.16em" columnalign="left left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>x</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>y</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>z</mi></mstyle></mtd></mtr></mtable><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\left[\begin{array}{lll}{x} &amp; {y} &amp; {z}\end{array}\right]^{T}=\left[\begin{array}{l}{x} \\ {y} \\ {z}\end{array}\right] \quad\left[\begin{array}{l}{x} \\ {y} \\ {z}\end{array}\right]^{T}=\left[\begin{array}{lll}{x} &amp; {y} &amp; {z}\end{array}\right]</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.4312em;vertical-align:-0.35em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0812em;"><span style="top:-3.3029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:3.8313em;vertical-align:-1.55em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:1em;"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M403 1759 V84 H666 V0 H319 V1759 v0 v1759 h347 v-84H403z M403 1759 V0 H319 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span></span></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span><span style="top:-1.81em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.05em;"><span class="pstrut" style="height:5.6em;"></span><span style="width:0.667em;height:3.600em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.667em' height='3.600em' viewBox='0 0 667 3600'><path d='M347 1759 V0 H0 V84 H263 V1759 v0 v1759 H0 v84 H347zM347 1759 V0 H263 V1759 v0 v1759 h84z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.55em;"><span></span></span></span></span></span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:2.2812em;"><span style="top:-4.5029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.2em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">[</span></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">y</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">z</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">]</span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">(</mo><mrow><mi mathvariant="bold">A</mi><mi mathvariant="bold">B</mi></mrow><msup><mo stretchy="false">)</mo><mi>T</mi></msup><mo>=</mo><msup><mi mathvariant="bold">B</mi><mi mathvariant="normal">T</mi></msup><msup><mi mathvariant="bold">A</mi><mi mathvariant="normal">T</mi></msup></mrow><annotation encoding="application/x-tex">(\mathbf{A B})^{T}=\mathbf{B}^{\mathrm{T}} \mathbf{A}^{\mathrm{T}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1413em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathbf">AB</span></span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8913em;"></span><span class="mord"><span class="mord mathbf">B</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathrm mtight">T</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">A</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathrm mtight">T</span></span></span></span></span></span></span></span></span></span></span></span></span></p><h4 id="矩阵的逆">矩阵的逆</h4><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mrow><mo fence="true">(</mo><msup><mi mathvariant="bold">M</mi><mi mathvariant="normal">T</mi></msup><mo fence="true">)</mo></mrow><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo>=</mo><msup><mrow><mo fence="true">(</mo><msup><mi mathvariant="bold">M</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">)</mo></mrow><mi mathvariant="normal">T</mi></msup></mrow><annotation encoding="application/x-tex">\left(\mathbf{M}^{\mathrm{T}}\right)^{-1}=\left(\mathbf{M}^{-1}\right)^{\mathrm{T}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.4453em;vertical-align:-0.35em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8913em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathrm mtight">T</span></span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0953em;"><span style="top:-3.3442em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.4453em;vertical-align:-0.35em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:1.0953em;"><span style="top:-3.317em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathrm mtight">T</span></span></span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mrow><mo fence="true">(</mo><msub><mi mathvariant="bold">M</mi><mn>1</mn></msub><msub><mi mathvariant="bold">M</mi><mn>2</mn></msub><mo>⋯</mo><msub><mi mathvariant="bold">M</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msub><msub><mi mathvariant="bold">M</mi><mi>n</mi></msub><mo fence="true">)</mo></mrow><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo>=</mo><msubsup><mi mathvariant="bold">M</mi><mi>n</mi><mrow><mo>−</mo><mn>1</mn></mrow></msubsup><msubsup><mi mathvariant="bold">M</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow><mrow><mo>−</mo><mn>1</mn></mrow></msubsup><mo>⋯</mo><msubsup><mi mathvariant="bold">M</mi><mn>2</mn><mrow><mo>−</mo><mn>1</mn></mrow></msubsup><msubsup><mi mathvariant="bold">M</mi><mn>1</mn><mrow><mo>−</mo><mn>1</mn></mrow></msubsup></mrow><annotation encoding="application/x-tex">\left(\mathbf{M}_{1} \mathbf{M}_{2} \cdots \mathbf{M}_{n-1} \mathbf{M}_{n}\right)^{-1}=\mathbf{M}_{n}^{-1} \mathbf{M}_{n-1}^{-1} \cdots \mathbf{M}_{2}^{-1} \mathbf{M}_{1}^{-1}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.204em;vertical-align:-0.25em;"></span><span class="minner"><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">⋯</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2083em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.954em;"><span style="top:-3.2029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1789em;vertical-align:-0.3148em;"></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-2.453em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.247em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-2.4436em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.3148em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">⋯</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-2.4436em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2564em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-2.4436em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2564em;"><span></span></span></span></span></span></span></span></span></span></span></p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mo fence="true">∣</mo><msup><mi mathvariant="bold">M</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">∣</mo></mrow><mo>=</mo><mn>1</mn><mi mathvariant="normal">/</mi><mi mathvariant="normal">∣</mi><mi mathvariant="bold">M</mi><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">\left|\mathbf{M}^{-1}\right|=1 /|\mathbf{M}|</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2141em;vertical-align:-0.35em;"></span><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-2.85em;"><span class="pstrut" style="height:3.2em;"></span><span style="width:0.333em;height:1.200em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.333em' height='1.200em' viewBox='0 0 333 1200'><path d='M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mclose"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.85em;"><span style="top:-2.85em;"><span class="pstrut" style="height:3.2em;"></span><span style="width:0.333em;height:1.200em;"><svg xmlns="http://www.w3.org/2000/svg" width='0.333em' height='1.200em' viewBox='0 0 333 1200'><path d='M145 15 v585 v0 v585 c2.667,10,9.667,15,21,15c10,0,16.667,-5,20,-15 v-585 v0 v-585 c-2.667,-10,-9.667,-15,-21,-15c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v0 v585 h43z'/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35em;"><span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1/∣</span><span class="mord mathbf">M</span><span class="mord">∣</span></span></span></span></span></p><p>矩阵与矩阵的逆相乘得到单位矩阵：</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold">M</mi><mrow><mo fence="true">(</mo><msup><mi mathvariant="bold">M</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">)</mo></mrow><mo>=</mo><msup><mi mathvariant="bold">M</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mi mathvariant="bold">M</mi><mo>=</mo><mi mathvariant="bold">I</mi></mrow><annotation encoding="application/x-tex">\mathbf{M}\left(\mathbf{M}^{-1}\right)=\mathbf{M}^{-1} \mathbf{M}=\mathbf{I}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2141em;vertical-align:-0.35em;"></span><span class="mord mathbf">M</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;"><span class="delimsizing size1">(</span></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;"><span class="delimsizing size1">)</span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord"><span class="mord mathbf">M</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mord mathbf">M</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6861em;"></span><span class="mord mathbf">I</span></span></span></span></span></p><h2 id="矩阵变换">矩阵变换</h2><h3 id="缩放">缩放</h3><p>二维空间下和三维空间下的缩放很简单，直接乘上相应的缩放倍数即可。</p><p><img src="http://img.frankorz.com/59c453fdb5b93.png" alt=""><br><img src="http://img.frankorz.com/59c34830cf218.png" alt=""></p><h3 id="切变">切变</h3><p><img src="http://img.frankorz.com/59c45377c6df7.png" alt=""></p><h3 id="旋转">旋转</h3><h4 id="二维空间下的旋转">二维空间下的旋转</h4><p>二维空间下，物体的旋转可以靠矩阵在X方向的变换于矩阵在Y方向的变换叠加得到，但不适用于三维空间。</p><p>下面是推导过程：</p><p><img src="http://img.frankorz.com/59c3a241c6dd6.png" alt=""></p><p><img src="http://img.frankorz.com/59c35fd14cf71.png" alt=""></p><p>这三种矩阵变换<strong>不符合交换律</strong>，也就是说变换的顺序改变会得到不同的结果，要恢复变换的话也要从最后一次变换开始恢复。（Unity 的默认变换顺序是先缩放，再旋转，最后平移）</p><p><img src="http://img.frankorz.com/59c39af8088e2.png" alt=""></p><h4 id="三维空间下的旋转">三维空间下的旋转</h4><p>下图分别给出了绕 Z 轴旋转、X 轴旋转和 Y 轴旋转的变换矩阵：</p><p><img src="http://img.frankorz.com/59c3b45f62ef9.png" alt=""></p><p>二维旋转可以看作是绕着 Z 轴的特殊旋转，因为 Z 轴保持不变。因此 Rz（绕着 Z 轴的旋转）可以直接在二维空间旋转矩阵外的 Z 轴处填充 1 和 0 来使用，同理可得绕X轴的旋转矩阵和 Y 轴的旋转矩阵。</p><h5 id="绕任意轴旋转">绕任意轴旋转</h5><p><img src="http://img.frankorz.com/59c3b89114b72.png" alt=""></p><p>在这其中，三个正交向量（相互垂直）可以构成一个旋转矩阵，这样就可以将点映射到新的坐标系下。这个概念非常重要，因为在图形学中常常需要这样的变换，如把每个3D模型的顶点的法线从模型空间转到剪裁空间（不同的坐标系）再统一进行计算。</p><p>这个旋转矩阵的逆只需要将 XYZ 轴换成 u，v，w 即可。</p><p><img src="http://img.frankorz.com/59c4467c7cf99.png" alt=""></p><p>详见<a href="https://baike.baidu.com/item/%E7%BD%97%E5%BE%B7%E9%87%8C%E6%A0%BC%E6%97%8B%E8%BD%AC%E5%85%AC%E5%BC%8F">罗德里格旋转公式</a></p><hr><p>下一篇笔记会写更多关于三维空间下的矩阵变换。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://book.douban.com/subject/1400419/">3D数学基础：图形与游戏开发</a></li><li><a href="https://www.edx.org/course/computer-graphics-uc-san-diegox-cse167x-3">edx-Computer Graphics</a></li></ul>]]></content>
      
      
      <categories>
          
          <category> 图形学 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 游戏开发 </tag>
            
            <tag> 3D数学 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Windows 效率软件推荐</title>
      <link href="/2017/08/05/recommandation-of-some-efficient-software/"/>
      <url>/2017/08/05/recommandation-of-some-efficient-software/</url>
      
        <content type="html"><![CDATA[<p>用了几年苹果电脑之后，最近开始用回 Windows 系统，找到一些十分好用的效率向的工具，同时受 <a href="http://www.ituring.com.cn/article/469111">效率必备，软件推荐</a> 启发，写篇文章推荐一下。</p><span id="more"></span><h2 id="ShareX">ShareX</h2><p><a href="https://getsharex.com/">ShareX</a> 是一款主打捕获屏幕、文件分享、和生产力的开源工具。下面是我用 ShareX 来录制软件界面的 GIF 动图，可以看看其丰富的功能。</p><p><img src="http://img.frankorz.com/59855a1a27551.gif" alt=""></p><p>和 QQ 自带的截图功能和国人开发的 Snipaste 不同，ShareX 的截图不仅仅止于截图后进行批注。ShareX 除了支持和 Snipaste 相似的捕捉窗格（活动的窗口、窗口内的元素等）截图功能外，还支持各种后续的动作，形成一个动作链以节省时间。</p><h3 id="截图">截图</h3><p>例如截完图后可以打开编辑窗口编辑，同时保存图片到自定的目录和剪贴板等等，自己可以自定义其动作链。建议勾选复制到剪贴板，文后会完成一个利用快捷键完成截图上传的动作链（你也可以直接配置截图完进行上传，看个人习惯）。</p><p><img src="http://img.frankorz.com/59855c41b3c13.png" alt=""></p><h3 id="上传">上传</h3><p><img src="http://img.frankorz.com/59855f6348f83.png" alt=""></p><p>ShareX 的上传分为图片上传、文字上传和 URL 短链分享。图片可以上传到不同的图床：Imgur、Google Photos、TinyPic 等等，同样的，文件也可以上传到不同的文件共享网站：Dropbox、OneDrive 等，不过软件内置的多是国外的服务。开放性十足的 ShareX 还允许开发者共享自定义上传插件，这些上传插件可以在 <a href="https://github.com/ShareX/CustomUploaders">CustomUploaders</a> 里找到。</p><h4 id="图片上传">图片上传</h4><p>由于不同上传的设置类似，这里只写图片上传的一些个人配置。</p><p>我自己使用的图床是开放了 API 的 <a href="https://sm.ms/">SM.MS</a> 图床，这图床背后的大佬就是卖 <a href="http://t.tt">t.tt</a> 域名给罗永浩的那位，据他说自己的流量多的用不完就拿来做点公益项目…SM.MS 图床的上传插件可以在 <a href="https://github.com/ShareX/CustomUploaders/blob/master/sm.ms.sxcu">这里</a> 下载，下载后双击即可导入。导入后可以在软件界面的 [上传至…] -&gt; [上传至…设置] -&gt; 左列表最底端的自定义上传者，在目标类型选上 Image uploader 再点更新就能完成上传插件的配置。</p><p><img src="http://img.frankorz.com/598562961f2ff.png" alt=""></p><p>接着在 [上传至…] 选项里勾选好对应的上传插件就完成配置了。</p><p><img src="http://img.frankorz.com/598563005b31f.png" alt=""></p><h4 id="上传后的动作">上传后的动作</h4><p><img src="http://img.frankorz.com/598563e94e64e.png" alt=""></p><p>与截图后的动作类似，上传后的动作也十分实用，这里我只选了 URL 复制到剪贴板。</p><h3 id="动作链">动作链</h3><p>目前截图、上传这两动作都已经配置好，可以开始配置快捷键了。</p><h4 id="快捷键">快捷键</h4><p><img src="http://img.frankorz.com/5985658c61a01.png" alt=""></p><p>这里我增加了一个从剪贴板上传的快捷键，这样的话 [快捷键截图] -&gt; 截图保存到剪贴板 -&gt; [快捷键上传] -&gt; 剪贴板内的截图上传到相应图床 -&gt; 返回图片 URL 到剪贴板的动作链就完成了。这对于经常在电脑写东西的我来说很实用，在写这篇文章时也利用这软件节省了不少时间。</p><p>你可以在 <a href="https://getsharex.com/">ShareX 官网</a> 进行下载，其文件托管在 github。</p><h2 id="QuickLook">QuickLook</h2><p>macOS 系统内置了一个好用的功能，就是空格查看文件内容，现在 QuickLook 也为 Windows 提供类似的体验。</p><h3 id="空格查看-markdown-文件">空格查看 markdown 文件</h3><p><img src="http://img.frankorz.com/5985685e65c3e.png" alt=""></p><h3 id="空格查看代码文件">空格查看代码文件</h3><p><img src="http://img.frankorz.com/598568e01542e.png" alt=""></p><h3 id="空格查看压缩包">空格查看压缩包</h3><p><img src="http://img.frankorz.com/598569121426e.png" alt=""></p><p>你可以在 <a href="http://pooi.moe/QuickLook/">QuickLook 官网</a> 下载安装包，QuickLook 也能在 <a href="https://www.microsoft.com/store/apps/9nv4bs3l1h4s?ocid=badge">Windows 应用商店</a> 下载。</p><h2 id="Zeal">Zeal</h2><p>受 macOS 平台上 Dash 代码 API 文档查询软件的启发，Zeal 是一款免费开源的支持 Windows 和 Linux 系统的代码 API 文档查询软件。</p><h3 id="丰富的文档">丰富的文档</h3><p><img src="http://img.frankorz.com/59856b2f6673b.png" alt=""></p><h3 id="查询关键词">查询关键词</h3><p><img src="http://img.frankorz.com/59856a27b4431.png" alt=""></p><h3 id="与编辑器联动">与编辑器联动</h3><p>开发者为 Atom、VSCode、Jetbrain 全家桶 IDE 等提供了相关的插件，例如 Atom 的 <a href="https://github.com/blakeembrey/atom-dash">atom-dash</a>，VSCode 的 <a href="https://marketplace.visualstudio.com/items?itemName=deerawan.vscode-dash">Dash</a>，Jetbrain 全家桶的 <a href="https://plugins.jetbrains.com/plugin/7351-dash">Dash</a>。</p><p>有了这些插件，你可以做到在写 Python 代码的时候，调用快捷键查询 Zeal 中 Python 文档对应的 API。</p><p>你可以在 <a href="https://zealdocs.org/">Zeal 官网</a> 获得 Zeal。</p><h2 id="结语">结语</h2><p>上面提到的三款效率软件都是开源并能免费下载使用，现在越发钦佩开源者了~</p>]]></content>
      
      
      
        <tags>
            
            <tag> 效率 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>自己的阅读兴趣</title>
      <link href="/2017/08/04/my-reading-habit/"/>
      <url>/2017/08/04/my-reading-habit/</url>
      
        <content type="html"><![CDATA[<p>本文是为了响应图灵社区自己的兴趣爱好的撰文活动，搬来博客刷下活跃度。</p><p><img src="http://img.frankorz.com/5982fcf675064.png" alt="番茄土豆"></p><span id="more"></span><p>才发现自己的完成番茄数就在不久前破千了！趁着活动还在，来社区写一篇文章总结下。</p><p>翻了翻番茄的记录，最早的番茄是在今年的2月10日凌晨完成的，直至今天共过了175天，这么算来一天才完成5.73个番茄，这远远达不到自己当初设定每天八个番茄的目标，但是这习惯也算是坚持下来了。自己当初使用 <a href="https://pomotodo.com/">番茄土豆</a> 记录番茄的初衷，就是想从懒散的生活节奏中挤出一点时间，分给阅读、学习、写作和英语（除去上课的时间）。现在看统计图，阅读占了其中一大半的时间。</p><p><img src="http://img.frankorz.com/5982e59516955.png" alt="阅读时间"></p><p>自己阅读了将近248个小时，显然阅读已经成为了我的兴趣爱好。</p><p>自己看书看的题材很广泛、也很狭窄。广泛的是编程什么领域都会看一点，编程语言如 Swift、Golang、Python等，技术如算法、前端向的 Angular、后端向的 Rails on Ruby、Django、移动端的 iOS 等等，狭窄的就是很少去看计算机外的书。</p><p>现在看来，什么都想了解的兴趣能够让你翻开一本书，但是兴趣太广泛则不是什么好事情，自己现在还没有构建技术栈。好处也有，什么都略懂一点，这也是自己社区简介中 “全栈 Hello World 工程师” 的由来。如今这学期又要学网络和游戏开发（又要学习新领域…），苦也~</p><p>前段时间在简书读过一篇文章 <a href="http://www.jianshu.com/p/7525dabdd65a">为什么我们不再购买技术类书籍</a>，我赞同其中“除读书外还有很多学习技术的途径”，但不认同其中的“技术书籍无用论”。当然，这些学习途径本身就不能拿来比较，因为 <strong>有价值的是这些载体其中承载的知识</strong>，不同类型的知识有不同的作用。对于一些 bug 的报错、紧跟技术潮流的资讯、小知识点等，我一向喜欢从别人的博客或者问题的回答中寻找答案。而对于一连串成体系的知识点，我更青睐去阅读相关的评价较高的技术书籍。系统地阅读，能够使我理解和构建好这门技术的体系结构，从而打好自己的基础，而这往往是作者在写书前就需要花心思构建好的大纲所体现出来的。</p><p>但是阅读技术书籍也容易使我走进一些误区。读完一本技术书的充实感容易使我“飘飘然”，误认为自己已经搞定了一本书，不去下手写代码，从而变成一个“花瓶”程序员。再者，书上的代码和经验往往是正确的，自己跟着作者的思路写代码，能很快地了解一门技术背后的逻辑和设计，却让读者丧失了思考、推理逻辑的过程。因此，我更喜欢去阅读书中带习题、倾向于引导读者思考的书籍，这样能时刻警醒自己，不要沉溺于莫名的“技术满足感”之中。</p><p>当我习惯了阅读之后，无论是学校上课，还是深入一些领域，我都更倾向于参考相关的好书。一方面，对于一个知识点，我喜欢把老师的描述和其他书中作者的描述相对比，以作查漏补缺。另一方面，上课前看其他书提前预习将要教授的知识点，能够使我跟着老师的思路去思考，而不会被生疏的名词、知识点打断了自己上课思考的过程。自己前天的游戏开发课就是使用 Unity3D 来教授，自己提前看了眼大纲，找了本书预习了 C# 和 Unity3D 开发的一些简单的概念和过程。后来的上机课顺利地把一周的作业以满分的标准完成（老师有给出作业的完成标准，按照完成程度打分），还能抽出时间问读书过程中和写代码过程中不懂的问题，而不是浪费时间在提问基础知识上。</p><p>晒晒六月份放假的时候写的一些日记，那时候在刷图灵书。</p><p><img src="http://img.frankorz.com/5982f8ce4ee46.jpg" alt="日记"></p><p>以前图灵半价、京东亚马逊优惠买的书，我一定会回来翻阅你们的！</p><p>感谢图灵社区，提供这么多高质量的技术书籍翻译，一些书还拿到了电子版权出了电子版，真是海外党福音~最后希望大家能开卷有益！</p>]]></content>
      
      
      
        <tags>
            
            <tag> 学习 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>《流畅的 Python》读书笔记</title>
      <link href="/2017/07/01/fluent-python-note/"/>
      <url>/2017/07/01/fluent-python-note/</url>
      
        <content type="html"><![CDATA[<h2 id="两个问题">两个问题</h2><h3 id="学了-Python-基础就够了吗？">学了 Python 基础就够了吗？</h3><p>前言的引言给出了答案：</p><blockquote><p>要不这样吧，如果编程语言里有个地方你弄不明白，而正好又有个人用了这个功能，那就开枪把他打死。这比学习新特性要容易些，然后过不了多久，那些活下来的程序员就会开始用 0.9.6 版的 Python，而且他们只需要使用这个版本中易于理解的那一小部分就好了（眨眼） 。<br>—— Tim Peters<br>传奇的核心开发者， “Python 之禅”作者</p></blockquote><span id="more"></span><h3 id="这本书的目的是什么？">这本书的目的是什么？</h3><p>第十一章的杂谈里给出了答案：</p><blockquote><p>这正是本书的主要目的：着重讲解这门语言的基本惯用法，让你的代码简洁、高效且可读，把你打造成熟练的 Python 程序员。</p></blockquote><p>另外本书的<a href="http://www.ituring.com.cn/book/tupubarticle/13727">前言</a>里也有提及书本的目标读者和非目标读者。</p><blockquote><p>如果你才刚刚开始学 Python，本书的内容可能会显得有些“超纲”。比难懂更糟的是，如果在学习 Python 的过程中过早接触本书的内容，你可能会误以为所有的 Python 代码都应该利用特殊方法和元编程（metaprogramming）技巧。我们知道，不成熟的抽象和过早的优化一样，都会坏事。</p></blockquote><h2 id="对内容的一些评价">对内容的一些评价</h2><p>从书目录结构来看，作者的眼界十分开阔，每章最后有小结、延伸阅读、和相关的一些杂谈。书的前一部分从 Python 特性出发，参考了很多语言的相关做法和实现，来解释 Python 的设计。</p><p>书中时常引用一些参考资料，有些是邮件列表里的讨论、维基百科、一些十分优秀的程序员的撰写的文章和演讲视频。这意味着你可以在某一个概念看到不同的观点，看到优秀的程序员是怎么思考一个问题的。</p><p>作者从1998年成为了 Python 程序员，是巴西一家培训机构的共同所有者，也为巴西的媒体、银行和政府部门教授 Python 课程，由此可见本书的代码会是十分透彻和浅显易懂的，事实也的确如此。从代码示例来看，作者为大部分代码提供了 doctest 测试，并且在为某一个知识点提供代码示例时，追求的是简单、直接，同时示例的难度是循序渐进的。加上作者在大部分代码行提供了说明，让读者能十分流畅地理解概念。（对比：《Go 程序设计语言》讲复数语法时用 Mandelbrot 图像作为示例，苦笑）</p><h2 id="对翻译的一些评价">对翻译的一些评价</h2><p>整体翻译还是不错的，几百页的书的勘误也才十多个，部分术语可能还要参考书里的术语翻译表，个人认为容易弄混的有特性（properties）和属性（attributes），还有函数（function）和方法（method）。后者的区别可以参考<a href="https://stackoverflow.com/questions/155609/difference-between-a-method-and-a-function">Difference between a method and a function</a>，简单的说法就是函数（function）定义在类外面，而方法（method）定义在类里面，是类的一部分。两者也可以根据是否独立于对象来判断。</p><blockquote><p>黄志斌：这本书第2次印刷时已经把“期物”改为“future”了。</p></blockquote><p>P21 前面那种方式显然能够节省内存。 前者指的是 genexp，即生成器表达式。</p><h2 id="章节简介">章节简介</h2><p>这本书的结构十分优秀，每一章节都有前言和小结，因此章节简介我偏向于写些零散的知识点和个人感受，会比较乱。大部分章节的章节简介最后会有个人阅读时做的笔记，章节简介没提及的内容可以看看我的笔记。</p><p>全部的笔记还可以在这里找到：<a href="https://github.com/Latias94/fluent-python-notes">Latias94/fluent-python-notes</a></p><h3 id="第一章">第一章</h3><p>第一章作者就介绍了 Python 中的特殊方法，特殊方法也是贯穿这本书的基础。</p><p>读这本书之前，自己通常会遇到<code>__init__</code>、<code>__new__</code>、<code>__name__ == '__main__'</code>等等的带双下划线的特殊方法，但是通过零散的知识点很难形成体系，而这本书涵盖了绝大部分的特殊方法，并且分章节详细地讨论其背后特殊方法的作用，而这一章就是了解特殊方法的第一步。</p><p>作者提到了合理的字符串表示形式：<code>__repr__</code> 和 <code>__str__</code>。前者方便我们调试和记录日志，后者则是给终端用户看的。</p><p>作者开篇就提出了两个问题，第一个问题是：</p><blockquote><p>为什么说 Python 最好的品质之一是一致性？</p></blockquote><p>并且在第十二页给出了答案：</p><blockquote><p>不能让特例特殊到开始破坏既定规则。</p></blockquote><p>第二个问题是：</p><blockquote><p><code>len(collection)</code> 和 <code>collection.len()</code> 有什么不同？和“Python 风格” （Pythonic）有什么关系？</p></blockquote><p>核心开发者 Raymond Hettinger 的答案是：</p><blockquote><p>实用胜于纯粹<br>practicality beats purity<br>——《<a href="https://www.python.org/doc/humor/#the-zen-of-python">The Zen of Python</a>》</p></blockquote><p>作者给出解释：</p><blockquote><p>len 是特殊方法，是为了让 Python 自带的数据结构可以走后门，abs 也是同理。（解释：因为如果 x 是一个内置类型的实例，<code>len(x)</code>的背后会用 CPython 直接从 C 结构体中读取对象的长度，不调用任何方法，以至于 <code>len(x)</code> 会非常快。）<br>…<br>这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点，也印证了“Python 之禅”中的另外一句话：“不能让特例特殊到开始破坏既定规则。”</p></blockquote><p>从这两个问题就能看出作者想要强调的是：「<strong>Python 风格</strong> 无处不在」。为了更好地理解 Python 实现，最好了解 Python 的设计风格。</p><p>笔记传送门：<a href="https://my.mindnode.com/z2mHiiCMKpYW5CpEC1voainyDyh7zqaA2jqnK6AU">特殊方法</a></p><h3 id="第二章">第二章</h3><p>第二三四章主讲 Python 数据结构及其背后的实现，而第二章主要讲了可变类型与不可变类型的区别。</p><p>要想写出准确、高效和地道的 Python 代码，对标准库里的序列类型的掌握是不可或缺的。数据结构的产生就是为了满足各种不同的需求，例如能让用户在不复制内容的情况下操作同一个数组的不同切片的 memoryview，能高效处理矩阵、矢量等高级数值运算的 NumPy 和专为线性代数、数值积分和统计学而设计并基于 NumPy 的 SciPy。计算机科学家主要抽象了几大数据类型：字典、数组、列表等，这些数据类型都有不同的使用环境，使用好这些工具能让你事半功倍、节省不必要的消耗。</p><p>另外，在读这本书的前几天，我刚好在 Segmentfault 里面看到一个问题 <a href="https://segmentfault.com/q/1010000009754631">python小白 问关于a+=a 和a=a+a的区别</a>，当时看完答案还有点似懂非懂的感觉，而读完这章我能完全理解其区别所在了。</p><p>扩展阅读：<a href="https://facert.gitbooks.io/python-data-structure-cn">problem-solving-with-algorithms-and-data-structure-using-python 中文版</a></p><p>笔记传送门：<a href="https://my.mindnode.com/rabZb1p3GadkxZD7sWms2Wyzaz6mELZAKaGX9De2">序列构成的数组</a></p><h3 id="第三章">第三章</h3><p>前一章提到了列表、元组这两种序列，以及它们的生成器表达式。这一章则介绍了散列表的基本概念、其背后的算法和由散列表实现的数据类型：字典和集合。</p><ul><li>由于字典是由散列表实现的，因此字典的键必须是可散列的。</li><li>set 类型本身不是可散列的（因为 set 是可变的），但其元素必须可散列。（这也是为什么 list 不能作为字典键的原因）</li><li>frozenset 是可散列的。</li><li>散列表的实现导致它实现的数据类型效率很高，但这是以牺牲空间的代价所带来的。</li></ul><blockquote><p>“优化往往是可维护性的对立面”<br>由于字典使用了散列表，而散列表又必须是稀疏的，这导致它在空间上的效率低下。举例而言，如果你需要存放数量巨大的记录，那么放在由元组或是具名元组构成的列表中会是比较好的选择；最好不要根据 JSON 的风格，用由字典组成的列表来存放这些记录。用元组取代字典就能节省空间的原因有两个：其一是避免了散列表所耗费的空间，其二是无需把记录中字段的名字在每个元素里都存一遍。</p></blockquote><p>笔记传送门：<a href="https://my.mindnode.com/sy6m2gQJ5CdFBQz7cyCRq6YjKLzzkqQXofC8w26V">字典和集合</a></p><h3 id="第四章">第四章</h3><p>目前没遇到过编码问题，不看。</p><h3 id="第五章">第五章</h3><p>第五章的主题是：高阶函数没这么重要了。</p><p>先来一段吐槽：</p><blockquote><p>Lundh 提出的 lambda 表达式重构秘笈如果使用 lambda 表达式导致一段代码难以理解，Fredrik Lundh 建议像下面这样重构。<br>(1) 编写注释，说明 lambda 表达式的作用。<br>(2) 研究一会儿注释，并找出一个名称来概括注释。<br>(3) 把 lambda 表达式转换成 def 语句，使用那个名称来定义函数。<br>(4) 删除注释。<br>摘自“Functional Programming HOWTO”</p></blockquote><p>现在函数式编程十分流行，但 Python 独特的语法使得 lambda、map、filter 和 reduce 这些函数没这么重要了，因为我们有 sum、all 等归约函数，还有 sorted、min、max 和 functools 这样的内置的高阶函数。</p><p>最后（5.10.2小节）讲了一个和函数柯里化（Currying）十分相像的概念——偏函数（Partial Application），这两者概念其实不一样。</p><p>笔记传送门：<a href="https://my.mindnode.com/EpbDRnypkaZgmDb6cLwgPcenxExjWA75689ah91p">一等函数</a></p><h3 id="第六章">第六章</h3><p>作者从策略模式开始，讨论了一等函数在设计模式中的角色，并用一等函数简化了设计模式的实现方式，以此来展示 Pythonic 的设计模式应该是什么样子的。</p><p>扩展阅读：<a href="https://github.com/wklken/py-patterns">设计模式的python实现</a></p><p>笔记传送门：<a href="https://my.mindnode.com/rwYjfffMs8Zi8cKSWyU1ybshb6mgXiiFp4MbDrj6">使用一等函数实现设计模式</a></p><h3 id="第七章">第七章</h3><p>第七章介绍了装饰器和闭包，作者给闭包下了一个清晰的定义：</p><blockquote><p>闭包指延伸了作用域的函数，其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系，关键是它能访问定义体之外定义的非全局变量。</p></blockquote><p>作者用一个闭包实例和作用相同的类来比较，引出了自由变量（free variable）的概念，以此指出闭包与普通函数不一样的地方——闭包会保留定义函数时存在的自由变量的绑定。在此之后，再引出可变类型与不可变类型对自由变量的影响，从而引出可能导致闭包失效的原因（第二章的主题：可变类型与不可变类型的区别），同时给出了解决办法：nonlocal 声明。</p><p>本章结尾的杂谈提到了「一般来说，实现“装饰器”模式时最好使用类表示装饰器和要包装的组件。」，也就是通过实现 <code>__call__</code> 方法的类来实现装饰器。遗憾的是本书只通过函数来解说装饰器以助于理解，类装饰器没有提及多少。</p><p>笔记传送门：<a href="https://my.mindnode.com/bZa9sX9dA7qcbsGbVqnU6c3ryky8g2QDWfiHqqhm">函数装饰器和闭包</a></p><h3 id="第八章">第八章</h3><blockquote><p>不可变集合不变的是所含对象的标识。</p></blockquote><p>第八章中，作者从「元组是不可变的，但是其中的值可以改变」引申到浅复制和深复制的区别。</p><p><img src="http://img.frankorz.com/5956006435780.jpg" alt=""></p><p>浅复制带来的影响可以参考 <a href="http://www.pythontutor.com/visualize.html#code=l1%20%3D%20%5B3,%20%5B66,%2055,%2044%5D,%20%287,%208,%209%29%5D%0Al2%20%3D%20list%28l1%29%20%0Al1.append%28100%29%0Al1%5B1%5D.remove%2855%29%0Aprint%28'l1%3A',%20l1%29%0Aprint%28'l2%3A',%20l2%29%0Al2%5B1%5D%20%2B%3D%20%5B33,%2022%5D%0Al2%5B2%5D%20%2B%3D%20%2810,%2011%29%20%0Aprint%28'l1%3A',%20l1%29%0Aprint%28'l2%3A',%20l2%29&amp;cumulative=false&amp;curInstr=0&amp;heapPrimitives=false&amp;mode=display&amp;origin=opt-frontend.js&amp;py=3&amp;rawInputLstJSON=%5B%5D&amp;textReferences=false">example</a>（点 foward 显示下一步）</p><p>作者还提到了两个容易忽略的函数参数引用问题：</p><ol><li>不要使用可变类型作为参数的默认值</li><li>防御可变参数</li></ol><p>最后一节讨论垃圾回收、del 命令，以及如何使用弱引用“记住”对象，而无需对象本身存在。</p><p>另外这章有意思的地方在于作者提到了一个常见的说法错误：「对引用式变量来说，说把变量分配给对象更合理，反过来说就有问题。毕竟对象在赋值之前就创建了。」</p><p>笔记传送门：<a href="https://my.mindnode.com/6ZayEUVKgEVAApAPc6aDmjByaB3Qq4hUPQSWBdK2">对象引用、可变性和垃圾回收</a></p><h3 id="第九章">第九章</h3><blockquote><p>要构建符合 Python 风格的对象，就要观察真正的 Python 对象的行为。<br>——古老的中国谚语</p></blockquote><p>第九章主要讲如何编写 Pythonic 的对象。作者从构建一个 Vector 类型来介绍符合 Python 风格的类需要注意的地方，例如<code>__repr__</code>不要硬编码类名、类属性的私有化、格式规范微语言、散列化要注意的条件等。</p><p>作者还讲了构建一个可散列的类型所需要实现的条件：</p><ol><li>正确的实现<code>__hash__</code>和<code>__eq__</code>方法</li><li>不一定要实现只读属性，但是要保证实例的散列值绝不能变化。</li></ol><p>类属性用于为实例属性提供默认值。Django 的类视图也大量用到了这个特性。</p><p>个人十分喜欢名称改写（属性的私有化）中的一张示意图：<br>「避免意外访问，但不能防止故意做错事。」，以此来提醒名称改写所实现的私有化自身的缺陷。</p><p><img src="http://img.frankorz.com/5956006706191.jpg" alt=""></p><p>笔记传送门：<a href="https://my.mindnode.com/sJg5G9wfSRP4Le67FDqErjYGGqxhEPNYpMCnpj1Q">符合 Python 风格的对象</a></p><h3 id="第十章">第十章</h3><blockquote><p>不要检查它是不是鸭子、它的叫声像不像鸭子、它的走路姿势像不像鸭子，等等。具体检查什么取决于你想使用语言的哪些行为。 （comp.lang.python，2000 年 7 月 26 日）<br>——Alex Martelli</p></blockquote><p>其中介绍了鸭子类型，指忽略对象的真正类型，转而关注对象有没有实现所需的方法、签名和语义。在 Python 中指免使用 isinstance 检查对象的类型。</p><p>如果我想实现一个序列，可以实现<code>__init__</code>、<code>__len__</code>、<code>__getitem__</code>等一序列的方法，使其行为像序列，那么这就是一个序列，这也就是人们所称的鸭子类型（duck typing）。</p><p>自己的理解：要明白自己希望的鸭子有哪些特性，只要我实现出来了，那么这就是鸭子。</p><p>Tips:</p><ul><li>可以用 <code>dir(slice)</code> 来查看序列的属性</li><li>当 Python 库文档查询不到方法的文档的时候，可以尝试用 <code>help(slice.indices)</code> 来查询。（ 直接查询<code>__doc__</code>属性的信息 ）</li></ul><p>第四小节讲可切片的序列需要关注的两个问题：</p><ol><li><p>如果创建的序列内部由数组（或其他序列）实现，那么就要考虑切片对象的实现：切片返回的是自创建的序列对象 还是数组（或其他序列）？如果需要考虑，就是在 <code>__getitem__</code> 方法里修改其实现方式。</p></li><li><p>动态存取属性，使序列能通过名称访问序列的属性（v.x,v.y代替v[0],v[1]）。也提到了实现 <code>__getitem__</code> 时可能会产生的问题，和解决方法。</p></li></ol><p>章节末尾的杂谈提到了要遵循 KISS 原则（Keep it simple, stupid），不要过度设计协议。</p><p>笔记传送门：<a href="https://my.mindnode.com/EMWUZqqSPmCA2JtZZhgWaCi7ugQfiLyiLrsy1Tqb">序列的修改、散列和切片</a></p><h3 id="第十一章">第十一章</h3><blockquote><p>本章讨论的话题是接口：从鸭子类型的代表特征动态协议，到使接口更明确、能验证实现是否符合规定的抽象基类（Abstract Base Class，ABC）</p></blockquote><p>我们可能不需要写抽象基类，但是阅读本章能够教我们怎么阅读标准库和其他包中的抽象基类源码。</p><p>其中，作者引用了 Alex Martelli 的一篇文章，用表型系统学(phenetics)和支序系统学（cladistics）用水禽来类比抽象基类。(⊙﹏⊙)b</p><p>其中有第十章提到的「鸭子类型」，还有以前没提过的、描述一种新的 Python 编程风格的「白鹅类型」（goose typing）。</p><blockquote><p>白鹅类型指，只要 cls 是抽象基类，即 cls 的元类是 <code>abc.ABCMeta</code>，就可以使用 <code>isinstance(obj, cls)</code>。</p></blockquote><p>对此，作者在章节小结里面提到：</p><blockquote><p>借助「白鹅类型」，可以使用抽象基类明确声明接口，而且类可以子类化抽象基类或使用抽象基类注册（无需在继承关系中确立静态的强链接），宣称它实现了某个接口。</p></blockquote><p>本章最后还介绍了和 Go 语言协议的功能十分类似的 <code>__subclasshook__</code> 方法。</p><p>笔记传送门：<a href="https://my.mindnode.com/SiejkpsT7xLeNL7wCH2Mrp5SaMN7Ex2EyJ5VivGL">接口：从协议到抽象基类</a></p><h3 id="第十二章">第十二章</h3><p>分析 GUI 工具包 Tkhinter 的多重继承，并且展开分析了多次继承所带来的「菱形问题」，以及 Python 对应的解决方案——方法解析顺序（Method Resolution Order，MRO)，最后作者给了八条关于处理多重继承的建议。</p><p><a href="https://docs.python.org/3/tutorial/classes.html#multiple-inheritance">9.5.1. Multiple Inheritance</a> 里面提到了继承顺序是深度优先从左至右不重复。</p><blockquote><p>For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as <strong>depth-first, left-to-right, not searching twice in the same class</strong> where there is an overlap in the hierarchy.</p></blockquote><p><img src="http://img.frankorz.com/595600652d904.jpg" alt=""></p><p>笔记传送门：<a href="https://my.mindnode.com/qn4fVVx826kDNss71p4f3d6P2rxHsFwo5h7pQb1q">继承的优缺点</a></p><h3 id="第十三章">第十三章</h3><p>第十三章介绍了重载运算符的时候要考虑多重情况。</p><blockquote><p>In the face of ambiguity, refuse the temptation to guess.<br>面对太多的可能，不要尝试猜测。（ZoomQuiet禅译）<br>——《<a href="https://www.python.org/doc/humor/#the-zen-of-python">The Zen of Python</a>》</p></blockquote><p>我们要严谨地对待可能会出现的操作数。</p><p>对于为什么需要重载运算符，在杂谈中作者提到了对于一部分人来说，重载运算符是十分重要的，<strong>符合人类直觉的表示法</strong>十分重要，例如金融工作会接触到一些由不同类型的参数（整数、或其他精度的数字）组成的公式。相比于不支持运算符重载的 Go 与 Java 语言，Python 采取了折中的方式，允许重载运算符，也有一些限制，如：不能重载内置类型的运算符、不能新建运算符、一些运算符也不能重载（is、and、or、not）。</p><p>笔记传送门：<a href="https://my.mindnode.com/keGv1PsLvmtApbmt6bzFjhjtwwgziGGUqyx9vN8y">正确重载运算符</a></p><h3 id="第十四章">第十四章</h3><p>作者分别介绍了迭代器、生成器表达式和生成器函数，并详细地列举了每个标准库生成器函数的用法。</p><p>前面介绍过 Python 内置的数据类型，如列表和元组，能让我们高效地访问数据集，但这些序列只能表示已知且<strong>长度有限</strong>的数据集。要表示无限长度的数据集，例如斐波拉契数列，就需要用到新的构造方式，这也是本章的话题的由来。</p><blockquote><p>扫描内存中放不下的数据集时，我们要找到一种惰性获取数据项的方式，即按需一次获取一个数据项。这就是迭代器模式（Iterator pattern） 。</p></blockquote><p>其中作者依然很注意用词，生成器是 “yields or produces” 生成值，而不是 “returns” 返回值，这样有助于理解生成器获取结果的过程，因为生成器不是以「常规」方式返回值的。</p><p>笔记传送门：<a href="https://my.mindnode.com/rbWsvgTKPcs7v9TmzxrzryuYPs631RCrXW4USZqR">可迭代的对象、迭代器和生成器</a></p><h3 id="第十五章">第十五章</h3><p>介绍了 else 的三种用法与上下文管理器和 with 的作用，作者用<code>__enter__</code>、<code>__exit__</code>等方法手动地实现了一个上下文管理器，还介绍了 <code>@contextmanager</code> 作为另外一种更优雅的实现上下文管理器的方法。其中 <code>@contextmanager</code> 的 <code>yield</code> 语句也引出了第十六章中协程的概念。</p><p>笔记传送门：<a href="https://my.mindnode.com/eTfDEjFxWMpScyZYGfzZYGaGpooKF2LJGFvbdbRF">上下文管理器和else块</a></p><h3 id="第十六章">第十六章</h3><p>建议看第十六、十七、十八章之前先理解五个概念：线程、进程、协程、并发和并行。</p><p>自己参考了：</p><ul><li><p><a href="http://www.jianshu.com/p/f11724034d50">进程，线程，协程与并行，并发- 简书</a></p></li><li><p><a href="https://www.zhihu.com/question/20511233">协程的好处是什么？- 知乎</a></p></li><li><p><a href="http://www.jianshu.com/p/02b7a279c588">Python进程、线程、回调与协程 总结笔记</a></p></li></ul><p>在本章中，作者介绍了如何构建协程，和协程的一些使用场景，章节末尾，作者举了一个离散事件仿真示例，说明如何使用生成器代替线程和回调，实现并发。</p><p>在前些章节的基础上，作者在这章提到 yield 可以看做是控制流程的方式，即 yield 能获取值（<code>.send(foo)</code>），也能产出值（<code>foo = yield</code>），还能不获取和产出值（yield 后没有表达式）。因此，我们能用它来构建协程。</p><blockquote><p>不管数据如何流动，yield 都是一种流程控制工具，使用它可以实现协作式多任务：协程可以把控制器让步给中心调度程序，从而激活其他的协程。</p></blockquote><p>除了调用 <code>.send(...)</code> 方法发送数据，本章还介绍使用 <code>yield from</code> 结构驱动的生成器函数。</p><p>扩展阅读：</p><ul><li><p><a href="http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/">SICP in Python</a> 有一段关于并行计算的非常精彩的解释：<a href="https://wizardforcel.gitbooks.io/sicp-py/content/ch4.html#43-%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97">4.3 并行计算</a></p></li><li><p><a href="http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/">SICP in Python</a> 中协程的一节里着重讲了将复杂程序解构为小型、模块化组件的技巧：<a href="https://wizardforcel.gitbooks.io/sicp-py/content/ch5.html#53-%E5%8D%8F%E7%A8%8B">5.3 协程</a></p></li></ul><p>笔记传送门：<a href="https://my.mindnode.com/4qpfqK3UY5ZdNVyEje4sXnFJ9PoXTFpNJVsn2pZx">协程</a></p><h3 id="第十七章">第十七章</h3><blockquote><p>并发是计算机科学中最难的概念之一（通常最好别去招惹它） 。<br>——David Beazley Python 教练和科学狂人</p></blockquote><p>在第十七章，作者用一个下载国旗图片的例子来介绍网络下载的三种风格：依序下载、<code>concurrent.futures</code> 模块（<code>ThreadPoolExecutor</code> 和 <code>ProcessPoolExecutor</code> 类）实现的并发下载和 asyncio 包实现的并发下载。作者还介绍了阻塞性 I/O 和 GIL，最后介绍了如何借助 <code>concurrent.futures.ProcessPoolExecutor</code> 类使用多进程。</p><p>future 指一种对象，表示异步执行的操作。</p><p>早期的计算机从单用户操作系统（同一时间只能运行一个任务）转变成多任务操作系统（同一时间可以运行多个任务），又由于多任务操作系统中程序经常抢夺系统资源而引发死锁这种缺陷，在 20 世纪 60 年代，计算机科学家就开始探索并发编程的道路，并发指交替执行多个任务，解决的就是前面提到的多任务操作系统的缺陷。直到现在，很多编程语言都为并发提供了支持，其中包括原生支持并发的 Go 语言，和有相关模块支持的 Python。</p><blockquote><p>并发（concurrency）不是并行（parallelism）。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情，而并发是指同时管理很多事情，这些事情可能只做了一半就被暂停去做别的事情了。在很多情况下，并发的效果比并行好，因为操作系统和硬件的总资源一般很少，但能支持系统同时做很多事情。<br>——《Go 语言实战》</p></blockquote><p>笔记传送门：<a href="https://my.mindnode.com/A6WWkGQgdYx9LtzvnziszgHXwKcxidy2cKKbww8r">使用future处理并发</a></p><h3 id="第十八章">第十八章</h3><blockquote><p>并发是指一次处理多件事。<br>并行是指一次做多件事。<br>二者不同，但是有联系。<br>一个关于结构，一个关于执行。<br>并发用于制定方案，用来解决可能（但未必）并行的问题。<br>——Rob Pike Go 语言的创造者之一</p></blockquote><p>第十八章中，作者主要介绍了新的并发编程方式，对比了 <code>asyncio.Task</code> （协程）对象与 <code>threading.Thread</code> （线程）对象的区别，包括 Python 包使用方式的区别和中断时协程与线程的区别：锁的保留。章节尾，作者介绍了 asyncio 包的使用和并发编程需要注意的地方。</p><p>笔记待补</p><h3 id="第十九章">第十九章</h3><p>第十九章主要介绍了动态属性编程。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">Foo</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">pass</span></span><br><span class="line"><span class="meta">... </span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo = Foo()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo.a = <span class="number">3</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Foo.b = <span class="built_in">property</span>(<span class="keyword">lambda</span> self: self.a + <span class="number">1</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo.b</span><br><span class="line"><span class="number">4</span></span><br></pre></td></tr></table></figure><p>这就叫做动态属性（dynamic attribute），不同于属于静态语言的 Java 需要依靠 setter 和 getter 方法，Python 能十分方便地设置属性和读取属性。</p><p>作者拿 FrozenJSON 类做例子：把嵌套的字典和列表转换成嵌套的 FrozenJSON 实例和实例列表。FrozenJSON 类的代码展示了如何使用特殊的 <code>__getattr__</code> 方法（处理属性的函数）在读取属性时即时转换数据结构。</p><p>作者还介绍了很多处理属性的属性和函数以及利用特性(@properties)来修改设置属性和读取属性的方式。</p><p>笔记传送门：<a href="https://my.mindnode.com/ByXkheqCf71cuELJZyYHHwt47HiaeoVqrSY3nxZB">动态属性和特性</a></p><h3 id="第二十章">第二十章</h3><p>有时候看书看着就忘了一些名词是什么了，因此参考了下<a href="https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_descriptor.html">【译】Python描述符指南</a>，描述符类就是实现描述符协议的类。</p><p>相比于第十九章中利用特性（@properties）来修改属性的存取逻辑，第二十章主要介绍了描述符——对多个属性运用相同存取逻辑的一种方式。两者的区别是特性有时更合适和简单，而描述符更灵活。这章还介绍了覆盖型与非覆盖型描述符的对比，最后也给出了使用描述符的建议和优缺点。</p><p>笔记传送门：<a href="https://my.mindnode.com/GVWcPSN148gphwzVNEok3gGhFNCVWyPkVjmFe2yY">属性描述符</a></p><h3 id="第二十一章">第二十一章</h3><blockquote><p>（元类）是深奥的知识，99% 的用户都无需关注。如果你想知道是否需要使用元类，我告诉你，不需要（真正需要使用元类的人确信他们需要，无需解释原因） 。<br>——Tim Peters<br>Timsort 算法的发明者，活跃的 Python 贡献者</p></blockquote><p>上面是第二十一章的引言，我听从这位传奇开发者的建议，没有看。</p><h2 id="总结">总结</h2><p>整本书都在强调如何最大限度地利用 Python 标准库以及讲述 Python 背后的设计思想。身处众多动态编程语言中间，Python 无疑是独行独立的，这也是为什么很多 Python 开发者骄傲地宣称自己是一名 Pythonista。</p><p>自己只是不求甚解地通读了一遍书，学到了很多，但书中仍有太多不熟悉的知识点。因为假期不多了，只能等日后二刷这本书。过一遍这本书最大的收获莫过于在面对问题的时候，自己的工具箱又多了不少工具，即使这工具还不太「趁手」。其中感受最深的就是现在看一些 Segmentfault 或 StackOverflow 问题的答案的时候不再那么毫无头绪，并开始试着从前辈们的角度思考问题。另外书中多次提到 Django 的一些实现方式，对自己日后读源码的时候有帮助。</p><blockquote><p>仓促本身就是最要不得的态度。当你做某件事的时候，一旦想要求快，就表示你再也不关心它，而想去做别的事。<br>——罗伯特 · M · 波西格 《禅与摩托车维修艺术》</p></blockquote><p>自己的确因为阅读计划的期限而读的仓促了一些，这句话放到文尾，提醒自己在读下一本书的时候，尽量做到静下心来。</p>]]></content>
      
      
      
        <tags>
            
            <tag> 读后感 </tag>
            
            <tag> Python </tag>
            
            <tag> 笔记 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>《流畅的 Python》读书笔记</title>
      <link href="/2017/07/01/go-learning-source/"/>
      <url>/2017/07/01/go-learning-source/</url>
      
        <content type="html"><![CDATA[<h2 id="两个问题">两个问题</h2><h3 id="学了-Python-基础就够了吗？">学了 Python 基础就够了吗？</h3><p>前言的引言给出了答案：</p><blockquote><p>要不这样吧，如果编程语言里有个地方你弄不明白，而正好又有个人用了这个功能，那就开枪把他打死。这比学习新特性要容易些，然后过不了多久，那些活下来的程序员就会开始用 0.9.6 版的 Python，而且他们只需要使用这个版本中易于理解的那一小部分就好了（眨眼） 。<br>—— Tim Peters<br>传奇的核心开发者， “Python 之禅”作者</p></blockquote><span id="more"></span><h3 id="这本书的目的是什么？">这本书的目的是什么？</h3><p>第十一章的杂谈里给出了答案：</p><blockquote><p>这正是本书的主要目的：着重讲解这门语言的基本惯用法，让你的代码简洁、高效且可读，把你打造成熟练的 Python 程序员。</p></blockquote><p>另外本书的<a href="http://www.ituring.com.cn/book/tupubarticle/13727">前言</a>里也有提及书本的目标读者和非目标读者。</p><blockquote><p>如果你才刚刚开始学 Python，本书的内容可能会显得有些“超纲”。比难懂更糟的是，如果在学习 Python 的过程中过早接触本书的内容，你可能会误以为所有的 Python 代码都应该利用特殊方法和元编程（metaprogramming）技巧。我们知道，不成熟的抽象和过早的优化一样，都会坏事。</p></blockquote><h2 id="对内容的一些评价">对内容的一些评价</h2><p>从书目录结构来看，作者的眼界十分开阔，每章最后有小结、延伸阅读、和相关的一些杂谈。书的前一部分从 Python 特性出发，参考了很多语言的相关做法和实现，来解释 Python 的设计。</p><p>书中时常引用一些参考资料，有些是邮件列表里的讨论、维基百科、一些十分优秀的程序员的撰写的文章和演讲视频。这意味着你可以在某一个概念看到不同的观点，看到优秀的程序员是怎么思考一个问题的。</p><p>作者从1998年成为了 Python 程序员，是巴西一家培训机构的共同所有者，也为巴西的媒体、银行和政府部门教授 Python 课程，由此可见本书的代码会是十分透彻和浅显易懂的，事实也的确如此。从代码示例来看，作者为大部分代码提供了 doctest 测试，并且在为某一个知识点提供代码示例时，追求的是简单、直接，同时示例的难度是循序渐进的。加上作者在大部分代码行提供了说明，让读者能十分流畅地理解概念。（对比：《Go 程序设计语言》讲复数语法时用 Mandelbrot 图像作为示例，苦笑）</p><h2 id="对翻译的一些评价">对翻译的一些评价</h2><p>整体翻译还是不错的，几百页的书的勘误也才十多个，部分术语可能还要参考书里的术语翻译表，个人认为容易弄混的有特性（properties）和属性（attributes），还有函数（function）和方法（method）。后者的区别可以参考<a href="https://stackoverflow.com/questions/155609/difference-between-a-method-and-a-function">Difference between a method and a function</a>，简单的说法就是函数（function）定义在类外面，而方法（method）定义在类里面，是类的一部分。两者也可以根据是否独立于对象来判断。</p><blockquote><p>黄志斌：这本书第2次印刷时已经把“期物”改为“future”了。</p></blockquote><p>P21 前面那种方式显然能够节省内存。 前者指的是 genexp，即生成器表达式。</p><h2 id="章节简介">章节简介</h2><p>这本书的结构十分优秀，每一章节都有前言和小结，因此章节简介我偏向于写些零散的知识点和个人感受，会比较乱。大部分章节的章节简介最后会有个人阅读时做的笔记，章节简介没提及的内容可以看看我的笔记。</p><p>全部的笔记还可以在这里找到：<a href="https://github.com/Latias94/fluent-python-notes">Latias94/fluent-python-notes</a></p><h3 id="第一章">第一章</h3><p>第一章作者就介绍了 Python 中的特殊方法，特殊方法也是贯穿这本书的基础。</p><p>读这本书之前，自己通常会遇到<code>__init__</code>、<code>__new__</code>、<code>__name__ == '__main__'</code>等等的带双下划线的特殊方法，但是通过零散的知识点很难形成体系，而这本书涵盖了绝大部分的特殊方法，并且分章节详细地讨论其背后特殊方法的作用，而这一章就是了解特殊方法的第一步。</p><p>作者提到了合理的字符串表示形式：<code>__repr__</code> 和 <code>__str__</code>。前者方便我们调试和记录日志，后者则是给终端用户看的。</p><p>作者开篇就提出了两个问题，第一个问题是：</p><blockquote><p>为什么说 Python 最好的品质之一是一致性？</p></blockquote><p>并且在第十二页给出了答案：</p><blockquote><p>不能让特例特殊到开始破坏既定规则。</p></blockquote><p>第二个问题是：</p><blockquote><p><code>len(collection)</code> 和 <code>collection.len()</code> 有什么不同？和“Python 风格” （Pythonic）有什么关系？</p></blockquote><p>核心开发者 Raymond Hettinger 的答案是：</p><blockquote><p>实用胜于纯粹<br>practicality beats purity<br>——《<a href="https://www.python.org/doc/humor/#the-zen-of-python">The Zen of Python</a>》</p></blockquote><p>作者给出解释：</p><blockquote><p>len 是特殊方法，是为了让 Python 自带的数据结构可以走后门，abs 也是同理。（解释：因为如果 x 是一个内置类型的实例，<code>len(x)</code>的背后会用 CPython 直接从 C 结构体中读取对象的长度，不调用任何方法，以至于 <code>len(x)</code> 会非常快。）<br>…<br>这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点，也印证了“Python 之禅”中的另外一句话：“不能让特例特殊到开始破坏既定规则。”</p></blockquote><p>从这两个问题就能看出作者想要强调的是：「<strong>Python 风格</strong> 无处不在」。为了更好地理解 Python 实现，最好了解 Python 的设计风格。</p><p>笔记传送门：<a href="https://my.mindnode.com/z2mHiiCMKpYW5CpEC1voainyDyh7zqaA2jqnK6AU">特殊方法</a></p><h3 id="第二章">第二章</h3><p>第二三四章主讲 Python 数据结构及其背后的实现，而第二章主要讲了可变类型与不可变类型的区别。</p><p>要想写出准确、高效和地道的 Python 代码，对标准库里的序列类型的掌握是不可或缺的。数据结构的产生就是为了满足各种不同的需求，例如能让用户在不复制内容的情况下操作同一个数组的不同切片的 memoryview，能高效处理矩阵、矢量等高级数值运算的 NumPy 和专为线性代数、数值积分和统计学而设计并基于 NumPy 的 SciPy。计算机科学家主要抽象了几大数据类型：字典、数组、列表等，这些数据类型都有不同的使用环境，使用好这些工具能让你事半功倍、节省不必要的消耗。</p><p>另外，在读这本书的前几天，我刚好在 Segmentfault 里面看到一个问题 <a href="https://segmentfault.com/q/1010000009754631">python小白 问关于a+=a 和a=a+a的区别</a>，当时看完答案还有点似懂非懂的感觉，而读完这章我能完全理解其区别所在了。</p><p>扩展阅读：<a href="https://facert.gitbooks.io/python-data-structure-cn">problem-solving-with-algorithms-and-data-structure-using-python 中文版</a></p><p>笔记传送门：<a href="https://my.mindnode.com/rabZb1p3GadkxZD7sWms2Wyzaz6mELZAKaGX9De2">序列构成的数组</a></p><h3 id="第三章">第三章</h3><p>前一章提到了列表、元组这两种序列，以及它们的生成器表达式。这一章则介绍了散列表的基本概念、其背后的算法和由散列表实现的数据类型：字典和集合。</p><ul><li>由于字典是由散列表实现的，因此字典的键必须是可散列的。</li><li>set 类型本身不是可散列的（因为 set 是可变的），但其元素必须可散列。（这也是为什么 list 不能作为字典键的原因）</li><li>frozenset 是可散列的。</li><li>散列表的实现导致它实现的数据类型效率很高，但这是以牺牲空间的代价所带来的。</li></ul><blockquote><p>“优化往往是可维护性的对立面”<br>由于字典使用了散列表，而散列表又必须是稀疏的，这导致它在空间上的效率低下。举例而言，如果你需要存放数量巨大的记录，那么放在由元组或是具名元组构成的列表中会是比较好的选择；最好不要根据 JSON 的风格，用由字典组成的列表来存放这些记录。用元组取代字典就能节省空间的原因有两个：其一是避免了散列表所耗费的空间，其二是无需把记录中字段的名字在每个元素里都存一遍。</p></blockquote><p>笔记传送门：<a href="https://my.mindnode.com/sy6m2gQJ5CdFBQz7cyCRq6YjKLzzkqQXofC8w26V">字典和集合</a></p><h3 id="第四章">第四章</h3><p>目前没遇到过编码问题，不看。</p><h3 id="第五章">第五章</h3><p>第五章的主题是：高阶函数没这么重要了。</p><p>先来一段吐槽：</p><blockquote><p>Lundh 提出的 lambda 表达式重构秘笈如果使用 lambda 表达式导致一段代码难以理解，Fredrik Lundh 建议像下面这样重构。<br>(1) 编写注释，说明 lambda 表达式的作用。<br>(2) 研究一会儿注释，并找出一个名称来概括注释。<br>(3) 把 lambda 表达式转换成 def 语句，使用那个名称来定义函数。<br>(4) 删除注释。<br>摘自“Functional Programming HOWTO”</p></blockquote><p>现在函数式编程十分流行，但 Python 独特的语法使得 lambda、map、filter 和 reduce 这些函数没这么重要了，因为我们有 sum、all 等归约函数，还有 sorted、min、max 和 functools 这样的内置的高阶函数。</p><p>最后（5.10.2小节）讲了一个和函数柯里化（Currying）十分相像的概念——偏函数（Partial Application），这两者概念其实不一样。</p><p>笔记传送门：<a href="https://my.mindnode.com/EpbDRnypkaZgmDb6cLwgPcenxExjWA75689ah91p">一等函数</a></p><h3 id="第六章">第六章</h3><p>作者从策略模式开始，讨论了一等函数在设计模式中的角色，并用一等函数简化了设计模式的实现方式，以此来展示 Pythonic 的设计模式应该是什么样子的。</p><p>扩展阅读：<a href="https://github.com/wklken/py-patterns">设计模式的python实现</a></p><p>笔记传送门：<a href="https://my.mindnode.com/rwYjfffMs8Zi8cKSWyU1ybshb6mgXiiFp4MbDrj6">使用一等函数实现设计模式</a></p><h3 id="第七章">第七章</h3><p>第七章介绍了装饰器和闭包，作者给闭包下了一个清晰的定义：</p><blockquote><p>闭包指延伸了作用域的函数，其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系，关键是它能访问定义体之外定义的非全局变量。</p></blockquote><p>作者用一个闭包实例和作用相同的类来比较，引出了自由变量（free variable）的概念，以此指出闭包与普通函数不一样的地方——闭包会保留定义函数时存在的自由变量的绑定。在此之后，再引出可变类型与不可变类型对自由变量的影响，从而引出可能导致闭包失效的原因（第二章的主题：可变类型与不可变类型的区别），同时给出了解决办法：nonlocal 声明。</p><p>本章结尾的杂谈提到了「一般来说，实现“装饰器”模式时最好使用类表示装饰器和要包装的组件。」，也就是通过实现 <code>__call__</code> 方法的类来实现装饰器。遗憾的是本书只通过函数来解说装饰器以助于理解，类装饰器没有提及多少。</p><p>笔记传送门：<a href="https://my.mindnode.com/bZa9sX9dA7qcbsGbVqnU6c3ryky8g2QDWfiHqqhm">函数装饰器和闭包</a></p><h3 id="第八章">第八章</h3><blockquote><p>不可变集合不变的是所含对象的标识。</p></blockquote><p>第八章中，作者从「元组是不可变的，但是其中的值可以改变」引申到浅复制和深复制的区别。</p><p><img src="http://img.frankorz.com/5956006435780.jpg" alt=""></p><p>浅复制带来的影响可以参考 <a href="http://www.pythontutor.com/visualize.html#code=l1%20%3D%20%5B3,%20%5B66,%2055,%2044%5D,%20%287,%208,%209%29%5D%0Al2%20%3D%20list%28l1%29%20%0Al1.append%28100%29%0Al1%5B1%5D.remove%2855%29%0Aprint%28'l1%3A',%20l1%29%0Aprint%28'l2%3A',%20l2%29%0Al2%5B1%5D%20%2B%3D%20%5B33,%2022%5D%0Al2%5B2%5D%20%2B%3D%20%2810,%2011%29%20%0Aprint%28'l1%3A',%20l1%29%0Aprint%28'l2%3A',%20l2%29&amp;cumulative=false&amp;curInstr=0&amp;heapPrimitives=false&amp;mode=display&amp;origin=opt-frontend.js&amp;py=3&amp;rawInputLstJSON=%5B%5D&amp;textReferences=false">example</a>（点 foward 显示下一步）</p><p>作者还提到了两个容易忽略的函数参数引用问题：</p><ol><li>不要使用可变类型作为参数的默认值</li><li>防御可变参数</li></ol><p>最后一节讨论垃圾回收、del 命令，以及如何使用弱引用“记住”对象，而无需对象本身存在。</p><p>另外这章有意思的地方在于作者提到了一个常见的说法错误：「对引用式变量来说，说把变量分配给对象更合理，反过来说就有问题。毕竟对象在赋值之前就创建了。」</p><p>笔记传送门：<a href="https://my.mindnode.com/6ZayEUVKgEVAApAPc6aDmjByaB3Qq4hUPQSWBdK2">对象引用、可变性和垃圾回收</a></p><h3 id="第九章">第九章</h3><blockquote><p>要构建符合 Python 风格的对象，就要观察真正的 Python 对象的行为。<br>——古老的中国谚语</p></blockquote><p>第九章主要讲如何编写 Pythonic 的对象。作者从构建一个 Vector 类型来介绍符合 Python 风格的类需要注意的地方，例如<code>__repr__</code>不要硬编码类名、类属性的私有化、格式规范微语言、散列化要注意的条件等。</p><p>作者还讲了构建一个可散列的类型所需要实现的条件：</p><ol><li>正确的实现<code>__hash__</code>和<code>__eq__</code>方法</li><li>不一定要实现只读属性，但是要保证实例的散列值绝不能变化。</li></ol><p>类属性用于为实例属性提供默认值。Django 的类视图也大量用到了这个特性。</p><p>个人十分喜欢名称改写（属性的私有化）中的一张示意图：<br>「避免意外访问，但不能防止故意做错事。」，以此来提醒名称改写所实现的私有化自身的缺陷。</p><p><img src="http://img.frankorz.com/5956006706191.jpg" alt=""></p><p>笔记传送门：<a href="https://my.mindnode.com/sJg5G9wfSRP4Le67FDqErjYGGqxhEPNYpMCnpj1Q">符合 Python 风格的对象</a></p><h3 id="第十章">第十章</h3><blockquote><p>不要检查它是不是鸭子、它的叫声像不像鸭子、它的走路姿势像不像鸭子，等等。具体检查什么取决于你想使用语言的哪些行为。 （comp.lang.python，2000 年 7 月 26 日）<br>——Alex Martelli</p></blockquote><p>其中介绍了鸭子类型，指忽略对象的真正类型，转而关注对象有没有实现所需的方法、签名和语义。在 Python 中指免使用 isinstance 检查对象的类型。</p><p>如果我想实现一个序列，可以实现<code>__init__</code>、<code>__len__</code>、<code>__getitem__</code>等一序列的方法，使其行为像序列，那么这就是一个序列，这也就是人们所称的鸭子类型（duck typing）。</p><p>自己的理解：要明白自己希望的鸭子有哪些特性，只要我实现出来了，那么这就是鸭子。</p><p>Tips:</p><ul><li>可以用 <code>dir(slice)</code> 来查看序列的属性</li><li>当 Python 库文档查询不到方法的文档的时候，可以尝试用 <code>help(slice.indices)</code> 来查询。（ 直接查询<code>__doc__</code>属性的信息 ）</li></ul><p>第四小节讲可切片的序列需要关注的两个问题：</p><ol><li><p>如果创建的序列内部由数组（或其他序列）实现，那么就要考虑切片对象的实现：切片返回的是自创建的序列对象 还是数组（或其他序列）？如果需要考虑，就是在 <code>__getitem__</code> 方法里修改其实现方式。</p></li><li><p>动态存取属性，使序列能通过名称访问序列的属性（v.x,v.y代替v[0],v[1]）。也提到了实现 <code>__getitem__</code> 时可能会产生的问题，和解决方法。</p></li></ol><p>章节末尾的杂谈提到了要遵循 KISS 原则（Keep it simple, stupid），不要过度设计协议。</p><p>笔记传送门：<a href="https://my.mindnode.com/EMWUZqqSPmCA2JtZZhgWaCi7ugQfiLyiLrsy1Tqb">序列的修改、散列和切片</a></p><h3 id="第十一章">第十一章</h3><blockquote><p>本章讨论的话题是接口：从鸭子类型的代表特征动态协议，到使接口更明确、能验证实现是否符合规定的抽象基类（Abstract Base Class，ABC）</p></blockquote><p>我们可能不需要写抽象基类，但是阅读本章能够教我们怎么阅读标准库和其他包中的抽象基类源码。</p><p>其中，作者引用了 Alex Martelli 的一篇文章，用表型系统学(phenetics)和支序系统学（cladistics）用水禽来类比抽象基类。(⊙﹏⊙)b</p><p>其中有第十章提到的「鸭子类型」，还有以前没提过的、描述一种新的 Python 编程风格的「白鹅类型」（goose typing）。</p><blockquote><p>白鹅类型指，只要 cls 是抽象基类，即 cls 的元类是 <code>abc.ABCMeta</code>，就可以使用 <code>isinstance(obj, cls)</code>。</p></blockquote><p>对此，作者在章节小结里面提到：</p><blockquote><p>借助「白鹅类型」，可以使用抽象基类明确声明接口，而且类可以子类化抽象基类或使用抽象基类注册（无需在继承关系中确立静态的强链接），宣称它实现了某个接口。</p></blockquote><p>本章最后还介绍了和 Go 语言协议的功能十分类似的 <code>__subclasshook__</code> 方法。</p><p>笔记传送门：<a href="https://my.mindnode.com/SiejkpsT7xLeNL7wCH2Mrp5SaMN7Ex2EyJ5VivGL">接口：从协议到抽象基类</a></p><h3 id="第十二章">第十二章</h3><p>分析 GUI 工具包 Tkhinter 的多重继承，并且展开分析了多次继承所带来的「菱形问题」，以及 Python 对应的解决方案——方法解析顺序（Method Resolution Order，MRO)，最后作者给了八条关于处理多重继承的建议。</p><p><a href="https://docs.python.org/3/tutorial/classes.html#multiple-inheritance">9.5.1. Multiple Inheritance</a> 里面提到了继承顺序是深度优先从左至右不重复。</p><blockquote><p>For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as <strong>depth-first, left-to-right, not searching twice in the same class</strong> where there is an overlap in the hierarchy.</p></blockquote><p><img src="http://img.frankorz.com/595600652d904.jpg" alt=""></p><p>笔记传送门：<a href="https://my.mindnode.com/qn4fVVx826kDNss71p4f3d6P2rxHsFwo5h7pQb1q">继承的优缺点</a></p><h3 id="第十三章">第十三章</h3><p>第十三章介绍了重载运算符的时候要考虑多重情况。</p><blockquote><p>In the face of ambiguity, refuse the temptation to guess.<br>面对太多的可能，不要尝试猜测。（ZoomQuiet禅译）<br>——《<a href="https://www.python.org/doc/humor/#the-zen-of-python">The Zen of Python</a>》</p></blockquote><p>我们要严谨地对待可能会出现的操作数。</p><p>对于为什么需要重载运算符，在杂谈中作者提到了对于一部分人来说，重载运算符是十分重要的，<strong>符合人类直觉的表示法</strong>十分重要，例如金融工作会接触到一些由不同类型的参数（整数、或其他精度的数字）组成的公式。相比于不支持运算符重载的 Go 与 Java 语言，Python 采取了折中的方式，允许重载运算符，也有一些限制，如：不能重载内置类型的运算符、不能新建运算符、一些运算符也不能重载（is、and、or、not）。</p><p>笔记传送门：<a href="https://my.mindnode.com/keGv1PsLvmtApbmt6bzFjhjtwwgziGGUqyx9vN8y">正确重载运算符</a></p><h3 id="第十四章">第十四章</h3><p>作者分别介绍了迭代器、生成器表达式和生成器函数，并详细地列举了每个标准库生成器函数的用法。</p><p>前面介绍过 Python 内置的数据类型，如列表和元组，能让我们高效地访问数据集，但这些序列只能表示已知且<strong>长度有限</strong>的数据集。要表示无限长度的数据集，例如斐波拉契数列，就需要用到新的构造方式，这也是本章的话题的由来。</p><blockquote><p>扫描内存中放不下的数据集时，我们要找到一种惰性获取数据项的方式，即按需一次获取一个数据项。这就是迭代器模式（Iterator pattern） 。</p></blockquote><p>其中作者依然很注意用词，生成器是 “yields or produces” 生成值，而不是 “returns” 返回值，这样有助于理解生成器获取结果的过程，因为生成器不是以「常规」方式返回值的。</p><p>笔记传送门：<a href="https://my.mindnode.com/rbWsvgTKPcs7v9TmzxrzryuYPs631RCrXW4USZqR">可迭代的对象、迭代器和生成器</a></p><h3 id="第十五章">第十五章</h3><p>介绍了 else 的三种用法与上下文管理器和 with 的作用，作者用<code>__enter__</code>、<code>__exit__</code>等方法手动地实现了一个上下文管理器，还介绍了 <code>@contextmanager</code> 作为另外一种更优雅的实现上下文管理器的方法。其中 <code>@contextmanager</code> 的 <code>yield</code> 语句也引出了第十六章中协程的概念。</p><p>笔记传送门：<a href="https://my.mindnode.com/eTfDEjFxWMpScyZYGfzZYGaGpooKF2LJGFvbdbRF">上下文管理器和else块</a></p><h3 id="第十六章">第十六章</h3><p>建议看第十六、十七、十八章之前先理解五个概念：线程、进程、协程、并发和并行。</p><p>自己参考了：</p><ul><li><p><a href="http://www.jianshu.com/p/f11724034d50">进程，线程，协程与并行，并发- 简书</a></p></li><li><p><a href="https://www.zhihu.com/question/20511233">协程的好处是什么？- 知乎</a></p></li><li><p><a href="http://www.jianshu.com/p/02b7a279c588">Python进程、线程、回调与协程 总结笔记</a></p></li></ul><p>在本章中，作者介绍了如何构建协程，和协程的一些使用场景，章节末尾，作者举了一个离散事件仿真示例，说明如何使用生成器代替线程和回调，实现并发。</p><p>在前些章节的基础上，作者在这章提到 yield 可以看做是控制流程的方式，即 yield 能获取值（<code>.send(foo)</code>），也能产出值（<code>foo = yield</code>），还能不获取和产出值（yield 后没有表达式）。因此，我们能用它来构建协程。</p><blockquote><p>不管数据如何流动，yield 都是一种流程控制工具，使用它可以实现协作式多任务：协程可以把控制器让步给中心调度程序，从而激活其他的协程。</p></blockquote><p>除了调用 <code>.send(...)</code> 方法发送数据，本章还介绍使用 <code>yield from</code> 结构驱动的生成器函数。</p><p>扩展阅读：</p><ul><li><p><a href="http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/">SICP in Python</a> 有一段关于并行计算的非常精彩的解释：<a href="https://wizardforcel.gitbooks.io/sicp-py/content/ch4.html#43-%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97">4.3 并行计算</a></p></li><li><p><a href="http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/">SICP in Python</a> 中协程的一节里着重讲了将复杂程序解构为小型、模块化组件的技巧：<a href="https://wizardforcel.gitbooks.io/sicp-py/content/ch5.html#53-%E5%8D%8F%E7%A8%8B">5.3 协程</a></p></li></ul><p>笔记传送门：<a href="https://my.mindnode.com/4qpfqK3UY5ZdNVyEje4sXnFJ9PoXTFpNJVsn2pZx">协程</a></p><h3 id="第十七章">第十七章</h3><blockquote><p>并发是计算机科学中最难的概念之一（通常最好别去招惹它） 。<br>——David Beazley Python 教练和科学狂人</p></blockquote><p>在第十七章，作者用一个下载国旗图片的例子来介绍网络下载的三种风格：依序下载、<code>concurrent.futures</code> 模块（<code>ThreadPoolExecutor</code> 和 <code>ProcessPoolExecutor</code> 类）实现的并发下载和 asyncio 包实现的并发下载。作者还介绍了阻塞性 I/O 和 GIL，最后介绍了如何借助 <code>concurrent.futures.ProcessPoolExecutor</code> 类使用多进程。</p><p>future 指一种对象，表示异步执行的操作。</p><p>早期的计算机从单用户操作系统（同一时间只能运行一个任务）转变成多任务操作系统（同一时间可以运行多个任务），又由于多任务操作系统中程序经常抢夺系统资源而引发死锁这种缺陷，在 20 世纪 60 年代，计算机科学家就开始探索并发编程的道路，并发指交替执行多个任务，解决的就是前面提到的多任务操作系统的缺陷。直到现在，很多编程语言都为并发提供了支持，其中包括原生支持并发的 Go 语言，和有相关模块支持的 Python。</p><blockquote><p>并发（concurrency）不是并行（parallelism）。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情，而并发是指同时管理很多事情，这些事情可能只做了一半就被暂停去做别的事情了。在很多情况下，并发的效果比并行好，因为操作系统和硬件的总资源一般很少，但能支持系统同时做很多事情。<br>——《Go 语言实战》</p></blockquote><p>笔记传送门：<a href="https://my.mindnode.com/A6WWkGQgdYx9LtzvnziszgHXwKcxidy2cKKbww8r">使用future处理并发</a></p><h3 id="第十八章">第十八章</h3><blockquote><p>并发是指一次处理多件事。<br>并行是指一次做多件事。<br>二者不同，但是有联系。<br>一个关于结构，一个关于执行。<br>并发用于制定方案，用来解决可能（但未必）并行的问题。<br>——Rob Pike Go 语言的创造者之一</p></blockquote><p>第十八章中，作者主要介绍了新的并发编程方式，对比了 <code>asyncio.Task</code> （协程）对象与 <code>threading.Thread</code> （线程）对象的区别，包括 Python 包使用方式的区别和中断时协程与线程的区别：锁的保留。章节尾，作者介绍了 asyncio 包的使用和并发编程需要注意的地方。</p><p>笔记待补</p><h3 id="第十九章">第十九章</h3><p>第十九章主要介绍了动态属性编程。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">class</span> <span class="title class_">Foo</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta">... </span>    <span class="keyword">pass</span></span><br><span class="line"><span class="meta">... </span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo = Foo()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo.a = <span class="number">3</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>Foo.b = <span class="built_in">property</span>(<span class="keyword">lambda</span> self: self.a + <span class="number">1</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>foo.b</span><br><span class="line"><span class="number">4</span></span><br></pre></td></tr></table></figure><p>这就叫做动态属性（dynamic attribute），不同于属于静态语言的 Java 需要依靠 setter 和 getter 方法，Python 能十分方便地设置属性和读取属性。</p><p>作者拿 FrozenJSON 类做例子：把嵌套的字典和列表转换成嵌套的 FrozenJSON 实例和实例列表。FrozenJSON 类的代码展示了如何使用特殊的 <code>__getattr__</code> 方法（处理属性的函数）在读取属性时即时转换数据结构。</p><p>作者还介绍了很多处理属性的属性和函数以及利用特性(@properties)来修改设置属性和读取属性的方式。</p><p>笔记传送门：<a href="https://my.mindnode.com/ByXkheqCf71cuELJZyYHHwt47HiaeoVqrSY3nxZB">动态属性和特性</a></p><h3 id="第二十章">第二十章</h3><p>有时候看书看着就忘了一些名词是什么了，因此参考了下<a href="https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_descriptor.html">【译】Python描述符指南</a>，描述符类就是实现描述符协议的类。</p><p>相比于第十九章中利用特性（@properties）来修改属性的存取逻辑，第二十章主要介绍了描述符——对多个属性运用相同存取逻辑的一种方式。两者的区别是特性有时更合适和简单，而描述符更灵活。这章还介绍了覆盖型与非覆盖型描述符的对比，最后也给出了使用描述符的建议和优缺点。</p><p>笔记传送门：<a href="https://my.mindnode.com/GVWcPSN148gphwzVNEok3gGhFNCVWyPkVjmFe2yY">属性描述符</a></p><h3 id="第二十一章">第二十一章</h3><blockquote><p>（元类）是深奥的知识，99% 的用户都无需关注。如果你想知道是否需要使用元类，我告诉你，不需要（真正需要使用元类的人确信他们需要，无需解释原因） 。<br>——Tim Peters<br>Timsort 算法的发明者，活跃的 Python 贡献者</p></blockquote><p>上面是第二十一章的引言，我听从这位传奇开发者的建议，没有看。</p><h2 id="总结">总结</h2><p>整本书都在强调如何最大限度地利用 Python 标准库以及讲述 Python 背后的设计思想。身处众多动态编程语言中间，Python 无疑是独行独立的，这也是为什么很多 Python 开发者骄傲地宣称自己是一名 Pythonista。</p><p>自己只是不求甚解地通读了一遍书，学到了很多，但书中仍有太多不熟悉的知识点。因为假期不多了，只能等日后二刷这本书。过一遍这本书最大的收获莫过于在面对问题的时候，自己的工具箱又多了不少工具，即使这工具还不太「趁手」。其中感受最深的就是现在看一些 Segmentfault 或 StackOverflow 问题的答案的时候不再那么毫无头绪，并开始试着从前辈们的角度思考问题。另外书中多次提到 Django 的一些实现方式，对自己日后读源码的时候有帮助。</p><blockquote><p>仓促本身就是最要不得的态度。当你做某件事的时候，一旦想要求快，就表示你再也不关心它，而想去做别的事。<br>——罗伯特 · M · 波西格 《禅与摩托车维修艺术》</p></blockquote><p>自己的确因为阅读计划的期限而读的仓促了一些，这句话放到文尾，提醒自己在读下一本书的时候，尽量做到静下心来。</p>]]></content>
      
      
      
        <tags>
            
            <tag> 读后感 </tag>
            
            <tag> Python </tag>
            
            <tag> 笔记 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Go 语言的网络编程简介</title>
      <link href="/2017/04/29/go-from-tcp-to-http/"/>
      <url>/2017/04/29/go-from-tcp-to-http/</url>
      
        <content type="html"><![CDATA[<p>本文通过 Go 语言写几个简单的通信示例，从 TCP 服务器过渡到 HTTP 开发，从而简单介绍 net 包的运用。</p><span id="more"></span><h2 id="TCP-服务器">TCP 服务器</h2><p>首先来看一个 TCP 服务器例子</p><figure class="highlight go"><figcaption><span>tcp-write/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;net&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// net 包提供方便的工具用于 network I/O 开发，包括TCP/IP, UDP 协议等。</span></span><br><span class="line"><span class="comment">// Listen 函数会监听来自 8080 端口的连接，返回一个 net.Listener 对象。</span></span><br><span class="line">li, err := net.Listen(<span class="string">&quot;tcp&quot;</span>, <span class="string">&quot;:8080&quot;</span>)</span><br><span class="line"><span class="comment">// 错误处理</span></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Panic(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 释放连接，通过 defer 关键字可以让连接在函数结束前进行释放</span></span><br><span class="line"><span class="comment">// 这样可以不关心释放资源的语句位置，增加代码可读性</span></span><br><span class="line"><span class="keyword">defer</span> li.Close()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 不断循环，不断接收来自客户端的请求</span></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="comment">// Accept 函数会阻塞程序，直到接收到来自端口的连接</span></span><br><span class="line"><span class="comment">// 每接收到一个链接，就会返回一个 net.Conn 对象表示这个连接</span></span><br><span class="line">conn, err := li.Accept()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 字符串写入到客户端</span></span><br><span class="line">fmt.Fprintln(conn, <span class="string">&quot;Hello from TCP server&quot;</span>)</span><br><span class="line"></span><br><span class="line">conn.Close()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在对应的文件夹下启动服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ go run main.go</span><br></pre></td></tr></table></figure><p>模拟客户端程序发出请求，这里使用 netcat 工具，也就是 nc 命令。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ nc localhost 8080</span><br><span class="line">Hello from TCP server</span><br></pre></td></tr></table></figure><p>通过 net 包，我们可以很简单的去写一个 TCP 服务器，代码可读性强。</p><h2 id="TCP-客户端">TCP 客户端</h2><p>那么我们能不能用 Go 语言来模拟客户端，从而连接前面的服务器呢？答案是肯定的。</p><figure class="highlight go"><figcaption><span>tcp-read/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;io/ioutil&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;net&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// net 包的 Dial 函数能创建一个 TCP 连接</span></span><br><span class="line">conn, err := net.Dial(<span class="string">&quot;tcp&quot;</span>, <span class="string">&quot;:8080&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatal(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 别忘了关闭连接</span></span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line"><span class="comment">// 通过 ioutil 来读取连接中的内容，返回一个 []byte 类型的对象</span></span><br><span class="line"><span class="type">byte</span>, err := ioutil.ReadAll(conn)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// []byte 类型的数据转成字符串型，再将其打印输出</span></span><br><span class="line">fmt.Println(<span class="type">string</span>(<span class="type">byte</span>))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行服务器后，再在所在的文件夹下启动客户端，会看到来自服务器的问候。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ go run main.go</span><br><span class="line">Hello from TCP server</span><br></pre></td></tr></table></figure><h2 id="TCP-协议模拟-HTTP-请求">TCP 协议模拟 HTTP 请求</h2><p>我们知道 TCP/IP 协议是传输层协议，主要解决的是数据如何在网络中传输。而 HTTP 是应用层协议，主要解决的是如何包装这些数据。</p><p>下面的七层网络协议图也能看到 HTTP 协议是处于 TCP 的上层，也就是说，HTTP 使用 TCP 来传输其报文数据。</p><p><img src="http://img.frankorz.com/59047bbfe298f.png" alt="七层网络协议图"></p><p>现在我们写一个基于 TCP 协议的服务器，并能模拟。在这其中，我们需要模拟发送 HTTP 响应头信息，我们可以用 <code>curl -i</code> 命令先来查看一下其他网站的响应头信息。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">$ curl -i <span class="string">&quot;www.baidu.com&quot;</span></span><br><span class="line">HTTP/1.1 200 OK  <span class="comment"># HTTP 协议及请求码</span></span><br><span class="line">Server: bfe/1.0.8.18<span class="comment"># 服务器使用的WEB软件名及版本</span></span><br><span class="line">Date: Sat, 29 Apr 2017 07:30:33 GMT  <span class="comment"># 发送时间</span></span><br><span class="line">Content-Type: text/html   <span class="comment"># MIME类型</span></span><br><span class="line">Content-Length: 277<span class="comment"># 内容长度</span></span><br><span class="line">Last-Modified: Mon, 13 Jun 2016 02:50:23 GMT</span><br><span class="line">...  <span class="comment"># balabala</span></span><br><span class="line">Accept-Ranges: bytes</span><br><span class="line"></span><br><span class="line">&lt;!DOCTYPE html&gt;  <span class="comment"># 消息体</span></span><br><span class="line">&lt;!--STATUS OK--&gt;&lt;html&gt;</span><br><span class="line">...</span><br><span class="line">&lt;/body&gt; &lt;/html&gt;</span><br></pre></td></tr></table></figure><p>接下来，我们尝试写出能输出对应格式响应内容的服务器。</p><figure class="highlight go"><figcaption><span>tcp-server-for-http/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;net&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">li, err := net.Listen(<span class="string">&quot;tcp&quot;</span>, <span class="string">&quot;:8080&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalln(err.Error())</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> li.Close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">conn, err := li.Accept()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Fatalln(err.Error())</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 函数前添加 go 关键字，就能使其拥有 Go 语言的并发功能</span></span><br><span class="line"><span class="comment">// 这样我们可以同时处理来自不同客户端的请求</span></span><br><span class="line"><span class="keyword">go</span> handle(conn)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handle</span><span class="params">(conn net.Conn)</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line"><span class="comment">// 回应客户端的请求</span></span><br><span class="line">respond(conn)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">respond</span><span class="params">(conn net.Conn)</span></span> &#123;</span><br><span class="line"><span class="comment">// 消息体</span></span><br><span class="line">body := <span class="string">`&lt;!DOCTYPE html&gt;&lt;html lang=&quot;en&quot;&gt;&lt;head&gt;&lt;meta charet=&quot;UTF-8&quot;&gt;&lt;title&gt;Go example&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;strong&gt;Hello World&lt;/strong&gt;&lt;/body&gt;&lt;/html&gt;`</span></span><br><span class="line"><span class="comment">// HTTP 协议及请求码</span></span><br><span class="line">fmt.Fprint(conn, <span class="string">&quot;HTTP/1.1 200 OK\r\n&quot;</span>)</span><br><span class="line"><span class="comment">// 内容长度</span></span><br><span class="line">fmt.Fprintf(conn, <span class="string">&quot;Content-Length: %d\r\n&quot;</span>, <span class="built_in">len</span>(body)) </span><br><span class="line"><span class="comment">// MIME类型</span></span><br><span class="line">fmt.Fprint(conn, <span class="string">&quot;Content-Type: text/html\r\n&quot;</span>)</span><br><span class="line">fmt.Fprint(conn, <span class="string">&quot;\r\n&quot;</span>)</span><br><span class="line">fmt.Fprint(conn, body)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>go run main.go</code> 启动服务器之后，跳转到 <a href="localhost:8080">localhost:8080</a>，就能看到网页内容，并且用开发者工具能看到其请求头。</p><p><img src="http://img.frankorz.com/59047bb81e615.jpg" alt=""></p><h2 id="最简单的-HTTP-服务器">最简单的 HTTP 服务器</h2><p>几行代码就能实现一个最简单的 HTTP 服务器。</p><figure class="highlight go"><figcaption><span>simple-http/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;net/http&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>打开后会发现显示「404 page not found」，这说明 HTTP 已经开始服务了！</p><h2 id="ListenAndServe">ListenAndServe</h2><blockquote><p>Go 是通过一个函数 ListenAndServe 来处理这些事情的，这个底层其实这样处理的：初始化一个server 对象，然后调用了 net.Listen(“tcp”, addr)，也就是底层用 TCP 协议搭建了一个服务，然后监控我们设置的端口。</p><footer><strong>《Build web application with golang》</strong><cite>astaxie</cite></footer></blockquote><p>前面我们已经对 TCP 服务器有点熟悉了，而 HTTP 使用 TCP 来传输其报文数据，接下来看看如何用 net/http 包来实现在其上的 HTTP 层。</p><p>查文档可以发现 http 包下的 ListenAndServe 函数第一个参数是地址，而第二个是 Handler 类型的参数，我们想要显示内容就要在第二个参数下功夫。</p><figure class="highlight go"><figcaption><span>ListenAndServe</span><a href="https://golang.org/src/net/http/server.go?s">link</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ListenAndServe</span><span class="params">(addr <span class="type">string</span>, handler Handler)</span></span> <span class="type">error</span></span><br></pre></td></tr></table></figure><p>再次查文档，得知 Handler 是一个接口，也就是说只要我们给某一个类型创建 <code>ServeHTTP(ResponseWriter, *Request)</code> 方法，就能符合接口的要求，也就实现了接口。</p><figure class="highlight go"><figcaption><span>http.Handler</span><a href="https://golang.org/src/net/http/server.go?s">link</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Handler <span class="keyword">interface</span> &#123;</span><br><span class="line">        ServeHTTP(ResponseWriter, *Request)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><figcaption><span>servehttp/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="comment">// 创建一个 foo 类型</span></span><br><span class="line"><span class="keyword">type</span> foo <span class="keyword">struct</span> &#123;&#125;</span><br><span class="line"><span class="comment">// 为 foo 类型创建 ServeHTTP 方法，以实现 Handle 接口</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f foo)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Implement the Handle interface.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建对象，类型名写后面..</span></span><br><span class="line"><span class="keyword">var</span> f foo</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>,f)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行代码后打开能看到输出的字符串。</p><p><img src="http://img.frankorz.com/59047bb28f70d.jpg" alt=""></p><h3 id="http-Request">*http.Request</h3><p>上面我们实现的小服务器里，我们无论访问 <a href="localhost:8080">localhost:8080</a> 还是 <a href="localhost:8080/foo">localhost:8080/foo</a> 都是一样的页面，这说明我们之前设定的是默认的页面，还没有为特定的路由(route)设置内容。</p><p>路由这些信息实际上就存在 ServeHTTP 函数的第二个参数 <code>*http.Request</code> 中， <code>*http.Request</code> 存放着客户端发送至服务器的请求信息，例如请求链接、请求方法、响应头、消息体等等。</p><p>现在我们可以把上面的代码改造一下。</p><figure class="highlight go"><figcaption><span>serveHTTP/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"><span class="comment">// 创建一个 foo 类型</span></span><br><span class="line"><span class="keyword">type</span> foo <span class="keyword">struct</span> &#123;&#125;</span><br><span class="line"><span class="comment">// 为 foo 类型创建 ServeHTTP 方法，以实现 Handle 接口</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f foo)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line"><span class="comment">// 根据 URL 的相对路径来设置网页内容（不优雅）</span></span><br><span class="line"><span class="keyword">switch</span> r.URL.Path &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;/boy&quot;</span>:</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;I love you!!!&quot;</span>)</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;/girl&quot;</span>:</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;hehe.&quot;</span>)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Men would stop talking and women would shed tears when they see this.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 创建对象，类型名写后面..</span></span><br><span class="line"><span class="keyword">var</span> f foo</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>,f)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="再优雅一点">再优雅一点</h3><p>我们可以用 HTTP 请求多路复用器(HTTP request multiplexer) 来实现分发路由，而<code>http.NewServeMux()</code> 返回的 <code>*ServeMux</code> 对象就能实现这样的功能。下面是 <code>*ServeMux</code> 的部分源码，能看到通过 <code>*ServeMux</code> 就能为每一个路由设置单独的一个 handler 了，简单地说就是不同的内容。</p><figure class="highlight go"><figcaption><span>ServeMux</span><a href="https://golang.org/src/net/http/server.go?s">link</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ServeMux <span class="keyword">struct</span> &#123;</span><br><span class="line">mu    sync.RWMutex         <span class="comment">// 读写锁</span></span><br><span class="line">m     <span class="keyword">map</span>[<span class="type">string</span>]muxEntry  <span class="comment">// 路由信息（键值对）</span></span><br><span class="line">hosts <span class="type">bool</span>                 <span class="comment">// 是否包含 hostnames</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> muxEntry <span class="keyword">struct</span> &#123;</span><br><span class="line">explicit <span class="type">bool</span>     <span class="comment">// 是否精确匹配</span></span><br><span class="line">h        Handler  <span class="comment">// muxEntry.Handler 是接口</span></span><br><span class="line">pattern  <span class="type">string</span>   <span class="comment">// 路由</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Handler <span class="keyword">interface</span> &#123;</span><br><span class="line">ServeHTTP(ResponseWriter, *Request)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用 <code>*ServeMux</code> 来写一个例子。</p><figure class="highlight go"><figcaption><span>newServeMux/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> boy <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b boy)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;I love you!!!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> girl <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(g girl)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;hehe.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> foo <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f foo)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Men would stop talking and women would shed tears when they see this.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> b boy</span><br><span class="line"><span class="keyword">var</span> g girl</span><br><span class="line"><span class="keyword">var</span> f foo</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回一个 *ServeMux 对象</span></span><br><span class="line">mux := http.NewServeMux()  </span><br><span class="line">mux.Handle(<span class="string">&quot;/boy/&quot;</span>, b)</span><br><span class="line">mux.Handle(<span class="string">&quot;/girl/&quot;</span>, g)</span><br><span class="line">mux.Handle(<span class="string">&quot;/&quot;</span>, f)</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, mux)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就能为每一个路由设置单独的页面了。</p><h3 id="再再优雅一点">再再优雅一点</h3><p><code>http.Handle(pattern string, handler Handler)</code> 还能帮我们简化代码，它默认创建一个 <code>DefaultServeMux</code>，也就是默认的 <code>ServeMux</code> 来存 handler 信息，这样就不需要 <code>http.NewServeMux()</code> 函数了。这看起来虽然没有什么少写多少代码，但是这是下一个更加优雅方法的转折点。</p><figure class="highlight go"><figcaption><span>defaultServeMux/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> boy <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b boy)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;I love you!!!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> girl <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(g girl)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;hehe.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> foo <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f foo)</span></span> ServeHTTP(w http.ResponseWriter, r *http.Request) &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Men would stop talking and women would shed tears when they see this.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> b boy</span><br><span class="line"><span class="keyword">var</span> g girl</span><br><span class="line"><span class="keyword">var</span> f foo</span><br><span class="line"></span><br><span class="line">http.Handle(<span class="string">&quot;/boy/&quot;</span>, b)</span><br><span class="line">http.Handle(<span class="string">&quot;/girl/&quot;</span>, g)</span><br><span class="line">http.Handle(<span class="string">&quot;/&quot;</span>, f)</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="再再再优雅一点">再再再优雅一点</h3><p><code>http.HandleFunc(pattern string, handler func(ResponseWriter, *Request))</code> 可以看做 <code>http.Handle(pattern string, handler Handler)</code> 的一种包装。前者的第二个参数变成了一个函数，这样我们就不用多次新建对象，再为对象实现 <code>ServeHTTP()</code> 方法来实现不同的 handler 了。下面是 <code>http.HandleFun()</code> 的部分源码。</p><figure class="highlight go"><figcaption><span>http.HandleFun()</span><a href="https://golang.org/src/net/http/server.go?s">link</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">HandleFunc</span><span class="params">(pattern <span class="type">string</span>, handler <span class="keyword">func</span>(ResponseWriter, *Request)</span></span>) &#123;</span><br><span class="line"><span class="comment">// 同样利用 DefaultServeMux 来存路由信息</span></span><br><span class="line">DefaultServeMux.HandleFunc(pattern, handler)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(mux *ServeMux)</span></span> HandleFunc(pattern <span class="type">string</span>, handler <span class="function"><span class="keyword">func</span><span class="params">(ResponseWriter, *Request)</span></span>) &#123;</span><br><span class="line"><span class="comment">// 是不是似曾相识？</span></span><br><span class="line">mux.Handle(pattern, HandlerFunc(handler))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用 <code>http.HandleFun()</code> 来重写之前的例子。</p><figure class="highlight go"><figcaption><span>handleFun/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">boy</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;I love you!!!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">girl</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;hehe.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Men would stop talking and women would shed tears when they see this.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/boy/&quot;</span>, boy)</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/girl/&quot;</span>, girl)</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/&quot;</span>, foo)</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="HandlerFunc">HandlerFunc</h3><p>另外，http 包里面还定义了一个类型 <code>http.HandlerFunc</code>，该类型默认实现 Handler 接口，我们可以通过 <code>HandlerFunc(foo)</code> 的方式来实现类型强转，使 <code>foo</code> 也实现了 Handler 接口。</p><figure class="highlight go"><figcaption><span>HandlerFunc</span><a href="https://golang.org/src/net/http/server.go?s">link</a></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> HandlerFunc <span class="function"><span class="keyword">func</span><span class="params">(ResponseWriter, *Request)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 实现 Handler 接口</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f HandlerFunc)</span></span> ServeHTTP(w ResponseWriter, r *Request) &#123;</span><br><span class="line">f(w, r)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><figcaption><span>handleFun/main.go</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">boy</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;I love you!!!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">girl</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;hehe.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">foo</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">fmt.Fprintln(w, <span class="string">&quot;Men would stop talking and women would shed tears when they see this.&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// http.Handler() 的第二个参数是要实现了 Handler 接口的类型</span></span><br><span class="line"><span class="comment">// 可以通过类型强转来重新使用该函数来实现</span></span><br><span class="line">http.Handle(<span class="string">&quot;/boy/&quot;</span>, http.HandlerFunc(boy))</span><br><span class="line">http.Handle(<span class="string">&quot;/girl/&quot;</span>, http.HandlerFunc(girl))</span><br><span class="line">http.Handle(<span class="string">&quot;/&quot;</span>, http.HandlerFunc(foo))</span><br><span class="line">http.ListenAndServe(<span class="string">&quot;:8080&quot;</span>, <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="结尾">结尾</h2><p>本文从搭建 TCP 服务器一步步到搭建 HTTP 服务器，展示了 Go 语言网络库的强大，我认为 Go 语言是熟悉网络协议的一个很好的工具。自己从熟悉了拥有各种 feature 的 Swift 语言之后再入门到看似平凡无奇的 Go 语言，经历了从为语言的平庸感到惊讶不解到为其遵循规范和良好的工业语言设计而感到惊叹和兴奋的转变。</p><p>最后希望本文能为有基础的同学理清思路，也能吸引更多同学来学习这门优秀的语言。</p>]]></content>
      
      
      
        <tags>
            
            <tag> Go </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>聊聊慕课与时间管理工具</title>
      <link href="/2017/03/29/mooc-and-pomotodo/"/>
      <url>/2017/03/29/mooc-and-pomotodo/</url>
      
        <content type="html"><![CDATA[<p>在图灵社区活动「分享学习方法」中写了这篇文章，现在搬运过来，浅浅地普及下慕课这种学习新方式和目前所选用的时间管理工具。</p><span id="more"></span><h2 id="MOOC">MOOC</h2><p>我以前有过看视频学编程的时光，视频里老师写一点代码，我就写一点代码。如今想起来这种方法虽然浪费时间，但是对小白而言是一个很好的入门方法，能够有效的让小白熟悉 IDE 和手把手入门编程。</p><p>MOOC 则是最近又让我眼前一亮的学习方式，其代表有：<a href="http://coursera.org">Coursera</a>、<a href="http://edx.org">Edx</a> 、<a href="http://cn.udacity.com">Udacity</a>等。同样是看视频学习，Coursera 使用了短视频+限时作业+考试的方法来让学生能轻松上课的同时，用 deadline 督促学生。其次付费证书和其课堂论坛也是亮点，你上完一个课程，可以付费购买课程的证书以彰显你的努力，论坛则是开放给全球各地上同样一节课的学生。Coursera 里有部分课程都是有中文字幕的，例如普林斯顿大学的「<a href="https://www.coursera.org/learn/algorithms-part1">Algorithms</a>」课。算法红宝书《算法（第4版）》的作者就是这门课的老师，Robert Sedgewick 老师在课程中给的图例和制作的动画都十分清晰精致，并且配合课件能循序渐进地分解算法。其中小课程一般一节讲一个算法或知识点，内容充实并且需要学生课下消化。这种方式和看《算法（第4版）》结合起来，学习效率事半功倍。PS：上课的同时也请完成红宝书中的课后练习。</p><p>我认为 MOOC 可贵的一个地方还是它的课程质量，因为国外 CS 课程的授课老师往往同时有很强的 academic 和 industry 的背景，所以课程通常会兼顾深度和实用性。美国大学一门课收费大约在五千刀左右，所以请努力争取白赚这些钱吧~</p><p>在这里随便列举几门课程：有被称为第一神课的「<a href="https://www.coursera.org/learn/programming-languages">程序设计语言</a>」、斯坦福密码学大牛 Dan Boneh 讲的「<a href="https://www.coursera.org/learn/crypto">Cryptography I</a>」、Coursera 创始人吴恩达讲的「<a href="https://www.coursera.org/learn/machine-learning">机器学习</a>」等等。</p><p>遗憾的是 Coursera 改版之后删除了很多经典神课，例如以 CSAPP 为教材的「<a href="https://courses.cs.washington.edu/courses/cse351/16sp/videos.html">The Hardware/Software Interface</a>」，斯坦福大学的编译原理课「Compilers」、斯坦福的另外一门偏理论的算法课「Algorithms: Design and Analysis」等等，这些课程需要自己去学校官网上找，或者去 Youtube 看视频。同时科学上网会有更好的上课体验，另外英语的学习也是很有必要的，毕竟不是门门课都有志愿者翻译成中文。</p><p>PS: 斯坦福大学也有自己的 mooc 网站，<a href="https://lagunita.stanford.edu">Stanford Online</a></p><p>已经翻译了的公开课也有志愿者搬运到了 B 站（汗…）、网易公开课、学堂在线等平台。例如哈佛大学的计算机入门课「<a href="http://open.163.com/special/opencourse/cs50.html">计算机科学 cs50</a>」、麻省理工学院的「<a href="http://open.163.com/special/opencourse/algorithms.html">算法导论</a>」课、线性代数神课「<a href="https://www.bilibili.com/video/av6731067/">线性代数的本质</a>」、用 Python 讲解的计算机科学入门课「<a href="http://www.xuetangx.com/courses/course-v1:MITx+6_00_1x+sp/about">计算机科学和Python编程导论</a>」、B 站 UP 主翻译的斯坦福密码学课「<a href="http://www.bilibili.com/video/av1269426/">密码学</a>」、微软信仰中心翻译的「<a href="http://space.bilibili.com/18340402/#!/channel/detail?cid=1436">UWP 开发入门教程</a>」等等。</p><p>更多课程及查看评分：<br><a href="http://mooc.guokr.com">mooc 学院</a><br><a href="http://blog.coursegraph.com/coursera%E8%AF%BE%E7%A8%8B%E4%B8%8B%E8%BD%BD%E5%92%8C%E5%AD%98%E6%A1%A3%E8%AE%A1%E5%88%92%E5%9B%9B%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E5%9F%BA%E7%A1%80%E5%85%AC%E5%BC%80%E8%AF%BE">Coursera课程下载和存档计划四：计算机科学基础公开课</a><br><a href="https://www.zhihu.com/question/21095181">三大 MOOC 网站：Coursera 与 Udacity 和 edX 比较，哪个更适合中国人？你有何经验分享？</a></p><h2 id="番茄工作法">番茄工作法</h2><p>近年来时间管理话题变得十分火热，番茄工作法大家应该都耳熟能详了，我就不详细介绍了。很多番茄工作法的应用如雨后春笋般出现，由于受限于单平台，我没有坚持使用下来。今天推荐一款我在用一段时间的「<a href="http://pomotodo.com">番茄土豆</a>」服务，番茄土豆拥有全平台应用，涵盖了我的苹果本、iPhone 和 iPad。另外一点就是能方便地回溯自己的番茄时间。</p><p>例如下面是在 Coursera 上算法课的统计，右下角统计了时间。</p><p><img src="http://img.frankorz.com/58db5ca5de499.jpg" alt=""></p><p>下面是最近看书学 Go 语言的记录。</p><p><img src="http://img.frankorz.com/58db5ca6c13fa.jpg" alt=""></p><p>查看自己花在阅读上的时间。</p><p><img src="http://img.frankorz.com/58db5ca64e605.jpg" alt=""></p><p>我认为每完成一个番茄就记录这段时间所做的事，这样能够培养主动记录的习惯，也方便自己对时间的统计分析。建议在记录任务的时候分配好标签，记录完整在做的事情，这样利用很短的时间就能代替追踪时间的效果。</p><p>一般免费版就足够用了，我购买了高级版是因为能够补充线下没有统计的番茄时间，高级版对于学生或教师有半价。</p><h2 id="总结">总结</h2><p>学习方法太多了，我敢写在这的都是我感觉的确对我有帮助的方法。自己也是个菜鸟，还在探索自己的学习方法。现在越学习越清楚英语的重要性，希望大家学好英语，这样你可用的学习资源也会多出几十倍来，而不需要靠别人翻译拾人牙慧。</p>]]></content>
      
      
      
        <tags>
            
            <tag> 学习 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>装饰器模式（Swift）</title>
      <link href="/2017/02/20/decorator-in-swift/"/>
      <url>/2017/02/20/decorator-in-swift/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/58ac04972e7f3.jpg" alt=""></p><p>阅读《大话设计模式》和《精通 Swift 设计模式》中的装饰器模式，本文为笔记。</p><span id="more"></span><h2 id="简介">简介</h2><p>装饰器模式，可以用于在运行时选择性地修改对象的行为，在处理无法修改的类时能发挥其强大的能力。</p><p>我们可以在想修改<strong>对象的行为</strong>时，又不想修改<strong>对象所属的类或其使用者</strong>，就可以使用装饰器模式。如果想修改对象的类实现，则不推荐使用装饰器模式，此时直接修改类往往更简单。</p><h2 id="例子">例子</h2><p>阅读前请下载 OS X 命令工具行初始项目 <a href="https://github.com/Latias94/Decorator_in_Swift/tree/master/Decorator_start">Decorator_start</a>，项目中注释较详细，如有问题请联系我。</p><p>项目地址：<a href="https://github.com/Latias94/Decorator_in_Swift">Decorator_in_Swift</a></p><p><img src="http://img.frankorz.com/58ac03cfe0d06.png" alt="初始项目 UML 图"></p><p>Purchase 类表示顾客在商店买了什么，其中定义了两个属性来存储商品名称和价格，还有两个计算属性把信息提供给外界。</p><p>CustomerAccount 类表示一组 Purchase 对象，代表了顾客所购买的商品，<code>addPurchase(Purchase)</code>方法代表顾客购买了新商品。</p><p>Options 类中包含三个类，为前两个类提供了礼品服务，例如：2 元的礼品包装、1 元的彩带和 5 元的礼品配送。这里利用继承在 Options 类创建了三个装饰器类解决一个小问题——在不修改原来的两个类时添加了礼品服务功能。</p><p>Purchase 类和 CustomerAccount 类实现了创建一个用户对象，然后买一个特定价格的商品，如：张三购买了 10 元的帽子。后三个类则扩展了功能，能为每一个商品增加一个礼品服务的选项，如：张三购买了有彩带包装的 10 元的帽子。</p><p>但是已有的代码实现不了为一个商品添加多个礼品服务，如：张三购买了有礼品和彩带包装的 10 元的帽子。</p><p>源码运行结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Purchase Red Hat, Price ￥10.00</span><br><span class="line">Purchase Scarf, Price ￥20.00</span><br><span class="line">Purchase Scarf + delivery, Price ￥25.00</span><br><span class="line">Total due: ￥55.00</span><br></pre></td></tr></table></figure><h3 id="装饰器模式">装饰器模式</h3><p>装饰器模式通过创建装饰器类解决上述组合问题（礼品包装+彩带、礼品包装+彩带+礼品配送等），装饰器类是指用于封装原始类并改变其行为的类。</p><p>装饰器类提供的 API 和封装的原始类相同，为了创建其他组合，装饰器还可以封装其他装饰器。</p><p>这里的单个礼品服务类 <code>Options.swift</code> 可以看做是一个小小的装饰器。</p><p><img src="http://img.frankorz.com/58ac03d000641.png" alt="装饰器模式"></p><h3 id="实现">实现</h3><p>装饰器类需要继承无法修改的类，来创建一个拥有该类所有方法和属性的类，用来替换原始类的功能。装饰器需要定义一个用来存储被封装对象的私有属性，从而为外界提供该对象的基本功能。</p><p>Options.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 装饰器</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BasePurchasDecorator</span> : <span class="title class_">Purchase</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">let</span> wrappedPurchase: <span class="type">Purchase</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">init</span>(<span class="params">purchase</span>: <span class="type">Purchase</span>) &#123;</span><br><span class="line">    wrappedPurchase <span class="operator">=</span> purchase</span><br><span class="line">    <span class="keyword">super</span>.<span class="keyword">init</span>(product: purchase.description, price: purchase.totalPrice)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 礼品包装</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PurchaseWithGiftWrap</span> : <span class="title class_">BasePurchasDecorator</span> &#123;</span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> description: <span class="type">String</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;<span class="subst">\(<span class="keyword">super</span>.description)</span> + giftwrap&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">+</span> <span class="number">2</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 礼带</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PurchaseWithRibbon</span> : <span class="title class_">BasePurchasDecorator</span> &#123;</span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> description: <span class="type">String</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;<span class="subst">\(<span class="keyword">super</span>.description)</span> + ribbon&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 礼品配送</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PurchaseWithDelivery</span> : <span class="title class_">BasePurchasDecorator</span> &#123;</span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> description: <span class="type">String</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;<span class="subst">\(<span class="keyword">super</span>.description)</span> + delivery&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">+</span> <span class="number">5</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里在开头创建了一个继承自 Purchase 类的礼品服务的基类 BasePurchasDecorator，并把原有礼品服务类的父类修改成了它。在 BasePurchasDecorator 类中，不仅定义了一个私有属性 <code>wrappedPurchase</code> 来保存 Purchase 对象（<a href="http://frankorz.com/2017/02/20/decorator-in-swift/#%E5%A2%9E%E5%8A%A0%E6%96%B0%E5%8A%9F%E8%83%BD">增加新功能</a>小节能看到用途），还会对礼品服务类的属性（<code>description</code> 和 <code>totalPrice</code>）进行处理，以供继承的子类去根据自身的配送描述和价格修改其内容。</p><p>main.swift 修改为</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> account <span class="operator">=</span> <span class="type">CustomerAccount</span>(name: <span class="string">&quot;Joe&quot;</span>)</span><br><span class="line">account.addPurchase(<span class="type">Purchase</span>(product: <span class="string">&quot;Red Hat&quot;</span>, price: <span class="number">10</span>))</span><br><span class="line">account.addPurchase(<span class="type">Purchase</span>(product: <span class="string">&quot;Scarf&quot;</span>, price: <span class="number">20</span>))</span><br><span class="line"><span class="comment">// 礼品配送服务</span></span><br><span class="line"><span class="comment">//account.addPurchase(PurchaseWithDelivery(product: &quot;Scarf&quot;, price: 20))</span></span><br><span class="line"><span class="comment">// 带礼品配送服务的20元围巾</span></span><br><span class="line">account.addPurchase(</span><br><span class="line">  <span class="type">PurchaseWithDelivery</span>(purchase:</span><br><span class="line">    <span class="type">Purchase</span>(product: <span class="string">&quot;Scarf&quot;</span>, price: <span class="number">20</span>)))</span><br><span class="line">    </span><br><span class="line"><span class="comment">// 带礼品包装和礼品配送服务的25元太阳眼镜</span></span><br><span class="line">account.addPurchase(</span><br><span class="line">  <span class="type">PurchaseWithDelivery</span>(purchase:</span><br><span class="line">    <span class="type">PurchaseWithGiftWrap</span>(purchase:</span><br><span class="line">      <span class="type">Purchase</span>(product: <span class="string">&quot;Sunglasses&quot;</span>, price:<span class="number">25</span>))))</span><br><span class="line"></span><br><span class="line">account.printAccount()</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Purchase Red Hat, Price ￥10.00</span><br><span class="line">Purchase Scarf, Price ￥20.00</span><br><span class="line">Purchase Scarf + delivery, Price ￥25.00</span><br><span class="line">Purchase Sunglasses + giftwrap + delivery, Price ￥32.00</span><br><span class="line">Total due: ￥87.00</span><br></pre></td></tr></table></figure><h3 id="增加新功能">增加新功能</h3><p>如果我们还想要用装饰器为原对象增加新的方法或属性，例如节日打折，我们可以继续定义一个折扣装饰器 <code>DiscountDecorator</code>，并创建子类实现不同折扣。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 新增了折扣功能的装饰器</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">DiscountDecorator</span>: <span class="title class_">Purchase</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">let</span> wrappedPurchase: <span class="type">Purchase</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">init</span>(<span class="params">purchase</span>: <span class="type">Purchase</span>) &#123;</span><br><span class="line">    <span class="keyword">self</span>.wrappedPurchase <span class="operator">=</span> purchase</span><br><span class="line">    <span class="keyword">super</span>.<span class="keyword">init</span>(product: purchase.description, price: purchase.totalPrice)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> description: <span class="type">String</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.description</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">/// 优惠了多少元</span></span><br><span class="line">  <span class="keyword">var</span> discountAmount: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">/// 判断产品是否适用折扣优惠，计算已用折扣数</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">countDiscounts</span>() -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> total <span class="operator">=</span> <span class="number">1</span></span><br><span class="line">    <span class="comment">// 注意这里对象本身就已经是一个折扣，wrappedPurchase 是折扣后跟着的purchase对象，还是折扣的话折扣数+1</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">let</span> discounter <span class="operator">=</span> wrappedPurchase <span class="keyword">as?</span> <span class="type">DiscountDecorator</span> &#123;</span><br><span class="line">      total <span class="operator">+=</span> discounter.countDiscounts()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> total</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 黑色星期五打8折</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BlackFridayDecorator</span>: <span class="title class_">DiscountDecorator</span> &#123;</span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">-</span> discountAmount</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> discountAmount: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">*</span> <span class="number">0.20</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// 清仓大甩卖打3折</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EndOfLineDecorator</span>: <span class="title class_">DiscountDecorator</span> &#123;</span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">-</span> discountAmount</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> discountAmount: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">super</span>.totalPrice <span class="operator">*</span> <span class="number">0.70</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中我们根据 <code>wrappedPurchase</code> 属性是否继承自折扣装饰器，来判断方法所包含的对象是否是有折扣的商品。例如下面代码中的 <code>EndOfLineDecorator(Purchase)</code> 方法所包含的对象： <code>BlackFirdayDecorator(purchase:       PurchaseWithDelivery(purchase:         PurchaseWithGiftWrap(purchase:           Purchase(product: &quot;Towel&quot;, price: 12))))</code> 有黑五折扣的礼品配送和礼品包装服务的12元太阳眼镜。</p><p>main.swift 中新增用了折扣的商品的代码</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//... 忽略部分代码</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 带礼品配送+礼品包装服务的12元太阳眼镜共19元，3折加8折后4.56元。</span></span><br><span class="line">account.addPurchase(</span><br><span class="line">  <span class="type">EndOfLineDecorator</span>(purchase:</span><br><span class="line">    <span class="type">BlackFridayDecorator</span>(purchase:</span><br><span class="line">      <span class="type">PurchaseWithDelivery</span>(purchase:</span><br><span class="line">        <span class="type">PurchaseWithGiftWrap</span>(purchase:</span><br><span class="line">          <span class="type">Purchase</span>(product: <span class="string">&quot;Towel&quot;</span>, price: <span class="number">12</span>))))))</span><br><span class="line"></span><br><span class="line">account.printAccount()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 输出每个商品的折扣数</span></span><br><span class="line"><span class="keyword">for</span> purchase <span class="keyword">in</span> account.purchases &#123;</span><br><span class="line">  <span class="keyword">if</span> <span class="keyword">let</span> discountPurchase <span class="operator">=</span> purchase <span class="keyword">as?</span> <span class="type">DiscountDecorator</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;<span class="subst">\(discountPurchase)</span> has <span class="subst">\(discountPurchase.countDiscounts())</span> discounts&quot;</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;<span class="subst">\(purchase)</span> has no discounts&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Purchase Red Hat, Price ￥10.00</span><br><span class="line">Purchase Scarf, Price ￥20.00</span><br><span class="line">Purchase Scarf + delivery, Price ￥25.00</span><br><span class="line">Purchase Sunglasses + giftwrap + delivery, Price ￥32.00</span><br><span class="line">Purchase Towel + giftwrap + delivery, Price ￥4.56</span><br><span class="line">Total due: ￥91.56</span><br><span class="line">Red Hat has no discounts</span><br><span class="line">Scarf has no discounts</span><br><span class="line">Scarf + delivery has no discounts</span><br><span class="line">Sunglasses + giftwrap + delivery has no discounts</span><br><span class="line">Towel + giftwrap + delivery has 2 discounts</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/58ac03d361786.png" alt="UML 图"></p><p>如果想要让折扣只作用在产品价格而不对礼品服务做影响，也是很简单的。</p><p>main.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 带礼品配送+礼品包装服务的12元太阳眼镜共19元，黑五8折只对产品价格有效，之后再打清仓3折后4.98元。</span></span><br><span class="line">account.addPurchase(</span><br><span class="line">  <span class="type">EndOfLineDecorator</span>(purchase:</span><br><span class="line">    <span class="type">PurchaseWithDelivery</span>(purchase:</span><br><span class="line">      <span class="type">PurchaseWithGiftWrap</span>(purchase:</span><br><span class="line">        <span class="type">BlackFridayDecorator</span>(purchase:</span><br><span class="line">          <span class="type">Purchase</span>(product: <span class="string">&quot;Towel&quot;</span>, price: <span class="number">12</span>))))))</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">Purchase Towel + giftwrap + delivery, Price ￥4.98</span><br><span class="line">...</span><br><span class="line">Towel + giftwrap + delivery has 1 discounts</span><br></pre></td></tr></table></figure><p>计算折扣数的时候有点小问题，但是价格是对的。</p><p>用装饰器的时候需要谨慎，要评估对应用其他部分带来哪些影响，特别是已经使用了其他装饰器的情况下。</p><h3 id="合并装饰器">合并装饰器</h3><p>装饰器可以为原始类新增新功能，也能合并装饰器。</p><p>注意：</p><ul><li>装饰器的作用应该是增强或者拓展原始类的功能，而不是给现有的 API 渗透功能。</li><li>小项目中直接用多个独立的装饰器类会比较简单，对于复杂的项目会不便于维护，所以将相关联的装饰器类合并会比较合适。</li></ul><p>Options.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 合并装饰器</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GiftOptionDecorator</span>: <span class="title class_">Purchase</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">let</span> wrappedPurchase: <span class="type">Purchase</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">let</span> options: [<span class="type">Option</span>]</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">enum</span> <span class="title class_">Option</span> &#123;</span><br><span class="line">    <span class="keyword">case</span> giftWrap</span><br><span class="line">    <span class="keyword">case</span> ribbon</span><br><span class="line">    <span class="keyword">case</span> delivery</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">init</span>(<span class="params">purchase</span>: <span class="type">Purchase</span>, <span class="params">options</span>: [<span class="type">Option</span>]) &#123;</span><br><span class="line">    <span class="keyword">self</span>.wrappedPurchase <span class="operator">=</span> purchase</span><br><span class="line">    <span class="keyword">self</span>.options <span class="operator">=</span> options</span><br><span class="line">    <span class="keyword">super</span>.<span class="keyword">init</span>(product: purchase.description, price: purchase.totalPrice)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> description: <span class="type">String</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> result <span class="operator">=</span> wrappedPurchase.description</span><br><span class="line">    <span class="keyword">for</span> option <span class="keyword">in</span> options &#123;</span><br><span class="line">      <span class="keyword">switch</span> option &#123;</span><br><span class="line">      <span class="keyword">case</span> .giftWrap:</span><br><span class="line">        result <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(result)</span> + giftwrap&quot;</span></span><br><span class="line">      <span class="keyword">case</span> .ribbon:</span><br><span class="line">        result <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(result)</span> + ribbon&quot;</span></span><br><span class="line">      <span class="keyword">case</span> .delivery:</span><br><span class="line">        result <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(result)</span> + delivery&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">var</span> totalPrice: <span class="type">Float</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> result <span class="operator">=</span> wrappedPurchase.totalPrice</span><br><span class="line">    <span class="keyword">for</span> option <span class="keyword">in</span> options &#123;</span><br><span class="line">      <span class="keyword">switch</span> option &#123;</span><br><span class="line">      <span class="keyword">case</span> .giftWrap:</span><br><span class="line">        result <span class="operator">+=</span> <span class="number">2</span></span><br><span class="line">      <span class="keyword">case</span> .ribbon:</span><br><span class="line">        result <span class="operator">+=</span> <span class="number">1</span></span><br><span class="line">      <span class="keyword">case</span> .delivery:</span><br><span class="line">        result <span class="operator">+=</span> <span class="number">5</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>main.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 利用合并装饰器实现：带礼品配送+礼品包装服务的12元太阳眼镜共19元，3折加8折后4.56元。</span></span><br><span class="line">account.addPurchase(<span class="type">EndOfLineDecorator</span>(purchase:</span><br><span class="line">  <span class="type">BlackFridayDecorator</span>(purchase:</span><br><span class="line">    <span class="type">GiftOptionDecorator</span>(purchase:</span><br><span class="line">      <span class="type">Purchase</span>(product: <span class="string">&quot;Towel&quot;</span>, price: <span class="number">12</span>),</span><br><span class="line">                        options: [<span class="type">GiftOptionDecorator</span>.<span class="type">Option</span>.giftWrap,</span><br><span class="line">                                  <span class="type">GiftOptionDecorator</span>.<span class="type">Option</span>.delivery]))))</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">Purchase Towel + giftwrap + delivery, Price ￥4.56</span><br><span class="line">...</span><br><span class="line">Towel + giftwrap + delivery has 2 discounts</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/58ac06eae716c.png" alt="完整项目 UML 图"></p><p>结果一样，但是实现方式简洁了不少。</p><p>合并装饰器的优点是能把类的核心职责和装饰功能区分开，去除相关类重复的装饰逻辑。</p><h2 id="总结">总结</h2><p>完整的项目放在 <a href="https://github.com/Latias94/Decorator_in_Swift/tree/master/Decorator_end">Decorator_end</a>。</p><p>如果我们错误地实现了装饰器模式，那么装饰器所做的修改会对所有对象产生影响，或者另应用多出一些和对象无关的功能。</p><p>如<a href="http://frankorz.com/2017/02/20/decorator-in-swift/#%E5%A2%9E%E5%8A%A0%E6%96%B0%E5%8A%9F%E8%83%BD">增加新功能</a>小节中所提及的，实现装饰器模式后，要注意装饰顺序，否则不同的折扣就应用到不同的范围上了。</p><p>重申一遍，装饰器模式是在<strong>已有功能</strong>上加<strong>更多</strong>功能的一种方式，不能修改原始类的已有功能和属性。</p>]]></content>
      
      
      
        <tags>
            
            <tag> 设计模式 </tag>
            
            <tag> Swift </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>IRB 中操作 MongoDB</title>
      <link href="/2017/02/16/mongodb_operate_in_irb/"/>
      <url>/2017/02/16/mongodb_operate_in_irb/</url>
      
        <content type="html"><![CDATA[<blockquote><p>又在 Coursera 里面选了门课坑自己</p></blockquote><p>最近又学了很多东西，其中记不住的做笔记记下来，这篇文章是为 Ruby on Rails 运用 MongoDB 做准备。</p><span id="more"></span><h2 id="基础命令">基础命令</h2><p>Mac 下安装 MongoDB 可以参考 <a href="http://hcysun.me/2015/11/21/Mac%E4%B8%8B%E4%BD%BF%E7%94%A8brew%E5%AE%89%E8%A3%85mongodb/">Mac下使用brew安装mongodb</a>。</p><p>启动数据库</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">% mongod</span><br></pre></td></tr></table></figure><p>进入命令行模式</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">% mongo</span><br></pre></td></tr></table></figure><h3 id="导入-MongoDB-提供的示例数据">导入 MongoDB 提供的示例数据</h3><center><a id="download" href="http://media.mongodb.org/zips.json"><i class="fa fa-download"></i><span> 示例数据下载</span></a></center><p>在 json 文件所在的文件夹下导入数据库。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">% mongoimport --db test --collection zips --drop --file zips.json</span><br></pre></td></tr></table></figure><h3 id="irb-Shell-中的一些基础操作">irb Shell 中的一些基础操作</h3><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">irb</span><br><span class="line"><span class="meta prompt_">2.3.3 :001 &gt;</span> <span class="keyword">require</span> <span class="string">&#x27;mongo&#x27;</span></span><br><span class="line"> =&gt; true</span><br><span class="line"><span class="meta prompt_">2.3.3 :002 &gt;</span> db = <span class="title class_">Mongo::Client</span>.new(<span class="string">&#x27;mongodb://localhost:27017&#x27;</span>)</span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T00:</span><span class="number">06</span><span class="symbol">:</span><span class="number">03</span>.<span class="number">651134</span> <span class="comment">#39047] DEBUG -- : MONGODB | Topology type &#x27;unknown&#x27; initializing.</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T00:</span><span class="number">06</span><span class="symbol">:</span><span class="number">03</span>.<span class="number">651314</span> <span class="comment">#39047] DEBUG -- : MONGODB | Server localhost:27017 initializing.</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T00:</span><span class="number">06</span><span class="symbol">:</span><span class="number">03</span>.<span class="number">654477</span> <span class="comment">#39047] DEBUG -- : MONGODB | Topology type &#x27;unknown&#x27; changed to type &#x27;single&#x27;.</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T00:</span><span class="number">06</span><span class="symbol">:</span><span class="number">03</span>.<span class="number">654581</span> <span class="comment">#39047] DEBUG -- : MONGODB | Server description for localhost:27017 changed from &#x27;unknown&#x27; to &#x27;standalone&#x27;.</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T00:</span><span class="number">06</span><span class="symbol">:</span><span class="number">03</span>.<span class="number">654674</span> <span class="comment">#39047] DEBUG -- : MONGODB | There was a change in the members of the &#x27;single&#x27; topology.</span></span><br><span class="line"> =&gt; #&lt;Mongo::Client:0x70333621287660 cluster=localhost:27017&gt;</span><br><span class="line"><span class="meta prompt_">2.3.3 :003 &gt;</span> db = db.use(<span class="string">&#x27;test&#x27;</span>)</span><br><span class="line"> =&gt; #&lt;Mongo::Client:0x70333622110940 cluster=localhost:27017&gt;</span><br><span class="line"><span class="meta prompt_">2.3.3 :004 &gt;</span> db.database.name</span><br><span class="line"> =&gt; <span class="string">&quot;test&quot;</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :005 &gt;</span> db.database.collection_names</span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T11:</span><span class="number">11</span><span class="symbol">:</span><span class="number">07</span>.<span class="number">299786</span> <span class="comment">#39047] DEBUG -- : MONGODB | localhost:27017 | test.listCollections | STARTED | &#123;&quot;listCollections&quot;=&gt;1, &quot;cursor&quot;=&gt;&#123;&#125;, &quot;filter&quot;=&gt;&#123;:name=&gt;&#123;&quot;$not&quot;=&gt;/system\.|\$/&#125;&#125;&#125;</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T11:</span><span class="number">11</span><span class="symbol">:</span><span class="number">07</span>.<span class="number">326259</span> <span class="comment">#39047] DEBUG -- : MONGODB | localhost:27017 | test.listCollections | SUCCEEDED | 0.025663000000000002s</span></span><br><span class="line"> =&gt; [<span class="string">&quot;zips&quot;</span>]</span><br><span class="line"><span class="meta prompt_">2.3.3 :006 &gt;</span> db[<span class="symbol">:zips</span>].find.first</span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T11:</span><span class="number">12</span><span class="symbol">:</span><span class="number">51.044213</span> <span class="comment">#39047] DEBUG -- : MONGODB | localhost:27017 | test.find | STARTED | &#123;&quot;find&quot;=&gt;&quot;zips&quot;, &quot;filter&quot;=&gt;&#123;&#125;&#125;</span></span><br><span class="line">D, [<span class="number">2017</span>-<span class="number">02</span>-16<span class="symbol">T11:</span><span class="number">12</span><span class="symbol">:</span><span class="number">51.059517</span> <span class="comment">#39047] DEBUG -- : MONGODB | localhost:27017 | test.find | SUCCEEDED | 0.015213s</span></span><br><span class="line"> =&gt; &#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01001&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AGAWAM&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.622739</span>, <span class="number">42.070206</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">15338</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>Win 系统下可以用 <code>system('cls')</code> 命令来清空 irb shell<br>Mac OS X 和 Linux 系统可以用 Ctrl + L 来清空屏幕</p><h3 id="精简输出">精简输出</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">2.3.3 :008 &gt; Mongo::Logger.logger.level = ::Logger::INFO</span><br><span class="line"> =&gt; 1</span><br><span class="line">2.3.3 :009 &gt; db[:zips].find.first</span><br><span class="line"> =&gt; &#123;&quot;_id&quot;=&gt;&quot;01001&quot;, &quot;city&quot;=&gt;&quot;AGAWAM&quot;, &quot;loc&quot;=&gt;[-72.622739, 42.070206], &quot;pop&quot;=&gt;15338, &quot;state&quot;=&gt;&quot;MA&quot;&#125;</span><br></pre></td></tr></table></figure><h2 id="Create-and-Read">Create and Read</h2><p>介绍 CRUD 增查改删操作的前两步操作</p><h3 id="C-reate">&quot;C&quot;reate</h3><ul><li>Select a collection on the client and call <code>insert_one</code> or <code>insert_many</code></li><li><code>insert_one</code>: insert <strong>one</strong> document to collection</li><li><code>insert_many</code>: insert <strong>multiple</strong> documents to the collection</li></ul><h4 id="insert-one">insert_one</h4><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db[<span class="symbol">:zips</span>].insert_one(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;100&quot;</span>,<span class="symbol">:city</span> =&gt; <span class="string">&quot;city01&quot;</span>, <span class="symbol">:loc</span> =&gt; [ -<span class="number">76.05922700000001</span>, <span class="number">39.564894</span>], <span class="symbol">:pop</span> =&gt; <span class="number">4678</span>,<span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span>)</span><br></pre></td></tr></table></figure><p>用 find 和 count 来搜索数据</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;city01&quot;</span>).count</span><br></pre></td></tr></table></figure><p>插入一项数据，并且测试该项数据的存在</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :010 &gt;</span> db[<span class="symbol">:zips</span>].insert_one(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;100&quot;</span>,<span class="symbol">:city</span> =&gt; <span class="string">&quot;city01&quot;</span>, <span class="symbol">:loc</span> =&gt; [ -<span class="number">76.05922700000001</span>, <span class="number">39.564894</span>], <span class="symbol">:pop</span> =&gt; <span class="number">4678</span>,<span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span>)</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70333617635280 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">011</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;city01&quot;</span>).count</span><br><span class="line"> =&gt; <span class="number">1</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :012 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;city01&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;100&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;city01&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">76.059227</span>, <span class="number">39.564894</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4678</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MD&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><h4 id="insert-many">insert_many</h4><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">db[<span class="symbol">:zips</span>].insert_many([</span><br><span class="line">&#123; <span class="symbol">:_id</span> =&gt; <span class="string">&quot;200&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;city02&quot;</span>,</span><br><span class="line">  <span class="symbol">:loc</span> =&gt; [ -<span class="number">74.05922700000001</span>, <span class="number">37.564894</span> ], </span><br><span class="line">  <span class="symbol">:pop</span> =&gt; <span class="number">2000</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;CA&quot;</span> &#125;,</span><br><span class="line">&#123; <span class="symbol">:_id</span> =&gt; <span class="string">&quot;201&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;city03&quot;</span>, </span><br><span class="line">  <span class="symbol">:loc</span> =&gt; [ -<span class="number">75.05922700000001</span>, <span class="number">35.564894</span> ],</span><br><span class="line">  <span class="symbol">:pop</span> =&gt; <span class="number">3000</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;CA&quot;</span> &#125;</span><br><span class="line">])</span><br></pre></td></tr></table></figure><p>同时插入多项数据</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :013 &gt;</span> db[<span class="symbol">:zips</span>].insert_many([</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">014</span> &gt;       &#123; <span class="symbol">:_id</span> =&gt; <span class="string">&quot;200&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;city02&quot;</span>,</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">015</span> &gt;           <span class="symbol">:loc</span> =&gt; [ -<span class="number">74.05922700000001</span>, <span class="number">37.564894</span> ],</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">016</span> &gt;           <span class="symbol">:pop</span> =&gt; <span class="number">2000</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;CA&quot;</span> &#125;,</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">017</span> &gt;       &#123; <span class="symbol">:_id</span> =&gt; <span class="string">&quot;201&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;city03&quot;</span>,</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span>018 &gt;           <span class="symbol">:loc</span> =&gt; [ -<span class="number">75.05922700000001</span>, <span class="number">35.564894</span> ],</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span>019 &gt;           <span class="symbol">:pop</span> =&gt; <span class="number">3000</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;CA&quot;</span> &#125;</span><br><span class="line"><span class="meta prompt_">2.3.3 :020?&gt;</span>     ])</span><br><span class="line"> =&gt; #&lt;Mongo::BulkWrite::Result:0x007fefa58136d8 @results=&#123;&quot;n_inserted&quot;=&gt;<span class="number">2</span>, <span class="string">&quot;n&quot;</span>=&gt;<span class="number">2</span>, <span class="string">&quot;inserted_ids&quot;</span>=&gt;[<span class="string">&quot;200&quot;</span>, <span class="string">&quot;201&quot;</span>]&#125;&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">021</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="string">&quot;city&quot;</span> =&gt; <span class="string">&quot;city02&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;200&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;city02&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">74.059227</span>, <span class="number">37.564894</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">2000</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;CA&quot;</span>&#125;]</span><br><span class="line"><span class="meta prompt_">2.3.3 :022 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="string">&quot;city&quot;</span> =&gt; <span class="string">&quot;city03&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;201&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;city03&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">75.059227</span>, <span class="number">35.564894</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">3000</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;CA&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><h3 id="R-ead">&quot;R&quot;ead</h3><ul><li>find command</li><li>find – returns a <strong>cursor</strong> object – allows us to <strong>iterate</strong> over the selected document(s)</li><li>Can be used with <strong>query</strong> criteria</li></ul><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># basic find</span></span><br><span class="line">db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;BALTIMORE&quot;</span>)</span><br><span class="line"><span class="comment"># find first</span></span><br><span class="line">db[<span class="symbol">:zips</span>].find.first</span><br><span class="line">db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span>).first</span><br><span class="line"><span class="comment"># find with double conditions and count them</span></span><br><span class="line">db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&quot;NY&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;GERMANTOWN&quot;</span>).count</span><br><span class="line"><span class="comment"># find distinct</span></span><br><span class="line">db[<span class="symbol">:zips</span>].find.distinct(<span class="symbol">:state</span>)</span><br></pre></td></tr></table></figure><p>使用 pp (pretty-printing) 来美化命令行的输出，并且找到符合条件的第一项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :035 &gt;</span> <span class="keyword">require</span> <span class="string">&#x27;pp&#x27;</span></span><br><span class="line"> =&gt; true</span><br><span class="line"><span class="meta prompt_">2.3.3 :036 &gt;</span> pp db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&quot;NY&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;GERMANTOWN&quot;</span>).first</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;12526&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;GERMANTOWN&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">73.862451</span>, <span class="number">42.1219</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4061</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;NY&quot;</span>&#125;</span><br><span class="line"> =&gt; &#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;12526&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;GERMANTOWN&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">73.862451</span>, <span class="number">42.1219</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4061</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;NY&quot;</span>&#125;</span><br></pre></td></tr></table></figure><h4 id="Cursor-Iterations">Cursor Iterations</h4><p>print all</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db[<span class="symbol">:zips</span>].find().each &#123; |<span class="params">r</span>| puts r &#125;</span><br></pre></td></tr></table></figure><p>pretty printing</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">require</span> <span class="string">&#x27;pp&#x27;</span></span><br><span class="line">db[<span class="symbol">:zips</span>].find().each &#123; |<span class="params">r</span>| pp r &#125;</span><br></pre></td></tr></table></figure><h4 id="Projection">Projection</h4><ul><li><strong>Limits</strong> the fields to return from all matching documents</li><li>We can <strong>specify</strong> inclusion or exclusion.</li><li>_id is automatically included by default.</li><li>true or 1: inclusive</li><li>false or 0: exclusive</li></ul><p>限制返回项的输出，例如只输出 <code>state</code> 属性，<code>_id</code> 是默认输出的，我们也可以取消输出 <code>_id</code> 项。</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :040 &gt;</span> db[<span class="symbol">:zips</span>].find(&#123;<span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span>&#125;).projection(<span class="symbol">state:</span><span class="literal">true</span>).first</span><br><span class="line"> =&gt; &#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;20331&quot;</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MD&quot;</span>&#125;</span><br><span class="line"><span class="meta prompt_">2.3.3 :041 &gt;</span> db[<span class="symbol">:zips</span>].find(&#123;<span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span>&#125;).projection(<span class="symbol">state:</span><span class="literal">true</span>, <span class="symbol">_id:</span><span class="literal">false</span>).first</span><br><span class="line"> =&gt; &#123;<span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MD&quot;</span>&#125;</span><br></pre></td></tr></table></figure><h2 id="Paging">Paging</h2><ul><li>Paging is accomplished with skip and limit</li><li><code>skip(n)</code> - tells mongodb that it should skip ‘n’ results</li><li><code>limit(n)</code> - instructs mongodb that it should limit the result length to ‘n’ results</li></ul><h3 id="limit">limit</h3><p>限制搜索只有三项输出</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :044 &gt;</span> db[<span class="symbol">:zips</span>].find.limit(<span class="number">3</span>).each&#123; |<span class="params">r</span>| pp r&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01001&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AGAWAM&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.622739</span>, <span class="number">42.070206</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">15338</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01002&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;CUSHMAN&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.51565</span>, <span class="number">42.377017</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">36963</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01005&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BARRE&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.108354</span>, <span class="number">42.409698</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4546</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"> =&gt; #&lt;Enumerator: #&lt;Mongo::Cursor:0x70333637935980 @view=#&lt;Mongo::Collection::View:0x70333616708020 namespace=&#x27;test.zips&#x27; @filter=&#123;&#125; @options=&#123;&quot;limit&quot;=&gt;<span class="number">3</span>&#125;&gt;&gt;<span class="symbol">:each&gt;</span></span><br></pre></td></tr></table></figure><h3 id="skip">skip</h3><p>跳过前三项，显示后三项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :045 &gt;</span> db[<span class="symbol">:zips</span>].find.skip(<span class="number">3</span>).limit(<span class="number">3</span>).each &#123; |<span class="params">r</span>| pp r&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01007&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BELCHERTOWN&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.410953</span>, <span class="number">42.275103</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">10579</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01008&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BLANDFORD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.936114</span>, <span class="number">42.182949</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">1240</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01010&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BRIMFIELD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.188455</span>, <span class="number">42.116543</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">3706</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"> =&gt; #&lt;Enumerator: #&lt;Mongo::Cursor:0x70333639364960 @view=#&lt;Mongo::Collection::View:0x70333639398700 namespace=&#x27;test.zips&#x27; @filter=&#123;&#125; @options=&#123;&quot;skip&quot;=&gt;<span class="number">3</span>, <span class="string">&quot;limit&quot;</span>=&gt;<span class="number">3</span>&#125;&gt;&gt;<span class="symbol">:each&gt;</span></span><br></pre></td></tr></table></figure><h3 id="Sort">Sort</h3><p>sort - Specifies the order in which the query returns matching documents.</p><p><code>&#123; field: value &#125;</code></p><p>1 for Ascending, -1 for Descending.</p><p>搜索后以城市名升序输出，若改为<code>&#123;:city =&gt; -1 &#125;</code>则为逆序输出。</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :046 &gt;</span> db[<span class="symbol">:zips</span>].find.limit(<span class="number">3</span>).sort(&#123;<span class="symbol">:city</span> =&gt; <span class="number">1</span> &#125;).each &#123; |<span class="params">r</span>| pp r&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;42601&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AARON&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">85.199114</span>, <span class="number">36.812827</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">270</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;KY&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;16820&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AARONSBURG&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">77.387977</span>, <span class="number">40.876944</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">100</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;PA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;31794&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;ABAC&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">83.498867</span>, <span class="number">31.451722</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">27906</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;GA&quot;</span>&#125;</span><br><span class="line"> =&gt; #&lt;Enumerator: #&lt;Mongo::Cursor:0x70333639793460 @view=#&lt;Mongo::Collection::View:0x70333626982300 namespace=&#x27;test.zips&#x27; @filter=&#123;&#125; @options=&#123;&quot;limit&quot;=&gt;<span class="number">3</span>, <span class="string">&quot;sort&quot;</span>=&gt;&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="number">1</span>&#125;&#125;&gt;&gt;<span class="symbol">:each&gt;</span></span><br></pre></td></tr></table></figure><h2 id="Advanced-Find">Advanced Find</h2><p>Find By Criteria</p><ul><li>‘lt’ &amp; ‘gt’</li><li>Evaluations</li><li>Regex</li><li>Exists</li><li>Not</li><li>Type</li></ul><h3 id="lt-and-gt">lt and gt</h3><p>lt -&gt; less than<br>gt -&gt; great than</p><p>找到城市名比「P」小的，且比「B」大的三项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :049 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; &#123;<span class="symbol">:</span><span class="variable">$lt</span> =&gt; <span class="string">&#x27;P&#x27;</span>, <span class="symbol">:</span><span class="variable">$gt</span> =&gt; <span class="string">&#x27;B&#x27;</span>&#125;).limit(<span class="number">3</span>).to_a.each &#123; |<span class="params">r</span>| pp r&#125;;<span class="literal">nil</span></span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01002&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;CUSHMAN&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.51565</span>, <span class="number">42.377017</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">36963</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01005&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BARRE&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.108354</span>, <span class="number">42.409698</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4546</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01007&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BELCHERTOWN&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.410953</span>, <span class="number">42.275103</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">10579</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">=&gt; nil</span><br></pre></td></tr></table></figure><p>小 Tips：<br>我们已经用 pp 来格式化输出了，可以在语句结尾加<code>;nil</code>来精简额外的输出，如上所示。</p><h3 id="Regex">Regex</h3><p>Regex – supports regular expression capabilities for pattern matching <strong>strings</strong> in queries.</p><p>用正则表达式匹配城市名含有 X 的项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :052 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; &#123;<span class="symbol">:</span><span class="variable">$regex</span> =&gt; <span class="string">&#x27;X&#x27;</span>&#125;).limit(<span class="number">3</span>).each &#123;|<span class="params">r</span>| pp r&#125;;<span class="literal">nil</span></span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01240&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;LENOX&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">73.271322</span>, <span class="number">42.364241</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">5001</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01537&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;NORTH OXFORD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">71.885953</span>, <span class="number">42.16549</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">3031</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01540&quot;</span>,</span><br><span class="line"> <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;OXFORD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">71.868677</span>, <span class="number">42.11285</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">9557</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"> =&gt; nil</span><br></pre></td></tr></table></figure><p><code>X&amp;</code> :Displays cities ending with X<br><code>^X</code> :Displays cities starting with X<br><code>^[A- E]</code> :Displays cities that match the regex (A to E)</p><h3 id="exist">$exist</h3><p>Will check to see of the document exists when the boolean is <strong>true</strong></p><p>mongo 中的 documents 可能会没有一些 values，我们可以用<code>$exist</code>来找出存在相关 values 的项。</p><p>找出 <code>:city</code> 属性不为空的三项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :053 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; &#123;<span class="symbol">:</span><span class="variable">$exists</span> =&gt; <span class="literal">true</span>&#125;).projection(&#123;<span class="symbol">:_id</span> =&gt; <span class="literal">false</span>&#125;).limit(<span class="number">3</span>).to_a.each &#123;|<span class="params">r</span>| pp r&#125;</span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AGAWAM&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.622739</span>, <span class="number">42.070206</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">15338</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;CUSHMAN&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.51565</span>, <span class="number">42.377017</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">36963</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BARRE&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.108354</span>, <span class="number">42.409698</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4546</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AGAWAM&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.622739</span>, <span class="number">42.070206</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">15338</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;, &#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;CUSHMAN&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.51565</span>, <span class="number">42.377017</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">36963</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;, &#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BARRE&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.108354</span>, <span class="number">42.409698</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4546</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><h3 id="not">$not</h3><p><code>$not</code> performs a logical NOT operation<br>Selects the documents that do not match the <operator- expression></p><p>找到 :pop 属性不大于9500的项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :012 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:pop</span> =&gt;&#123;<span class="string">&#x27;$not&#x27;</span> =&gt; &#123;<span class="string">&#x27;$gt&#x27;</span> =&gt; <span class="number">9500</span>&#125;&#125;).projection(&#123;<span class="symbol">_id:</span><span class="literal">false</span>&#125;).limit(<span class="number">3</span>).to_a.each &#123;|<span class="params">r</span>| pp r&#125;;<span class="literal">nil</span></span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BARRE&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.108354</span>, <span class="number">42.409698</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">4546</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BLANDFORD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.936114</span>, <span class="number">42.182949</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">1240</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;BRIMFIELD&quot;</span>,</span><br><span class="line"> <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.188455</span>, <span class="number">42.116543</span>],</span><br><span class="line"> <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">3706</span>,</span><br><span class="line"> <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"> =&gt; nil</span><br></pre></td></tr></table></figure><h3 id="type">$type</h3><p><code>$type</code> - selects the documents where the value of the field is an instance of the specified numeric BSON type.</p><p>简而言之，<code>$type</code>的作用就是验证相关属性的类型。这在我们不知道数据类型的时候很好用，我们可以在 <a href="https://docs.mongodb.com/manual/reference/bson-types/">BSON Types</a> 页面中查看各数字代表着什么，例如 1 代表 Double 类型，2 代表 String 类型。</p><p>在下面示例中，<code>:state</code> 属性应是 String 类型，所以 <code>:&amp;type =&gt; 2</code> 会有结果。<code>:&amp;type =&gt; 1</code>没有结果，是因为 <code>:state</code> 不是 Double 类型。</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :014 &gt;</span> db[<span class="symbol">:zips</span>].find(&#123;<span class="symbol">:state=&gt;</span> &#123;<span class="symbol">:</span><span class="variable">$type</span> =&gt; <span class="number">2</span>&#125;&#125;).first</span><br><span class="line"> =&gt; &#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;01001&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;AGAWAM&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">72.622739</span>, <span class="number">42.070206</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">15338</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MA&quot;</span>&#125;</span><br><span class="line"><span class="meta prompt_">2.3.3 :015 &gt;</span> db[<span class="symbol">:zips</span>].find(&#123;<span class="symbol">:state=&gt;</span> &#123;<span class="symbol">:</span><span class="variable">$type</span> =&gt; <span class="number">1</span>&#125;&#125;).first</span><br><span class="line"> =&gt; nil</span><br></pre></td></tr></table></figure><h2 id="Replace-Update-and-Delete">Replace, Update and Delete</h2><p>介绍 Replace 和 CRUD 的后两项操作</p><ul><li>replace_one</li><li>update_one</li><li>update_many</li><li>delete_one</li><li>delete_many</li><li>upsert</li></ul><h3 id="Replace">Replace</h3><h4 id="replace-one">replace_one</h4><p>replace_one – <strong>Replace</strong> a document in the collection according to the <strong>specified parameters</strong>.</p><p>替换<code>:_id =&gt; &quot;101&quot;</code>项的所有信息</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :017 &gt;</span> db[<span class="symbol">:zips</span>].insert_one(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;citytemp&quot;</span>, <span class="symbol">:loc</span> =&gt; [ -<span class="number">76.05922700000001</span>, <span class="number">39.564894</span> ], <span class="symbol">:pop</span> =&gt; <span class="number">4678</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span> )</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70126374694180 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span>018 &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt;<span class="string">&quot;101&quot;</span>).replace_one(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>, <span class="symbol">:city</span> =&gt; <span class="string">&quot;city02&quot;</span>, <span class="symbol">:loc</span> =&gt; [ -<span class="number">78.22</span>, <span class="number">36.22</span> ], <span class="symbol">:pop</span> =&gt; <span class="number">2000</span>, <span class="symbol">:state</span> =&gt; <span class="string">&quot;MD&quot;</span> )</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70126384942080 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;nModified&quot;</span>=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span>019 &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;101&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;city02&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">78.22</span>, <span class="number">36.22</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">2000</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MD&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><h3 id="U-pdate">&quot;U&quot;pdate</h3><h4 id="update-one">update_one</h4><p>update_one – <strong>Update</strong> a <strong>single</strong> document in the collection according to the <strong>specified arguments</strong>.</p><p>只更新单项中的属性，例如改城市名或同时更改多个属性</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :022 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>).update_one(<span class="symbol">:</span><span class="variable">$set</span> =&gt; &#123;<span class="symbol">:city</span> =&gt; <span class="string">&quot;name2&quot;</span>&#125;)</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70126374531300 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;nModified&quot;</span>=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">023</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;101&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;name2&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[-<span class="number">78.22</span>, <span class="number">36.22</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">2000</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;MD&quot;</span>&#125;]</span><br><span class="line"><span class="meta prompt_">2.3.3 :024 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>).update_one(<span class="symbol">:</span><span class="variable">$set</span> =&gt; &#123;<span class="symbol">:city</span> =&gt; <span class="string">&quot;name3&quot;</span>, <span class="symbol">:loc</span> =&gt; [ <span class="number">11.11</span>, <span class="number">11.11</span> ]&#125;)</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70126373842240 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;nModified&quot;</span>=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">025</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:_id</span> =&gt; <span class="string">&quot;101&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="string">&quot;101&quot;</span>, <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;name3&quot;</span>, <span class="string">&quot;loc&quot;</span>=&gt;[<span class="number">11.11</span>, <span class="number">11.11</span>], <span class="string">&quot;pop&quot;</span>=&gt;<span class="number">2000</span>, <span class="string">&quot;state&quot;</span>=&gt;<span class="string">&quot;XX&quot;</span>&#125;]</span><br></pre></td></tr></table></figure><h4 id="update-many">update_many</h4><p>update_many – <strong>Updates single or multiple</strong> documents in the collection according to the <strong>specified arguments</strong>.</p><p>更新多项属性，例如把所有州名为<code>MD</code>的项中的州名更改为<code>XX</code></p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :026 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;MD&#x27;</span>).count</span><br><span class="line"> =&gt; <span class="number">422</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :027 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;MD&#x27;</span>).update_many(<span class="symbol">:</span><span class="variable">$set</span> =&gt; &#123;<span class="symbol">:state</span> =&gt; <span class="string">&#x27;XX&#x27;</span>&#125;)</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70126374300980 documents=[&#123;&quot;n&quot;=&gt;<span class="number">422</span>, <span class="string">&quot;nModified&quot;</span>=&gt;<span class="number">422</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span>028 &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;MD&#x27;</span>).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :029 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;XX&#x27;</span>).count</span><br><span class="line"> =&gt; <span class="number">422</span></span><br></pre></td></tr></table></figure><h3 id="D-elete">&quot;D&quot;elete</h3><h4 id="delete-one">delete_one</h4><p>delete_one – will <strong>delete</strong> a <strong>single</strong> document in the collection according to the <strong>specified arguments</strong>.</p><p>删除之前添加的城市名为 <code>name3</code> 的项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :005 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&#x27;name3&#x27;</span>).count</span><br><span class="line"> =&gt; <span class="number">1</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :006 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&#x27;name3&#x27;</span>).delete_one()</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70293222869840 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">007</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&#x27;name3&#x27;</span>).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br></pre></td></tr></table></figure><h4 id="delete-many">delete_many</h4><p>delete_many – <strong>deletes single or multiple</strong> documents in the collection according to the <strong>specified arguments</strong>.</p><p>删除所有州名为<code>XX</code>的项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :009 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;XX&#x27;</span> ).count</span><br><span class="line"> =&gt; <span class="number">421</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :010 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;XX&#x27;</span> ).delete_many()</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70293222986380 documents=[&#123;&quot;n&quot;=&gt;<span class="number">421</span>, <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">011</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:state</span> =&gt; <span class="string">&#x27;XX&#x27;</span> ).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br></pre></td></tr></table></figure><h3 id="upsert">upsert</h3><p>If upsert is <strong>true</strong> and <strong>no</strong> document matches the query criteria, update() inserts a <strong>single</strong> document.</p><p>如果把 upsert （更新插入）设为 true，即使没有找到相关的项（条件不满足），也会执行 <code>update_one</code>来创建一个项</p><figure class="highlight irb"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">2.3.3 :014 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE1&quot;</span>).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :015 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE2&quot;</span>).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :016 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt;<span class="string">&quot;ODENVILLE1&quot;</span>).update_one(&#123;<span class="symbol">:</span><span class="variable">$set</span> =&gt; &#123;<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE2&quot;</span>&#125;&#125;, <span class="symbol">:upsert</span> =&gt; <span class="literal">true</span>)</span><br><span class="line"> =&gt; #&lt;Mongo::Operation::Result:70293217700720 documents=[&#123;&quot;n&quot;=&gt;<span class="number">1</span>, <span class="string">&quot;nModified&quot;</span>=&gt;<span class="number">0</span>, <span class="string">&quot;upserted&quot;</span>=&gt;[&#123;<span class="string">&quot;index&quot;</span>=&gt;<span class="number">0</span>, <span class="string">&quot;_id&quot;</span>=&gt;<span class="variable constant_">BSON</span><span class="symbol">:</span><span class="symbol">:ObjectId</span>(<span class="string">&#x27;58a5746b62b11bdc607226a8&#x27;</span>)&#125;], <span class="string">&quot;ok&quot;</span>=&gt;<span class="number">1.0</span>&#125;]&gt;</span><br><span class="line"><span class="number">2.3</span>.<span class="number">3</span> <span class="symbol">:</span><span class="number">017</span> &gt; db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE1&quot;</span>).count</span><br><span class="line"> =&gt; <span class="number">0</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :018 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE2&quot;</span>).count</span><br><span class="line"> =&gt; <span class="number">1</span></span><br><span class="line"><span class="meta prompt_">2.3.3 :019 &gt;</span> db[<span class="symbol">:zips</span>].find(<span class="symbol">:city</span> =&gt; <span class="string">&quot;ODENVILLE2&quot;</span>).to_a</span><br><span class="line"> =&gt; [&#123;<span class="string">&quot;_id&quot;</span>=&gt;<span class="variable constant_">BSON</span><span class="symbol">:</span><span class="symbol">:ObjectId</span>(<span class="string">&#x27;58a5746b62b11bdc607226a8&#x27;</span>), <span class="string">&quot;city&quot;</span>=&gt;<span class="string">&quot;ODENVILLE2&quot;</span>&#125;]</span><br></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> MongoDB </tag>
            
            <tag> Ruby </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>iOS 动画笔记</title>
      <link href="/2017/01/22/ios-animations-note/"/>
      <url>/2017/01/22/ios-animations-note/</url>
      
        <content type="html"><![CDATA[<p><em>大坑未填完，发布下博文降低罪恶感…</em></p><blockquote><p>人生的意义就是不断地挖坑，并不断地去填补。<br>——猫冬</p></blockquote><p>本文为《iOS Animations by Tutorials》笔记上篇，代码用 swift 3 编写。</p><span id="more"></span><h2 id="动画属性">动画属性</h2><h3 id="简单的动画">简单的动画</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@IBOutlet</span> <span class="keyword">var</span> heading: <span class="type">UILabel</span>!</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidAppear(animated)</span><br><span class="line">      <span class="type">UIView</span>.animate(withDuration: <span class="number">0.5</span>, delay: <span class="number">0.3</span>, options: [], animations: &#123;</span><br><span class="line">    <span class="comment">// 动画后位置 </span></span><br><span class="line">    <span class="keyword">self</span>.heading.center.x <span class="operator">+=</span> <span class="keyword">self</span>.view.bounds.width</span><br><span class="line">  &#125;, completion: <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewWillAppear(animated)</span><br><span class="line">  <span class="comment">// 动画前位置</span></span><br><span class="line">  heading.center.x <span class="operator">-=</span> view.bounds.width</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面这段代码的作用是把在屏幕左边的标题通过动画移动到相应位置。</p><p><img src="http://img.frankorz.com/58861fddb3f39.jpg" alt=""></p><p>这里使用了动画方法：<code>animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)</code></p><ul><li>withDuration: 动画持续时间。</li><li>delay: 动画开始前的延迟时间，值为0时动画效果立刻显示。</li><li>options: 表现动画的一系列方式，如淡入淡出等，可以直接用[]表示无动画选项。</li><li>animations: 代码块中提供你的动画效果，注意代码块中无参数和返回值。</li><li>completion: 动画执行后要干什么。</li></ul><h3 id="位置和大小">位置和大小</h3><p><img src="http://img.frankorz.com/58861fddc0e47.jpg" alt=""></p><p>我们可以通过改变一个 view 的位置和 frame 来实现放大、收缩或者移动效果。</p><ul><li><strong>frame</strong>: 该 view 在父view坐标系统中的位置和大小。</li><li><strong>bounds</strong>: 该 view 在本地坐标系统中的位置和大小。</li><li><strong>center</strong>: 想要移动 view 到一个新位置时改变该属性。</li></ul><p><img src="http://img.frankorz.com/58861fddc1903.jpg" alt=""></p><p>关于frame 和 bounds 的更详细的区别可以参考<a href="http://blog.csdn.net/mad1989/article/details/8711697">ios view的frame和bounds之区别（位置和大小）</a>。</p><h3 id="外观">外观</h3><p><img src="http://img.frankorz.com/58861fddc6124.jpg" alt=""></p><p>我们可以通过改变背景色或透明度来改变 view 的外观。</p><ul><li><strong>backgroundColor</strong>: 改变背景颜色</li><li><strong>alpha</strong>: 改变该属性来创造淡入淡出效果</li></ul><h3 id="形变">形变</h3><p><img src="http://img.frankorz.com/58861fddbab3c.jpg" alt=""></p><ul><li><strong>transform</strong>: 在动画块中改变该属性可以去使 view 旋转、改变 view 大小或位置。</li></ul><h2 id="动画选项">动画选项</h2><p>options 能让你知道 UIKit 是怎么创建我们的动画的，下面是一系列声明在 UIViewAnimationOptions 集中的动画选项，可以以不同形式来结合，并在我们的动画中使用。</p><h3 id="重复">重复</h3><ul><li>.repeat: 这选项能让你的动画循环起来。</li><li>.autoreverse: 这选项只能和 <code>.repeat</code> 放在一起使用，作用是重复播放动画，然后逆向播放动画。</li></ul><p><img src="http://img.frankorz.com/58861fddb971c.gif" alt="repeat and .autoreverse"></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">UIView</span>.animate(withDuration: <span class="number">0.5</span>, delay: <span class="number">0.4</span>,</span><br><span class="line">               options: [.repeat, .autoreverse], animations: &#123;</span><br><span class="line">  <span class="keyword">self</span>.password.center.x <span class="operator">+=</span> <span class="keyword">self</span>.view.bounds.width</span><br><span class="line">&#125;, completion: <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><h3 id="动画缓动">动画缓动</h3><p><img src="http://img.frankorz.com/58861fde014a0.jpg" alt=""></p><p>就像火车出站的时候会加速，到站时会减速一样，我们也可以给动画加上缓入缓出效果。</p><ul><li>.curveLinear: 没有加速也没有减速。</li><li>.curveEaseIn: 在动画开始时加速。</li><li>.curveEaseOut: 在动画结束时减速。</li><li>.curveEaseInOut: 动画开始时加速，结束时减速。</li></ul><p><img src="http://img.frankorz.com/58861fe7dfc4a.gif" alt=".curveEaseInOut"></p><h2 id="弹性">弹性</h2><p><img src="http://img.frankorz.com/58861fde2ce7d.jpg" alt=""></p><p><img src="http://img.frankorz.com/58861fde788e9.jpg" alt=""></p><p>像弹簧一样，动画也能实现这种来回弹动，最终停止在 point B 的效果。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewWillAppear(animated)</span><br><span class="line"> </span><br><span class="line">  loginButton.center.y <span class="operator">+=</span> <span class="number">30.0</span></span><br><span class="line">  loginButton.alpha <span class="operator">=</span> <span class="number">0.0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidAppear(animated)</span><br><span class="line"></span><br><span class="line">  <span class="type">UIView</span>.animate(withDuration: <span class="number">0.5</span>, delay: <span class="number">0.5</span>, usingSpringWithDamping: <span class="number">0.5</span>, </span><br><span class="line">    initialSpringVelocity: <span class="number">0.0</span>, options: [], animations: &#123; </span><br><span class="line">    <span class="keyword">self</span>.loginButton.center.y <span class="operator">-=</span> <span class="number">30.0</span></span><br><span class="line">    <span class="keyword">self</span>.loginButton.alpha <span class="operator">=</span> <span class="number">1.0</span></span><br><span class="line">  &#125;, completion: <span class="literal">nil</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/58861fe869a3a.gif" alt="spring"></p><p>这里用了新的动画方法：<code>animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)</code></p><ul><li>usingSpringWithDamping: 控制阻尼系数，接受值在0.0和1.0之间，靠近0.0能创建一个有弹性的动画，靠近1.0能创建一个僵硬的效果。</li><li>initialSpringVelocity: 控制动画的速率，表示在一秒中动画所走的距离占总距离的比率。例如，动画中的距离为200 points，我们想达到100 pt/s的速度，则用0.5做值。数值越大移动越快。</li></ul><h2 id="过渡">过渡</h2><p>当你想通过动画来增加一个 view 或移除一个 view 的时候，你仍然可以使用前面提到的方法，这里将会告诉你如何使用过渡（transitions）来动画显示 view 中一系列的改变。</p><p>Transitions are predefined animations you can apply to views. These predefined animations don’t attempt to interpolate between the start and end states of your view. Instead, you’ll design the animations so that the various changes in state appear natural.</p><h3 id="增加一个-view">增加一个 view</h3><p><img src="http://img.frankorz.com/58861fdf00ba2.jpg" alt=""></p><p>和前面一样，我们也是调用类似的方法，不同的是这次将会选择一个预先定义好的过渡效果并在 <strong>animation container view</strong> 中显示动画。</p><p>过渡通过 container view 来显示，在动画播放的时候其他新的 view 将会作为子view加入其中。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> animationContainerView: <span class="type">UIView</span>!</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123; </span><br><span class="line">  <span class="comment">//创建 animation container </span></span><br><span class="line">  animationContainerView <span class="operator">=</span> <span class="type">UIView</span>(frame: view.bounds)</span><br><span class="line">  animationContainerView.frame <span class="operator">=</span> view.bounds</span><br><span class="line">  view.addSubview(animationContainerView<span class="operator">!</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123; </span><br><span class="line">  <span class="keyword">super</span>.viewDidAppear(animated)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 创建新的 view </span></span><br><span class="line">  <span class="keyword">let</span> newView <span class="operator">=</span> <span class="type">UIImageView</span>(image: <span class="type">UIImage</span>(named: <span class="string">&quot;banner&quot;</span>)<span class="operator">!</span>)</span><br><span class="line">  newView.center <span class="operator">=</span> animationContainerView.center</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 通过过渡把新的 view 加入其中</span></span><br><span class="line">  <span class="type">UIView</span>.transition(with: animationContainerView, duration: <span class="number">0.33</span>,</span><br><span class="line">                    options: [.curveEaseOut, .transitionFlipFromBottom],</span><br><span class="line">                    animations: &#123;</span><br><span class="line">                      <span class="keyword">self</span>.animationContainerView.addSubview(newView)</span><br><span class="line">  &#125;, completion: <span class="literal">nil</span> )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码可以看到，过渡的方法比之前看过的方法多了一个参数「view」，就是指要把过渡效果加入其中的 container view。另外未见过的还有<code>.transitionFlipFromBottom</code>，这也是定义好的过渡选项，显示的是一个底部翻转的效果，下面还列出所有的过渡动画选项。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">.transitionFlipFromLeft </span><br><span class="line">.transitionFlipFromRight </span><br><span class="line">.transitionCurlUp </span><br><span class="line">.transitionCurlDown </span><br><span class="line">.transitionCrossDissolve </span><br><span class="line">.transitionFlipFromTop </span><br><span class="line">.transitionFlipFromBottom</span><br></pre></td></tr></table></figure><h3 id="移除-view">移除 view</h3><p><img src="http://img.frankorz.com/58861fe7e37f4.jpg" alt=""></p><p>代码和增加 view 类似，不过这次调用的是<code>removeFromSuperview()</code>方法。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">UIView</span>.transition(with: animationContainerView, duration: <span class="number">0.33</span>,</span><br><span class="line">                  options: [.curveEaseOut, .transitionFlipFromBottom],</span><br><span class="line">                  animations: &#123;</span><br><span class="line">                    <span class="keyword">self</span>.newView.removeFromSuperview()</span><br><span class="line">&#125;, completion: <span class="literal">nil</span> )</span><br></pre></td></tr></table></figure><h3 id="隐藏-显示-view">隐藏/显示 view</h3><p><img src="http://img.frankorz.com/58861fe7c75a2.jpg" alt=""></p><p>到目前，我们只知道过渡效果要区分 view 的层次，这也是为什么我们需要一个 container view 的原因。对于隐藏或显示一个 view 的时候，我们可以通过用要隐藏或显示的 view 来当做动画容器（animation container）。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过过渡隐藏 newView，</span></span><br><span class="line"><span class="type">UIView</span>.transition(with: <span class="keyword">self</span>.newView, duration: <span class="number">0.33</span>,</span><br><span class="line">                  options: [.curveEaseOut, .transitionFlipFromBottom],</span><br><span class="line">                  animations: &#123;</span><br><span class="line">                    <span class="keyword">self</span>.newView.isHidden <span class="operator">=</span> <span class="literal">true</span></span><br><span class="line">&#125;, completion: <span class="literal">nil</span> )</span><br></pre></td></tr></table></figure><h3 id="用一个-view-来取代另一个-view">用一个 view 来取代另一个 view</h3><p><img src="http://img.frankorz.com/58861fe7cf2bb.jpg" alt=""></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从 oldView 变为 newView</span></span><br><span class="line"><span class="type">UIView</span>.transition(from: oldView, to: newView, duration: <span class="number">0.33</span>, </span><br><span class="line">  options: .transitionFlipFromTop, completion: <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><h2 id="更酷的动画">更酷的动画</h2><ol><li><strong>Crossfade animation:</strong> 淡入淡出动画，把一张图片与另一张图片混合的动画效果。</li><li><strong>Cube transition animation:</strong> 立方体过渡动画，创建伪 3D 的过渡效果。</li><li><strong>Fade and bounce transition:</strong> 淡入淡出和反弹过渡，与简单动画的结合有一些不同。</li></ol><h3 id="Crossfade-animation">Crossfade animation</h3><p><img src="http://img.frankorz.com/58861fe7e4ebb.jpg" alt=""></p><p>如果需要把一张图片直接变成另外一张图片，之前的淡入淡出就不太好用了，因为能看到中间的淡入淡出效果。</p><hr><p>楼主的坑已经不想填了，看到这的朋友可以参考：<br><a href="https://zsisme.gitbooks.io/ios-/content/">iOS 核心动画高级技巧</a> ：<a href="http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC/ref=sr_1_1?ie=UTF8&amp;qid=1423192842&amp;sr=8-1&amp;keywords=Core+Animation+Advanced+Techniques">iOS Core Animation: Advanced Techniques</a> 的译本，由 OC 编写</p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Swift </tag>
            
            <tag> iOS </tag>
            
            <tag> 动画 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Core Data 笔记[上篇]</title>
      <link href="/2017/01/16/core-data-note-1/"/>
      <url>/2017/01/16/core-data-note-1/</url>
      
        <content type="html"><![CDATA[<blockquote><p>Core Data 应该被当成一个对象图管理系统来正确使用，得益于其内建的缓存和对象管理机制，它在很多方面比其他数据库实际上反而更快。抽象级别更高的 API 可以让你专注于优化 APP 里关键部分的性能，而不是从头一开始来实现如何持久化。</p><footer><strong>《Core Data》</strong><cite>ObjC中国</cite></footer></blockquote><p>本文为《Core Data by Tutorials》笔记上篇，代码用 swift 3 编写。等这系列写完会根据 ObjC 的《Core Data》 补充笔记，另外也推荐斯坦福课程 <a href="https://itunes.apple.com/us/course/developing-ios-9-apps-swift/id1104579961">Developing iOS 9 Apps with Swift</a> 中的第十课《Core Data》，用来大致地学习下 Core Data 重要的知识点，本文也有部分笔记参考了这课程。</p><p>下面的代码只给其中关键部分，请指教。由于笔记是给自己看的，部分地方可能会跳跃性比较大。</p><span id="more"></span><h2 id="入门">入门</h2><p>Core Data 是 Apple 为 iOS、OS X、watchOS 和 tvOS 而设计的对象图管理(object graph management)和数据持久化框架。</p><ul><li><strong>entity</strong> 是 Core Data 中的类定义，也称为「实体」。例如一个雇员或一个公司。在一个关系型数据库中，一个 entity 类似于一张表。</li><li><strong>attribute</strong> 是跟一个特定 entity 相关的一系列信息，例如一个 Employee entity 可以有属性如：雇员的名字，地址和薪水。在一个数据库中，一个 attribute 类似于一个表中特定的 field。</li><li><strong>relationship</strong> 是很多 entities 之间的连接，也称为「关系」。在 Core Data 中，两个 entities 之间的关系叫做<strong>一对一关系</strong>，一个和多个 entities 之间则称为<strong>一对多关系</strong>。例如，一个管理者和一组雇员可以有一个<strong>一对多关系</strong>，然而一个单独的雇员与他的上司会有一个<strong>一对一关系</strong>。</li></ul><h3 id="保存数据">保存数据</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">save</span>(<span class="params">name</span>: <span class="type">String</span>) &#123;</span><br><span class="line">  <span class="keyword">guard</span> <span class="keyword">let</span> appDelegate <span class="operator">=</span> <span class="type">UIApplication</span>.shared.delegate <span class="keyword">as?</span> <span class="type">AppDelegate</span> <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1</span></span><br><span class="line">  <span class="keyword">let</span> managedContext <span class="operator">=</span> appDelegate.persistentContainer.viewContext</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2</span></span><br><span class="line">  <span class="keyword">let</span> entity <span class="operator">=</span> <span class="type">NSEntityDescription</span>.entity(forEntityName: <span class="string">&quot;Person&quot;</span>, in: managedContext)<span class="operator">!</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> person <span class="operator">=</span> <span class="type">NSManagedObject</span>(entity: entity, insertInto: managedContext)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3</span></span><br><span class="line">  person.setValue(name, forKey: <span class="string">&quot;name&quot;</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> managedContext.save()</span><br><span class="line">    people.append(person)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not save. <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><p>从 Core Data 中保存或恢复数据之前，都要用到<code>NSManagedObjectContext</code>。managed object context 就像一个内存「暂存器」用来处理 managed objects。</p><p>把一个新的 managed object 加进一个 managed object context，如果满意这些修改，我们可以直接在 managed object context 中「commit」 这些修改然后保存起来。</p></li><li><p>创建一个新的 managedObject 然后保存到 context 中。</p></li><li><p>保存键值对。</p></li><li><p>commit 改动。</p></li></ol><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Save test bow tie</span></span><br><span class="line">  <span class="keyword">let</span> bowtie <span class="operator">=</span> <span class="type">NSEntityDescription</span>.insertNewObject(forEntityName: <span class="string">&quot;Bowtie&quot;</span>, into: <span class="keyword">self</span>.persistentContainer.viewContext) <span class="keyword">as!</span> <span class="type">Bowtie</span></span><br><span class="line">  bowtie.name <span class="operator">=</span> <span class="string">&quot;My bow tie&quot;</span></span><br><span class="line">  bowtie.lastWorn <span class="operator">=</span> <span class="type">NSDate</span>()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// Retrieve test bow tie</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> request <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">Bowtie</span>&gt;(entityName: <span class="string">&quot;Bowtie&quot;</span>)</span><br><span class="line">    <span class="keyword">let</span> ties <span class="operator">=</span> <span class="keyword">try</span> <span class="keyword">self</span>.persistentContainer.viewContext.fetch(request)</span><br><span class="line">    <span class="keyword">let</span> sample <span class="operator">=</span> ties.first</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Name: <span class="subst">\(sample<span class="operator">?</span>.name)</span>, Worn: <span class="subst">\(sample<span class="operator">?</span>.lastWorn)</span>&quot;</span>)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Fetching error: <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><h3 id="提取数据">提取数据</h3><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 viewDidLoad() 之前执行</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewWillAppear</span>(<span class="keyword">_</span> <span class="params">animated</span>: <span class="type">Bool</span>) &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewWillAppear(animated)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1 从 application delegate 中获取它 persistent container 的引用并得到 NSManagedObjectContext</span></span><br><span class="line">  <span class="keyword">guard</span> <span class="keyword">let</span> appDelegate <span class="operator">=</span> <span class="type">UIApplication</span>.shared.delegate <span class="keyword">as?</span> <span class="type">AppDelegate</span> <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> managedContext <span class="operator">=</span> appDelegate.persistentContainer.viewContext</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 FetchRequest 可以有不同方式去获取数据</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">NSManagedObject</span>&gt;(entityName: <span class="string">&quot;Person&quot;</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3 获取数据</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    people <span class="operator">=</span> <span class="keyword">try</span> managedContext.fetch(fetchRequest)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch. <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="NSManagedObject-子类">NSManagedObject 子类</h3><p>之前通过获取应用 delegate 的 managed object context 来获得访问权限，现在可以把 managed object context 当做一个属性在类和类之间传送。<br>这样 ViewController 可以不需要知道它来自哪就使用它，链式传递 context，这样能使代码变得简洁。</p><h3 id="传递-viewContext">传递 viewContext</h3><p>AppDelegate.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> window: <span class="type">UIWindow</span>?</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">application</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>, <span class="params">didFinishLaunchingWithOptions</span> <span class="params">launchOptions</span>: [<span class="params">UIApplicationLaunchOptionsKey</span>: <span class="keyword">Any</span>]<span class="operator">?</span>) -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line">  <span class="keyword">guard</span> <span class="keyword">let</span> vc <span class="operator">=</span> window<span class="operator">?</span>.rootViewController <span class="keyword">as?</span> <span class="type">ViewController</span> <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  vc.managedContext <span class="operator">=</span> persistentContainer.viewContext</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="搜索">搜索</h3><p>ViewController.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> managedContext: <span class="type">NSManagedObjectContext</span>!</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1 导入 plist 数据</span></span><br><span class="line">  insertSampleData()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 搜索条件</span></span><br><span class="line">  <span class="keyword">let</span> request <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">Bowtie</span>&gt;(entityName: <span class="string">&quot;Bowtie&quot;</span>)</span><br><span class="line">  <span class="keyword">let</span> firstTitle <span class="operator">=</span> segmentedControl.titleForSegment(at: <span class="number">0</span>)<span class="operator">!</span></span><br><span class="line">  request.predicate <span class="operator">=</span> <span class="type">NSPredicate</span>(format: <span class="string">&quot;searchKey == %@&quot;</span>, firstTitle)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="comment">// 3 获取数据 [Bowtie]</span></span><br><span class="line">    <span class="keyword">let</span> results <span class="operator">=</span> <span class="keyword">try</span> managedContext.fetch(request)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4 展示数据</span></span><br><span class="line">    populate(bowtie: results.first<span class="operator">!</span>)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Counld not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="校验数据">校验数据</h3><p><img src="http://img.frankorz.com/587cc4c02140a.jpg" alt=""></p><h2 id="Core-Data-Stack">Core Data Stack</h2><p>栈(Stack)由四个 Core Data 类组成：</p><ul><li>NSManagedObjectModel</li><li>NSPersistentStore</li><li>NSPersistentStoreCoordinator</li><li>NSManagedObjectContext</li></ul><p>清楚栈的工作是很有必要的，例如要从旧的数据库迁移数据。</p><p>下面是 objc 的《Core Data》中的一幅图，定义的有些不一样：</p><p><img src="http://img.frankorz.com/587e16f03b676.jpg" alt=""></p><h3 id="The-persistent-store">The persistent store</h3><ol><li><strong>NSQLiteStoreType</strong> 依靠一个 SQLite 数据库，这是仅有的 non-atomic 的 Core Data 支持的，轻量级、有效率的储存。通常 Xcode 的 Core Data 默认模板就是用这个。</li><li><strong>NSXMLStoreType</strong> 依靠一个 XML 文件，这让它有很好的可读性。store type 是 atomic 的，所以他可以有大储存 footprint。只在 OS X 上可用。</li><li><strong>NSBinaryStoreType</strong> 依靠一个二进制文件，像 NSXMLStoreType ，它的 store type 也是是 atomic 的，所以二进制文件必须被读取进内存，之后才能使用它。你将很少看到这类型的 persistent store。</li><li><strong>NSInMemoryStoreType</strong> 是 in-memory persistent store type。某种程度上讲，这不是真的可持久的，终止应用或关掉手机，存储在内存中的数据就会消失，尽管这像是跟 Core Data 的目的背道而驰，但这 in-memory persistent stores 对单元测试和某些缓存有帮助。</li></ol><h3 id="The-persistent-store-coordinator">The persistent store coordinator</h3><p>NSPersistentStoreCoordinator 是 managed object model 和 persistent store 的桥梁。它理解 NSManagedObjectModel，也知道怎么去从 NSPersistentStore 传消息和获取消息。</p><p>NSPersistentStoreCoordinator 同时隐藏了实现 persistent store 或 stores 配置的细节，有两个原因：</p><ol><li>NSManagedObjectContext 没必要知道怎么去保存到一个 SQLite 数据库、XML 文件或者一个定制增量存储(custom incremental store)。</li><li>如果你有多个 persistent stores，那么 persistent store coordinator 会提供一个统一的接口去管理 context。至于 context 的管理，它经常和一个单独的、聚合的 persistent store 交互。</li></ol><h3 id="The-managed-object-context">The managed object context</h3><p>日常使用中，你会经常使用 NSManagedObjectContext，可能只有在用 Core Data 使用一些更高级的功能时才会看到其他三个部分。</p><p>理解 context 如何工作也是很重要的：</p><ul><li>一个 context 是一个 in-memory 暂存器用来处理你的 managed object。</li><li>你会用一个 managed object context 来做所有关于 Core Data objects 的事情。</li><li>只当你在 context 上调用<code>save()</code>，所有的改动才会影响到储存卡中的数据。</li></ul><p>更重要的还有：</p><ul><li>context 管理着创建 objects 或获得 objects 的生命周期。这生命周期的管理包含很强大的功能如挑错、逆关系处理(inverse relationship handling) 和校验数据。</li><li>一个 managed object 不能离开相关的 context 而存在。实际上，一个 managed object 和它的 context 是很紧密联系在一起的，每个 managed object 都会有一个它的 context 的引用，例如：<br><code>let managedContext = employee.managedObjectContext</code></li><li>context 是区域性的。一旦一个 managed object 关联上一个特定的 context，它将会一直在它生命周期中关联同样的 context。</li><li>一个应用可以有多于一个 context。然而一个 context 是暂存在内存中的，你可以同时取出同样的 Core Data object 到两个不同的 context。</li><li><strong>context 不是线程安全的</strong>，这同样适用于一个 managed object。你只能在它们被创建的相同的线程中使用 context 和 managed objects 。</li></ul><h3 id="The-persistent-store-container">The persistent store container</h3><p>在 iOS 10 中，NSPersistentContainer 是一个新的类，它能管理所有四个 Core Data stack 类——the managed model, the store coordinator, the persistent store 和 managed context。</p><h3 id="实例：遛狗">实例：遛狗</h3><p>Dog Walk.xcdatamodeld<br><img src="http://img.frankorz.com/587cc4c298ae7.jpg" alt=""></p><p><img src="http://img.frankorz.com/587cc4c0b17f1.jpg" alt=""></p><p>其中狗对遛狗这个行为是一对多的关系，而遛狗行为对狗而言是一对一的关系，如下图：</p><p><img src="http://img.frankorz.com/587cc4c15c3e2.jpg" alt=""></p><p>新建一个Core Data Stack<br>CoreDataStack.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Foundation</span><br><span class="line"><span class="keyword">import</span> CoreData</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CoreDataStack</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">let</span> modelName: <span class="type">String</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">init</span>(<span class="params">modelName</span>: <span class="type">String</span>) &#123;</span><br><span class="line">    <span class="keyword">self</span>.modelName <span class="operator">=</span> modelName</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 只有这个不加 private 是因为 managed context 是 stack 的唯一的入口</span></span><br><span class="line">  <span class="keyword">lazy</span> <span class="keyword">var</span> managedContext: <span class="type">NSManagedObjectContext</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">self</span>.storeContainer.viewContext</span><br><span class="line">  &#125;()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">lazy</span> <span class="keyword">var</span> storeContainer: <span class="type">NSPersistentContainer</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="comment">// initialization</span></span><br><span class="line">    <span class="keyword">let</span> container <span class="operator">=</span> <span class="type">NSPersistentContainer</span>(name: <span class="keyword">self</span>.modelName)</span><br><span class="line">    <span class="comment">// 读取 persistent stores</span></span><br><span class="line">    container.loadPersistentStores &#123; (storeDescription, error) <span class="keyword">in</span></span><br><span class="line">      <span class="keyword">if</span> <span class="keyword">let</span> error <span class="operator">=</span> error <span class="keyword">as</span> <span class="type">NSError</span>? &#123;</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;Unresolved error <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> container</span><br><span class="line">  &#125;()</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">saveContext</span> () &#123;</span><br><span class="line">    <span class="keyword">guard</span> managedContext.hasChanges <span class="keyword">else</span> &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">      <span class="keyword">try</span> managedContext.save()</span><br><span class="line">    &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">      <span class="built_in">print</span>(<span class="string">&quot;Unresolved error <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>AppDelegate.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> UIKit</span><br><span class="line"><span class="keyword">import</span> CoreData</span><br><span class="line"></span><br><span class="line"><span class="keyword">@UIApplicationMain</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AppDelegate</span>: <span class="title class_">UIResponder</span>, <span class="title class_">UIApplicationDelegate</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> window: <span class="type">UIWindow</span>?</span><br><span class="line">  <span class="keyword">lazy</span> <span class="keyword">var</span> coreDataStack <span class="operator">=</span> <span class="type">CoreDataStack</span>(modelName: <span class="string">&quot;Dog Walk&quot;</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">application</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>, <span class="params">didFinishLaunchingWithOptions</span> <span class="params">launchOptions</span>: [<span class="params">UIApplicationLaunchOptionsKey</span>: <span class="keyword">Any</span>]<span class="operator">?</span>) -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> navController <span class="operator">=</span> window<span class="operator">?</span>.rootViewController <span class="keyword">as?</span> <span class="type">UINavigationController</span>,</span><br><span class="line">      <span class="keyword">let</span> viewController <span class="operator">=</span> navController.topViewController <span class="keyword">as?</span> <span class="type">ViewController</span> <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    viewController.managedContext <span class="operator">=</span> coreDataStack.managedContext</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// MARK: 进入后台前或终止前，应用会用CoreDataStack.swift 中的 saveContext() 保存数据变更</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">applicationDidEnterBackground</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>) &#123;</span><br><span class="line">    coreDataStack.saveContext()</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">applicationWillTerminate</span>(<span class="keyword">_</span> <span class="params">application</span>: <span class="type">UIApplication</span>) &#123;</span><br><span class="line">    coreDataStack.saveContext()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ViewController.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> UIKit</span><br><span class="line"><span class="keyword">import</span> CoreData</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ViewController</span>: <span class="title class_">UIViewController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// MARK: - Properties</span></span><br><span class="line">  <span class="keyword">lazy</span> <span class="keyword">var</span> dateFormatter: <span class="type">DateFormatter</span> <span class="operator">=</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> formatter <span class="operator">=</span> <span class="type">DateFormatter</span>()</span><br><span class="line">    formatter.dateStyle <span class="operator">=</span> .short</span><br><span class="line">    formatter.timeStyle <span class="operator">=</span> .medium</span><br><span class="line">    <span class="keyword">return</span> formatter</span><br><span class="line">  &#125;()</span><br><span class="line">  <span class="keyword">var</span> currentDog: <span class="type">Dog</span>?</span><br><span class="line">  <span class="keyword">var</span> managedContext: <span class="type">NSManagedObjectContext</span>!</span><br><span class="line"></span><br><span class="line">  <span class="comment">// MARK: - IBOutlets</span></span><br><span class="line">  <span class="keyword">@IBOutlet</span> <span class="keyword">var</span> tableView: <span class="type">UITableView</span>!</span><br><span class="line"></span><br><span class="line">  <span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">    <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line"></span><br><span class="line">    tableView.register(<span class="type">UITableViewCell</span>.<span class="keyword">self</span>, forCellReuseIdentifier: <span class="string">&quot;Cell&quot;</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">let</span> dogName <span class="operator">=</span> <span class="string">&quot;Fido&quot;</span></span><br><span class="line">    <span class="keyword">let</span> dogFetch: <span class="type">NSFetchRequest</span>&lt;<span class="type">Dog</span>&gt; <span class="operator">=</span> <span class="type">Dog</span>.fetchRequest()</span><br><span class="line">    dogFetch.predicate <span class="operator">=</span> <span class="type">NSPredicate</span>(format: <span class="string">&quot;%K == %@&quot;</span>, <span class="keyword">#keyPath</span>(<span class="type">Dog</span>.name), dogName)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">      <span class="keyword">let</span> results <span class="operator">=</span> <span class="keyword">try</span> managedContext.fetch(dogFetch)</span><br><span class="line">      <span class="keyword">if</span> results.count <span class="operator">&gt;</span> <span class="number">0</span> &#123;</span><br><span class="line">        <span class="comment">// Fido found, use Fido</span></span><br><span class="line">        currentDog <span class="operator">=</span> results.first</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// Fido not found, create Fido</span></span><br><span class="line">        currentDog <span class="operator">=</span> <span class="type">Dog</span>(context: managedContext)</span><br><span class="line">        currentDog<span class="operator">?</span>.name <span class="operator">=</span> dogName</span><br><span class="line">        <span class="keyword">try</span> managedContext.save()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">      <span class="built_in">print</span>(<span class="string">&quot;Fetch error: <span class="subst">\(error)</span> description: <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - IBActions</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">@IBAction</span> <span class="keyword">func</span> <span class="title function_">add</span>(<span class="keyword">_</span> <span class="params">sender</span>: <span class="type">UIBarButtonItem</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> walk <span class="operator">=</span> <span class="type">Walk</span>(context: managedContext)</span><br><span class="line">    walk.date <span class="operator">=</span> <span class="type">NSDate</span>()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 把新的 Walk 插进 Dog&#x27;s walks 中</span></span><br><span class="line"><span class="comment">//    if let dog = currentDog, let walks = dog.walks?.mutableCopy() as? NSMutableOrderedSet &#123;</span></span><br><span class="line"><span class="comment">//      walks.add(walk)</span></span><br><span class="line"><span class="comment">//      dog.walks = walks</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line">    <span class="comment">// 和上面注释的代码同样效果</span></span><br><span class="line">    currentDog<span class="operator">?</span>.addToWalks(walk)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 保存</span></span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">      <span class="keyword">try</span> managedContext.save()</span><br><span class="line">    &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">     <span class="built_in">print</span>(<span class="string">&quot;Save error: <span class="subst">\(error)</span> description: <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    tableView.reloadData()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: UITableViewDataSource</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span>: <span class="title class_">UITableViewDataSource</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">numberOfRowsInSection</span> <span class="params">section</span>: <span class="type">Int</span>) -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> walks <span class="operator">=</span> currentDog<span class="operator">?</span>.walks <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> walks.count</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">cellForRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) -&gt; <span class="type">UITableViewCell</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> cell <span class="operator">=</span> tableView.dequeueReusableCell(withIdentifier: <span class="string">&quot;Cell&quot;</span>, for: indexPath)</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> walk <span class="operator">=</span> currentDog<span class="operator">?</span>.walks<span class="operator">?</span>[indexPath.row] <span class="keyword">as?</span> <span class="type">Walk</span>,</span><br><span class="line">      <span class="keyword">let</span> walkDate <span class="operator">=</span> walk.date <span class="keyword">as?</span> <span class="type">Date</span> <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> cell</span><br><span class="line">    &#125;</span><br><span class="line">    cell.textLabel<span class="operator">?</span>.text <span class="operator">=</span> dateFormatter.string(from: walkDate)</span><br><span class="line">    <span class="keyword">return</span> cell</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">titleForHeaderInSection</span> <span class="params">section</span>: <span class="type">Int</span>) -&gt; <span class="type">String</span>? &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;List of Walks&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 删除数据</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">commit</span> <span class="params">editingStyle</span>: <span class="type">UITableViewCellEditingStyle</span>, <span class="params">forRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> walkToRemove <span class="operator">=</span> currentDog<span class="operator">?</span>.walks<span class="operator">?</span>[indexPath.row] <span class="keyword">as?</span> <span class="type">Walk</span>,</span><br><span class="line">      editingStyle <span class="operator">==</span> .delete <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// managed context 中 删除数据</span></span><br><span class="line">    managedContext.delete(walkToRemove)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">      <span class="keyword">try</span> managedContext.save()</span><br><span class="line">      <span class="comment">//表中删除行</span></span><br><span class="line">      tableView.deleteRows(at: [indexPath], with: .automatic)</span><br><span class="line">    &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">      <span class="built_in">print</span>(<span class="string">&quot;Saving error: <span class="subst">\(error)</span>, description: <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//tableView 左划删除</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">canEditRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) -&gt; <span class="type">Bool</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>总结：<br>从<code>CoreDataStack.swift</code>可以看到，我们新建了个 Core Data Stack 来管理 context，其中 managed context 由初始化后的 NSPersistentContainer 类实例的 <code>.viewContext</code> 属性获取。另外<code>AppDelegate.swift</code>也用到<code>CoreDataStack.swift</code>中的<code>saveContext()</code>方法来保存数据变更。至此，我们完成通过 Stack 来对数据增删改查，第一阶段结束。</p><h2 id="Intermediate-Fetching">Intermediate Fetching</h2><p>前面我们都是一下子获取所有搜索到的数据，这节讲的是如何更好地获取数据。</p><p>本节目标：</p><ul><li>只获取想要的数据</li><li>用 predicate 限制获取到的数据</li><li>避免屏蔽 UI，转为在后台获取数据</li><li>在 persistent store 中通过直接更新 object 来避免不必要的数据获取。</li></ul><h3 id="NSFetchRequest">NSFetchRequest</h3><p>之前获取数据都是先创建一个 NSFetchRequest 实例，配置好搜索范围然后再在 context 上获取数据。但实际上，我们有五种不同的方法来实现操作。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1</span></span><br><span class="line"><span class="keyword">let</span> fetchRequest1 <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">Venue</span>&gt;()</span><br><span class="line"><span class="keyword">let</span> entity <span class="operator">=</span> <span class="type">NSEntityDescription</span>.entity(forEntityName: <span class="string">&quot;Venue&quot;</span>, in: managedContext)<span class="operator">!</span></span><br><span class="line">fetchRequest1.entity <span class="operator">=</span> entity</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2 第一种写法的缩写形式</span></span><br><span class="line"><span class="keyword">let</span> fetchRequest2 <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">Venue</span>&gt;(entityName: <span class="string">&quot;Venue&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3 第二种写法的缩写形式 fetchRequest()方法被定义在 Venue+CoreDataProperties.swift</span></span><br><span class="line"><span class="keyword">let</span> fetchRequest3: <span class="type">NSFetchRequest</span>&lt;<span class="type">Venue</span>&gt; <span class="operator">=</span> <span class="type">Venue</span>.fetchRequest()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4 从 NSManagedObjectModel 中获取数据</span></span><br><span class="line"><span class="keyword">let</span> fetchRequest4 <span class="operator">=</span> managedObjectModel.fetchRequestTemplate(forName: <span class="string">&quot;venueFR&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5 和第四种写法类似，但这里多了一些参数去限制获取结果。</span></span><br><span class="line"><span class="keyword">let</span> fetchRequest5 <span class="operator">=</span> managedObjectModel.fetchRequestFromTemplate(withName: <span class="string">&quot;venueFR&quot;</span>, substitutionVariables: [<span class="string">&quot;NAME&quot;</span> : <span class="string">&quot;Vivi Bubble Tea&quot;</span>])</span><br></pre></td></tr></table></figure><h3 id="获取不同结果的类型">获取不同结果的类型</h3><p>NSFetchResult 不仅仅是一个简单的工具，实际上，它可以说是 Core Data 框架中的瑞士军刀。</p><p>你可以用它来获取单独的数据，对数据进行统计，例如：平均数、最小值、最大值等等。</p><p>NSFetchRequest 有个属性叫 resultType，</p><ul><li>.managedObjectResultType：返回 managed objects（默认值）</li><li>.countResultType：返回满足抓取要求的数据数量</li><li>.dictionaryResultType：这是一个获取所有返回数据的类型，能返回经过不同计算后的数据。</li><li>.managedObjectIDResultType：代替完整的 managed object 返回唯一识别(unique identifiers)。</li></ul><h4 id="例如：获取其数量！">例如：获取其数量！</h4><p>拿第二点 <code>.countResultType</code> 来说，有的人可能会直接获取所有的 managed objects 之后再调用数组的<code>count</code>属性得到 object 的数量。但是一旦要获取一个城市的人口数量的时候，先获取所有人口的 object 再得到数量这样显然对内存是很不友好的，这时候通过 <code>.countResultType</code> 获取结果的数量会更有效率。</p><p>例如，我想获得价格分类只有一个「$」的珍珠奶茶店数量，我们给 fetchRequest 配置好 <code>resultType</code> 结果类型属性，在配置好 <code>predicate</code> 查询范围后，就可以直接从 <code>countResult.first!.intValue</code> 得到。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> coreDataStack: <span class="type">CoreDataStack</span>!</span><br><span class="line"><span class="keyword">lazy</span> <span class="keyword">var</span> cheapVenuePredicate: <span class="type">NSPredicate</span> <span class="operator">=</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="type">NSPredicate</span>(format: <span class="string">&quot;%K == %@&quot;</span>, <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.priceInfo.priceCategory), <span class="string">&quot;$&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数量并配置 label</span></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">populateCheapVenueCountLabel</span>() &#123;</span><br><span class="line">  <span class="comment">// 抓取的是数量 所以是 NSNumber</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">NSNumber</span>&gt;(entityName: <span class="string">&quot;Venue&quot;</span>)</span><br><span class="line">  <span class="comment">// 返回满足抓取要求的数据数量</span></span><br><span class="line">  fetchRequest.resultType <span class="operator">=</span> .countResultType</span><br><span class="line">  <span class="comment">// 只抓取一个 $ 的</span></span><br><span class="line">  fetchRequest.predicate <span class="operator">=</span> cheapVenuePredicate</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> countResult <span class="operator">=</span> <span class="keyword">try</span> coreDataStack.managedContext.fetch(fetchRequest)</span><br><span class="line">    <span class="comment">// 获取数量</span></span><br><span class="line">    <span class="keyword">let</span> count <span class="operator">=</span> countResult.first<span class="operator">!</span>.intValue</span><br><span class="line">    firstPriceCategoryLabel.text <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(count)</span> bubble tea places&quot;</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然我们还可以有不同的搜索数量的方式，这里我们获得价格分类有三个「$」的珍珠奶茶店数量。和以前一样先把搜索范围定位所有的 Venue object，然后在获取结果的时候点名只获取数量。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">lazy</span> <span class="keyword">var</span> expensiveVenuePredicate: <span class="type">NSPredicate</span> <span class="operator">=</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="type">NSPredicate</span>(format: <span class="string">&quot;%K == %@&quot;</span>, <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.priceInfo.priceCategory), <span class="string">&quot;$$$&quot;</span>)</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">populateExpensiveVenueCountLabel</span>() &#123;</span><br><span class="line">  <span class="comment">// 构建获得 Venue object 的请求</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest: <span class="type">NSFetchRequest</span>&lt;<span class="type">Venue</span>&gt; <span class="operator">=</span> <span class="type">Venue</span>.fetchRequest()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 获得三个 $ 的</span></span><br><span class="line">  fetchRequest.predicate <span class="operator">=</span> expensiveVenuePredicate</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="comment">// 用 count 属性获取数量</span></span><br><span class="line">    <span class="keyword">let</span> count <span class="operator">=</span> <span class="keyword">try</span> coreDataStack.managedContext.count(for: fetchRequest)</span><br><span class="line">    thirdPriceCategoryLabel.text <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(count)</span> bubble tea places&quot;</span></span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="例如：获取后计算！">例如：获取后计算！</h4><p>第三点的<code>.dictionaryResultType</code> 能能返回经过不同计算后的数据，同样的我们通过 NSExpression 来实现一些简单的计算。下图是 API 文档中的部分属性，供参考。</p><p><img src="http://img.frankorz.com/587cc4c84abdc.jpg" alt=""></p><p>例如，我们要搜索所有珍珠奶茶的优惠数量，我们同样没有必要找出所有相关的属性再自己求和，Core Data 可以帮我们完成任务。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">populateDealsCountLabel</span>() &#123;</span><br><span class="line">  <span class="comment">// 1 .dictionaryResultType 告诉 fetchRequest 要进行计算</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest <span class="operator">=</span> <span class="type">NSFetchRequest</span>&lt;<span class="type">NSDictionary</span>&gt;(entityName: <span class="string">&quot;Venue&quot;</span>)</span><br><span class="line">  fetchRequest.resultType <span class="operator">=</span> .dictionaryResultType</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 创建一个 NSExpressionDescription 去请求求和后的数据，然后把这个请求过程的名字定为 sumDeals</span></span><br><span class="line">  <span class="keyword">let</span> sumExpressionDesc <span class="operator">=</span> <span class="type">NSExpressionDescription</span>()</span><br><span class="line">  sumExpressionDesc.name <span class="operator">=</span> <span class="string">&quot;sumDeals&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3 构建表达式 一开始说明要计算的数据来源是 specialCount（优惠的数量）</span></span><br><span class="line">  <span class="comment">// 然后说明计算方式为&quot;sum:&quot;求和，结果为 integer32AttributeType 类型</span></span><br><span class="line">  <span class="keyword">let</span> specialCountExp <span class="operator">=</span> <span class="type">NSExpression</span>(forKeyPath: <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.specialCount))</span><br><span class="line">  sumExpressionDesc.expression <span class="operator">=</span> <span class="type">NSExpression</span>(forFunction: <span class="string">&quot;sum:&quot;</span>, arguments: [specialCountExp])</span><br><span class="line">  sumExpressionDesc.expressionResultType <span class="operator">=</span> .integer32AttributeType</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 4 配置 fetchRequest</span></span><br><span class="line">  fetchRequest.propertiesToFetch <span class="operator">=</span> [sumExpressionDesc]</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 5 返回字典类型数据，再按之前的名字取出对应的值</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> results <span class="operator">=</span> <span class="keyword">try</span> coreDataStack.managedContext.fetch(fetchRequest)</span><br><span class="line">    <span class="keyword">let</span> resultDict <span class="operator">=</span> results.first<span class="operator">!</span></span><br><span class="line">    <span class="keyword">let</span> numDeals <span class="operator">=</span> resultDict[<span class="string">&quot;sumDeals&quot;</span>]<span class="operator">!</span></span><br><span class="line">    numDealsLabel.text <span class="operator">=</span> <span class="string">&quot;<span class="subst">\(numDeals)</span> total deals&quot;</span></span><br><span class="line">    </span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="managedObjectResultType">managedObjectResultType</h4><p>剩下一个类型是<code>.managedObjectResultType</code>，当你用这类型去获取结果的时候，结果会是一个<code>NSManagedObjectID</code>组成的数组，而不是原来的 managed objects。一个<code>NSManagedObjectID</code>是一个managed object 的压缩的统一标识，作用就像数据库中的主键一样。</p><p>在 iOS 5，人们通常通过 ID 来获取数据，因为<code>NSManagedObjectID</code>是线程安全的，而且通过用它能帮助开发者实现并发线程限制模型(thread confinement concurrency model)。</p><p>现在线程限制对于很多并发模型来说已经过时了，通过 object ID 来获取数据的做法也在逐渐减少。</p><p>目前我们尝试过关于获取数据的不同方式，但是有时候我们需要限制获取的数据数量，我们有时没有必要去一次性获取所有对象图(object graph)，这样对内存也不友好。</p><p>我们有不同方式去限制获取结果的数量，例如<code>NSFetchRequest</code>支持获取的批次数量 (fetching batches)。我们可以用<code>fetchBatchSize</code>、<code>fetchLimit</code>、<code>fetchOffset</code>去控制获取批次数量的行为。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> request <span class="operator">=</span> <span class="type">NSFetchRequest</span>(entityName: <span class="string">&quot;Item&quot;</span>)</span><br><span class="line">request.fetchBatchSize <span class="operator">=</span> <span class="number">20</span></span><br><span class="line">request.fetchLimit <span class="operator">=</span> <span class="number">100</span></span><br><span class="line">request.sortDescriptors <span class="operator">=</span> [sortDescriptor]</span><br><span class="line">request.predicate <span class="operator">=</span> <span class="operator">...</span></span><br></pre></td></tr></table></figure><p>例如数据库有1000个「item」，上面的代码限制了请求，一次获取20个 item，并且将会在获取100个 item 之后停止继续获取数据。</p><p>Core Data 也尝试通过一种名叫「<a href="http://frankorz.com/2017/01/16/core-data-note-1/#Faulting">faulting</a>」的技术去减少内存消耗，一个 fault 是一个占位符，用来表示 managed object 没有被完全送进内存里面。</p><p>最后，限制对象图的另外一种方法是用 predicates，就像之前做的一样。</p><h4 id="Faulting">Faulting</h4><p>Faulting 可以看做是 Core Data 的一种懒加载，简单地说就是 object 的一些属性只会在你需要的时候才会去获取。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> user <span class="keyword">in</span> twitterUsers &#123;</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;fetched user <span class="subst">\(user)</span>&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可能看不到 user 的 name 属性被打印出来（我们可能只会看到「unfaulted object」，这依赖于 Core Data 是否已经提前获取了属性），但如果我们像下面这么做…</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> user <span class="keyword">in</span> twitterUsers &#123;</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;fetched user named <span class="subst">\(user.name)</span>&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们一定能够从数据库中获取所有的 TwitterUsers，因为我们实际上在获取 NSManagedObject 的数据（其属性）。</p><h3 id="排序">排序</h3><p>NSFetchRequest 另外一个强大的功能就是能帮你排序好数据，它是通过 NSSortDescriptor 来实现的。这样的排序是在 SQLite 层面的，而不是在内存中，所以这让 Core Data 中的排序即快又有效率。</p><p><img src="http://img.frankorz.com/587cc4c058be7.jpg" alt=""></p><p>现在要实现根据珍珠奶茶店的名字升序、降序、距离、价格来排序，首先定义好 NSSortDescriptor。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 按名字排序</span></span><br><span class="line"><span class="keyword">lazy</span> <span class="keyword">var</span> nameSortDescriptor: <span class="type">NSSortDescriptor</span> <span class="operator">=</span> &#123;</span><br><span class="line">  <span class="keyword">let</span> compareSelector <span class="operator">=</span> <span class="keyword">#selector</span>(<span class="type">NSString</span>.localizedStandardCompare(<span class="keyword">_</span>:))</span><br><span class="line">  <span class="keyword">return</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.name),</span><br><span class="line">                          ascending: <span class="literal">true</span>,</span><br><span class="line">                          selector: compareSelector)</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按距离排序</span></span><br><span class="line"><span class="keyword">lazy</span> <span class="keyword">var</span> distanceSortDescriptor: <span class="type">NSSortDescriptor</span> <span class="operator">=</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.location.distance),</span><br><span class="line">                          ascending: <span class="literal">true</span>)</span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 按价格排序</span></span><br><span class="line"><span class="keyword">lazy</span> <span class="keyword">var</span> priceSortDescriptor: <span class="type">NSSortDescriptor</span> <span class="operator">=</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Venue</span>.priceInfo.priceCategory),</span><br><span class="line">                          ascending: <span class="literal">true</span>)</span><br><span class="line">&#125;()</span><br></pre></td></tr></table></figure><p>初始化一个 NSSortDescriptor 的实例需要做三件事：有一个 key path 去指出要排序的属性路径(数据库表中：表→属性，表→表→属性等等)，要求升序或降序， 一个可选的选择器(option selector)去实现比较操作。</p><p>如果你之前用过 NSSortDescriptor，你可能知道有一种基于块的(block-based) API 可以把比较器(comparator)代替为选择器(seletor)。遗憾的是，Core Data 不支持通过这种方法来定义一个 sort descriptor。</p><p>同样的 Core Data 也不支持 NSPredicate 中基于块的(block-based)方法，原因是过滤和分类操作是在 SQLite 数据库中完成的，所以 predicate/sort descriptor 不得不去很好的匹配数据并写成 SQLite 语句。</p><p>另外，<code>NSString.localizedStandardCompare(_:)</code> 是苹果推荐用来根据符合当前语言环境(the current locale)的语言规则来排序，这可以更好地去处理一些特殊字符，例如 <em>bien sûr</em> :]</p><p>在<code>tableView(didSelectRowAt:)</code>方法中完成赋值，「Search」按钮事件为触发<code>ViewController.swift</code>中的委托方法。</p><p>FilterViewController.swift</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// 定义一个委托方法：当用户选择一个新的过滤操作时候(sort/filter combination)，会通知委托。</span></span><br><span class="line"><span class="keyword">protocol</span> <span class="title class_">FilterViewControllerDelegate</span>: <span class="title class_">class</span> &#123;</span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">filterViewController</span>(<span class="params">filter</span>: <span class="type">FilterViewController</span>,</span><br><span class="line">                            <span class="params">didSelectPredicate</span> <span class="params">predicate</span>: <span class="type">NSPredicate</span>?,</span><br><span class="line">                            <span class="params">sortDescriptor</span>: <span class="type">NSSortDescriptor</span>?)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FilterViewController</span>: <span class="title class_">UITableViewController</span> &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - UITableViewDelegate</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">FilterViewController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">didSelectRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> cell <span class="operator">=</span> tableView.cellForRow(at: indexPath) <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Price section</span></span><br><span class="line">    <span class="keyword">switch</span> cell &#123;</span><br><span class="line">    <span class="comment">//Sort By section</span></span><br><span class="line">    <span class="keyword">case</span> nameAZSortCell:</span><br><span class="line">      selectedSortDescriptor <span class="operator">=</span> nameSortDescriptor</span><br><span class="line">    <span class="keyword">case</span> nameZASortCell:</span><br><span class="line">      selectedSortDescriptor <span class="operator">=</span> nameSortDescriptor.reversedSortDescriptor</span><br><span class="line">                               <span class="keyword">as?</span> <span class="type">NSSortDescriptor</span></span><br><span class="line">    <span class="keyword">case</span> distanceSortCell:</span><br><span class="line">      selectedSortDescriptor <span class="operator">=</span> distanceSortDescriptor</span><br><span class="line">    <span class="keyword">case</span> priceSortCell:</span><br><span class="line">      selectedSortDescriptor <span class="operator">=</span> priceSortDescriptor</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">      <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    cell.accessoryType <span class="operator">=</span> .checkmark</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - IBActions</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">FilterViewController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">@IBAction</span> <span class="keyword">func</span> <span class="title function_">saveButtonTapped</span>(<span class="keyword">_</span> <span class="params">sender</span>: <span class="type">UIBarButtonItem</span>) &#123;</span><br><span class="line">    delegate<span class="operator">?</span>.filterViewController(filter: <span class="keyword">self</span>,</span><br><span class="line">                                   didSelectPredicate: selectedPredicate,</span><br><span class="line">                                   sortDescriptor: selectedSortDescriptor)</span><br><span class="line">    </span><br><span class="line">    dismiss(animated: <span class="literal">true</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ViewController.swift 中补充委托方法。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - FilterViewControllerDelegate</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span>: <span class="title class_">FilterViewControllerDelegate</span> &#123;</span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">filterViewController</span>(<span class="params">filter</span>: <span class="type">FilterViewController</span>, <span class="params">didSelectPredicate</span> <span class="params">predicate</span>: <span class="type">NSPredicate</span>?, <span class="params">sortDescriptor</span>: <span class="type">NSSortDescriptor</span>?) &#123;</span><br><span class="line">    fetchRequest.predicate <span class="operator">=</span> <span class="literal">nil</span></span><br><span class="line">    fetchRequest.sortDescriptors <span class="operator">=</span> <span class="literal">nil</span></span><br><span class="line">    </span><br><span class="line">    fetchRequest.predicate <span class="operator">=</span> predicate</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">let</span> sr <span class="operator">=</span> sortDescriptor &#123;</span><br><span class="line">      fetchRequest.sortDescriptors <span class="operator">=</span> [sr]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//获取数据并 reload tableView</span></span><br><span class="line">    fetchAndReload()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="异步获取数据">异步获取数据</h3><p>当你看到这里，我好消息和坏消息要告诉你。好消息是我们已经说了很多关于 NSFetchRequest 可以做的事，坏消息是我们每次获取数据都会屏蔽主线程，直到获取到数据。</p><p>当你屏蔽了主线程，屏幕就会变得不可交互，还会产生一些其他的问题，之前没有感觉到屏蔽主线程的感觉，是因为我们获取的数据还太少。iOS 8 中，Core Data 有一个 API 能让我们长时间在后台获取数据，获取到数据后还能得到一个回调方法。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不先初始化的话 回调方法会报错</span></span><br><span class="line"><span class="keyword">var</span> venues: [<span class="type">Venue</span>] <span class="operator">=</span> []</span><br><span class="line"></span><br><span class="line"><span class="comment">// 父类是 NSPersistentStoreRequest 而不是 NSFetchRequest</span></span><br><span class="line"><span class="keyword">var</span> asyncFetchRequest: <span class="type">NSAsynchronousFetchRequest</span>&lt;<span class="type">Venue</span>&gt;!</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1 准备获取数据</span></span><br><span class="line">  fetchRequest <span class="operator">=</span> <span class="type">Venue</span>.fetchRequest()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 用 fetchRequest 和回调完成请求，数据在 result.finalResult 中</span></span><br><span class="line">  asyncFetchRequest <span class="operator">=</span> <span class="type">NSAsynchronousFetchRequest</span>&lt;<span class="type">Venue</span>&gt;(fetchRequest: fetchRequest) &#123;</span><br><span class="line">    [<span class="keyword">unowned</span> <span class="keyword">self</span>] (result: <span class="type">NSAsynchronousFetchResult</span>) <span class="keyword">in</span></span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> venues <span class="operator">=</span> result.finalResult <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">self</span>.venues <span class="operator">=</span> venues</span><br><span class="line">    <span class="keyword">self</span>.tableView.reloadData()</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3 执行异步请求</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> coreDataStack.managedContext.execute(asyncFetchRequest)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还要注意的是要获取的 venues 实例，由于现在获取数据是异步的，所以获取数据的步骤会在 table view 初始化之后再执行，所以要先初始化好实例，不然不能解包实例，应用会报错。</p><p>另外，如果要取消获取数据的请求(fetch request)，可以调用<code>NSAsynchronousFetchResult</code>的<code>cancel()</code>方法。</p><h3 id="批量更新-Batch-updates">批量更新(Batch updates)</h3><p>有时候我们需要从 Core Data 中获取数据是去改变一个单独的属性(attribute)，改动后，我们还要去 commit 回 persistent store。但如果我们想要去一次性更新十万计的数据呢？这将会消耗大量的时间和内存去只更新一个属性。</p><p>iOS 8 中，有一个新的方法能不从内存中获取所有数据来完成更新数据：batch updates。这新的技术能绕过 NSManagedObjectContext 来直接操作 persistent store。通常批量更新的做法就像邮件客户端中的「Mark all as read」功能一样。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> batchUpdate <span class="operator">=</span> <span class="type">NSBatchUpdateRequest</span>(entityName: <span class="string">&quot;Venue&quot;</span>)</span><br><span class="line">  batchUpdate.propertiesToUpdate <span class="operator">=</span> [<span class="keyword">#keyPath</span>(<span class="type">Venue</span>.favorite) : <span class="literal">true</span>]</span><br><span class="line">  </span><br><span class="line">  batchUpdate.affectedStores <span class="operator">=</span> coreDataStack.managedContext</span><br><span class="line">    .persistentStoreCoordinator<span class="operator">?</span>.persistentStores</span><br><span class="line">  </span><br><span class="line">  batchUpdate.resultType <span class="operator">=</span> .updatedObjectsCountResultType</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> batchResult <span class="operator">=</span> <span class="keyword">try</span> coreDataStack.managedContext</span><br><span class="line">      .execute(batchUpdate) <span class="keyword">as!</span> <span class="type">NSBatchUpdateResult</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Records updated <span class="subst">\(batchResult.result<span class="operator">!</span>)</span>&quot;</span>)</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Could not fetch <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行应用后显示：<code>Records updated 30</code></p><h3 id="批量删除">批量删除</h3><p>iOS 9 中，NSBatchDeleteRequest 能帮我批量删除数据，如批量更新一样，不需要把数据读取到内存再操作，而且父类也是 NSPersistentStoreRequest。</p><p>我们在回避 NSManagedObjectContext，所以批量更新或批量删除时，我们不会进行数据验证。数据的改动不会影响我们的 managed context，所以在用一个 persistent store request 之前要验证好数据。</p><h2 id="NSFetchedResultsController">NSFetchedResultsController</h2><p>之前我们都是把 Core Data 和 UITableView 放在一起用，Core Data，提供了一个类来专门处理这种使用方式：NSFetchedResultsController。NSFetchedResultsController 是一个 controller，但是它不是一个 view controller，它没有界面，它的目的在于帮助开发者通过抽象大部分代码更容易地在 table view 上同步数据。</p><h3 id="实例：世界杯">实例：世界杯</h3><p>下面的代码是关于一个世界杯胜场计数的应用示例。</p><p><img src="http://img.frankorz.com/587cc4c556a02.jpg" alt=""></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fetchedResultsController: <span class="type">NSFetchedResultsController</span>&lt;<span class="type">Team</span>&gt;!</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">  <span class="comment">// 1 fetchRequest 是万能的</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest: <span class="type">NSFetchRequest</span>&lt;<span class="type">Team</span>&gt; <span class="operator">=</span> <span class="type">Team</span>.fetchRequest()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 初始化 fetchedResultsController</span></span><br><span class="line">  fetchedResultsController <span class="operator">=</span> <span class="type">NSFetchedResultsController</span>(</span><br><span class="line">    fetchRequest: fetchRequest,</span><br><span class="line">    managedObjectContext: coreDataStack.managedContext,</span><br><span class="line">    sectionNameKeyPath: <span class="literal">nil</span>,</span><br><span class="line">    cacheName: <span class="literal">nil</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3 开始获取数据</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> fetchedResultsController.performFetch()</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Fetching error: <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里有点奇怪的是 NSFetchedResultsController 没有返回什么值就可以获取数据了，其实 NSFetchedResultsController 既是 fetch request 的包装，也是一个获取数据用的 container，我们可以从中获取到数据。例如我们可以通过<code>fetchedObject</code>属性或<code>object(at:)</code>方法来获得。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - Internal</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">configure</span>(<span class="params">cell</span>: <span class="type">UITableViewCell</span>, <span class="params">for</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> cell <span class="operator">=</span> cell <span class="keyword">as?</span> <span class="type">TeamCell</span> <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> team <span class="operator">=</span> fetchedResultsController.object(at: indexPath)</span><br><span class="line">    cell.flagImageView.image <span class="operator">=</span> <span class="type">UIImage</span>(named: team.imageName<span class="operator">!</span>)</span><br><span class="line">    cell.teamLabel.text <span class="operator">=</span> team.teamName</span><br><span class="line">    cell.scoreLabel.text <span class="operator">=</span> <span class="string">&quot;Wins: <span class="subst">\(team.wins)</span>&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - UITableViewDataSource</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span>: <span class="title class_">UITableViewDataSource</span> &#123;</span><br><span class="line"><span class="comment">// section 数 </span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">numberOfSections</span>(<span class="params">in</span> <span class="params">tableView</span>: <span class="type">UITableView</span>) -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> sections <span class="operator">=</span> fetchedResultsController.sections <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sections.count</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">numberOfRowsInSection</span> <span class="params">section</span>: <span class="type">Int</span>) -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">guard</span> <span class="keyword">let</span> sectionInfo <span class="operator">=</span> fetchedResultsController.sections<span class="operator">?</span>[section] <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sectionInfo.numberOfObjects</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">cellForRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) -&gt; <span class="type">UITableViewCell</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> cell <span class="operator">=</span> tableView.dequeueReusableCell(withIdentifier: teamCellIdentifier, for: indexPath)</span><br><span class="line">    configure(cell: cell, for: indexPath)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> cell</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MARK: - UITableViewDelegate</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span>: <span class="title class_">UITableViewDelegate</span> &#123;</span><br><span class="line"><span class="comment">//点击后胜场加一，保存数据到 Core Data，并重载 tableview</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">didSelectRowAt</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> team <span class="operator">=</span> fetchedResultsController.object(at: indexPath)</span><br><span class="line">    team.wins <span class="operator">=</span> team.wins <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">    coreDataStack.saveContext()</span><br><span class="line">    tableView.reloadData()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="section">section</h4><p>到了这步，启动应用的话还会报<code>'An instance of NSFetchedResultsController requires a fetch request with sort descriptors'</code>的错，因为我们使用 NSFetchedResultsController，它需要我们给它提供至少一个 sort descriptor，才能知道如何整理数据。与之前不一样的是，前面获取数据的时候可以不提供 sort descriptor。</p><p>于是在之前的基础上加上 sort descriptor，这里同时增加了三种排序。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - View Life Cycle</span></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">  <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">  <span class="comment">// 1 fetchRequest 是万能的</span></span><br><span class="line">  <span class="keyword">let</span> fetchRequest: <span class="type">NSFetchRequest</span>&lt;<span class="type">Team</span>&gt; <span class="operator">=</span> <span class="type">Team</span>.fetchRequest()</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 必须提供至少一个 sort descriptor</span></span><br><span class="line">  <span class="keyword">let</span> zoneSort <span class="operator">=</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Team</span>.qualifyingZone), ascending: <span class="literal">true</span>)</span><br><span class="line">  <span class="keyword">let</span> scoreSort <span class="operator">=</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Team</span>.wins), ascending: <span class="literal">false</span>)</span><br><span class="line">  <span class="keyword">let</span> nameSort <span class="operator">=</span> <span class="type">NSSortDescriptor</span>(key: <span class="keyword">#keyPath</span>(<span class="type">Team</span>.teamName), ascending: <span class="literal">true</span>)</span><br><span class="line">  fetchRequest.sortDescriptors <span class="operator">=</span> [zoneSort, scoreSort, nameSort]</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 2 初始化 fetchedResultsController</span></span><br><span class="line">  fetchedResultsController <span class="operator">=</span> <span class="type">NSFetchedResultsController</span>(</span><br><span class="line">    fetchRequest: fetchRequest,</span><br><span class="line">    managedObjectContext: coreDataStack.managedContext,</span><br><span class="line">    sectionNameKeyPath: <span class="literal">nil</span>,</span><br><span class="line">    cacheName: <span class="literal">nil</span>)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 3 开始获取数据</span></span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> fetchedResultsController.performFetch()</span><br><span class="line">  &#125; <span class="keyword">catch</span> <span class="keyword">let</span> error <span class="keyword">as</span> <span class="type">NSError</span> &#123;</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Fetching error: <span class="subst">\(error)</span>, <span class="subst">\(error.userInfo)</span>&quot;</span>)</span><br><span class="line">  &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果在前面<code>viewDidLoad()</code>方法中更改下 fetchedResultsController 的 sectionNameKeyPath，就可以直接按照相关的字符串分类，例如这里把国家按照大洲分类。注意前面的 sort descriptor 也要加上相应的分类，例如<code>let zoneSort = NSSortDescriptor(key: #keyPath(Team.qualifyingZone), ascending: true)</code>，否则分类顺序会错乱。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2 初始化 fetchedResultsController</span></span><br><span class="line">fetchedResultsController <span class="operator">=</span> <span class="type">NSFetchedResultsController</span>(</span><br><span class="line">  fetchRequest: fetchRequest,</span><br><span class="line">  managedObjectContext: coreDataStack.managedContext,</span><br><span class="line">  sectionNameKeyPath: <span class="keyword">#keyPath</span>(<span class="type">Team</span>.qualifyingZone),</span><br><span class="line">  cacheName: <span class="literal">nil</span>)</span><br></pre></td></tr></table></figure><p>再增加一个委托方法提供 section 的标题：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">tableView</span>(<span class="keyword">_</span> <span class="params">tableView</span>: <span class="type">UITableView</span>, <span class="params">titleForHeaderInSection</span> <span class="params">section</span>: <span class="type">Int</span>) -&gt; <span class="type">String</span>? &#123;</span><br><span class="line">  <span class="keyword">let</span> sectionInfo <span class="operator">=</span> fetchedResultsController.sections<span class="operator">?</span>[section]</span><br><span class="line">  <span class="keyword">return</span> sectionInfo<span class="operator">?</span>.name</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/587cc4c965d8b.jpg" alt=""></p><h4 id="缓存">缓存</h4><p>NSFetchedResultsController 提供了缓存功能，只要在之前的<code>viewDidLoad()</code>方法中更改下<code>fetchedResultsController</code>的 cacheName 就能实现了。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">fetchedResultsController <span class="operator">=</span> <span class="type">NSFetchedResultsController</span>(</span><br><span class="line">  fetchRequest: fetchRequest,</span><br><span class="line">  managedObjectContext: coreDataStack.managedContext,</span><br><span class="line">  sectionNameKeyPath: <span class="keyword">#keyPath</span>(<span class="type">Team</span>.qualifyingZone),</span><br><span class="line">  cacheName: <span class="string">&quot;worldCup&quot;</span>)</span><br></pre></td></tr></table></figure><p>我们要注意缓存是把数据缓存到硬盘中，和 Core Data 的 persistent store 是分开的。如果我们要更改获取的数据，或者不一样的 sort descriptor 等等导致缓存无效的时候，我们必须用<code>deleteCache(withName:)</code>删除现有缓存，或者换一个缓存名。</p><h4 id="数据控制">数据控制</h4><p>前面更新数据的方法就是调用 table view 的<code>reloadData()</code>方法，其实 NSFetchedResultsController 直接给我们提供了委托方法，让我们可以在数据改动的时候直接更新 table view。</p><p><img src="http://img.frankorz.com/587cc4c7d0865.gif" alt="gif"></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// MARK: - NSFetchedResultsControllerDelegate</span></span><br><span class="line"><span class="keyword">extension</span> <span class="title class_">ViewController</span>: <span class="title class_">NSFetchedResultsControllerDelegate</span> &#123;</span><br><span class="line">  <span class="comment">// 数据将会改变，调用 beginUpdates() 方法</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">controllerWillChangeContent</span>(<span class="keyword">_</span> <span class="params">controller</span>: <span class="type">NSFetchedResultsController</span>&lt;<span class="type">NSFetchRequestResult</span>&gt;) &#123;</span><br><span class="line">    tableView.beginUpdates()</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 球队相关数据改变</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">controller</span>(<span class="keyword">_</span> <span class="params">controller</span>: <span class="type">NSFetchedResultsController</span>&lt;<span class="type">NSFetchRequestResult</span>&gt;, <span class="params">didChange</span> <span class="params">anObject</span>: <span class="keyword">Any</span>, <span class="params">at</span> <span class="params">indexPath</span>: <span class="type">IndexPath</span>?, <span class="params">for</span> <span class="params">type</span>: <span class="type">NSFetchedResultsChangeType</span>, <span class="params">newIndexPath</span>: <span class="type">IndexPath</span>?) &#123;</span><br><span class="line">    <span class="keyword">switch</span> type &#123;</span><br><span class="line">    <span class="keyword">case</span> .insert:</span><br><span class="line">      tableView.insertRows(at: [newIndexPath<span class="operator">!</span>], with: .automatic)</span><br><span class="line">    <span class="keyword">case</span> .delete:</span><br><span class="line">      tableView.deleteRows(at: [indexPath<span class="operator">!</span>], with: .automatic)</span><br><span class="line">    <span class="keyword">case</span> .update:</span><br><span class="line">      <span class="keyword">let</span> cell <span class="operator">=</span> tableView.cellForRow(at: indexPath<span class="operator">!</span>) <span class="keyword">as!</span> <span class="type">TeamCell</span></span><br><span class="line">      configure(cell: cell, for: indexPath<span class="operator">!</span>)</span><br><span class="line">    <span class="keyword">case</span> .move:</span><br><span class="line">      tableView.deleteRows(at: [indexPath<span class="operator">!</span>], with: .automatic)</span><br><span class="line">      tableView.insertRows(at: [newIndexPath<span class="operator">!</span>], with: .automatic)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 数据完成改变，应用变化。</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">controllerDidChangeContent</span>(<span class="keyword">_</span> <span class="params">controller</span>: <span class="type">NSFetchedResultsController</span>&lt;<span class="type">NSFetchRequestResult</span>&gt;) &#123;</span><br><span class="line">    tableView.endUpdates()</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// section 相关数据改变</span></span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">controller</span>(<span class="keyword">_</span> <span class="params">controller</span>: <span class="type">NSFetchedResultsController</span>&lt;<span class="type">NSFetchRequestResult</span>&gt;, <span class="params">didChange</span> <span class="params">sectionInfo</span>: <span class="type">NSFetchedResultsSectionInfo</span>, <span class="params">atSectionIndex</span> <span class="params">sectionIndex</span>: <span class="type">Int</span>, <span class="params">for</span> <span class="params">type</span>: <span class="type">NSFetchedResultsChangeType</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> indexSet <span class="operator">=</span> <span class="type">IndexSet</span>(integer: sectionIndex)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">switch</span> type &#123;</span><br><span class="line">    <span class="keyword">case</span> .insert:</span><br><span class="line">      tableView.insertSections(indexSet, with: .automatic)</span><br><span class="line">    <span class="keyword">case</span> .delete:</span><br><span class="line">      tableView.deleteSections(indexSet, with: .automatic)</span><br><span class="line">    <span class="keyword">default</span>: <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="未完待续">未完待续</h2><p>Todo:</p><ul><li>多线程</li><li>数据库迁移</li><li>Optimistic locking (deleteConflictsForObject)</li><li>Rolling back unsaved changes</li><li>Undo/Redo</li><li>Staleness (how long after a fetch until a refetch of an object is required?)</li><li>…</li></ul><p>如同上面的列表所示，本文没涉及的话题还很多，但会随着自己的学习逐渐补充。如何去保持 Core Data 高性能的最佳实践，如何正确地组合使用它，是使用 Core Data 的挑战所在。</p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Swift </tag>
            
            <tag> iOS </tag>
            
            <tag> Core Data </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>用 Workflow 把知乎答案存到 Instapaper</title>
      <link href="/2016/11/05/workflow-of-zhihu-to-instapaper/"/>
      <url>/2016/11/05/workflow-of-zhihu-to-instapaper/</url>
      
        <content type="html"><![CDATA[<p>随着 Instapaper 宣布免费，我开始尝试使用这款应用。这款应用很适合我，唯独抓取的时候对知乎支持不太好，有强迫症的我尝试解决它，其中比较有效的方法是：右上角菜单「Safari 打开」-打开阅读器视图-发邮件至 Instapaper 邮箱。但是这依旧有点麻烦了，我后来找到 <a href="https://mercury.postlight.com">Mercury</a> 这个服务，并用 Workflow 解决了这问题。</p><span id="more"></span><h3 id="Workflow-特点">Workflow 特点</h3><ul><li>支持知乎答案抓取（答案太短可能会抓取失败）</li><li>知乎专栏文章和其他网站直接原生添加到 Instapapaer</li><li>清除知乎答案内的知乎超链接跳转</li></ul><h3 id="前提">前提</h3><ul><li>免费的 <a href="https://itunes.apple.com/cn/app/workflow-powerful-automation-made-simple/id915249334?mt=8">Workflow</a></li><li>在 <a href="https://mercury.postlight.com">Mercury</a> 上免费注册个账号</li></ul><h3 id="注册-Mercury">注册 Mercury</h3><p>Mercury 是一个免费的在线文本解析网站，允许我们提供网址并得到 JSON 格式的解析结果。我们需要使用它们的服务，所以要注册个账号得到 API KEY 来配置 Workflow 使用。</p><p>首先进入 <a href="https://mercury.postlight.com">Mercury</a> ，点击右上角的「SIGN UP FOR FREE」。注册完成并验证邮箱后，就能看到你专属的 API KEY 了。</p><p><img src="http://img.frankorz.com/581e10644e725.jpg" alt=""></p><h3 id="配置-Workflow">配置 Workflow</h3><p>你可以在下面获取到我写的 Workflow。</p><center><a id="download" href="https://workflow.is/workflows/946d07f3f3e44ff09fccb59d9fafdb96"><i class="fa fa-download"></i><span> Workflow V5 下载</span></a></center><p>点击「GET WORKFLOW」，应该就能把这 workflow 保存到你应用当中了。</p><h4 id="配置-Mercury-API-KEY-和邮箱">配置 Mercury API KEY 和邮箱</h4><p>KEY 我们已经拿到了，另外需要的 Instapaper 的邮箱地址可以在 <a href="https://www.instapaper.com/save/email">How to Save</a> 中找到Instapaper接收邮件的邮箱地址。</p><p><img src="http://img.frankorz.com/581e1064c8968.jpg" alt=""></p><p>把 Workflow 往下拉，找到注释，把 KEY 和 Instapaper 接收邮件的邮箱分别填到「Text」框中和「Email Address」框中。</p><p><img src="http://img.frankorz.com/581e10670bc0c.png" alt="IMG_0177"></p><p>然后把 Workflow 拉到中间，找到绿色的「Ask When Run」圈圈，删除后添加自己用来发邮件的个人邮箱。再往下拉到 Workflow 四分之三的位置，同样配置好个人邮箱。第一次使用 Workflow 的同学需要授权邮箱应用，另外要注意的是邮箱服务器、用户名、密码都确认无误仍然提示 incorrect 的话，直接保存就好了，运行 Workflow 发送邮件无效后再修改。</p><p><img src="http://img.frankorz.com/581e1068234c3.png" alt="IMG_0178"></p><p>Workflow 中共有四次要配置的地方，并且要在 Workflow 应用中对 Instapaper 授权，都配置完毕要在 Workflow 应用中运行一次，获得对新下载的 Workflow 运行的许可。</p><h3 id="运行">运行</h3><p><img src="http://img.frankorz.com/581ea49772c6a.gif" alt="知乎答案"></p><p>操作：右上角菜单-<strong>复制链接</strong>-运行 Workflow</p><h3 id="最后">最后</h3><p>自己实在等不到知乎官方支持 Instapaper 的那天了，于是写了这个 Workflow 。拿到 Matrix 内测资格后，这个 Workflow 已经是第五个版本了，相对比较完善。如果有什么疑问或者建议，请在评论区指出，我会尽快回复。</p><p>另外 Workflow 中对知乎答案的支持都是通过其他服务抓取数据而来，所以抓取时相当于下载一次网络数据，再用邮件发出，对于图片较多的答案或专栏会耗费较多流量。</p><p>注意事项：</p><ul><li>Workflow 对当前复制的文字中是否含有「http」判断是否为链接</li><li>如果不能运行请换其他邮箱测试（确保邮箱的SMTP、IMAP地址、端口号和邮箱密码正确，QQ 邮箱需要生成授权码来当密码使用）</li><li>运行后会清除当前剪贴板</li><li>图片越多，发送邮件速度越慢，耗费流量越多</li><li>已知不支持新浪文章</li><li><strong>该方法需要重新下载网页数据并发邮件，使用的时候请注意流量消耗</strong></li><li>如果有运行 Workflow 时自动打开应用再运行的现象，尝试重启设备</li><li>Workflow 点击运行后就会在后台运行，不用死等</li><li>如还是发送空邮件，且以上所有问题都排除了，那就是知乎答案过长，例如<a href="https://www.zhihu.com/question/22164041/answer/148128347">这篇知乎答案</a>，导致 Mercury 解析超时…</li></ul><p>找到解决方法，马上写博文分享这也是一种强迫症吧哈哈哈(´ ˘ `๑)</p>]]></content>
      
      
      <categories>
          
          <category> 工具癖 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Workflow </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>macOS 上管理书的一些分享</title>
      <link href="/2016/10/24/manage-books-on-macOS/"/>
      <url>/2016/10/24/manage-books-on-macOS/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/580d9e54d12dd.jpg" alt=""></p><p>在生活中，我时不时会买一些书，包括电子书和实体书。随着书籍的增多，我作为一个工具控也有着自己一套管理书籍的方式，在这里与大家分享。</p><p>本文不会涉及电子书解密与分享，仅作经验分享。</p><span id="more"></span><h3 id="电子书">电子书</h3><p>随着亚马逊的大力推广，我较关注的技术书也因其时效性多以电子书形式发布，电子书实际上占用了我生活中所购买书的一大部分。</p><p>下图是我所经历亚马逊一些较大的优惠，其中第二个限时优惠更是允许我不到 8 元买到四本接近百元的电子书(亚马逊药丸！！)。我也经常从<a href="http://www.ituring.com.cn">图灵社区</a>购买一些技术书籍，图灵社区提供的是未加密的电子书，加之一些网友也有分享自制的电子书，因此能很方便地放到不同工具中阅读和管理。</p><p><img src="http://img.frankorz.com/580d9e570b2b2.jpg" alt=""></p><p><img src="http://img.frankorz.com/580d9e5b42da5.jpg" alt=""></p><p>我们所经常遇到的电子书格式通常有 Mobi、Epub、PDF 等格式，阅读 PDF 我推荐使用 PDF Expert 2，其余类型电子书我推荐用 Clearview 阅读，epub 用 iBook 阅读也是不错的体验，Mobi 也能放到 Kindle for mac 上阅读。</p><p><a href="https://book.douban.com">豆瓣读书</a>也提供了一个很好的管理书籍的平台，也方便书友写书评和交流。</p><p><img src="http://img.frankorz.com/580d9e57aa7b5.png" alt="书"></p><h4 id="Calibre">Calibre</h4><p><a href="https://calibre-ebook.com">Calibre</a> 是一款强大的免费开源电子书管理软件，支持 macOS、Windows、Linux。作者几乎每两周就会更新一次软件，这里是<a href="https://github.com/kovidgoyal/calibre">开源地址</a>。</p><p><img src="http://img.frankorz.com/580d9e60009b6.jpg" alt=""></p><h5 id="编辑书籍元数据">编辑书籍元数据</h5><p><img src="http://img.frankorz.com/580d9e5f5d8c5.jpg" alt=""></p><h5 id="下载元数据">下载元数据</h5><p><img src="http://img.frankorz.com/580d9e679cdca.jpg" alt=""></p><h5 id="转换书籍">转换书籍</h5><p>Calibre 转换书籍也方便，能自定义字体、字体大小、自动检测增加目录、更改页面设置等，转 PDF 的时候注意下页面边距和行距。</p><p><img src="http://img.frankorz.com/580d9e61c08a8.jpg" alt=""></p><h5 id="多种分类方便搜索">多种分类方便搜索</h5><p><img src="http://img.frankorz.com/580d9e681f75d.jpg" alt=""></p><p>在电子书右键选择 Calibre 打开后，Calibre 会把电子书复制到你设置的仓库中，根据作者名分类，编辑的书籍元数据也会储存到一起。仓库也能备份成一个文件，防止电子书丢失，方便转移。</p><p><img src="http://img.frankorz.com/580d9e6071f09.jpg" alt=""></p><h5 id="推送电子书">推送电子书</h5><p><img src="http://img.frankorz.com/581864c7e63f2.jpg" alt=""></p><p><img src="http://img.frankorz.com/581864cb36b3f.jpg" alt=""></p><p>发送邮件的配置可以参考邮箱网页版的设置页面中STMP服务器项填写。</p><p>点击测试邮件发送按钮后会尝试发送一个内容为“Test mail from calibre”的邮件到亚马逊接收推送的电子邮箱，稍等片刻若收到如下图的邮件则说明测试成功。</p><p><img src="http://img.frankorz.com/581864cd976a3.jpg" alt=""></p><p>其中接收电子书推送的邮箱可以在<a href="https://www.amazon.cn/gp/digital/fiona/manage?ref=sa_menu_kindle_l3_device&amp;#manageDevices">管理我的内容和设备</a>中“我的设备”或“设置”中找到。</p><p><img src="http://img.frankorz.com/581864ca6499a.jpg" alt=""></p><p>同时别忘了把你的邮箱添加到“已认可的发件人电子邮箱列表”中，否则 Kindle 将会接收不到推送，该页面可以在同页面的“设置”底部。</p><p><img src="http://img.frankorz.com/581864c9b4dd7.jpg" alt=""></p><p><img src="http://img.frankorz.com/581864cd8a9a0.jpg" alt=""></p><p>大概在半小时过后我的 Kindle 应用才同步到这本电子书，另外我只给 iPad 的 kindle 推送邮箱推送后，全部设备的 Kindle 应用云端上都可以看到这本电子书，还是很方便的。</p><p>更多Calibre 技巧可以参考<a href="http://kindlefere.com/post/tag/calibre">Calibre 使用教程</a>，本文仅作抛砖引玉。</p><h3 id="实体书">实体书</h3><p>京东抽奖得的 200-100 图书券、会员每月可得的 200-80 勋章券，还有亚马逊时不时的活动，我对买实体书完全没有抵抗力…</p><p>实体书的管理可能没电子书管理这么有必要，我们仍然可以管理书籍读书状态，拥有的书籍信息。这里要介绍的是一款新生的工具——Shelf。</p><p>macOS 上在书籍方面有很多出色的工具，不仅仅是管理书籍，更多的是创作文字、阅读、制作电子书等各方面的工具。iPhone 上也有很多方便管理实体书的 App，例如：美丽阅读、藏书阁等，通过扫书籍条形码添加书籍再管理。这方面也是不同设备的优点吧，我认为 macOS 实际上更适合写书评、读后感之类的创造性行为。</p><h3 id="小脚本">小脚本</h3><p>买书时我喜欢参考豆瓣网的评分，但是我不喜欢复制书名再打开新的页面搜索，有个小脚本可以帮到我们。</p><p><img src="http://img.frankorz.com/580dfdbd9cb03.jpg" alt=""></p><p>这个油猴脚本需要浏览器插件支持，详情如下：</p><ol><li>Microsoft Edge 14以上：<a href="http://tampermonkey.net/index.php?ext=dhdg&amp;browser=edge">Tampermonkey</a>。</li><li>Firefox 及相关的浏览器：<a href="https://addons.mozilla.org/zh-CN/firefox/addon/greasemonkey/">Greasemonkey</a>。</li><li>Google Chrome、Chromium 及相关的浏览器：<a href="http://tampermonkey.net/index.php?ext=dhdg&amp;browser=chrome">Tampermonkey</a>。</li><li>Opera (版本 15 及更晚)：<a href="https://addons.opera.com/extensions/details/tampermonkey-beta/">Tampermonkey</a> 或者 <a href="https://addons.opera.com/zh-cn/extensions/details/violent-monkey/?display=en">Violentmonkey</a>。</li><li>Opera 版本 12 及更早原生支持用户脚本。但 <a href="https://addons.opera.com/zh-cn/extensions/details/violent-monkey/?display=en">Violentmonkey</a> 能提供更友好的界面和更好的兼容性。</li><li>Safari： <a href="http://tampermonkey.net/index.php?ext=dhdg&amp;browser=safari">Tampermonkey</a></li></ol><p>装完插件后到这安装脚本 <a href="https://greasyfork.org/zh-CN/scripts/3737-douban-book-bar">Douban Book Bar</a> [安装此脚本]-&gt;[安装]</p><p>脚本适用于：图灵社区、京东、亚马逊中国、当当、多看、苏宁易购、文轩和 China-pub</p><p>祝买书快乐！</p><h3 id="总结">总结</h3><p>“买书如山倒，读书如抽丝”这句话很适合我，本博文只为买书提供了一个管理方法的整理，关键还是”读书”。</p><p>前不久我写了篇关于<a href="http://frankorz.com/2016/10/21/new-time-tracking-app-on-macOS/">macOS 上的时间跟踪软件</a>的文章，直到今天我写完本文章，心情是挺苦恼的，因为我能帮别人节省的仅仅是找书的时间。我真正想做到的是让人读书，估计得自己还有很长一段路要走。(一刹那宛如鲁迅再生，毅然弃医从文！！)</p><p>有了挤出来的时间，有了最好的阅读工具，有了方便的书籍管理工具，我们距离读书还差点什么呢？</p>]]></content>
      
      
      <categories>
          
          <category> 工具癖 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> APP </tag>
            
            <tag> macOS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>macOS 上的时间跟踪软件</title>
      <link href="/2016/10/21/new-time-tracking-app-on-macOS/"/>
      <url>/2016/10/21/new-time-tracking-app-on-macOS/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/5809ed7f43054.jpg" alt=""></p><blockquote><p>时间统计法-不是一个节俭的计划工作者的预算，而是向时间自我剖析，对时间的崇敬。</p><footer><strong>《奇特的一生》</strong><cite>格拉宁</cite></footer></blockquote><p>今天在 Next 上发现又一款时间追踪软件横空出世了，所以对几个 macOS 上的时间追踪软件做个总结。近年以来，可以看到很多时间方面的应用遍地开花：Todo 类、番茄钟、备忘事项、日历等等，可以看出来人们对自己的时间越来越看重，如何掌握和使用好碎片时间，是当今一个热门的主题。而时间追踪类软件是其中一个较低调的类别，我目前遇到的大多是帮助你知道时间是如何花掉的，所以这类软件还需要一个会分析时间的使用者。</p><span id="more"></span><p><img src="http://img.frankorz.com/5809ed777617a.png" alt="时间管理"></p><h3 id="RescueTime">RescueTime</h3><p><a href="http://rescuetime.com">RescueTime</a> 是一款易用的多平台时间追踪软件，支持 macOS、Windows、Android、Chrome、FireFox 等(iOS 版尚在开发中)，它可以挂在电脑后台自动追踪时间，生成时间报表，Pro 版可以禁用消极网页、设定目标、设定提醒等。</p><p>我曾经在 V2EX 社区上发起过一次调查：<a href="https://www.v2ex.com/t/311343?p=1">你会为什么样的网络服务付费？</a>，其中 RescueTime 也能占一席之地，我认为这是能代表一些东西的。</p><p><img src="http://img.frankorz.com/5809ed77774f5.jpg" alt=""></p><p><img src="http://img.frankorz.com/5809ed7a98536.jpg" alt=""></p><p>如图可以看到 RescueTime 把你的行为分成几类，大多数行为能够自动判断消极或者积极(知乎居然是积极的我很不信服…)。当然了，行为的类别和积极性都可以编辑，例如你可以把一些经常使用但未归类的网页编辑好属性，更好的监控你的时间，另外 RescueTime 还能每天晚上主动推送每日的追踪报告。</p><p>日视图：</p><p><img src="http://img.frankorz.com/5809ed777f516.jpg" alt=""></p><p>周视图：</p><p><img src="http://img.frankorz.com/5809ed7c9419f.jpg" alt=""></p><p>RescueTime 中详细的时间报表让你明白时间去哪儿了，上图右下角可以编辑行为的积极性和类别噢。值得注意的是基础的报表统计功能免费用户就能使用，因此是控制时间入门的好选择。</p><p>Pro用户的屏蔽消极网页功能，可以不用改 hosts 了…</p><p><img src="http://img.frankorz.com/5809ed8dd5bdf.jpg" alt=""></p><p><img src="http://img.frankorz.com/5809ed81bac2a.jpg" alt=""></p><p>(我这里就是今天消极时间到达半小时自动开始专注状态，把要本博文要查的一些网站都给屏蔽掉了…)</p><p>喜欢工作流的同学可以尝试下 RescueTime 提供的 API</p><p><img src="http://img.frankorz.com/5809eda37c832.jpg" alt=""></p><p>想要尝试Pro用户版的可以通过这个<a href="https://www.rescuetime.com/ref/1150093">推荐链接</a>注册，获得两星期的高级用户功能解锁，如果你想买一年，建议去数码荔枝买国人专供的99元一年，而不是原本坑爹的72美刀一年。</p><h3 id="Timing">Timing</h3><p>如果你不喜欢 RescueTime 那种把数据上传到服务器统计的感觉，你可以尝试下<a href="https://timingapp.com">Timing</a>，Timing 可以看做离线版的 RescueTime。</p><p><img src="http://img.frankorz.com/5809ed9ae72c6.jpg" alt=""></p><p><img src="http://img.frankorz.com/5809ed9314bfb.jpg" alt=""></p><p>详细的网站、文件地址记录，还支持播放记录等</p><p><img src="http://img.frankorz.com/5809ed8beb068.jpg" alt=""></p><p>支持数据导出</p><p><img src="http://img.frankorz.com/5809ed9403149.jpg" alt=""></p><p>点击<a href="https://timingapp.com/faq.php">这里</a>可以查看更详细的支持软件列表</p><p><img src="http://img.frankorz.com/5809ef362e08a.jpg" alt=""></p><p>相比 RescueTime，Timing 只是单纯地记录时间，唯一会联网的操作可能就是升级软件了…你可以在 <a href="https://itunes.apple.com/cn/app/timing-automatic-time-tracker/id431511738?mt=12&amp;ign-mpt=uo%3D4">Mac App Store</a> 花253元购买，不过在数码荔枝里面这 APP 是99元。</p><p>我以前购买The 2016 All-Star Mac Bundle的时候剩了一个，自己已经有一个了，如果不介意Registration Email是我邮箱的话，我打算50元出售这个 Key，有意的可以发邮件给我：<code>superfrankie621@gmail.com</code>。</p><h3 id="TopTracker">TopTracker</h3><p><a href="https://www.toptal.com/tracker">TopTracker</a> 就是我今天在 Next 上看到的令人惊喜的一个时间追踪软件，为什么说是惊喜呢？因为它是<strong>完全免费的</strong>！免费万岁！！</p><p><img src="http://img.frankorz.com/5809ed96c22a7.jpg" alt=""></p><p>TopTracker 适用于macOS、Windows、Linux。不过 TopTracker 是属于<strong>主动记录</strong>的时间追踪软件，你可以创建一个事件，开始做了就让软件开始记录，做完再停止。TopTracker 是设计给自由职业者的，所以会多一些团队协作的功能。例如下面可以邀请成员来到 Project 中，互相查看工作进度。</p><p><img src="http://img.frankorz.com/5809ed9634ddd.jpg" alt=""></p><p>你可以在网页上操作</p><p><img src="http://img.frankorz.com/5809ed9a97301.jpg" alt=""></p><p>也可以在客户端上操作</p><p><img src="http://img.frankorz.com/5809ed9e270f3.jpg" alt=""></p><p>你还可以把忙时没记录的事件补充进去</p><p><img src="http://img.frankorz.com/5809ed9ea4a63.jpg" alt=""></p><p>网页版也能记录事件，还能够查看桌面端查看不了的时间报表。</p><p><img src="http://img.frankorz.com/5809eda0a4abd.jpg" alt=""></p><p><img src="http://img.frankorz.com/5809edbd31827.jpg" alt=""></p><p>注意：这软件有个让我费解的功能就是会自动截图，在使用的过程中出现一个“是否同意截图”的框，拒绝后你的状态就是短暂变成 Offline。你可以在设置中设置自动模糊<code>Blur Screenshots Before Upload</code>，也可以主动删除截图文件夹，否则上传截图到服务器的行为会让一些用户炸毛，这里先打预防针。</p><p>总而言之 TopTracker 的功能较类似 iOS 上各种时间记录 App，例如时间块、iHour 等等，团队协作方面类似 macOS 上的奇妙清单。个人认为相对于传统的手打表格记录时间(洪荒气息扑面而来！)，TopTracker 也不失为一个好的选择，因为不是每个人都会每天在电脑面前呆十小时以上，每天尝试有意识地记录时间，一周下来再详细设定计划如何利用好荒废掉的时间，不过太琐碎的事情(比如5分钟以内)都记录的话反而会是一种累赘，最重要的是养成习惯再调整自己。</p><h3 id="总结">总结</h3><p>做个总结吧，RescueTime 免费提供的功能其实就已经足够使用了，目标和屏蔽网站这种高级功能按需购买吧(我已经剁手了！)。</p><p>另外 macOS 上的时间追踪软件还有Qbserve、Tyme 2、Time Sink等等，大家可以自己找找评测看，其中Tyme 2设置较繁琐，但是提供的接口也多。总之我把相对最好用最实惠的这三款时间追踪软件推荐给你们了，当然成效不是看工具而是看使用者如何使用。经常使用iPhone 的也可以尝试使用时间块和 iHour 培养记录时间的习惯，女生也可以把记录时间当成记账一样，细水长流嘛~</p><p>博文写到这里我 RescueTime 分数已经从65升到71了哈哈哈，还弹出来自己设的提醒，积极行为到达三小时发送自定义通知~</p><p><img src="http://img.frankorz.com/5809edc245a04.jpg" alt=""></p><p>希望你们能够通过这些足够好的工具管理好碎片时间，认真分析什么时间适合做什么。例如坐公交搭地铁就不要看要动脑筋的书，有大块的空闲时间也不要拿来浪费到无聊事情上。</p><table><thead><tr><th></th><th>有趣</th><th>无趣</th></tr></thead><tbody><tr><td>烧脑</td><td>高度专注力(看算法书、写博客)</td><td>高度专注力(背单词、写代码)</td></tr><tr><td></td><td>中度专注力(TED,纪录片,科普书)</td><td></td></tr><tr><td></td><td>中度专注力(游戏)</td><td></td></tr><tr><td>不烧脑</td><td>低度专注力(和朋友聊天)</td><td>低度专注力(打扫卫生、整理文件)</td></tr><tr><td></td><td>低度专注力(刷美剧、刷朋友圈、刷知乎)</td><td>低度专注力(删苹果广告、备份文件、整理收藏夹)</td></tr></tbody></table><p>最后推荐 <a href="https://www.zhihu.com/question/27297809/answer/110588267">怎样用 Mac 和 iPhone 高效学习？-胖子邓的回答</a>，共勉之~</p>]]></content>
      
      
      <categories>
          
          <category> 工具癖 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> APP </tag>
            
            <tag> macOS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>用JSONExport一键生成JSON Model</title>
      <link href="/2016/10/17/powerful-jsonexport/"/>
      <url>/2016/10/17/powerful-jsonexport/</url>
      
        <content type="html"><![CDATA[<p>以前跟着《第一行代码》入门 Android 的时候，学过几个解析 JSON 的方法，一个一个按 key 名找、建对象存等等，解析的工具也很多，以前对 JSON 不熟悉，这也浪费了我很多时间。现在刚入门 iOS 没多久就让我看到神器 JSONExport，解析 JSON 从此只是几行代码的事情~</p><p>JSONExport 是一个运行在 macOS 上通过 JSON 字符串转为 model 的开源工具，支持 Java、Objective-C 和 Swift。我发现了这工具之后忍不住用 Charles 到处抓 API 测试，这是后话~</p><p>每次学完了都觉得很基础…还是记下来吧…</p><span id="more"></span><h3 id="工具">工具</h3><p>JSONExport 的项目地址在这：<a href="https://github.com/Ahmed-Ali/JSONExport">JSONExport</a><br>不过项目需要自己编译，嫌麻烦的可以直接下载我汉化好的 <a href="https://pan.baidu.com/s/1dE4WX1J">JSONExport</a> ，如果失效请在评论留言，下面是界面：</p><p><img src="http://img.frankorz.com/5804ecadbf15f.jpg" alt=""></p><p>左边把 JSON 字符串放入，右下角选择要生成的 Model 即可。</p><h3 id="使用">使用</h3><p>这里我使用<a href="https://developers.douban.com/wiki/?title=book_v2">豆瓣图书 Api V2</a>的 API 做示范。</p><p>根据提供的 API ，假如我想获得十个书名为&quot;ios 开发&quot;的书籍名，可以构建 URL 为<code>&quot;https://api.douban.com/v2/book/search?count=10&amp;q=ios%20%E5%BC%80%E5%8F%91&quot;</code>，在网页中打开获得 JSON 字符串。</p><p>遇到的坑：</p><ul><li>JSON 字符串中含有中文可能会被说明“无效 JSON”，转成unicode再放到工具里去用吧，例如：<a href="http://www.bejson.com/convert/unicode_chinese/">在线unicode转中文</a>，或者用下图的 Paw。</li><li>Mac App Store 中也有同名工具，应该是别人直接修改原作者后上传的，能够根据 JSON 地址提取 JSON 数据，不过也是一搜中文就闪退。</li><li>JSONExport 没生成正确的 Model 就重启吧。</li></ul><p><img src="http://img.frankorz.com/5804ecb16dbe5.jpg" alt=""></p><p>复制至JSONExport中，右下角按需选择，这里我用 Swift - Struct 做示范。</p><p><img src="http://img.frankorz.com/5804ecb43951a.jpg" alt=""></p><p>之后点右下角保存，把这六个文件拷到项目中就能直接使用了！我们可以直接构建一个 Struct。</p><p><code>BookInfo.swift</code></p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">BookInfo</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> images <span class="operator">=</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">var</span> title <span class="operator">=</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">var</span> isbn13 <span class="operator">=</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">var</span> url <span class="operator">=</span> <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">var</span> summary <span class="operator">=</span> <span class="string">&quot;&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我只需要图片、标题、isbn13、url和简介，接下来在 ViewController 中新建个方法获取数据，这里用了用 Swift 写的第三方网络库 Just。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> searchUrl <span class="operator">=</span> <span class="string">&quot;https://api.douban.com/v2/book/search?count=10&amp;q=ios%20%E5%BC%80%E5%8F%91&quot;</span><span class="comment">//搜索&quot;ios 开发&quot;</span></span><br><span class="line"><span class="keyword">var</span> booklist : [<span class="type">BookInfo</span>] <span class="operator">=</span> []</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> <span class="keyword">func</span> <span class="title function_">viewDidLoad</span>() &#123;</span><br><span class="line">   <span class="keyword">super</span>.viewDidLoad()</span><br><span class="line">   loadList()</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line"><span class="keyword">func</span> <span class="title function_">loadList</span>() &#123;</span><br><span class="line">   <span class="type">Just</span>.get(searchUrl) &#123; (r) <span class="keyword">in</span></span><br><span class="line">       <span class="keyword">guard</span> <span class="keyword">let</span> json <span class="operator">=</span> r.json <span class="keyword">as?</span> <span class="type">NSDictionary</span> <span class="keyword">else</span> &#123;</span><br><span class="line">           <span class="built_in">print</span>(<span class="string">&quot;没有数据啊！！！&quot;</span>)</span><br><span class="line">           <span class="keyword">return</span></span><br><span class="line">       &#125;</span><br><span class="line">       </span><br><span class="line">       <span class="keyword">let</span> books <span class="operator">=</span> <span class="type">SearchBook</span>(fromDictionary: json).books<span class="operator">!</span></span><br><span class="line">       </span><br><span class="line">       <span class="keyword">self</span>.booklist <span class="operator">=</span> books.map(&#123; (book) -&gt; <span class="type">BookInfo</span> <span class="keyword">in</span></span><br><span class="line">           <span class="comment">//数组转换数组 直接在 map 中用闭包</span></span><br><span class="line">           <span class="keyword">return</span> <span class="type">BookInfo</span>(images: book.images.large, title: book.title, isbn13: book.isbn13, url: book.url, summary: book.summary)</span><br><span class="line">       &#125;)</span><br><span class="line"></span><br><span class="line">       <span class="built_in">dump</span>(<span class="keyword">self</span>.booklist)</span><br><span class="line">       </span><br><span class="line">       <span class="type">OperationQueue</span>.main.addOperation &#123;</span><br><span class="line">           <span class="keyword">self</span>.tableView.reloadData()</span><br><span class="line">           <span class="keyword">self</span>.refreshControl<span class="operator">?</span>.endRefreshing()</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果 Xcode 自动提示没有提供初始化语句，可以在刚刚的 <code>BookInfo.swift</code> 中自动补全代码，再剪切过来用。</p><p><img src="http://img.frankorz.com/5804ecaebfedf.jpg" alt=""></p><p>运行后可以看到 dump 的数据已经出来了：</p><p><img src="http://img.frankorz.com/5804ecb190f67.jpg" alt=""></p><p>其实本文到这就差不多了，主要注意 JSON 中数组和这里生成 Model 的关系。最后附上一个小 Demo ，还没加搜索框，没用完解析的数据，将就看吧…_(:ｪ｣∠)_</p><p><img src="http://img.frankorz.com/5804ecb0df52d.jpg" alt=""></p><p>项目可以参照这里 <a href="https://github.com/Latias94/Donban-demo-with-JSONExport">Donban-demo-with-JSONExport</a></p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Swift </tag>
            
            <tag> iOS </tag>
            
            <tag> JSON </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Swift 算法查找篇笔记</title>
      <link href="/2016/10/12/Swift-Algorithms-1/"/>
      <url>/2016/10/12/Swift-Algorithms-1/</url>
      
        <content type="html"><![CDATA[<blockquote><p>未完工<br>尚缺 k-th Largest Element、Selection Sampling、Union-Find</p></blockquote><p><img src="http://img.frankorz.com/57fe492d1e042.jpg" alt=""></p><h3 id="简介">简介</h3><p>这是一系列关于Swift语言的算法笔记，Swift版本为3.0，参考的教程来自 <a href="https://github.com/raywenderlich/swift-algorithm-club">Swift Algorithms Club</a> 。</p><p>所有的代码可以直接在Xcode的Playground中运行，前面的算法较简单，主要说说算法的基础、思路和一些Swift语言的特性，我十分推荐你把这里的算法独自实现一遍。</p><p>另外这里可以下载 Swift Algorithms Club 算法教程的Epub文件！ <a href="https://github.com/aquarchitect/swift-algorithm-club/releases/download/1.0.0/SwiftAlgo.epub">点我下载</a></p><span id="more"></span><h3 id="大-O-符号">大 O 符号</h3><p>大O符号（英语：Big O notation）是一种算法复杂度的相对表示方式。</p><p>这个句子里有一些重要而严谨的用词：</p><ul><li><p>相对(relative)：你只能比较相同的事物。你不能把一个做算数乘法的算法和排序整数列表的算法进行比较。但是，比较2个算法所做的算术操作（一个做乘法，一个做加法）将会告诉你一些有意义的东西。</p></li><li><p>表示(representation)：大O(用它最简单的形式)把算法间的比较简化为了一个单一变量。这个变量的选择基于观察或假设。例如，排序算法之间的对比通常是基于比较操作(比较2个结点来决定这2个结点的相对顺序)。这里面就假设了比较操作的计算开销很大。但是，如果比较操作的计算开销不大，而交换操作的计算开销很大，又会怎么样呢？这就改变了先前的比较方式。</p></li><li><p>复杂度(complexity)：如果排序10,000个元素花费了我1秒，那么排序1百万个元素会花多少时间？在这个例子里，复杂度就是相对其他东西的度量结果。</p></li></ul><p>简单地说，O 表示法能给你一个算法的运行时间和它使用的内存量的粗略表示，这两种表示分为时间复杂度和空间复杂度，不过我们通常用 O 来表示时间复杂度，即一个算法执行的快慢。</p><table><thead><tr><th>大 O</th><th>名字</th><th>说明</th></tr></thead><tbody><tr><td>O(1)</td><td>常数</td><td>这是最好的。 该算法不管有多少数据，总是花费相同的时间。 示例：通过索引查找数组的元素。</td></tr><tr><td>O(log n)</td><td>对数</td><td>特别好。 该算法将每次迭代的数据量减半。 如果你有100个元素，它需要大约7个步骤来找到答案。 有1000个，需要10个步骤。 100万个只需要20步。 即使对于大量的数据，这也是超快的。 示例：二进制搜索。</td></tr><tr><td>O(n)</td><td>线性，次线性</td><td>很好。 如果你有100个元素，需要100个步骤。 元素个数增加一倍，该算法花费的时间会是两倍。 示例：顺序搜索。</td></tr><tr><td>O(n log n)</td><td>线性对数</td><td>体面的表现。 这比线性稍差，但不太差。 示例：最快的通用排序算法。</td></tr><tr><td>O(n^2)</td><td>平方</td><td>有点慢。 如果你有100个元素，要执行100 ^ 2 = 10,000步骤。 加倍的元素数量使其慢四倍（因为2平方等于4）。 示例：使用嵌套循环的算法，如插入排序。</td></tr><tr><td>O(n^3)</td><td>立方</td><td>很慢。 如果你有100元素，会是100 ^ 3 = 1,000,000步骤。 输入大小加倍使其慢8倍。 示例：矩阵乘法。</td></tr><tr><td>O(2^n)</td><td>指数</td><td>特别慢。 你想避免这些算法，但有时你没有选择。 添加一个元素就会使运行时间加倍。 示例：<a href="http://baike.baidu.com/view/45957.htm">旅行推销员问题</a>。</td></tr><tr><td>O(n!)</td><td>阶乘</td><td>无法忍受的慢。 一百万年也运行不完。</td></tr></tbody></table><p><img src="http://img.frankorz.com/58076cee4c984.png" alt=""></p><h3 id="Linear-Search">Linear Search</h3><h4 id="简介-2">简介</h4><p>Linear Search，又称线性查找、顺序查找。在给定的数组中，我们会遍历所有的元素，并逐个对比是否与要找的特定元素相等，找到即停止查找，返回特定元素的索引，反之继续遍历对比直至对比完最后一个元素。</p><p>目标：从一个数组查找到一个元素</p><h4 id="代码">代码</h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">linearSearch</span>&lt;<span class="type">T</span>: <span class="type">Equatable</span>&gt;(<span class="keyword">_</span> <span class="params">array</span>: [<span class="type">T</span>], <span class="keyword">_</span> <span class="params">object</span>: <span class="type">T</span>) -&gt; <span class="type">Int</span>? &#123;</span><br><span class="line">  <span class="keyword">for</span> (index, obj) <span class="keyword">in</span> array.enumerated() <span class="keyword">where</span> obj <span class="operator">==</span> object &#123;</span><br><span class="line">    <span class="keyword">return</span> index</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>放进playground中测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array <span class="operator">=</span> [<span class="number">5</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">7</span>]</span><br><span class="line">linearSearch(array, <span class="number">2</span>) <span class="comment">//返回1</span></span><br></pre></td></tr></table></figure><p>这是最简单的一个查找了，这里说说Swift的语言特性。</p><p><code>&lt;T: Equatable&gt;</code> 中 <code>T</code> 指<a href="http://wiki.jikexueyuan.com/project/swift/chapter2/23_Generics.html">泛型</a>，而 <code>Equatable</code> 是Swift标准库中定义的一个协议，该协议要求任何遵循该协议的类型必须实现等式符 <code>==</code> 及不等符 <code>!=</code>，从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 <code>Equatable</code> 协议。</p><p><code>Int?</code> 中的 <code>?</code> 是指返回的值是 <code>Optional</code> 的，如果查找找到值就返回索引值，找不到就返回 <code>nil</code>，<code>Optional</code> 特性让一个值能同时兼容两种情况。</p><p><code>enumerated()</code>是一个实例方法，返回的是键值对 <code>(n, x)</code>，n 表示一个连续的从0开始的正整数，x 表示对应的元素。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array <span class="operator">=</span> [<span class="number">5</span>, <span class="number">2</span>, <span class="number">4</span>, <span class="number">7</span>]</span><br><span class="line"><span class="keyword">for</span> (index, obj) <span class="keyword">in</span> array.enumerated() &#123;<span class="comment">//返回一个 array 拷贝</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&quot;<span class="subst">\(index)</span> -&gt; <span class="subst">\(obj)</span>&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//0 -&gt; 5</span></span><br><span class="line"><span class="comment">//1 -&gt; 2</span></span><br><span class="line"><span class="comment">//2 -&gt; 4</span></span><br><span class="line"><span class="comment">//3 -&gt; 7</span></span><br></pre></td></tr></table></figure><h4 id="性能">性能</h4><p>线性查找的效率是<strong>O(n)</strong><a href="%E5%85%B3%E4%BA%8E%E5%A4%A7O%E7%AC%A6%E5%8F%B7%E5%8F%AF%E4%BB%A5%E5%8F%82%E8%80%83%E8%BF%99%E9%87%8C%EF%BC%9A%5B%E7%BB%B4%E5%9F%BA%E7%99%BE%E7%A7%91%5D(https://zh.wikipedia.org/wiki/%E5%A4%A7O%E7%AC%A6%E5%8F%B7)">^Big O notation</a>。在最差的情况，我们需要把全部元素都比较一边，最好的情况是我们第一次就查找到相同元素。</p><h3 id="Binary-Search">Binary Search</h3><h4 id="简介-3">简介</h4><p>Binary Search，就是著名、高效并应用广泛的<strong>二分查找</strong>算法。</p><p>目标：快速地从一个数组查找到一个元素</p><h4 id="代码-2">代码</h4><p>通常情况下，Swift 的<code>indexOf()</code>方法已经足够好了：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numbers <span class="operator">=</span> [<span class="number">11</span>, <span class="number">59</span>, <span class="number">3</span>, <span class="number">2</span>, <span class="number">53</span>, <span class="number">17</span>, <span class="number">31</span>, <span class="number">7</span>, <span class="number">19</span>, <span class="number">67</span>, <span class="number">47</span>, <span class="number">13</span>, <span class="number">37</span>, <span class="number">61</span>, <span class="number">29</span>, <span class="number">43</span>, <span class="number">5</span>, <span class="number">41</span>, <span class="number">23</span>]</span><br><span class="line"></span><br><span class="line">numbers.indexOf(<span class="number">43</span>)  <span class="comment">// returns 15</span></span><br></pre></td></tr></table></figure><p>内置的<code>indexOf()</code>方法实现了一个线性查找，实现方式类似以下代码：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">linearSearch</span>&lt;<span class="type">T</span>: <span class="type">Equatable</span>&gt;(<span class="keyword">_</span> <span class="params">a</span>: [<span class="type">T</span>], <span class="keyword">_</span> <span class="params">key</span>: <span class="type">T</span>) -&gt; <span class="type">Int</span>? &#123;</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="number">0</span> <span class="operator">..&lt;</span> a.count &#123;</span><br><span class="line">        <span class="keyword">if</span> a[i] <span class="operator">==</span> key &#123;</span><br><span class="line">            <span class="keyword">return</span> i</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">linearSearch(numbers, <span class="number">43</span>)  <span class="comment">// returns 15</span></span><br></pre></td></tr></table></figure><p>这代码其实和一开始的线性查找差不多，但是线性查找效率不太高，平均都要搜索半个数组，如果数组足够大，查找将会变得很慢。</p><h4 id="分而治之">分而治之</h4><blockquote><p>凡邦之有疾病者，疕疡者造焉，则使医分而治之，是亦不自医也。<br>──清·俞樾《群经平议·周官二》</p></blockquote><p>分而治之方法与软件设计的模块化方法非常相似。为了解决一个大的问题，可以：</p><ol><li>把它分成两个或多个更小的问题</li><li>分别解决每个小问题</li><li>把各小问题的解答组合起来，即可得到原问题的解答。小问题通常与原问题相似，可以递归地使用分而治之策略来解决。</li></ol><p>在本算法中，我们需要用这策略去不断的拆分数组，直到找到特定元素。另外二分查找的效率是<strong>O(log n)</strong>，也就是对一个有着1,000,000个元素的数组只要将近20步就能找到特定元素，因为<code>log_2(1,000,000) = 19.9</code>，十亿个元素也只需要30步就能完成查找！</p><p>听起来爽，但是二分查找有个缺点，就是数组必须是<strong>排序好</strong>的，不过这通常不是一个问题。</p><p>二分查找是怎么操作的呢？</p><ul><li>把排序好的数组平分成两部分，把特定元素与中间键比较，看特定元素应该会在数组左部分（较小）、右部分（较大）或直接相等。</li><li>如果在数组左部分，则把数组切剩下左部分，继续进行二分查找。</li><li>这里使用了递归，切分的是数组范围<code>range</code></li></ul><p>先来看看代码吧：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">binarySearch</span>&lt;<span class="type">T</span>: <span class="type">Comparable</span>&gt;(<span class="keyword">_</span> <span class="params">a</span>: [<span class="type">T</span>], <span class="params">key</span>: <span class="type">T</span>, <span class="params">range</span>: <span class="type">Range</span>&lt;<span class="type">Int</span>&gt;) -&gt; <span class="type">Int</span>? &#123;</span><br><span class="line">    <span class="keyword">if</span> range.lowerBound <span class="operator">&gt;=</span> range.upperBound &#123;</span><br><span class="line">        <span class="comment">// 如果经过不断的数组切分，导致范围下界大于等于范围上界，则说明找不到特定元素</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 计算从哪划分数组</span></span><br><span class="line">        <span class="keyword">let</span> midIndex <span class="operator">=</span> range.lowerBound <span class="operator">+</span> (range.upperBound <span class="operator">-</span> range.lowerBound) <span class="operator">/</span> <span class="number">2</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果中间键（索引为midIndex）比特定元素大，说明特定元素在数组左部分(range.lowerBound ..&lt; midIndex)</span></span><br><span class="line">        <span class="keyword">if</span> a[midIndex] <span class="operator">&gt;</span> key &#123;</span><br><span class="line">            <span class="keyword">return</span> binarySearch(a, key: key, range: range.lowerBound <span class="operator">..&lt;</span> midIndex)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果中间键（索引为midIndex）比特定元素小，说明特定元素在数组右部分(midIndex + 1 ..&lt; range.upperBound)</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> a[midIndex] <span class="operator">&lt;</span> key &#123;</span><br><span class="line">            <span class="keyword">return</span> binarySearch(a, key: key, range: midIndex <span class="operator">+</span> <span class="number">1</span> <span class="operator">..&lt;</span> range.upperBound)</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 如果中间键（索引为midIndex）与特定元素相等，说明找到啦！</span></span><br><span class="line">            <span class="keyword">return</span> midIndex</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试1：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numbers <span class="operator">=</span> [<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">17</span>, <span class="number">19</span>, <span class="number">23</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">37</span>, <span class="number">41</span>, <span class="number">43</span>, <span class="number">47</span>, <span class="number">53</span>, <span class="number">59</span>, <span class="number">61</span>, <span class="number">67</span>]</span><br><span class="line"></span><br><span class="line">binarySearch(numbers, key: <span class="number">43</span>, range: <span class="number">0</span> <span class="operator">..&lt;</span> numbers.count)  <span class="comment">// gives 13</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//要查找的特定元素：43</span></span><br><span class="line"><span class="comment">//第1次查找</span></span><br><span class="line"><span class="comment">//--数组：[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;19</span></span><br><span class="line"><span class="comment">//--中间键是：29</span></span><br><span class="line"><span class="comment">//第2次查找</span></span><br><span class="line"><span class="comment">//--数组：[31, 37, 41, 43, 47, 53, 59, 61, 67]</span></span><br><span class="line"><span class="comment">//--查找范围：10..&lt;19</span></span><br><span class="line"><span class="comment">//--中间键是：47</span></span><br><span class="line"><span class="comment">//第3次查找</span></span><br><span class="line"><span class="comment">//--数组：[31, 37, 41, 43]</span></span><br><span class="line"><span class="comment">//--查找范围：10..&lt;14</span></span><br><span class="line"><span class="comment">//--中间键是：41</span></span><br><span class="line"><span class="comment">//第4次查找</span></span><br><span class="line"><span class="comment">//--数组：[43]</span></span><br><span class="line"><span class="comment">//--查找范围：13..&lt;14</span></span><br><span class="line"><span class="comment">//--中间键是：43</span></span><br><span class="line"><span class="comment">//--查找到，索引为13</span></span><br></pre></td></tr></table></figure><p>测试2：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numbers <span class="operator">=</span> [<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">17</span>, <span class="number">19</span>, <span class="number">23</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">37</span>, <span class="number">41</span>, <span class="number">43</span>, <span class="number">47</span>, <span class="number">53</span>, <span class="number">59</span>, <span class="number">61</span>, <span class="number">67</span>]</span><br><span class="line"></span><br><span class="line">binarySearch(numbers, key: <span class="number">24</span>, range: <span class="number">0</span> <span class="operator">..&lt;</span> numbers.count)  <span class="comment">// gives nil</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//要查找的特定元素：24</span></span><br><span class="line"><span class="comment">//第1次查找</span></span><br><span class="line"><span class="comment">//--数组：[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;19</span></span><br><span class="line"><span class="comment">//--中间键是：29</span></span><br><span class="line"><span class="comment">//第2次查找</span></span><br><span class="line"><span class="comment">//--数组：[2, 3, 5, 7, 11, 13, 17, 19, 23]</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;9</span></span><br><span class="line"><span class="comment">//--中间键是：11</span></span><br><span class="line"><span class="comment">//第3次查找</span></span><br><span class="line"><span class="comment">//--数组：[13, 17, 19, 23]</span></span><br><span class="line"><span class="comment">//--查找范围：5..&lt;9</span></span><br><span class="line"><span class="comment">//--中间键是：19</span></span><br><span class="line"><span class="comment">//第4次查找</span></span><br><span class="line"><span class="comment">//--数组：[23]</span></span><br><span class="line"><span class="comment">//--查找范围：8..&lt;9</span></span><br><span class="line"><span class="comment">//--中间键是：23</span></span><br><span class="line"><span class="comment">//第5次查找</span></span><br><span class="line"><span class="comment">//--数组：[]</span></span><br><span class="line"><span class="comment">//--查找范围：9..&lt;9</span></span><br><span class="line"><span class="comment">//--找不到！不干了！</span></span><br></pre></td></tr></table></figure><p>这其中使用了 <code>Comparable</code> 协议，没有这协议，泛型和其他值是不能比较的， <code>Comparable</code> 协议就是说明<code>T</code>是可比较的。</p><p>测试1中用线性查找的话要查找13次，测试2中用线性查找要查找19次，而二分查找分别只用4次和5次就完成查找，能感受算法的威力吗~</p><h4 id="迭代和递归">迭代和递归</h4><p>二分查找是递归的因为我们每次查找都是相同的逻辑，当然用迭代去实现将会更加有效率，因为不需要一次一次地调用函数（考虑下内存空间！）。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">binarySearch</span>&lt;<span class="type">T</span>: <span class="type">Comparable</span>&gt;(<span class="keyword">_</span> <span class="params">a</span>: [<span class="type">T</span>], <span class="params">key</span>: <span class="type">T</span>) -&gt; <span class="type">Int</span>? &#123;</span><br><span class="line">    <span class="keyword">var</span> lowerBound <span class="operator">=</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">var</span> upperBound <span class="operator">=</span> a.count</span><br><span class="line">    <span class="keyword">while</span> lowerBound <span class="operator">&lt;</span> upperBound &#123;</span><br><span class="line">        <span class="keyword">let</span> midIndex <span class="operator">=</span> lowerBound <span class="operator">+</span> (upperBound <span class="operator">-</span> lowerBound) <span class="operator">/</span> <span class="number">2</span></span><br><span class="line">        <span class="keyword">if</span> a[midIndex] <span class="operator">==</span> key &#123;</span><br><span class="line">            <span class="keyword">return</span> midIndex</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> a[midIndex] <span class="operator">&lt;</span> key &#123;</span><br><span class="line">            lowerBound <span class="operator">=</span> midIndex <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            upperBound <span class="operator">=</span> midIndex</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> numbers <span class="operator">=</span> [<span class="number">2</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">11</span>, <span class="number">13</span>, <span class="number">17</span>, <span class="number">19</span>, <span class="number">23</span>, <span class="number">29</span>, <span class="number">31</span>, <span class="number">37</span>, <span class="number">41</span>, <span class="number">43</span>, <span class="number">47</span>, <span class="number">53</span>, <span class="number">59</span>, <span class="number">61</span>, <span class="number">67</span>]</span><br><span class="line"></span><br><span class="line">binarySearch(numbers, key: <span class="number">43</span>)  <span class="comment">// gives 13</span></span><br></pre></td></tr></table></figure><h4 id="最后">最后</h4><p>由于数组必须要先排序，排序和查找的时间加在一起算的话，二分查找耗时可能比顺序查找还多，因此二分查找适合在那种只用排序一次而要查找很多次的情况。</p><h3 id="Count-Occurrences">Count Occurrences</h3><h4 id="简介-4">简介</h4><p>Count Occurrences 就是计数，有时候我们需要计算一个数字出现的次数。当然我们可以用线性查找从头查到尾，这样这种计数的效率是<strong>O(n)</strong>，当然如果我们修改一下二分查找，也能把计数的效率提高到<strong>O(log n)</strong>，别忘了二分查找的条件是数组排序好。</p><p>目标：计算一个特定元素在一个<strong>排序好</strong>的数组中出现的次数</p><h4 id="代码-3">代码</h4><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">countOccurrencesOfKey</span>(<span class="keyword">_</span> <span class="params">key</span>: <span class="type">Int</span>, <span class="params">inArray</span> <span class="params">a</span>: [<span class="type">Int</span>]) -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">leftBoundary</span>() -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> low <span class="operator">=</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">var</span> high <span class="operator">=</span> a.count</span><br><span class="line">    <span class="keyword">while</span> low <span class="operator">&lt;</span> high &#123;</span><br><span class="line">      <span class="keyword">let</span> midIndex <span class="operator">=</span> low <span class="operator">+</span> (high <span class="operator">-</span> low)<span class="operator">/</span><span class="number">2</span></span><br><span class="line">      <span class="keyword">if</span> a[midIndex] <span class="operator">&lt;</span> key &#123;</span><br><span class="line">        low <span class="operator">=</span> midIndex <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        high <span class="operator">=</span> midIndex</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> low</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">func</span> <span class="title function_">rightBoundary</span>() -&gt; <span class="type">Int</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> low <span class="operator">=</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">var</span> high <span class="operator">=</span> a.count</span><br><span class="line">    <span class="keyword">while</span> low <span class="operator">&lt;</span> high &#123;</span><br><span class="line">      <span class="keyword">let</span> midIndex <span class="operator">=</span> low <span class="operator">+</span> (high <span class="operator">-</span> low)<span class="operator">/</span><span class="number">2</span></span><br><span class="line">      <span class="keyword">if</span> a[midIndex] <span class="operator">&gt;</span> key &#123;</span><br><span class="line">        high <span class="operator">=</span> midIndex</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        low <span class="operator">=</span> midIndex <span class="operator">+</span> <span class="number">1</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> low</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> rightBoundary() <span class="operator">-</span> leftBoundary()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> a <span class="operator">=</span> [ <span class="number">0</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">6</span>, <span class="number">8</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">11</span> ]</span><br><span class="line"></span><br><span class="line">countOccurrencesOfKey(<span class="number">3</span>, inArray: a)  <span class="comment">// returns 4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//右边界第1次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11]</span></span><br><span class="line"><span class="comment">//--索引为6的中间键：3</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;12</span></span><br><span class="line"><span class="comment">//右边界第2次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[6, 8, 10, 11, 11]</span></span><br><span class="line"><span class="comment">//--索引为9的中间键：10</span></span><br><span class="line"><span class="comment">//--查找范围：7..&lt;12</span></span><br><span class="line"><span class="comment">//右边界第3次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[6, 8]</span></span><br><span class="line"><span class="comment">//--索引为8的中间键：8</span></span><br><span class="line"><span class="comment">//--查找范围：7..&lt;9</span></span><br><span class="line"><span class="comment">//右边界第4次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[6]</span></span><br><span class="line"><span class="comment">//--索引为7的中间键：6</span></span><br><span class="line"><span class="comment">//--查找范围：7..&lt;8</span></span><br><span class="line"><span class="comment">//low值为：7</span></span><br><span class="line"><span class="comment">//---------------</span></span><br><span class="line"><span class="comment">//左边界第1次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11]</span></span><br><span class="line"><span class="comment">//--索引为6的中间键：3</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;12</span></span><br><span class="line"><span class="comment">//左边界第2次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[0, 1, 1, 3, 3, 3]</span></span><br><span class="line"><span class="comment">//--索引为3的中间键：3</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;6</span></span><br><span class="line"><span class="comment">//左边界第3次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[0, 1, 1]</span></span><br><span class="line"><span class="comment">//--索引为1的中间键：1</span></span><br><span class="line"><span class="comment">//--查找范围：0..&lt;3</span></span><br><span class="line"><span class="comment">//左边界第4次查找</span></span><br><span class="line"><span class="comment">//--划分范围后的数组：[1]</span></span><br><span class="line"><span class="comment">//--索引为2的中间键：1</span></span><br><span class="line"><span class="comment">//--查找范围：2..&lt;3</span></span><br><span class="line"><span class="comment">//low值为：3</span></span><br></pre></td></tr></table></figure><p><a href="https://gist.github.com/Latias94/3d85a5362ce68998f08310d2c7839efd">带print版二分查找计数</a></p><p>这里使用二分查找方式计数的技巧就是找到特定元素出现的左右边界，当然我们这次要查找的不是特定元素3，而是索引为2的1（左边界）和索引为7的6（右边界）。我们通过不断把左边界和特定元素比较，在第n-1次查找中，范围中的数组upperbound就是左边界，同理右边界在第n-1次查找中，范围中的数组lowerbound就是右边界。如果左边界和右边界都返回0，则代表找不到特定元素。</p><p>这是运用算法解决问题的一次实践。</p><p>跑个题，我在高中的时候是个小课代表，老师通常会把一叠改好的按学号排序的试卷交给我发放给全班同学。我记得同学各自的学号，查找的时候我会估摸着同学的学号，然后在一叠试卷中的相应位置抽出一张来。不是，放进去，试卷对应的学号小了，我就继续往后估摸位置再抽一张，直到找到为止，这很像二分查找吧！</p><h3 id="Select-Minimum-Maximum">Select Minimum / Maximum</h3><h4 id="最大值或最小值">最大值或最小值</h4><p>Select Minimum / Maximum，选择最小值或最大值，也是我们会遇到的情况。</p><p>目标：在一个未排序的数组中找到最小值或最大值</p><h5 id="🌰">🌰</h5><p>举个栗子，我们要在一个未排序的数组<code>[ 8, 3, 9, 4, 6 ]</code>中找到最大值。</p><ol><li>找到第一个元素<code>8</code>，存储为最大值</li><li>找到下一个元素<code>3</code>，比较现有最大值<code>8</code>，3&lt;8，所以最大值<code>8</code>不变</li><li>找到下一个元素<code>9</code>，比较现有最大值<code>8</code>，9&gt;8，所以最大值赋值为<code>9</code></li><li>重复步骤直到所有元素遍历一遍</li></ol><p>最小值和以上步骤类似，每次储存较小值即可。</p><h5 id="代码-4">代码</h5><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">minimum</span>&lt;<span class="type">T</span>: <span class="type">Comparable</span>&gt;(<span class="keyword">_</span> <span class="params">a</span>: [<span class="type">T</span>]) -&gt; <span class="type">T</span>? &#123; </span><br><span class="line">  <span class="comment">//本来swift2的写法是(var a:[T])直接定义一个可变数组..3.0不可用</span></span><br><span class="line">  <span class="keyword">var</span> array <span class="operator">=</span> a </span><br><span class="line">  <span class="keyword">guard</span> <span class="operator">!</span>array.isEmpty <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span> <span class="comment">//如果数组不是不空，就不返回 nil....</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> minimum <span class="operator">=</span> array.removeFirst()</span><br><span class="line">  <span class="keyword">for</span> element <span class="keyword">in</span> array &#123;</span><br><span class="line">    minimum <span class="operator">=</span> element <span class="operator">&lt;</span> minimum <span class="operator">?</span> element : minimum</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> minimum</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">func</span> <span class="title function_">maximum</span>&lt;<span class="type">T</span>: <span class="type">Comparable</span>&gt;(<span class="keyword">_</span> <span class="params">array</span>: [<span class="type">T</span>]) -&gt; <span class="type">T</span>? &#123;</span><br><span class="line">  <span class="keyword">var</span> array <span class="operator">=</span> a</span><br><span class="line">  <span class="keyword">guard</span> <span class="operator">!</span>array.isEmpty <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> maximum <span class="operator">=</span> array.removeFirst()</span><br><span class="line">  <span class="keyword">for</span> element <span class="keyword">in</span> array &#123;</span><br><span class="line">    maximum <span class="operator">=</span> element <span class="operator">&gt;</span> maximum <span class="operator">?</span> element : maximum</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> maximum</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array <span class="operator">=</span> [ <span class="number">8</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">4</span>, <span class="number">6</span> ]</span><br><span class="line">minimum(array)   <span class="comment">// return 3</span></span><br><span class="line">maximum(array)   <span class="comment">// return 9</span></span><br></pre></td></tr></table></figure><h5 id="Swift标准库">Swift标准库</h5><p>然而 Swift 标准库已经给我们提供了查最大值或最小值的方法</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array <span class="operator">=</span> [ <span class="number">8</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">4</span>, <span class="number">6</span> ]</span><br><span class="line">array.min()   <span class="comment">// This will return 3</span></span><br><span class="line">array.max()<span class="comment">// This will return 9</span></span><br></pre></td></tr></table></figure><h4 id="最大值和最小值">最大值和最小值</h4><p>如果我们要同时查找最大值和最小值呢？</p><h5 id="🌰-2">🌰</h5><p>如果我们要同时在一个未排序的数组<code>[ 8, 3, 9, 6, 4 ]</code>同时查找最大值和最小值，我们需要两两比较其中的元素。</p><ol><li>找到第一个元素<code>8</code>，同时存储为最大值和最小值</li><li>由于数组中有奇数个数组，当移除元素<code>8</code>之后，剩下<code>[3, 9]</code>和<code>[6, 4]</code>这两对子数组</li><li>在第一个子数组<code>[3, 9]</code>中，比较两数大小。<code>3</code>较小，与当前最小值<code>8</code>比较，3&lt;8，因此把最小值赋值为<code>3</code>。<code>9</code>较大，与当前最大值<code>8</code>比较，9&gt;8，因此把最大值赋值为<code>9</code>。</li><li>第二个子数组<code>[6, 4]</code>中，比较两数大小。<code>4</code>较小，与当前最小值<code>3</code>比较，4&gt;3，因此最小值不变。<code>6</code>较大，与当前最大值<code>9</code>比较，6&lt;9，因此最大值不变。</li><li>最大值为9，最小值为3。</li></ol><h5 id="代码-5">代码</h5><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">minimumMaximum</span>&lt;<span class="type">T</span>: <span class="type">Comparable</span>&gt;(<span class="keyword">_</span> <span class="params">a</span>: [<span class="type">T</span>]) -&gt; (minimum: <span class="type">T</span>, maximum: <span class="type">T</span>)<span class="operator">?</span> &#123;</span><br><span class="line">  <span class="keyword">var</span> array <span class="operator">=</span> a</span><br><span class="line">  <span class="keyword">guard</span> <span class="operator">!</span>array.isEmpty <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> minimum <span class="operator">=</span> array.first<span class="operator">!</span></span><br><span class="line">  <span class="keyword">var</span> maximum <span class="operator">=</span> array.first<span class="operator">!</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> hasOddNumberOfItems <span class="operator">=</span> array.count <span class="operator">%</span> <span class="number">2</span> <span class="operator">!=</span> <span class="number">0</span></span><br><span class="line">  <span class="keyword">if</span> hasOddNumberOfItems &#123;</span><br><span class="line">    array.removeFirst()</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">while</span> <span class="operator">!</span>array.isEmpty &#123;</span><br><span class="line">    <span class="keyword">let</span> pair <span class="operator">=</span> (array.removeFirst(), array.removeFirst())</span><br><span class="line">    <span class="keyword">if</span> pair.<span class="number">0</span> <span class="operator">&gt;</span> pair.<span class="number">1</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> pair.<span class="number">0</span> <span class="operator">&gt;</span> maximum &#123;</span><br><span class="line">        maximum <span class="operator">=</span> pair.<span class="number">0</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> pair.<span class="number">1</span> <span class="operator">&lt;</span> minimum &#123;</span><br><span class="line">        minimum <span class="operator">=</span> pair.<span class="number">1</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> pair.<span class="number">1</span> <span class="operator">&gt;</span> maximum &#123;</span><br><span class="line">        maximum <span class="operator">=</span> pair.<span class="number">1</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> pair.<span class="number">0</span> <span class="operator">&lt;</span> minimum &#123;</span><br><span class="line">        minimum <span class="operator">=</span> pair.<span class="number">0</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (minimum, maximum)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array <span class="operator">=</span> [ <span class="number">8</span>, <span class="number">3</span>, <span class="number">9</span>, <span class="number">4</span>, <span class="number">6</span> ]</span><br><span class="line"><span class="keyword">let</span> result <span class="operator">=</span> minimumMaximum(array)<span class="operator">!</span></span><br><span class="line">result.minimum   <span class="comment">// This will return 3</span></span><br><span class="line">result.maximum   <span class="comment">// This will return 9</span></span><br></pre></td></tr></table></figure><p><code>hasOddNumberOfItems</code>确保数组一直能够被两两分组，实际上数组元素在 while 循环中一直在被两个两个地 remove，同时在做比较大小并赋大小值。</p><h3 id="k-th-Largest-Element-Problem">k-th Largest Element Problem</h3><h4 id="简介-5">简介</h4><p>k-th Largest Element Problem，是查找一个数组中第 k 个较大的元素的问题。</p><p>例如，第一个较大的元素是数组中的最大值，如果数组有 n 个元素，那么第 n 个较大的元素就是该数组的最小值，中位数就是第 n/2 个较大的元素。</p><h4 id="naive-solution">naive solution</h4><p>下面的算法是 semi-naive (较幼稚…?)的，从第一次排序好数组后，时间复杂度为<code>O(n log n)</code>，并且额外用了 O(n)的空间复杂度。</p><figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">func</span> <span class="title function_">kthLargest</span>(<span class="params">a</span>: [<span class="type">Int</span>], <span class="params">k</span>: <span class="type">Int</span>) -&gt; <span class="type">Int</span>? &#123;</span><br><span class="line">  <span class="keyword">let</span> len <span class="operator">=</span> a.count</span><br><span class="line">  <span class="keyword">if</span> k <span class="operator">&gt;</span> <span class="number">0</span> <span class="operator">&amp;&amp;</span> k <span class="operator">&lt;=</span> len &#123;</span><br><span class="line">    <span class="keyword">let</span> sorted <span class="operator">=</span> a.sort()</span><br><span class="line">    <span class="keyword">return</span> sorted[len <span class="operator">-</span> k]</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这方法中，<code>a</code> 是接受输入的数组，<code>k</code> 是返回第 k 个较大的数。</p><p>举个例子，设 <code>k = 4</code>，输入数组为<br><code>[ 7, 92, 23, 9, -1, 0, 11, 6 ]</code><br>一开始没有直接的办法去找第 k 个较大的数，首先我们需要排序好数组。<br><code>[ -1, 0, 6, 7, 9, 11, 23, 92 ]</code><br>现在我们需要拿到索引为<code>a.count - k</code>的元素。<br><code>a[a.count - k] = a[8 - 4] = a[4] = 9</code><br>当然了，如果要找第 k 个较小的数，我们会用<code>a[k]</code>。</p><h4 id="A-faster-solution">A faster solution</h4><p>这里有一种结合了二分查找和快速排序的算法，时间复杂度能达到 <code>O(n)</code>。</p><p>还记得二分查找不断的划分一半数组吗？这样能快速地靠近要查找的元素，这里我们也需要这么做。</p><p>快速排序也划分数组，它把所有较小的元素移到中轴的左边，较大的元素放在右边。</p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 算法 </tag>
            
            <tag> 未完工 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>打造属于自己的RSS源</title>
      <link href="/2016/10/01/rss-tutor/"/>
      <url>/2016/10/01/rss-tutor/</url>
      
        <content type="html"><![CDATA[<p>阅读是一个主动寻求知识的过程，在如今碎片信息充斥着我们生活中的时代，我们需要清楚自己想要获取什么，把有限的时间放在消化信息上，而不是放在获取信息的途径上。如今我们可以通过各种 APP、公众号、聚合信息网站获取相对大众并符合自己的信息，但也有缺点：我需要下载这么多个 APP 去看新闻和文章吗？这些网站就拥有所有我想知道的吗？当然，像「即刻」、「Flipboard」这种阅读聚合类应用适当地缓解了这样的矛盾，但是这些信息源真的已经足够适合自己了吗？当初我找到了解决方法，也一直使用到现在，这个方法就是订阅 RSS 源。</p><p>本篇文章将简介 RSS，初步订阅适合自己的 RSS 源，并对于一部分不提供 RSS 订阅的网站，我们为其创建 RSS 源并订阅。</p><span id="more"></span><h2 id="RSS">RSS</h2><p>RSS（Really Simple Syndication，简易信息聚合）是一种消息来源格式规范，表达了「聚合真的很简单」这样的愿景，我们可以将其看作是一种定制个性化推送信息的服务。</p><h3 id="macOS-客户端">macOS 客户端</h3><p>我推荐 Reeder 3，因其功能足且美观，还支持iOS平台，付费。</p><p><img src="http://img.frankorz.com/57efd6ad03ce5.jpg" alt=""></p><p>另外 Leaf 也不错。</p><h3 id="Windows-客户端">Windows 客户端</h3><p>自己不使用 Windows，根据<a href="https://www.zhihu.com/question/19580096">你必读的 RSS 订阅源有哪些？</a>不负责地推荐 <a href="http://www.feeddemon.com">FeedDemon</a>。</p><p><img src="http://img.frankorz.com/57efd6a8da7fa.jpg" alt=""></p><h3 id="其他平台">其他平台</h3><p>RSS 平台提供了更广泛的服务，例如查找网站的 RSS 源并订阅，查看热门订阅源，管理分类订阅源等。优秀的 RSS 平台有 Feedly、Inoreader 等，由于不可描述的原因，国内这些平台连接速度会较慢，所以我给了一下两种推荐：</p><p>入门的话推荐国产的<a href="http://bluereader.org">深蓝阅读</a>，支持 iOS、Android 平台。</p><p><img src="http://img.frankorz.com/57efd6a7a1d78.jpg" alt=""></p><p>✨进阶推荐 Inoreader <a href="https://www.inoreader.com/">官网</a> <a href="http://sspai.com/27576">评测</a><br>Inoreader 也支持 iOS、Android、Windows Phone 平台，也有对应的浏览器插件。</p><p><img src="http://img.frankorz.com/58eb132d5167d.jpg" alt=""></p><p>Ioreader 推荐的第三方 RSS 阅读器。</p><p><img src="http://img.frankorz.com/58eb1327dc309.jpg" alt=""></p><p>RSS 阅读器只是显示 RSS 内容的一个平台，因此选自己喜欢的就好，我们的重点是如何获取适合自己的 RSS 源。</p><h2 id="RSS源">RSS源</h2><h3 id="常见网站-RSS-订阅位置">常见网站 RSS 订阅位置</h3><p>我们可以再去到自己常去的网站，看看有没有提供RSS订阅。例如<a href="http://sspai.com">少数派首页</a>，我搜索<code>rss</code>，就找到了RSS源。</p><p><img src="http://img.frankorz.com/57efd6a57f383.jpg" alt=""></p><p>再例如在一些博客中，WIFI 一样的图标就是 RSS 订阅地址，不认识的话现在就记下来吧~</p><p><img src="http://img.frankorz.com/58eb17bddd2a1.jpg" alt=""></p><p>我们可以直接复制链接地址到阅读器中，这样就能简单的订阅 RSS 了。</p><h3 id="播客">播客</h3><p><a href="http://picklemonkey.net/feedflipper-home">Feed Flipper</a> 可以把 iTunes 的播客链接转成 RSS 源地址。</p><p><img src="http://img.frankorz.com/58eb13311ee9c.jpg" alt=""></p><p>作者另外一个项目 <a href="http://picklemonkey.net/cloudflipper">Cloud Flipper</a> 能把 SoundCloud 转成 RSS。</p><p>关于中文播客网站的订阅可以参考<a href="https://www.jinbo123.com/6280.html">如何获取喜马拉雅、荔枝FM、考拉FM等中文播客RSS源</a>。</p><h3 id="Medium">Medium</h3><p>Medium 是国外版的「简书」，里面有很多独特而优质的内容和天才般的作家，单独拿出来讲是因为我实在是太喜欢 Medium 了！</p><p><img src="http://img.frankorz.com/58eb13282536a.jpg" alt=""></p><p><img src="http://img.frankorz.com/58eb132a54eb6.jpg" alt=""></p><p>注：这里使用的是 Safari 插件（macOS 专属）—— <a href="https://safari-extensions.apple.com/details/?id=co.kaishin.syndicate-5G38N4D8G2">Syndicate</a></p><h3 id="RSS-搜索利器">RSS 搜索利器</h3><p>如果不依赖平台自带的 RSS 源搜索功能，我们也有其他选择。</p><h4 id="浏览器插件">浏览器插件</h4><p>前面介绍了 Safari 的 RSS 源查找插件 <a href="https://safari-extensions.apple.com/details/?id=co.kaishin.syndicate-5G38N4D8G2">Syndicate</a>，这里介绍一个 Chrome 浏览器的插件——<a href="https://chrome.google.com/webstore/detail/rss-subscription-extensio/nlbjncdgjeocebhnmkbbbdekmmmcbfjd?hl=zh-CN">RSS Subscription Extension</a>。</p><p><img src="http://img.frankorz.com/58eb13314368e.jpg" alt=""></p><h4 id="微广场">微广场</h4><p><a href="http://www.iwgc.cn/">微广场</a>提供微信公众号的RSS订阅，免费用户可订阅10个内容源。</p><p><img src="http://img.frankorz.com/57efd6ac1ff41.jpg" alt=""></p><h3 id="RSS源推荐">RSS源推荐</h3><p><a href="https://www.zhihu.com/question/19580096">你必读的 RSS 订阅源有哪些？</a> 这里已经总结了很多优秀的订阅源。</p><p>例如：</p><table><thead><tr><th>名称</th><th>RSS地址</th></tr></thead><tbody><tr><td>知乎每日精选</td><td><a href="http://www.zhihu.com/rss">http://www.zhihu.com/rss</a></td></tr><tr><td>读书笔记</td><td><a href="http://www.write.org.cn/feed">http://www.write.org.cn/feed</a></td></tr><tr><td>褪墨</td><td><a href="http://feed.mifengtd.cn/">http://feed.mifengtd.cn/</a></td></tr></tbody></table><p>不再阐述太多，适合自己的才是最好的。</p><h2 id="创建RSS源">创建RSS源</h2><p>部分网站可能比较看重 PV 值，就是希望用户主动进入他们的网站，或者其他原因不提供 RSS 源，这时候就需要我们使用 <a href="http://feed43.com">feed43</a> 的服务来为我们扫平障碍。国内网站似乎不太热衷主动提供 RSS 订阅地址，这点国外做的较好。</p><h3 id="Step-1">Step 1</h3><p>首先点进 <a href="http://feed43.com">feed43</a> 右上角 <code>Create Account</code> 注册，登录完之后到主页点击 <code>Create new feed</code> 来创建自己的RSS源。接下来我将会用<a href="http://www.guokr.com/scientific/">科学人|果壳网</a>来做示范。</p><p>我们把<code>http://www.guokr.com/scientific/</code>复制进 Step 1的 Address，编码会自动选择，如果出现乱码可以尝试<code>utf-8</code>或其他编码，之后点击 Reload。</p><p><img src="http://img.frankorz.com/57efd6b430b63.jpg" alt=""></p><h3 id="Step-2">Step 2</h3><p>我们可以看到页面的源码，看到这不要慌，这些都有套路。</p><p><img src="http://img.frankorz.com/57efd6d71fbb4.jpg" alt=""></p><p>像上面的图一样，红框中就是我们想要获取的单条信息。其中文章都是一块一块的，样子一样，变的只是其中的文字内容、图片地址，所以相应的代码块也是相似的。我们要清楚的就是想要获取的信息，例如：标题、图片、简介。</p><p><img src="http://img.frankorz.com/57efd6b4e08ad.jpg" alt=""></p><p>网页源代码是一层一层的，我们首先定位到大红框单个文章块的代码块。简单点可以直接 Ctrl+F 查询其中的标题，例如弱光子。</p><p><img src="http://img.frankorz.com/57efd6b097fad.jpg" alt=""></p><p><img src="http://img.frankorz.com/57f05e81414b4.jpg" alt=""></p><p>如果不容易找到文章所在的代码块的话可以搜索下一个文章标题来找到代码之间的分界点，现在总结下找到的代码。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;article&quot;</span>&gt;</span></span><br><span class="line">...无关代码...</span><br><span class="line"><span class="tag">&lt;<span class="name">h3</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">class</span>=<span class="string">&quot;article-title&quot;</span> <span class="attr">href</span>=<span class="string">&quot;http://www.guokr.com/article/441758/&quot;</span> <span class="attr">target</span>=<span class="string">&quot;_blank&quot;</span> <span class="attr">data-gaevent</span>=<span class="string">&quot;scientific_title:v1.1.1.1:scientific&quot;</span>&gt;</span>“弱光子人体安检仪”会损害健康吗？<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span><br><span class="line">...无关代码...</span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;http://www.guokr.com/article/441758/&quot;</span> <span class="attr">target</span>=<span class="string">&quot;_blank&quot;</span> <span class="attr">data-gaevent</span>=<span class="string">&quot;scientific_image:v1.1.1.1:scientific&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">img</span> <span class="attr">data-height</span>=<span class="string">&quot;188&quot;</span> <span class="attr">data-width</span>=<span class="string">&quot;330&quot;</span> <span class="attr">src</span>=<span class="string">&quot;http://3.im.guokr.com/iTOiB3e1WalZmxiYApnzUIzxRoZcw6AHj2fjxVTRncNKAQAAvAAAAEpQ.jpg&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">...无关空格...</span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;article-summary&quot;</span>&gt;</span>成都双流机场使用的“弱光子人体安检仪”，其实是一种X射线成像装置，这种仪器会对人体健康产生什么影响，目前尚无法验证。而在安检中应该使用何种仪器，则不只是个科学问题。<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">...无关空格...</span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>你还可以用 Chrome 或其他浏览器来找到文章对应的代码块。</p><p><img src="http://img.frankorz.com/57efd6bae524b.jpg" alt=""></p><p><img src="http://img.frankorz.com/57efd6d76e52f.jpg" alt=""></p><p>注意HTML标记符号是两两对应的，在截取的时候尽量保持完整，例如：<code>&lt;h3&gt;标题代码&lt;/h3&gt;</code>、<code>&lt;a href...&gt;&lt;/a&gt;</code>、<code>&lt;p ..&gt;简介内容&lt;/p&gt;</code>。</p><p>在feed43中，我们会用到两种代码块：<code>&#123; % &#125;</code>和<code>&#123;*&#125;</code>，其中<code>&#123; % &#125;</code>替换你想获取的内容，<code>&#123;*&#125;</code>用来省略无关代码。</p><p><strong>注意，由于文章某些发布限制，{ % }大括号中间应没有空格，如下面代码所示</strong></p><p>替换之后得到：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;article&quot;</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">class</span>=<span class="string">&quot;article-title&quot;</span> <span class="attr">href</span>=<span class="string">&quot;&#123;*&#125;&quot;</span> <span class="attr">target</span>=<span class="string">&quot;_blank&quot;</span> <span class="attr">data-gaevent</span>=<span class="string">&quot;scientific_title:v1.1.1.1:scientific&quot;</span>&gt;</span>&#123;%&#125;<span class="tag">&lt;/<span class="name">a</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">h3</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&#123;%&#125;&quot;</span> <span class="attr">target</span>=<span class="string">&quot;_blank&quot;</span> <span class="attr">data-gaevent</span>=<span class="string">&quot;scientific_image:v1.1.1.1:scientific&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">img</span> <span class="attr">data-height</span>=<span class="string">&quot;&#123;*&#125;&quot;</span> <span class="attr">data-width</span>=<span class="string">&quot;&#123;*&#125;&quot;</span> <span class="attr">src</span>=<span class="string">&quot;&#123;%&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">a</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;article-summary&quot;</span>&gt;</span>&#123;%&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#123;*&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>注意：</p><ul><li><code>&lt;div&gt;</code>到<code>&lt;/div&gt;</code>之间就是一个层，注意保留对应文章代码块的完整</li><li>两个标签中间有空格可以直接用<code>&#123;*&#125;</code>代替</li><li>上面文章链接出现了不止一次，可以只找一个，其他忽略</li><li>观察代码，图片宽高不一致，所以<code>data-height</code>和<code>data-width</code>处参数要用<code>&#123;*&#125;</code>忽略</li><li><code>class=&quot;&quot;</code>可以看作为小套路，一般代码块对应的参数都是一致的，例如标题对标题，内容对内容。</li><li>网站中每个代码层之间可能会有冗余的空格存在（例如<code>&lt;div&gt;</code> 和下一个<code>&lt;div&gt;</code>之间），所以多用<code>&#123;*&#125;</code>替换你觉得出错的地方。（<code>&lt;div&gt;...&lt;/div&gt;&#123;*&#125;&lt;div&gt;...&lt;/div&gt;</code>）</li></ul><p>点击 Extract，可以看到想要的信息都被找出来了，接下来就是用这些信息去组成RSS文章的界面。</p><p><img src="http://img.frankorz.com/57efd6be382cf.jpg" alt=""></p><h3 id="Step-3">Step 3</h3><p><img src="http://img.frankorz.com/57efd6c854350.jpg" alt=""></p><p>把对应的文章标题、文章链接标记块填到对应区域，当然你可以自定义每个文章的标题、链接、内容，这里我们用我们获取到的。</p><p><code>&lt;center&gt;...&lt;/center&gt;</code>表示居中代码块中的元素，<code>&lt;img src=&quot;&quot;&gt;</code>是图片标签，中间填入图片链接以显示图片，<code>&lt;br&gt;</code>表示换一行。你还可以在内容内再添加一个指向文章的链接，例如<code>&lt;a href=&quot;&#123; %2 &#125;&quot;&gt;链接显示文字&lt;/a&gt;</code>，更多标签可以参考<a href="http://www.w3school.com.cn/tags/att_img_src.asp">HTML图片标签</a>左下角的标签列表。</p><p>点击Preview来到 Step 4 收获成果。</p><h3 id="Step-4">Step 4</h3><p><img src="http://img.frankorz.com/57efd6bd615be.jpg" alt=""></p><p>把地址添加到 RSS 阅读器上</p><p><img src="http://img.frankorz.com/57efd6bedc81d.jpg" alt=""></p><p>我们可以直接点击标题跳转到网页（其他阅读器可能不一样），现在说说 feed43 的注意事项。</p><h3 id="注意">注意</h3><h4 id="feed43-爬取限制">feed43 爬取限制</h4><p><img src="http://img.frankorz.com/57efd6bdc0486.jpg" alt=""></p><p>免费的计划是六小时刷新一次 RSS 地址，也就是六小时才更新一次内容，有需要的可以购买计划，不过一般够用了。</p><h4 id="feed43-爬取失败部分原因">feed43 爬取失败部分原因</h4><ul><li>需要登录才看到内容的网页，例如一些论坛。</li><li>内容由 JS 方式生成</li><li>网站禁止了 feed43 ip 的访问，即403错误。</li><li>网站不支持IE浏览器</li></ul><h2 id="RSS-全文输出">RSS 全文输出</h2><p>全文输出即是直接从文章链接提取内容，并替代与链接匹配的介绍内容，例如上文中科学人文章的简介和其文章内容。我在看 RSS 订阅源内容时一般先看标题与简介，对内容感兴趣的话才去查看原文内容，我认为这也是体现 RSS 获取信息效率的一方面。这里也给出 RSS 全文输出的教程，让大家有更多选择。</p><h3 id="Free-Full-RSS">Free Full RSS</h3><p>这里我用 <a href="https://www.freefullrss.com/">Free Full RSS</a> 做示范。</p><p><img src="http://img.frankorz.com/57efd6bd2d312.jpg" alt=""></p><p>创建成功后，会得到新的 RSS 订阅地址。<a href="https://www.freefullrss.com/makefulltextfeed.php?url=www.feed43.com%2F4536674726572775.xml&amp;max=10&amp;links=preserve&amp;exc=&amp;submit=Create+Full+Text+RSS">科学人 | 果壳网 全文输出RSS源地址</a></p><p><img src="http://img.frankorz.com/57efd6c1e928b.jpg" alt=""></p><p>效果：</p><p><img src="http://img.frankorz.com/57efd6c384ed1.jpg" alt=""></p><h3 id="其他全文输出网站">其他全文输出网站</h3><table><thead><tr><th>名称</th><th>网址</th></tr></thead><tbody><tr><td><a href="http://fulltextrssfeed.com/">Full Text RSS Feed Builder</a></td><td>完全免费</td></tr><tr><td><a href="http://fivefilters.org/content-only/">fivefilters</a></td><td>抠门的免费计划，有付费计划。</td></tr><tr><td><a href="http://www.fullcontentrss.com/">FULL CONTENT RSS</a></td><td>限时免费 KEY，过时失效</td></tr></tbody></table><h2 id="自制-RSS-源用途">自制 RSS 源用途</h2><p>学会了制作 RSS 源，那么我们能做什么有趣的事情呢？！</p><p>订阅自己关注的网站、有价值的博客、买买买信息、政府机关部门（？）、播客等等。</p><p>例如自己用 Feed43 做了些 RSS 源：<br><a href="http://www.queshu.com">缺书网</a>来获取买书优惠信息，<a href="http://feed43.com/0321836552054708.xml">RSS 源地址</a>。<br><a href="http://www.ituring.com.cn">图灵社区</a>关注新出版的技术书籍，<a href="http://feed43.com/3267066686713821.xml">RSS 源地址</a>。</p><p>想要了解更多关于 RSS 可以看看这篇文章 <a href="http://sspai.com/34280">使用 RSS 可以做什么你未曾想过的事</a>。</p><h2 id="总结">总结</h2><p>2013年 Google Reader 关闭，谷歌给的原因是用户流失，这最主流的RSS阅读器的退出似乎说明了 RSS 已经快要成为一个过时的阅读方式。这种阅读方式减少了网站和用户之间的交流，也不太适应短信息的节奏（想想你会用 RSS 刷微博吗？）。往大了看这是当今社会的节奏，人们更喜欢刷微博、刷朋友圈。海量的碎片信息会导致人们喜欢浅浅尝一口，而缺少深度的思考，逐渐迷失在海量的信息流中。生活离不开阅读，我们可能不能变成万事通，但我们可以更有效率地去获取自己想要的信息。</p><p>如果你看到这，或许可以考虑尝试下这种阅读方式？RSS 源贵精不贵多，先养成习惯，并判断这些信息对于自己的价值。</p><p>另外如果你根据本文成功创建了自己的 RSS 源，或者有疑问，请在本文评论中告诉我，欢迎分享你的 RSS 地址和经验。</p>]]></content>
      
      
      <categories>
          
          <category> 工具癖 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> RSS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Hexo 博客补丁</title>
      <link href="/2016/09/30/Hexo-patch/"/>
      <url>/2016/09/30/Hexo-patch/</url>
      
        <content type="html"><![CDATA[<p>本博客部分修改内容</p><p>注：原文的主题为 Next，现在在使用 Even，所以配置可能有些地方不一样。</p><span id="more"></span><h3 id="小建议">小建议</h3><p><code>hexo new &quot;标题&quot;</code> 的时候尽量取英文标题，在文章中再用 <code>title:</code> 指定中文标题，这样未来改标题好改，相应文章的多说评论也不会消失。</p><h3 id="Github-Pages-中增加-README-md">Github Pages 中增加 <a href="http://README.md">README.md</a></h3><p>很多朋友的 hexo 博客都是建在 Github 上的，作为一个项目，<code>README.md</code>文件能够在 Github 上介绍博客的一些信息，但是贸然把文件放到 <code>source</code> 文件夹中会被 hexo 检测并转为 html 文档，我们在站点配置文件中跳过即可。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">skip_render:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">README.md</span></span><br></pre></td></tr></table></figure><h3 id="网站底部字数统计">网站底部字数统计</h3><p>安装 hexo-wordcount<br><code>npm install hexo-wordcount --save</code></p><p>在<code>（博客主目录）/themes/next/layout/_partials/footer.swig</code>中最后加上</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;theme-info&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;powered-by&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">&quot;post-count&quot;</span>&gt;</span>博客全站共&#123;&#123; totalcount(site) &#125;&#125;字<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>更多参考：<a href="https://www.npmjs.com/package/hexo-wordcount">hexo-wordcount</a></p><h3 id="添加文章更新时间">添加文章更新时间</h3><p>修改<code>（博客主目录）/themes/next/layout/_macro/post.swig</code>文件，在<code>&lt;span class=&quot;post-time&quot;&gt;...&lt;/span&gt;</code>标签后添加</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;%<span class="keyword">if</span> post.<span class="property">updated</span> and post.<span class="property">updated</span> &gt; post.<span class="property">date</span>%&#125;</span><br><span class="line">  &lt;span <span class="keyword">class</span>=<span class="string">&quot;post-updated&quot;</span>&gt;</span><br><span class="line">&amp;nbsp; | &amp;nbsp; &#123;&#123; <span class="title function_">__</span>(<span class="string">&#x27;post.updated&#x27;</span>) &#125;&#125;</span><br><span class="line">&lt;time itemprop=<span class="string">&quot;dateUpdated&quot;</span> datetime=<span class="string">&quot;&#123;&#123; moment(post.updated).format() &#125;&#125;&quot;</span> content=<span class="string">&quot;&#123;&#123; date(post.updated, config.date_format) &#125;&#125;&quot;</span>&gt;</span><br><span class="line">  &#123;&#123; <span class="title function_">date</span>(post.<span class="property">updated</span>, config.<span class="property">date_format</span>) &#125;&#125;</span><br><span class="line">&lt;/time&gt;</span><br><span class="line">  &lt;/span&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br></pre></td></tr></table></figure><p><img src="http://img.frankorz.com/58004a76775f7.jpg" alt=""></p><p>根据博客配置文件中的 <code>language</code> 参数修改对应的语言配置文件<code>（博客主目录）/themes/next/languages/zh_Hans.yml</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">post:</span><br><span class="line">  updated: 更新于</span><br></pre></td></tr></table></figure><p>修改主题配置文件<code>（博客主目录）/themes/next/_config.yml</code>，增加一行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">display_updated: true</span><br></pre></td></tr></table></figure><p>写文章的时候可以直接在文章开头设置更新时间</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">updated: 2016-10-14 10:53:09</span><br></pre></td></tr></table></figure><p>没有这参数的话将会显示md文件的修改日期</p><h3 id="本地搜索">本地搜索</h3><p>安装 hexo-generator-search<br><code>npm install hexo-generator-search --save</code></p><p>编辑主目录下站点配置文件<code>_config.yml</code>加入</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">search:</span><br><span class="line">  path: search.xml</span><br><span class="line">  field: post</span><br></pre></td></tr></table></figure><h3 id="添加音乐">添加音乐</h3><h4 id="歌曲">歌曲</h4><p>在markdown文件中要添加音乐处加入iframe标签</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">iframe</span> <span class="attr">frameborder</span>=<span class="string">&quot;no&quot;</span> <span class="attr">border</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginwidth</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginheight</span>=<span class="string">&quot;0&quot;</span> <span class="attr">width</span>=<span class="string">330</span> <span class="attr">height</span>=<span class="string">86</span> <span class="attr">src</span>=<span class="string">&quot;http://music.163.com/outchain/player?type=2&amp;auto=0&amp;id=34578162&amp;height=66&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">iframe</span>&gt;</span></span><br></pre></td></tr></table></figure><p>需要居中可以在外添加<code>&lt;center&gt;</code>标签</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">center</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">iframe</span> <span class="attr">frameborder</span>=<span class="string">&quot;no&quot;</span> <span class="attr">border</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginwidth</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginheight</span>=<span class="string">&quot;0&quot;</span> <span class="attr">width</span>=<span class="string">330</span> <span class="attr">height</span>=<span class="string">86</span> <span class="attr">src</span>=<span class="string">&quot;http://music.163.com/outchain/player?type=2&amp;id=34578162&amp;auto=0&amp;height=66&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">iframe</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">center</span>&gt;</span></span><br></pre></td></tr></table></figure><p>其中id后数字为网易云音乐歌曲id，可以从<a href="http://music.daoapp.io">Music</a>处搜索歌名，例如搜索<code>memories</code>，我选择了<code>MEMORIES - 『MEMORIES』- ALAN WALKER</code>之后到达 <a href="http://music.daoapp.io/player?song=34578162">http://music.daoapp.io/player?song=34578162</a> ，song后数字便是歌曲id。另外高度宽度可以自己根据博客样式更改，<code>auto = 0</code>是不自动播放，需要自动播放请改为<code>auto = 1</code>。</p><p>你也可以直接从网易云音乐<a href="http://music.163.com/">官网</a>中搜索到歌曲，再生成外链播放器。有些版权保护的歌曲或歌单不支持生成外链，可以直接像上面的方法一样查 id 改代码即可。</p><p><img src="http://img.frankorz.com/5808e65d13ca0.jpg" alt=""></p><p>示例：</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="http://music.163.com/outchain/player?type=2&id=34578162&auto=0&height=66"></iframe><h4 id="歌单">歌单</h4><p>操作和上面一样，搜索后选择歌单，找到id再替换。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">iframe</span> <span class="attr">frameborder</span>=<span class="string">&quot;no&quot;</span> <span class="attr">border</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginwidth</span>=<span class="string">&quot;0&quot;</span> <span class="attr">marginheight</span>=<span class="string">&quot;0&quot;</span> <span class="attr">width</span>=<span class="string">330</span> <span class="attr">height</span>=<span class="string">450</span> <span class="attr">src</span>=<span class="string">&quot;http://music.163.com/outchain/player?type=0&amp;id=144236857&amp;auto=0&amp;height=430&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">iframe</span>&gt;</span></span><br></pre></td></tr></table></figure><p>示例：</p><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=450 src="http://music.163.com/outchain/player?type=0&id=144236857&auto=0&height=430"></iframe><p>其他高深玩法参考 <a href="https://github.com/YUX-IO/163music-APlayer-you-get-docker">163music-APlayer-you-get-docker</a></p><h4 id="歌词">歌词</h4><p>要带歌词的话需要用到适用于 Hexo 的 <a href="https://github.com/grzhan/hexo-tag-aplayer">Aplayer</a>。<br>如果歌词、音乐文件、音乐图片保存到博客服务器中，请参考上面的链接进去设置 <code>post asset folders</code>，这里用外链做例子。</p><p>安装 Aplayer：<br><code>npm install --save hexo-tag-aplayer</code></p><p>使用：</p><script src="//gist.github.com/7c95ad593ae121addbd0f49952346f95.js"></script><p>参数：</p><ul><li><code>title</code> : 音乐标题</li><li><code>author</code> : 歌手名</li><li><code>url</code>: 音乐文件路径</li><li><code>picture_url</code>: 可选，音乐图片路径</li><li><code>narrow</code>: 可选，狭窄的样式</li><li><code>autoplay</code>: 可选，自动播放，不支持移动浏览器</li><li><code>width:xxx</code> : 可选，前缀 <code>width:</code>，播放器的宽度 (默认: 100%)</li><li><code>lrc:xxx</code> : 可选，前缀 <code>lrc:</code>，LRC 文件路径</li></ul><p>例子：</p><script src="//gist.github.com/ebfae5e491e63bd58425b793596767e1.js"></script><p>建议使用七牛云存文件，直接用文件外链来设置，其中歌词文件内容参照 lrc 格式填写。</p><p><img src="http://img.frankorz.com/58367ff2a6f88.jpg" alt=""></p>        <div id="aplayer-ZfuYCeKL" class="aplayer aplayer-tag-marker" style="margin-bottom: 20px;">            <pre class="aplayer-lrc-content"></pre>        </div>        <script>          var ap = new APlayer({            element: document.getElementById("aplayer-ZfuYCeKL"),            narrow: false,            autoplay: false,            showlrc: 3,            music: {              title: "童话镇",              author: "陈一发儿",              url: "http://oelv7v3r6.bkt.clouddn.com/%E9%99%88%E4%B8%80%E5%8F%91%E5%84%BF%20-%20%E7%AB%A5%E8%AF%9D%E9%95%87.mp3",              pic: "http://p3.music.126.net/tfa811GLreJI_S0h9epqRA==/3394192426154346.jpg",              lrc: "http://oelv7v3r6.bkt.clouddn.com/lrc.txt"            }          });          window.aplayers || (window.aplayers = []);          window.aplayers.push(ap);        </script><h3 id="添加思维导图">添加思维导图</h3><p>利用之前App store 限免过的iOS版 MindNode，制作完导图之后，选择在MyMindNode上共享。</p><p><img src="http://img.frankorz.com/57ee83d2a3e7b.png" alt="IMG_0165"></p><p>上传文件后得到网址，打开后选择右上角导出按钮-&gt;Embed。</p><p><img src="http://img.frankorz.com/57ee83d13764e.jpg" alt=""></p><p>这里选择宽度和高度，再如添加音乐一样添加iframe标签即可，示例参考：<a href="http://latias94.github.io/books/">2016年书单、公开课</a> 。</p><h3 id="本博客动态背景">本博客动态背景</h3><p>在 <a href="https://pan.baidu.com/s/1jIuJ03s">百度云</a> 下载<code>particle.js</code>，并移动到<code>（博客主目录）/themes/next/source/js/src</code> 文件夹下。</p><p>然后在<code>（博客主目录）/themes/next/source/layout/_layout.swig</code>中的最后<code>body</code>标签上添加</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;/js/src/particle.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="控制首页中文章显示量">控制首页中文章显示量</h3><p>在要显示的文字后添加<code>&lt;!--more--&gt;</code>，例如本文章：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: Hexo 博客补丁</span><br><span class="line">date: 2016-09-30 22:46:29</span><br><span class="line">tags: [hexo]</span><br><span class="line">---</span><br><span class="line"></span><br><span class="line">本博客大部分的修改都在这里找到喔！</span><br><span class="line">&lt;!--more--&gt;</span><br></pre></td></tr></table></figure>]]></content>
      
      
      
        <tags>
            
            <tag> Hexo </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>利用iPad Pro学习方面的探索</title>
      <link href="/2016/09/29/iPad-Pro%20study/"/>
      <url>/2016/09/29/iPad-Pro%20study/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/57ee011831539.jpg" alt=""></p><p>前几天趁着Apple教育优惠买了9.7inch的iPad Pro 附送耳机，还买了个smart cover。昨天在香港剁手了Apple pencil，对深挖iPad Pro潜能做了些探索，在这里总结下。</p><span id="more"></span><h3 id="iPad-Pro是否有必要买？">iPad Pro是否有必要买？</h3><p>这个问题其实要从产品定位说起。对于我而言，我已经有iPhone 6 、Macbook 和 Kindle，我买了iPad Pro，就是想让它承担一些三者都不擅长或不方便的事情，例如看电子书，其中PDF为主(kindle对pdf体验差、我也不会在外出的时候带Macbook看，手机屏幕太小)、看视频(四个喇叭，大屏幕，爽！)、玩游戏(自己不太玩)、轻量的文字编辑(我没买键盘，因为觉得买了不如直接带Macbook)、手写笔记(本帖主题)、图片处理和绘画(真的很惊艳)等等。总而言之就是“带出去不麻烦的重量”+“一本书的大小”+“iOS系统”+“众多精致优秀的应用”。如果你觉得这些优点你愿意花五千多换取，那么你就不会买回来再抱怨iPad Pro也就那样。另外我入手的是128G，32G很容易满的相信我！</p><p>考虑到 iPad Pro 过剩的性能，如果没有触控笔的需求，可以考虑性价比更高的 iPad mini 或 iPad Air 2。</p><p>当然iPad Pro取代不了操作系统，它只是一种需求的折中处理方式，就像你不可能在其上进行大量的Excel编辑、视频编辑等，摆对iPad的位置，否则只会徒生烦恼。</p><p>应用方面我应该会另外写一帖出来，先放些链接待整理。<br><a href="https://www.zhihu.com/question/20077914">【知乎】怎样利用 iPad 学习？</a><br><a href="https://www.zhihu.com/question/22578656">【知乎】如何装备一个学术型的 iPad ？</a><br><a href="https://www.zhihu.com/question/21482079">【知乎】有哪些适合大学生使用的 app？</a></p><h3 id="支持手写的笔记软件">支持手写的笔记软件</h3><p>iPad Pro上的笔记软件非常多，评测也多，我简单放放一些链接，诸君自取。下面软件没提价格的就是免费的。<br><a href="http://matrix.sspai.com/p/cd982420">【推荐】手写笔记软件Notability，Notes Plus，Noteshelf，Upad横评</a><br><a href="https://www.zhihu.com/question/20009593">【知乎】iPad 是否可以取代纸质笔记本？</a><br><a href="https://www.zhihu.com/question/22238071">【知乎】iPad 笔记应用 UPad、Noteshelf、goodNotes 哪个更值得买？</a><br><a href="https://www.zhihu.com/question/37086615">【知乎】学生党，用到 PDF 的时候较多，各位觉得 iOS 设备上相对好用的PDF应用有哪些？</a><br><a href="https://www.zhihu.com/question/21218718">【知乎】如何利用 iPad 高效学习？</a><br><a href="http://www.********/single/20492/">手写笔记专题 Notability简评及和同类软件比较</a></p><p>经过自己试用Notability、Notes Plus、Upad3、GoodNotes，最后自己选用Notability。</p><h4 id="Notability">Notability</h4><p>iOS、Mac端都齐了，经常降价，有限免记录，50元。<br>优点：</p><ul><li>支持PDF导入</li><li>录音功能强：能分段录音，录音还能索引到，点击录音会跳转到相应笔记记录(未测试)。</li><li>编辑功能强：基本的标记功能都有，能插入基本的图片拍照、图形、Web片段和便签。长按会出现放大框供手写，编辑后也容易修改。</li><li>导出功能强：支持大部分国外网盘，还支持WebDAV(这代表支持国内的坚果云网盘)，输出格式有PDF(压缩包内另有m4a格式录音)、RTF、Notability的原生格式。</li><li>有Mac客户端，能在客户端上再次编辑。</li><li>稳定</li></ul><p>缺点：</p><ul><li>笔刷略少，只有铅笔和标记笔。</li><li>导出不保留原文件</li><li>模板少</li><li>耗电</li></ul><p>点评：<br>很全面的一款笔记软件，国外965的评价达到4星。其中录音功能是亮点，编辑功能也不弱，单单录音方面我觉得就是我心中最想要的笔记。</p><p><img src="http://img.frankorz.com/57eddeabf345f.jpg" alt=""></p><p><a href="http://ipadpapers.com/search.asp?q=&amp;search=Search">这个网站</a>能够下载一些pdf模板…</p><p>下面其他软件就一句带过，或者点上面的链接进去看评测。<br>Notes Plus手写棒，Upad3模板多，GoodNotes较中庸。另外还有一款MarginNote听说是学术神器，做笔记还有思维导图…未测试，留个坑。</p><h3 id="跨平台的笔记系统">跨平台的笔记系统</h3><p>笔记方面两大巨头印象笔记和OneNote分开说是因为他们比较适合当作一个“笔记系统”，就是我们可以通过上面几个软件搞定完一篇笔记之后可以放进去重新整理、分类、加标签等。上面说的笔记软件都是偏重手写和标记，其中的标记包括了PDF的标记和图片的标记，也就是我们可以通过一些扫描类的APP扫描PPT获得图片再在上面做手写笔记等(这里推荐Readdle家的Scanner Pro)，OCR(图片识别成文字)方面推荐刚限免过的TextGrabber。</p><p>笔记系统方面参考：<br><a href="http://www.15yan.com/story/eK9ubPnu9Q1/">从卡片柜到OneNote</a><br><a href="https://www.zhihu.com/question/23427617">【知乎】如何构建自己的笔记系统？</a></p><p>其实涉及到印象笔记和OneNote的话有能有很多方面的对比了…</p><h4 id="OneNote">OneNote</h4><p>优点：</p><ul><li>功能齐全</li><li>如果想建立自己的知识体系(无限层次的笔记系统)</li><li>丰富的格式支持</li><li>免费</li></ul><p>缺点：</p><ul><li>iOS、Mac端功能不全</li><li>onedrive同步慢</li></ul><p>我曾在知乎写过一篇关于OneNote的答案，想入门可以看看:)<br><a href="http://bbs.feng.com/%E6%80%8E%E6%A0%B7%E7%94%A8%E5%A5%BD%20OneNote%EF%BC%9F">【知乎】怎样用好 OneNote？</a></p><h4 id="印象笔记">印象笔记</h4><p>优点：</p><ul><li>界面精致</li><li>功能够用，移动端也很方便</li><li>同步快</li><li>有丰富的第三方接口</li></ul><p>缺点：</p><ul><li>笔记层次只有三层</li><li>每月免费上传空间只有60M</li><li>免费用户只能授权两台机器</li><li>原生不支持markdown</li></ul><h4 id="总结">总结</h4><p>总而言之，OneNote特点是功能强大，印象笔记强调的是用得爽。<br>如果你喜欢层次分明，很注重每个笔记的结构，和关联的附件等等的强大功能，OneNote适合你。</p><p>如果你喜欢知识片段或文章的收集和搜索(还能搜索图中的文字)，互联网上文章的摘抄(印象笔记强大的剪藏插件)，喜欢小巧精致实用这些特色的，印象笔记适合你。</p><p>(自己还有点偏见就是笔记就要用大公司的，小公司动不动就倒闭这咋整？)<br>实际上，这两个笔记软件并不排斥，还能和很多应用配合。其中的OneNote更是可以单独作为笔记软件用，支持手写。另外我们还可以想象下日常生活中的应用：</p><h5 id="实例一">实例一</h5><p>我在图书馆看书，看到某一页，“唔，非常有感悟！”，我会用Scanner Pro去扫描下来，还想要在上面手写些笔记的话，导入到Notability补充一些笔记，搞定了我导出到印象笔记加标签分类。如果你习惯用强大的OneNote，还能把印象笔记当成第一步知识的分类，再在OneNote构建你的笔记系统。这部分在上面的从卡片柜到OneNote有很详细的说明。</p><h5 id="实例二">实例二</h5><p>我事先有老师的PPT讲义，我事先在电脑把PPT导入的Readdle家的Documents 5(文件管理应用，我iPad的文件都存在内，能随时导出到其他应用操作)，或者直接导入Notability，上课的时候我可以一边录音一边做些标记，课后根据录音整理笔记，再导入到你的笔记系统：印象笔记或OneNote。<br>在这里也介绍下PDF Expert，之前不和笔记软件放一起是因为他是一款优秀的PDF阅读器和标记软件。</p><h3 id="PDF-阅读和批注">PDF 阅读和批注</h3><h4 id="PDF-Expert">PDF Expert</h4><p><a href="https://itunes.apple.com/cn/app/pdf-expert-5-tian-biao-pi/id743974925?mt=8">PDF Expert</a>是iOS版刚出时限免收的(没错就是炫耀！)，现价68元。</p><p>优点：</p><ul><li>支持大量PDF标记(Notability和其他笔记软件只支持手写的荧光笔，PDF Expert还支持选中文字标记)</li><li>支持众多网盘，包括WebDAV(包括国内的坚果云)</li><li>PDF加密、压缩。</li><li>阅读时的目录、书签都很人性化。</li><li>长按能加录音、图像、文字、笔记。</li><li>有Mac客户端，能在客户端进行二次编辑，</li><li>支持简单的裁剪，文本语音等。</li></ul><p>缺点：</p><ul><li>录音没有索引，只能自己记得插入录音时的文本位置去查找。</li></ul><p><img src="http://img.frankorz.com/57edde25949ac.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde2cf18ed.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde2b0a18d.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde1f42671.jpg" alt=""></p><p>云同步方面建议国外用dropbox或其他的国外网盘，国内用坚果云(国内唯一支持WebDAV，<a href="http://sspai.com/34613">介绍点我</a>)，我现在百度云存资源，坚果云配合PDF Expert、Notability、Scanner Pro等软件存文档(注意文档和资源的差别)，OneDrive(配合hosts勉强能用)配合OneNote。</p><p>这款软件和Notability其实不太一样，Notability注重会议、课堂或其他录音场合时做笔记，还支持导入网页视图等(维基百科或你能想到的，缺点是不能对视图进行编辑)，而PDF Expert注重阅读的体验和对PDF进行一些更漂亮的批注。<br>其实阅读和标注方面还有很多不错的软件，例如几乎万能的GoodReader、专注批注的iAnnotate PDF等，但Readdle公司的PDF Expert足够精致和优秀！</p><h4 id="Flexcil">Flexcil</h4><p><a href="https://itunes.apple.com/us/app/apple-store/id1146812963?mt=8">Flexcil</a> 是最近找到一款不错的免费应用，主要功能是对 PDF 文件提供标注和笔记需求。</p><p>优点：</p><ul><li>对 PDF 做的<strong>笔记</strong>(注意不是标注)能够整理到一起</li><li>支持 Apple pencil</li><li>手势强大，用手标记或复制文字图片到笔记中非常快捷</li><li>对触控笔的支持同样强大方便</li></ul><p>缺点：</p><ul><li>只主打对 PDF 写笔记的功能</li></ul><p><img src="http://img.frankorz.com/58393b52caf2c.jpg" alt=""></p><p><img src="http://img.frankorz.com/58393b511aa9d.jpg" alt=""></p><h3 id="扫描文档">扫描文档</h3><p>扫描文档我推荐Readdle家的<a href="https://itunes.apple.com/cn/app/scanner-pro-7-ocr-pdf-wen/id333710667?mt=8">Scanner Pro</a>, 易用，我就不横向对比其他应用了，大家看效果就好。最低曾到1元，现价25.</p><p><img src="http://img.frankorz.com/57edde39b0615.png" alt=""></p><p><img src="http://img.frankorz.com/57edde341c983.png" alt=""></p><p>图一的蓝框是自动选中的，能够自己再次移动，另外Scanner Pro 支持OCR，不过又慢又差。还支持自动上传和处理流程，其中自动上传网盘除了国外的著名网盘外还有OneNote、Evernote、WebDAV(国内的坚果云)。缺点是在黄光下支持不好，尽量在光线充足下照相。</p><p>同类型的应用还推荐<a href="https://itunes.apple.com/cn/app/sao-miao-quan-neng-wang-camscanner/id569846869?mt=8">扫描全能王</a>，如今还在限免，估计赚钱方向放在自家云空间上了。扫描效果可能没Scanner Pro好，但是OCR又快又棒！如果有这种需求的不妨选择他。他有自己的一套云空间供上传文件，另外上传还支持百度云网盘(亮点)，可惜不支持WebDAV。</p><h4 id="对比">对比</h4><h5 id="Scanner-Pro">Scanner Pro</h5><p>这应用的亮点是能在照相的时候动态框选文字，如果你手稳的话框选一小段时间会自动照相，很人性化。</p><p><img src="http://img.frankorz.com/57edde38dfc04.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde387d39e.jpg" alt=""></p><p>照完相能在编辑的地方重选区域，我这里是直接按软件识别的文档照相po出来。</p><h5 id="全能扫描王">全能扫描王</h5><p><img src="http://img.frankorz.com/57edde3989ff3.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde54f25c2.jpg" alt=""></p><p>这里是照完相自动选中的框，文档图是锐化过的，可以对比下。</p><p><img src="http://img.frankorz.com/57edde454cd52.jpg" alt=""></p><p>文字识别就是意外之喜了。</p><h5 id="总结-2">总结</h5><p>如果你们观察仔细的话，会看到Scanner Pro框选中上面一个带页码的横线是弯曲的，处理之后自动整平了。漫画处也能看出来，我认为他的算法比全能扫描王好，全能扫描王强在和TextGrabber相当的OCR文字且免费，但不支持WebDAV。</p><h3 id="OCR识别文字">OCR识别文字</h3><p>识别文字我使用<a href="https://itunes.apple.com/cn/app/textgrabber-%20-qr-code-scanner/id438475005?mt=8">TextGrabber</a>，该应用前不久免费，现价30。</p><p><img src="http://img.frankorz.com/57edde52c9642.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde451e721.jpg" alt=""></p><p>偶尔有用哈~</p><h3 id="查询单词释义">查询单词释义</h3><p>查单词倚重的是词库，权威的词库对一些专业而言重要性更是不言而喻。在这里我推荐扩展性极强的<a href="https://itunes.apple.com/cn/app/ou-lu-ying-yu-ci-dian-eudic/id393670998?mt=8">欧路词典Pro</a>，1元你买不了吃亏买不了上当(也有免费版的)，词库只要你找得到就是你的能耐。话不多说放截图：</p><p><img src="http://img.frankorz.com/57edde45e82db.jpg" alt=""></p><p>依次是红宝书、柯林斯英汉双解、Dictionary.com的词典…还能加有道在线翻译啊牛津啊什么的。<a href="http://www.pdawiki.com/forum/forum-4-1.html">词库点我</a><br>词典方面还推荐免费的<a href="https://itunes.apple.com/cn/app/merriam-webster-dictionary/id438477986?mt=8">Merriam-Webster Dictionary</a>，有道也不错。</p><h3 id="思维导图">思维导图</h3><p>思维导图不多介绍，可以给自己整理思路脉络，iPad版的思维导图中有两款很优秀：<a href="https://itunes.apple.com/cn/app/mindnode-delightful-mind-mapping/id312220102?mt=8">Mindnode</a> 68元，<a href="https://itunes.apple.com/cn/app/ithoughts-mindmap/id866786833?mt=8">iThoughts</a> 78元。前者漂亮，后者功能全，以前用的是后者，现在使用MindNode，因为可以把思维导图嵌入到博客~<a href="http://frankorz.com/books">实例</a></p><p>Mindnode:</p><p><img src="http://img.frankorz.com/57edde40419ce.png" alt=""></p><p>iThoughts:</p><p><img src="http://img.frankorz.com/57edde4943d2e.jpg" alt=""></p><h3 id="免VPN上谷哥搜索">免VPN上谷哥搜索</h3><p><img src="http://img.frankorz.com/57edde54443f3.jpg" alt=""></p><p>学习怎么能不谷鸽？！<br>原理：利用Surge+host配置实现免SS或VPN上谷歌，够用就好！ <a href="https://github.com/ifyour/Hosts-for-Surge">配置点我</a><br>支持Dropbox、OneDrive等，不过稳妥起见，还是建议使用国内的网盘，因为前者ip容易被封(经测试Dropbox ip被封了不少…)。</p><h3 id="浏览器">浏览器</h3><p>在iPad上，浏览器除了经常听见的Safari、QQ浏览器、Chrome、Opera等，其实还有不错的选择。<br>Safari有下载功能，但是却没有下载管理。这点下面两个浏览器都做的不错，自带扩展+下载。</p><h4 id="Mercury">Mercury</h4><p><a href="https://itunes.apple.com/cn/app/mercury-web-browser-pro-powerful/id1000610117?mt=8">下载地址</a><br>支持部分扩展、下载列表、阅读列表、用户代理、无痕浏览等等。</p><p><img src="http://img.frankorz.com/57edde51d4a89.jpg" alt=""></p><p>唯一的不足是广告拦截功能要钱…</p><h4 id="iCabMobile">iCabMobile</h4><p><a href="https://itunes.apple.com/cn/app/icab-mobile-web-browser/id308111628?mt=8">下载地址</a> 12元<br>这里的扩展名改成模块，也是为一些app提供了支持，下载功能、分页功能、设置中很多项可以调，具体请自己测试。</p><p><img src="http://img.frankorz.com/57edde5752c52.jpg" alt=""></p><h3 id="好用的画图工具">好用的画图工具</h3><p>这里介绍的是<a href="https://itunes.apple.com/cn/app/paper-fiftythree-ti-gong-zuo/id506003812?mt=8">Paper 53</a>，留心官网的话你们应该能在Apple pencil的介绍页上看到这款app的推荐！</p><p>这简直就是标识利器！又萌又好看！</p><p>有时候我们不需要秒天秒地的Procreate、photoshop，只是简单的给一张图加批注，或者简单画画表格、图案，小巧的paper 53就是你的选择！</p><p>前不久里面的笔刷已经全部免费，此时不入手更待何时？！</p><p><img src="http://img.frankorz.com/57edde55755ed.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde5648a03.jpg" alt=""></p><p>特别是画一些简图、图表插入到你的笔记中，不要太方便！</p><h3 id="计算工具">计算工具</h3><p><a href="https://itunes.apple.com/cn/app/wolframalpha/id334989259?mt=8">WolframAlpha</a> 18元，需联网，学术神器，自己看功能。</p><p><img src="http://img.frankorz.com/57edde67c39ca.jpg" alt=""></p><p><a href="https://itunes.apple.com/cn/app/you-shu-zhong-xin-ding-yi/id721606556?mt=8">有数</a> 12元，颜值还行的计算器。</p><p><a href="https://itunes.apple.com/cn/app/geogebra/id687678494?mt=8">GeoGebro</a> 动态教学软件，推荐给理工狗。</p><p>还有Matlab啊Calcbot啊自己去试吧不贴链接了。</p><h3 id="课程表">课程表</h3><p><a href="https://itunes.apple.com/cn/app/istudiez-pro-schedule-homework/id310636441?mt=8">iStudiez Pro</a> 才18！！！！！</p><p><img src="http://img.frankorz.com/57edde617bad3.jpg" alt=""></p><p><img src="http://img.frankorz.com/57edde617edc8.jpg" alt=""></p><p>毕业了才有iPad我真是追悔莫及…这个应用也有Mac版，你可以把所有考试、老师、作业、课程表等信息录入，配合日历使用，能追踪你整个学期的学业情况！为了用得上这个应用我打算考个研！学生日常必备！！</p><h3 id="阅读方式">阅读方式</h3><p>这里再总结下哈，如今网上的电子书大多分扫描版和非扫描版(主要为PDF文件)，非扫描版就是所谓的文字版，玩kindle的应该都知道一些格式：亚马逊主推的Mobi、AZW，还有Epub和PDF(TXT无排版，不考虑)。</p><p>这些格式你可以直接用Kindle for iPad 看，除了PDF。</p><ul><li>Epub，直接放iBooks上看，或者用多看阅读看也行，都能作批注，后者功能多些。Documents 5 也能看和批注，非常神奇…</li><li>Mobi，建议下个Calibre转换下格式，当然Kindle.app看也行。</li><li>AZW，同上。</li><li>PDF，用PDF Expert看，上文已经介绍很多，重点是能用笔批注。</li></ul><p>对我而言如果是小说之类批注不会太多的我会直接多看上看，Kindle看也行，主要看批注能否导出。</p><p>如果有需要批注较多的文件，我就直接用Calibre转PDF放PDF Expert上看，Apple pencil批注也是很方便的~(主要是因为不这样用我会觉得那只七百多的笔白买了你懂吧)</p><h3 id="分屏做笔记">分屏做笔记</h3><p>我觉得分屏做笔记不如直接在PDF上批注或者iPad看文档电脑来写。当然真的要分屏的话紧凑是肯定的，12.9的体验也高不到哪去，这些事情还要靠应用来配合。下面我举个PDF Expert+Notability的例子大家看看：</p><p><img src="http://img.frankorz.com/57eddea32f379.jpg" alt=""></p><p>右边Notability可以双指缩放笔记大小，手指长按或者点放大镜有缩放模式可以直接写，右边蓝色框是续写区域，在续写区域写完停顿后续写区域框会向右移动继续提供空白区域手写。</p><p><img src="http://img.frankorz.com/57edde6ada00d.jpg" alt=""></p><p>在左边的PDF Expert里右上角的按钮出现了之前没说过的文字重排功能(当然你也可以双指缩放去看文档)，但不支持扫描版(就是图片)。点击后的效果如下图：</p><p><img src="http://img.frankorz.com/57edde6f2e53c.jpg" alt=""></p><p>格式没了，不过可以调字体和大小。</p><p>受屏幕限制，笔记体验的确会差点，纠结这方面的建议考虑是否经常在桌上使用，是否需要这么大屏幕而且分屏做笔记。前一个问题是考虑到12.9的重量会让你只想放桌面上用、后一个问题如果你真的真的很依赖分屏做笔记那就直接12.9不要纠结了。</p><h3 id="通知面板">通知面板</h3><ul><li><a href="https://itunes.apple.com/cn/app/n-stats/id911261630?mt=8">N stats</a> 管理流量很直观，又免费！直接用iphone版的就好。</li><li><a href="https://itunes.apple.com/cn/app/omnifocus-2/id904071710?mt=8">OmniFocus</a> 258元，内购128元…其实以前感觉GTD的概念比较“玄学”，现在觉得还是挺好用的。生活中如果琐碎事太多，交给软件管理是很明智的选择，不过入门有学习成本，建议去少数派专题看看。</li><li><a href="https://itunes.apple.com/cn/app/atimelogger-2-si-ren-shi-jian/id576718804?mt=8">aTimeLogger 2</a> 25元，能管理你生活的一切所用时间，比较适合对自己时间管理十分严格的同学把玩！</li><li><a href="http://bbs.feng.com/Calendars%205">Calendars 5</a> 45元，也是Readdle家的日历软件，限免入的(哈哈哈哈哈哈爽啊)，能自动同步日历中的事件。上面就是Keep健身制定计划时候自动导入的日历，这里能直接显示出来，一条龙服务！(不用原生日历是因为日历的widget在面板上太丑…)</li></ul><h3 id="时间管理">时间管理</h3><p>其实时间管理这块推荐我纠结了蛮久的，一是不是所有人都喜欢管理自己的时间，二是市面上To-do list类的应用数不胜数(优秀的有如“潮汐”，“BeFocus Pro”等)，三是这类软件主观性略强，每个人可能习惯的管理方式都不一样。今天我来推荐一个新生的，较全面的时间监控软件<a href="https://itunes.apple.com/cn/app/timetrack.io/id1087340819?mt=8">Timetrack.io</a>。上面我推荐过aTimeLogger 2，这款软件则是同一个作者写的，功能更甚，加入了番茄钟，收费采取的是订阅制。<a href="http://sspai.com/34820">测评点我</a></p><p>功能不多说，点测评看。我的用法主要是给自己每天定一个目标，例如今天要读四小时的书，那我可以开始读的时候用软件开始计时，其中还能选择番茄钟模式。通过这种碎片时间，达到完成小目标的效果。(当然吃饭睡觉这些不用监控啦，监控你觉得必要的就好，例如工作、学习等)</p><h4 id="记录历史">记录历史</h4><p><img src="http://img.frankorz.com/57edde656e412.jpg" alt=""></p><p>目标页，你可以制定适合条件的监控时间(例如学习一块，也可以用标签计算)</p><p><img src="http://img.frankorz.com/57edde630ea35.jpg" alt=""></p><p>软件一开始使用的时候可能有些复杂，但是有心整理自己生活的同学可以尝试下~</p><p>widget和aTimeLogger 2差不多，订阅是6个月18元。不过目前大家可以先下应用，注册完之后email给作者邮箱 <a href="support@timetrack.io">support@timetrack.io</a> 获取一年专业版使用资格(用英文哈，邮件里带上你的账户邮箱)。</p><p>有人说iPad没啥用，有人说iPad必不可少，我觉得这是iPad有没有真正进入到你生活中的区别。当然再好的应用也只是工具，关键还要看使用工具的人，再好的工具也拦不住你刷漫画看段子哈&gt; w &lt;</p><h3 id="公开课">公开课</h3><p>只推两个，从iPad体验和知识获取方面都是最佳应用！</p><h4 id="网易公开课">网易公开课</h4><p>网易良心作，大名鼎鼎得大家可能都耳闻过，但是实在是太！好！用！了！</p><p><img src="http://img.frankorz.com/57edde6dd7dbd.jpg" alt=""></p><p>支持离线下载(满速)、调整字幕、记录播放位置等，世界名校有名的课程很多都找得到！(多嘴一句学iOS的话可以去找找斯坦福白胡子爷爷的课哈，里面也有，iOS8的)当然也要感谢无私的字幕组！</p><h4 id="Coursera">Coursera</h4><p>国外很火热的慕课平台，上完课还有认证证书！不过大多只有英文字幕，自己挑吧！</p><p><img src="http://img.frankorz.com/57edde6a582f2.jpg" alt=""></p><p>deadline 就是你学习的动力！</p><h4 id="其他">其他</h4><p>类似的还有<a href="https://itunes.apple.com/cn/app/xue-tang-zai-xianhd/id1021841572?mt=8">学堂在线</a>，给小孩子学的课程推荐<a href="https://itunes.apple.com/cn/app/khan-academy-you-can-learn/id469863705?mt=8">可汗学院</a>，编程的还有<a href="https://itunes.apple.com/cn/app/mu-ke-wang/id966761381?mt=8">慕课网</a>。</p><p>慕课方面还推荐一个网站<a href="http://blog.coursegraph.com/">课程图谱</a>，对慕课信息整理非常全面的一个网站，楼主只能帮你到这了！其他的自己挖掘吧哈！</p><h3 id="稍后阅读">稍后阅读</h3><p>人们生活越发繁忙，很多好文章不能及时看，稍后阅读类应用解决的就是这类痛点，让人们暂时存下网页，空闲时再查看信息。<br>通常来说稍后阅读有两大应用：1、<a href="https://itunes.apple.com/cn/app/instapaper/id288545208">Instapaper</a>  2、<a href="https://itunes.apple.com/cn/app/pocket-save-articles-videos/id309601447">Pocket</a></p><p>这两类应用对多平台都有很好的支持， 电脑端有各种浏览器插件能够支持保存网页内容，其中 Pocket 还有 macOS 客户端。鉴于 Pocket 收费，而 Instapaper 前不久宣布免费，这里介绍 Instapaper。</p><p>收集英文文章的时候会提供更美观的字体<br><img src="http://img.frankorz.com/5848e8756ee53.png" alt="IMG_0193"></p><p>应用内支持阅读文本信息，调整字体、字体大小、背景色，添加喜欢和归档文章<br><img src="http://img.frankorz.com/5848e887d1a45.png" alt="IMG_0190"></p><p>另外国内部分网站提高了反爬虫技术，所以一些网站例如知乎、新浪文章等等都会阻止这类应用的抓取。</p><p>我自己写了一个 Workflow ，支持收集知乎答案、知乎专栏、微信公众号文章和其他普通网站的文章到 Instapaper，暂时不支持新浪文章，有需要的可以看看博客中另外一篇文章：<a href="http://frankorz.com/2016/11/05/workflow-of-zhihu-to-instapaper/">用 Workflow 把知乎答案存到 Instapaper</a></p><h3 id="PDF写笔记新选择">PDF写笔记新选择</h3><p>有天无意发现了这个免费的应用——<a href="https://itunes.apple.com/cn/app/flexcil-pdf-reader-with-note/id1146812963?mt=8">Flexcil</a><br>之前说过 PDF Expert 是一个最佳的 PDF 阅读应用，但是这里 Flexcil 解决的痛点就是能在阅读 PDF 的同时在另外一块区域中写笔记！</p><p>偷个懒拿苹果商店的图片放在这…<br><img src="http://img.frankorz.com/sc552x414.jpeg" alt=""></p><p><img src="http://img.frankorz.com/9WiXK2eFo-sc552x414.jpeg" alt=""></p><p>这应用厉害的地方在于极其丰富的手势，和对 Apple Pencil 的支持，所以要玩转这个应用，手势一定要先熟悉！</p><p><img src="http://img.frankorz.com/5848e88015c81.png" alt="IMG_0191"></p><p><img src="http://img.frankorz.com/5848e87f86dad.png" alt="IMG_0192"></p><p><img src="http://img.frankorz.com/5848e88313f96.png" alt="IMG_0198"></p><p>应用的功能就这么简单，笔记本里面可以新建笔记本，整理笔记，书籍里面存放 PDF 文件，这样的免费应用简直良心~</p><h3 id="限免渠道">限免渠道</h3><p>收集限免应用这个坏习惯导致我成为松鼠症患者，不过这样能让你尽量不花钱的前提下找到额外的适合你的应用。我忽然想说这个就是因为我几乎上面大部分应用都是限免和冰点入的，Notability经常冰点到1元，其他的也偶尔半价。</p><p>1、<a href="https://itunes.apple.com/cn/app/appzapp-hd-pro-mei-ri-xin/id428248004?mt=8">Appzapp Pro</a><br>iPad最方便最全面的搜集限免冰点应用，能看历史价格，可以加降价提醒。<br>2、<a href="https://itunes.apple.com/cn/app/appzapp-hd-pro-mei-ri-xin/id428248004?mt=8">爱应用助手</a><br>国内应用，主打精品应用限免冰点推荐。<br>3、<a href="https://itunes.apple.com/cn/app/appshopper-social/id602522782?mt=8">AppShopper</a><br>和Appzapp类似的国外应用，功能差不多，缺点是只有iPhone版。<br>4、<a href="https://itunes.apple.com/cn/app/appso-wan-zhuan-*********eng/id966457637?mt=8">AppSo</a><br>国内应用，主打精品应用推荐，也有部分限免信息，缺点只有iPhone版。<br>5、<a href="http://sspai.com/">少数派</a><br>国内网站，主打移动设备上应用测评，有时候也会有优惠活动和限免信息，有很多有深度的精品文章。<br>6、<a href="http://free.apprcn.com/">反斗限免</a><br>这个网站就厉害了，啥限免信息都有，Windows、Mac、iOS、电子书、字体、游戏等，udemy的课程限免也有…RSS订阅也方便。</p><h3 id="保持专注">保持专注</h3><p>这是因为看到知乎的一篇答案让我有感而发。<a href="https://www.zhihu.com/question/27297809/answer/110588267">我们使用的软件太多，而不是太少</a></p><p>例如单单手写笔记应用就有这么多种，我们真的都需要吗？新闻资讯类我真的都要下几个客户端去看吗？</p><ul><li>我以前会在众多效率工具中迷失，追求做到极致的应用，后来发现浪费其上的时间还不如好好看一本书。</li><li>我以前会迷茫的去寻求一款最适合自己的背单词应用和慕课应用，都下载下来，大部分却没打开过。</li><li>我以前会在使用印象笔记和OneNote中纠结其功能，但是自己连笔记都很少记。</li></ul><p>软件再好，但你不用，只会剩下自己莫名的成就感。</p><p>其实我上面说了这么多，不是让各位都用用看，而是节省时间，从我的帖子中直接找到最适合自己的应用，并使用下去，真正用在生活之中。<br>确定好自己的需求，选择最适合自己的应用，在适合的时间使用它，并享受它给你带来的便利，这就够了！</p><p>这里贴一张那位知乎答主的图</p><p><img src="http://img.frankorz.com/57edde680af44.png" alt=""></p><p>在各种场景下找到可以充分利用这段时间的专注力的方式，例如你想用坐地铁公交的时间拿来背单词(高度专注力)，但是效果不佳，我是不是可以看看TED(中度专注力)？当然每个人的使用方式都不一样，最重要是找到适合自己的，并履行下去。</p><h3 id="总结-3">总结</h3><p>截至目前我也只是浅浅的说了自己围绕PDF文件学习的一些处理方案，也准备初步搭建自己的笔记系统，没有谈及一些学术方面和阅读方面的APP应用。另外Apple pencil很优秀，用到目前，没有手腕的误触现象。部分电容笔不支持屏蔽手腕误触，写字要悬着写，希望买触控笔的时候能考虑下这方面。题外话，画图方面的procreate配合Apple pencil简直爆炸，但是楼主不会画画啊TAT…</p><p>这里放一张上午寄过来的TEN DESIGN夹子照片，这样IPAD夹起来还能配合Dust Display作为Macbook的扩展屏！</p><p><img src="http://img.frankorz.com/57eddea316f91.jpg" alt=""></p><p>这夹子叫“Ten One Design Mountie”<br>购买链接：<a href="https://www.amazon.cn/gp/product/B00S74HI1K/ref=oh_aui_detailpage_o06_s00?ie=UTF8&amp;psc=1">Amazon</a> 具体的样子看这 <a href="https://www.v2ex.com/t/247157">v2ex</a><br>夹子的力道是分摊的，对不同型号的设备也提供的不同的橡胶替换，总得来说没想象中那么脆弱。</p><p>对于笔记来说，iPad作为一个过渡产品，我建议应用在考虑实用的同时，兼顾考虑跨平台这一方面，同样应用跨平台，说明也能在电脑端编辑你的批注，扩展更多的功能等。中间的云储存按自己需求选择坚果云、百度云或dropbox。重点是稳定，易用！</p><p>非常感谢你读到这里，如果有什么见解和疑问也请在评论提出，我进行会逐渐补充点图片和加入自己对iPad学习的想法，充分利用iPad Pro改变自己。</p><p>最后</p><p><strong>不要安装聊天应用！！！<br>不要安装聊天应用！！！<br>不要安装聊天应用！！！</strong></p>]]></content>
      
      
      
        <tags>
            
            <tag> iPad </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>iOS学习资源集合</title>
      <link href="/2016/09/24/iOS-learning-source/"/>
      <url>/2016/09/24/iOS-learning-source/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/57ee03bfdc293.jpg" alt=""></p><p>总结个人在学习 iOS 的过程中遇到的精品资源，包含书籍、视频、网站等，一切等待我去探索的好物，主要以Swift 3.0为主。</p><span id="more"></span><h3 id="视频">视频</h3><table><thead><tr><th>视频</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://itunes.apple.com/us/course/developing-ios-10-apps-swift/id1198467120">Developing iOS 10 Apps with Swift</a></td><td>斯坦福白胡子老爷爷最新的 iOS10 和 Swift 3.0 课程。创建于 2017年1月24日，预计时长9周。</td></tr><tr><td><a href="https://itunes.apple.com/us/course/developing-ios-9-apps-swift/id1104579961">Developing iOS 9 Apps with Swift</a></td><td>斯坦福白胡子老爷爷的 iOS9 和 Swift 课程，现在 <a href="https://github.com/SwiftGGTeam/Developing-iOS-9-Apps-with-Swift">GitHub</a> 只翻译到第三节课。</td></tr><tr><td><a href="https://itunes.apple.com/us/course/developing-ios-8-apps-swift/id961180099">Developing iOS 8 Apps with Swift</a></td><td>斯坦福白胡子老爷爷的 iOS8 和 Swift 课程，已经翻译完成 <a href="https://github.com/SwiftGGTeam/Developing_iOS_8_Apps_With_Swift">GitHub</a></td></tr><tr><td><a href="http://www.swiftv.cn">SwiftV课堂</a></td><td>国内一个Swift视频学习站</td></tr><tr><td><a href="http://www.pomo.tv">pomo.tv</a></td><td>国外一个收集关于Mac、iOS 和 Swift视频的网站</td></tr><tr><td><a href="https://www.youtube.com/playlist?list=PLgwNtYvZGv9STGDSx_knjYyzS7XzmpK84">Principle UI/UX Animation Tutorials</a></td><td>SketchTV 在 Youtube 上录制的 Principle (交互原型设计软件) 视频，Principle 相关<a href="http://principleux.com/principle-chinese-document/">中文文档</a>，<a href="http://principleformac.com/tutorial.html">官方文档</a></td></tr></tbody></table><h3 id="YouTube-频道">YouTube 频道</h3><table><thead><tr><th>频道名</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://www.youtube.com/channel/UCysEngjfeIYapEER9K8aikw">Brian Advent</a></td><td>Follow me around and learn iOS and macOS Development by examples.</td></tr><tr><td><a href="https://www.youtube.com/channel/UC-d1NWv5IWtIkfH47ux4dWA">The Swift Guy</a></td><td>On this channel I put out tutorials, and other types of videos that are relevant to the Swift programming language.</td></tr><tr><td><a href="https://www.youtube.com/channel/UC2D6eRvCeMtcF5OGHf1-trw">CodeWithChris</a></td><td>Learn how to make an app with Swift and Xcode and how to turn your app idea into a reality!</td></tr><tr><td><a href="https://www.youtube.com/channel/UCDIBBmkZIB2hjBsk1hUImdA">Jared Davidson</a></td><td>IOS Developer / How to Make Apps</td></tr><tr><td><a href="https://www.youtube.com/channel/UCuP2vJ6kRutQBfRmdcI92mA">Lets Build That App</a></td><td>I’ll do my best to teach you how to build working apps from the AppStore, i.e. YouTube, Facebook, FB Messenger, etc.</td></tr><tr><td><a href="https://www.youtube.com/channel/UChH6WbyYeX0INJjrK2-6WSg">Mark Moeykens</a></td><td>Free iOS, Xcode and Swift tutorials!</td></tr><tr><td><a href="https://www.youtube.com/channel/UCrhO60YNOqODpMPRQZj2NQg">Harry Ng</a></td><td>主要是 OSX 的教程</td></tr></tbody></table><h3 id="书籍">书籍</h3><table><thead><tr><th>书名</th><th>简介</th></tr></thead><tbody><tr><td><a href="http://gg.swiftguide.cn">中文版 Apple 官方 Swift 教程《The Swift Programming Language》</a></td><td>国人发起的官方教程翻译，目前更新到Swift 3.0 <a href="https://github.com/numbbbbb/the-swift-programming-language-in-chinese">Github</a> <a href="https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/index.html">Start Developing iOS Apps (Swift)</a></td></tr><tr><td><a href="https://www.raywenderlich.com/store/ios-10-by-tutorials">iOS 10 by tutorials</a></td><td>raywenderlich精品书籍，付费</td></tr><tr><td><a href="https://www.appcoda.com/swift/">Beginning iOS 10 Programming with Swift</a></td><td>appcoda精品书籍，付费</td></tr><tr><td><a href="http://www.appcoda.com.tw/books/">iOS 10 App 程式設計實力超進化實戰攻略</a></td><td>和楼上电子书对应的湾湾翻译版</td></tr><tr><td><a href="https://www.bignerdranch.com/we-write/ios-programming/">iOS Programming: The Big Nerd Ranch Guide 5th Edition</a></td><td>The Big Nerd Ranch Guide 推崇困难式学习，该系列书的第四版中译获得豆瓣9.0的高分，目前第六版更新到iOS 10和Swift 3.0，十分值得学习，付费</td></tr><tr><td><a href="https://itunes.apple.com/us/book/core-image-for-swift/id1073029980?mt=13">Core Image for Swift</a></td><td>学习 Core Image 框架，付费</td></tr><tr><td><a href="https://book.douban.com/subject/26919791/">Beginning iPhone Development with Swift 3 Exploring the iOS SDK</a></td><td>Apress精品书，本系列中译名为精通iOS编程。这版本讲iOS 10和Swift 3.0，付费</td></tr><tr><td><a href="https://www.gitbook.com/book/zsisme/ios-/details">ios核心动画高级技巧</a></td><td><a href="http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC/ref=sr_1_1?ie=UTF8&amp;qid=1423192842&amp;sr=8-1&amp;keywords=Core+Animation+Advanced+Techniques">iOS Core Animation: Advanced Techniques</a>中文译本，由 OC 编写</td></tr><tr><td><a href="https://www.gitbook.com/book/zonble/kkbox-ios-dev/details">KKBOX iOS/Mac OS X 基本開發教材</a></td><td>這份教材是為了 KKBOX iOS/Mac OS X 開發部門的新人訓練所設計，目的是培養 出可以開發、維護 KKBOX 的 iOS 與 Mac OS X 版本，以及我們其他軟體產品的工程師。本书由 OC 编写</td></tr><tr><td><a href="https://gumroad.com/l/JnWS">A GUIDE TO IOS ANIMATION 2.0</a></td><td>一本关于 iOS 动画的独具匠心的交互式电子书，付费</td></tr><tr><td><a href="https://www.hackingwithswift.com">Hacking With Swift</a></td><td>免费在线 Swift 教程</td></tr><tr><td><a href="https://designcode.io">Design+Code</a></td><td>学习 iOS 开发设计最好的书籍，付费</td></tr><tr><td><a href="https://designthencode.com">Design Then Code</a></td><td>学会设计和开发 iOS 应用，付费。<a href="https://github.com/Cloudox/Motion-Design-for-iOS">中文翻译</a></td></tr></tbody></table><h3 id="网站">网站</h3><table><thead><tr><th>网站名</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://developer.apple.com/swift/resources/">Swift Resources</a></td><td>苹果官方为开发者提供的 Swift 学习资源</td></tr><tr><td><a href="http://swift.gg">SwiftGG</a></td><td>国内走心的Swift翻译组，翻译了很多高质量文章</td></tr><tr><td><a href="https://swift.org/">Swift 官方网站</a></td><td>Swift的开源阶段成果和使用指导</td></tr><tr><td><a href="https://github.com/apple/swift-evolution">swift-evolution</a></td><td>Swift改进的最新进度</td></tr><tr><td><a href="https://www.raywenderlich.com">Ray Wenderlich</a></td><td>专注苹果开发，创造大量有价值的开发资源如文章、视频、播客、书籍等。<a href="https://www.raywenderlich.com/category/swift">Swift文章区</a> <a href="https://www.raywenderlich.com/category/ios">iOS文章区</a></td></tr><tr><td><a href="http://www.learnswift.tips/">LearnSwift.tips</a></td><td>国外一个学习Swift资源列表网站</td></tr><tr><td><a href="https://www.objc.io">objc</a></td><td>objc.io是一个专门为iOS和OS X开发者提供的深入讨论技术的平台,文章含金量很高。</td></tr><tr><td><a href="https://www.objccn.io">objc中国</a></td><td>objc翻译组，<a href="https://github.com/objccn/articles">Github</a></td></tr><tr><td><a href="http://www.appcoda.com.tw/">APPCoda 台湾</a></td><td><a href="http://www.appcoda.com">APPCoda</a> 中教程的翻译，APPCoda 也是专注苹果开发的网站，有自己的电子书和开发教学文章，<a href="http://www.appcoda.com.tw/feed/">RSS</a></td></tr><tr><td><a href="http://nshipster.cn">NSHipster翻译</a></td><td>NSHipster 关注被忽略的 Objective-C、Swift 和 Cocoa 特性，这里是NSHipster的翻译组网站</td></tr><tr><td><a href="http://www.cocoachina.com">CocoChina</a></td><td>国内苹果开发中文社区</td></tr><tr><td><a href="http://ios.jobbole.com">伯乐在线</a></td><td>国内分享iOS和Swift开发的网站</td></tr></tbody></table><h3 id="各大iOS资源合集">各大iOS资源合集</h3><table><thead><tr><th>Github</th><th>简介</th></tr></thead><tbody><tr><td><a href="https://github.com/insidegui/WWDC">WWDC</a></td><td>非官方的 WWDC app for macOS</td></tr><tr><td><a href="https://github.com/Aufree/trip-to-iOS">trip-to-iOS</a></td><td>6000+star的iOS学习资料整理</td></tr><tr><td><a href="https://github.com/ipader/SwiftGuide">SwiftGuide</a></td><td>1W+star的swift语言学习资源指南</td></tr><tr><td><a href="https://github.com/Tim9Liu9/TimLiu-iOS">TimLiu-iOS</a></td><td>4000+star的iOS开发常用三方库、插件、知名博客汇总</td></tr><tr><td><a href="https://github.com/jobbole/awesome-ios-cn">awesome-ios-cn</a></td><td>1W5+star的 <a href="https://github.com/vsouza/awesome-ios">awesome-ios</a> 中译版</td></tr><tr><td><a href="https://github.com/matteocrippa/awesome-swift">awesome-swift</a></td><td>9000+star的Swift资源集合</td></tr><tr><td><a href="https://github.com/dkhamsing/open-source-ios-apps">open-source-ios-apps</a></td><td>1W+star的开源iOS应用集合</td></tr><tr><td><a href="https://github.com/raywenderlich/swift-algorithm-club">swift-algorithm-club</a></td><td>8000+star的Swift算法和数据结构集合，由raywenderlich创建。</td></tr><tr><td><a href="https://github.com/larrynatalicio/15DaysofAnimationsinSwift">15DaysofAnimationsinSwift</a></td><td>2000+star学习用swift写动画的项目</td></tr><tr><td><a href="https://www.v2ex.com/t/309549">iOS 开发学习笔记</a></td><td>OneNote笔记，onepkg格式可在win版导入OneNote，笔记适合刚入门的 iOS 开发或有 C 、 OC 基础的同学。</td></tr><tr><td><a href="https://github.com/tangqiaoboy/iOSBlogCN">iOSBlogCN</a></td><td>中文 iOS/Mac 开发博客列表</td></tr><tr><td><a href="https://github.com/Draveness/iOS-Source-Code-Analyze">iOS-Source-Code-Analyze</a></td><td>深入解析 iOS 开源项目 <a href="http://draveness.me">http://draveness.me</a></td></tr></tbody></table><h3 id="相关的教程">相关的教程</h3><table><thead><tr><th>名称</th><th>简介</th></tr></thead><tbody><tr><td><a href="http://pomax.github.io/bezierinfo/">A Primer on Bézier Curves</a></td><td>关于贝塞尔曲线的教程</td></tr><tr><td><a href="https://github.com/yrq110/some-ios-tutorials-with-swift">some-ios-tutorials-with-swift</a></td><td><a href="https://github.com/yrq110">yrq110</a> 翻译的一些使用 Swift 的 iOS 教程 <a href="https://yrq110.gitbooks.io/some_ios_tutorials_with_swift/content/">Gitbook</a></td></tr><tr><td><a href="http://t.swift.gg/d/2-rxswift">RxSwift 系列教程</a></td><td>靛青K 发布的 RxSwift 系列教程</td></tr><tr><td><a href="https://www.shinobicontrols.com/blog/ios-10-day-by-day-index">iOS10 Day-by-Day</a></td><td>一个介绍 iOS 10 API 的系列教程</td></tr><tr><td><a href="https://www.shinobicontrols.com/blog/ios9-day-by-day-index">iOS9 Day-by-Day</a></td><td>一个介绍 iOS 9 API 的系列教程</td></tr><tr><td><a href="https://andrewmika.gitbooks.io/xcode-server-and-continuous-integration-guide-cn/content/">《Xcode Server and Continuous Integration Guide》中文版</a></td><td>使用Xcode Server进行持续集成，<a href="https://developer.apple.com/library/content/documentation/IDEs/Conceptual/xcode_guide-continuous_integration/#//apple_ref/doc/uid/TP40013292-CH1-SW1">About Continuous Integration in Xcode</a>的翻译，<a href="https://www.gitbook.com/book/andrewmika/-xcode-server-and-continuous-integration-guide/details">GitBook地址</a></td></tr><tr><td><a href="https://github.com/futurice/ios-good-practices">ios-good-practices</a></td><td>iOS 开发最佳实践</td></tr><tr><td><a href="https://developer.apple.com/ios/human-interface-guidelines/overview/design-principles/">iOS Human Interface Guidelines</a></td><td>iOS 人机界面指南，<a href="https://github.com/Cloudox/iOS-Human-Interface-Guidelines">中文翻译</a></td></tr><tr><td><a href="https://github.com/raywenderlich/swift-style-guide">Swift编码规范</a></td><td>来自 Ray Wenderlich 的 Swift 编码规范</td></tr><tr><td><a href="https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_markup_formatting_ref/">Xcode Markup Formatting</a></td><td>学习 Xcode 中如何使用 Markup Formatting</td></tr><tr><td><a href="https://askwonder.com/q/quartz-composer-55723eb43dcda4201a12cbc4">Quartz Composer学习</a></td><td>QC 学习路线，关于 iOS 的一些动态交互</td></tr><tr><td><a href="https://zsisme.gitbooks.io/ios-/content/">iOS 核心动画高级技巧</a></td><td><a href="http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC/ref=sr_1_1?ie=UTF8&amp;qid=1423192842&amp;sr=8-1&amp;keywords=Core+Animation+Advanced+Techniques">iOS Core Animation: Advanced Techniques</a> 的译本，由 OC 编写</td></tr><tr><td><a href="https://github.com/xitu/gold-miner/blob/master/TODO/google.interview.university.md#%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%E8%AF%BE%E7%A8%8B">Google Interview University</a></td><td>一套完整的学习手册帮助自己准备 Google 的面试</td></tr></tbody></table><h3 id="RSS">RSS</h3><p>我关注的一些 RSS 源，博客方面关注较少。</p><table><thead><tr><th>名称</th><th>Feed 源</th></tr></thead><tbody><tr><td>Swift – Ray Wenderlich</td><td><a href="https://www.raywenderlich.com/category/swift/feed">https://www.raywenderlich.com/category/swift/feed</a></td></tr><tr><td>AppCoda 台湾</td><td><a href="http://www.appcoda.com.tw/feed/">http://www.appcoda.com.tw/feed/</a></td></tr><tr><td>Big Nerd Ranch</td><td><a href="https://www.bignerdranch.com/rss">https://www.bignerdranch.com/rss</a></td></tr><tr><td>Swift GG 翻译组</td><td><a href="http://swift.gg/atom.xml">http://swift.gg/atom.xml</a></td></tr><tr><td><a href="http://mobilefrontier.github.io">移动开发前线</a></td><td><a href="http://mobilefrontier.github.io/index.xml">http://mobilefrontier.github.io/index.xml</a></td></tr><tr><td><a href="https://medium.com/swift-programming/all">Medium-Swift Programming</a></td><td><a href="https://medium.com/feed/swift-programming/">https://medium.com/feed/swift-programming/</a></td></tr><tr><td><a href="https://medium.com/ios-os-x-development/all">Medium-iOS App Development</a></td><td><a href="https://medium.com/feed/ios-os-x-development/">https://medium.com/feed/ios-os-x-development/</a></td></tr><tr><td>掘金-iOS</td><td><a href="https://api.prprpr.me/xitu/ios">https://api.prprpr.me/xitu/ios</a></td></tr><tr><td>IOS – 伯乐在线</td><td><a href="http://ios.jobbole.com/feed/">http://ios.jobbole.com/feed/</a></td></tr><tr><td>CocoaChina</td><td><a href="http://www.cocoachina.com/cms/rss.php">http://www.cocoachina.com/cms/rss.php</a></td></tr><tr><td><a href="http://mrpeak.cn/">MrPeak杂货铺</a></td><td><a href="http://mrpeak.cn/feed.xml">http://mrpeak.cn/feed.xml</a></td></tr><tr><td><a href="http://onevcat.com/">OneV’s Den</a></td><td><a href="https://onevcat.com/feed.xml">https://onevcat.com/feed.xml</a></td></tr><tr><td><a href="http://gank.io">干货集中营</a></td><td><a href="http://gank.io/feed">http://gank.io/feed</a></td></tr></tbody></table><p>关于 Medium 上的文章 RSS 可以参考 <a href="http://webapps.stackexchange.com/questions/85611/rss-atom-feed-for-medium-com-blog-tag">RSS/Atom feed for medium.com blog tag</a>。</p><!--### 工具 --><!--(写的不全又舍不得删，就这样吧。)| 名称 | 简介 || --- | --- || [RAP](http://rapapi.net/) | Web接口管理工具，开源免费，接口自动化，MOCK数据自动生成，自动化测试，企业级管理。 || [iOS-Images-Extractor](https://github.com/devcxm/iOS-Images-Extractor) | 提取iOS应用图片 || [接口参考](https://www.zhihu.com/question/39479153) | 知乎上关于接口资源的回答 || [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) | Swift OAuth 库 || [Alamofire](https://github.com/Alamofire/Alamofire) | Elegant HTTP Networking in Swift |--><p>&lt;未完待续&gt;</p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> Swift </tag>
            
            <tag> iOS </tag>
            
            <tag> Objective-C </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>阳历转阴历算法</title>
      <link href="/2016/02/04/%E9%98%B4%E5%8E%86%E7%AE%97%E6%B3%95/"/>
      <url>/2016/02/04/%E9%98%B4%E5%8E%86%E7%AE%97%E6%B3%95/</url>
      
        <content type="html"><![CDATA[<h3 id="概述">概述</h3><p>毕设要做万年历，但是对阴阳历了解不多，在此总结。</p><h3 id="阳历">阳历</h3><p>阳历，就是“太阳历”，是依据太阳的变化（严格来说，应该是地球围绕太阳运动时，人在地球上所观测到的太阳的变化）来修订的历法，以地球绕太阳一圈的时间为一年。</p><span id="more"></span><p>小时候老师教过：“一三五七八十腊，三十一天永不差”。</p><p>阳历有很强的规律性，每年12个月，1、3、5、7、8、10、12月都为31天，2月份平年28天，闰年29天，其余月份为30天。是否闰年也好算，能被100整除的年份中能被400整除的是闰年，不能被100整除的年份中能被4整除的是闰年。</p><h3 id="阴历">阴历</h3><h4 id="简介">简介</h4><p>阴历，也就是“月亮历”，是依据月亮的变化来修订的历法。月亮每经历一次从圆到缺的循环，就是一个月。我国古人常把月亮叫做“太阴”，所以也叫“阴历”。</p><p>阴历全年12个月的总天数是354.3672天，它同季节变化的周期——阳历1年365.2422天)相差约11天。阴历分大小月：大月30天、小月29天。由于这样算与阳历有差别，会不能正确反映季节，我国就创造了一种带有一定阳历成分的阴历———“阴阳合历”，也就是农历，又叫夏历（夏朝开始的）。</p><p>阴历同阳历一年相差11天。因此，农历每3年需要闰（增加）1个月，5年闰2个月，7年闰3个月，19年闰7个月，周而复始，有闰月的那一年就被称为闰年。这种设置闰月的方法叫“19年7闰法”。</p><p>因此，阴历平年12个月，闰年13个月。</p><h4 id="算法">算法</h4><p>这根本就没规律嘛！！</p><p>所以我们要从观测阴历的天文台处得到阴历数据，网上大多是1900年到2100年的数据(根本不是万年历啊摔(/ﾟДﾟ)/)。那么我们先从数据入手。</p><h5 id="数据">数据</h5><p>网上搜集：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">0x04bd8</span>,<span class="number">0x04ae0</span>,<span class="number">0x0a570</span>,<span class="number">0x054d5</span>,<span class="number">0x0d260</span>,<span class="number">0x0d950</span>,<span class="number">0x16554</span>,<span class="number">0x056a0</span>,<span class="number">0x09ad0</span>,<span class="number">0x055d2</span>,<span class="comment">//1900-1909</span></span><br><span class="line"> <span class="number">0x04ae0</span>,<span class="number">0x0a5b6</span>,<span class="number">0x0a4d0</span>,<span class="number">0x0d250</span>,<span class="number">0x1d255</span>,<span class="number">0x0b540</span>,<span class="number">0x0d6a0</span>,<span class="number">0x0ada2</span>,<span class="number">0x095b0</span>,<span class="number">0x14977</span>,<span class="comment">//1910-1919</span></span><br><span class="line"> <span class="number">0x04970</span>,<span class="number">0x0a4b0</span>,<span class="number">0x0b4b5</span>,<span class="number">0x06a50</span>,<span class="number">0x06d40</span>,<span class="number">0x1ab54</span>,<span class="number">0x02b60</span>,<span class="number">0x09570</span>,<span class="number">0x052f2</span>,<span class="number">0x04970</span>,<span class="comment">//1920-1929</span></span><br><span class="line"> <span class="number">0x06566</span>,<span class="number">0x0d4a0</span>,<span class="number">0x0ea50</span>,<span class="number">0x06e95</span>,<span class="number">0x05ad0</span>,<span class="number">0x02b60</span>,<span class="number">0x186e3</span>,<span class="number">0x092e0</span>,<span class="number">0x1c8d7</span>,<span class="number">0x0c950</span>,<span class="comment">//1930-1939</span></span><br><span class="line"> <span class="number">0x0d4a0</span>,<span class="number">0x1d8a6</span>,<span class="number">0x0b550</span>,<span class="number">0x056a0</span>,<span class="number">0x1a5b4</span>,<span class="number">0x025d0</span>,<span class="number">0x092d0</span>,<span class="number">0x0d2b2</span>,<span class="number">0x0a950</span>,<span class="number">0x0b557</span>,<span class="comment">//1940-1949</span></span><br><span class="line"> <span class="number">0x06ca0</span>,<span class="number">0x0b550</span>,<span class="number">0x15355</span>,<span class="number">0x04da0</span>,<span class="number">0x0a5b0</span>,<span class="number">0x14573</span>,<span class="number">0x052b0</span>,<span class="number">0x0a9a8</span>,<span class="number">0x0e950</span>,<span class="number">0x06aa0</span>,<span class="comment">//1950-1959</span></span><br><span class="line"> <span class="number">0x0aea6</span>,<span class="number">0x0ab50</span>,<span class="number">0x04b60</span>,<span class="number">0x0aae4</span>,<span class="number">0x0a570</span>,<span class="number">0x05260</span>,<span class="number">0x0f263</span>,<span class="number">0x0d950</span>,<span class="number">0x05b57</span>,<span class="number">0x056a0</span>,<span class="comment">//1960-1969</span></span><br><span class="line"> <span class="number">0x096d0</span>,<span class="number">0x04dd5</span>,<span class="number">0x04ad0</span>,<span class="number">0x0a4d0</span>,<span class="number">0x0d4d4</span>,<span class="number">0x0d250</span>,<span class="number">0x0d558</span>,<span class="number">0x0b540</span>,<span class="number">0x0b6a0</span>,<span class="number">0x195a6</span>,<span class="comment">//1970-1979</span></span><br><span class="line"></span><br><span class="line"><span class="number">0x095b0</span>,<span class="number">0x049b0</span>,<span class="number">0x0a974</span>,<span class="number">0x0a4b0</span>,<span class="number">0x0b27a</span>,<span class="number">0x06a50</span>,<span class="number">0x06d40</span>,<span class="number">0x0af46</span>,<span class="number">0x0ab60</span>,<span class="number">0x09570</span>,<span class="comment">//1980-1989</span></span><br><span class="line"> <span class="number">0x04af5</span>,<span class="number">0x04970</span>,<span class="number">0x064b0</span>,<span class="number">0x074a3</span>,<span class="number">0x0ea50</span>,<span class="number">0x06b58</span>,<span class="number">0x055c0</span>,<span class="number">0x0ab60</span>,<span class="number">0x096d5</span>,<span class="number">0x092e0</span>,<span class="comment">//1990-1999</span></span><br><span class="line"> <span class="number">0x0c960</span>,<span class="number">0x0d954</span>,<span class="number">0x0d4a0</span>,<span class="number">0x0da50</span>,<span class="number">0x07552</span>,<span class="number">0x056a0</span>,<span class="number">0x0abb7</span>,<span class="number">0x025d0</span>,<span class="number">0x092d0</span>,<span class="number">0x0cab5</span>,<span class="comment">//2000-2009</span></span><br><span class="line"> <span class="number">0x0a950</span>,<span class="number">0x0b4a0</span>,<span class="number">0x0baa4</span>,<span class="number">0x0ad50</span>,<span class="number">0x055d9</span>,<span class="number">0x04ba0</span>,<span class="number">0x0a5b0</span>,<span class="number">0x15176</span>,<span class="number">0x052b0</span>,<span class="number">0x0a930</span>,<span class="comment">//2010-2019</span></span><br><span class="line"> <span class="number">0x07954</span>,<span class="number">0x06aa0</span>,<span class="number">0x0ad50</span>,<span class="number">0x05b52</span>,<span class="number">0x04b60</span>,<span class="number">0x0a6e6</span>,<span class="number">0x0a4e0</span>,<span class="number">0x0d260</span>,<span class="number">0x0ea65</span>,<span class="number">0x0d530</span>,<span class="comment">//2020-2029</span></span><br><span class="line"> <span class="number">0x05aa0</span>,<span class="number">0x076a3</span>,<span class="number">0x096d0</span>,<span class="number">0x04bd7</span>,<span class="number">0x04ad0</span>,<span class="number">0x0a4d0</span>,<span class="number">0x1d0b6</span>,<span class="number">0x0d250</span>,<span class="number">0x0d520</span>,<span class="number">0x0dd45</span>,<span class="comment">//2030-2039</span></span><br><span class="line"> <span class="number">0x0b5a0</span>,<span class="number">0x056d0</span>,<span class="number">0x055b2</span>,<span class="number">0x049b0</span>,<span class="number">0x0a577</span>,<span class="number">0x0a4b0</span>,<span class="number">0x0aa50</span>,<span class="number">0x1b255</span>,<span class="number">0x06d20</span>,<span class="number">0x0ada0</span>,<span class="comment">//2040-2049</span></span><br><span class="line"></span><br><span class="line"><span class="number">0x14b63</span>,<span class="number">0x09370</span>,<span class="number">0x049f8</span>,<span class="number">0x04970</span>,<span class="number">0x064b0</span>,<span class="number">0x168a6</span>,<span class="number">0x0ea50</span>, <span class="number">0x06b20</span>,<span class="number">0x1a6c4</span>,<span class="number">0x0aae0</span>,<span class="comment">//2050-2059</span></span><br><span class="line"> <span class="number">0x0a2e0</span>,<span class="number">0x0d2e3</span>,<span class="number">0x0c960</span>,<span class="number">0x0d557</span>,<span class="number">0x0d4a0</span>,<span class="number">0x0da50</span>,<span class="number">0x05d55</span>,<span class="number">0x056a0</span>,<span class="number">0x0a6d0</span>,<span class="number">0x055d4</span>,<span class="comment">//2060-2069</span></span><br><span class="line"> <span class="number">0x052d0</span>,<span class="number">0x0a9b8</span>,<span class="number">0x0a950</span>,<span class="number">0x0b4a0</span>,<span class="number">0x0b6a6</span>,<span class="number">0x0ad50</span>,<span class="number">0x055a0</span>,<span class="number">0x0aba4</span>,<span class="number">0x0a5b0</span>,<span class="number">0x052b0</span>,<span class="comment">//2070-2079</span></span><br><span class="line"> <span class="number">0x0b273</span>,<span class="number">0x06930</span>,<span class="number">0x07337</span>,<span class="number">0x06aa0</span>,<span class="number">0x0ad50</span>,<span class="number">0x14b55</span>,<span class="number">0x04b60</span>,<span class="number">0x0a570</span>,<span class="number">0x054e4</span>,<span class="number">0x0d160</span>,<span class="comment">//2080-2089</span></span><br><span class="line"> <span class="number">0x0e968</span>,<span class="number">0x0d520</span>,<span class="number">0x0daa0</span>,<span class="number">0x16aa6</span>,<span class="number">0x056d0</span>,<span class="number">0x04ae0</span>,<span class="number">0x0a9d4</span>,<span class="number">0x0a2d0</span>,<span class="number">0x0d150</span>,<span class="number">0x0f252</span>,<span class="comment">//2090-2099</span></span><br><span class="line"></span><br><span class="line"><span class="number">0x0d520</span> <span class="comment">//2100</span></span><br></pre></td></tr></table></figure><h5 id="解析">解析</h5><p>十六进制的数据要转成二进制解析</p><p>二进制形式</p><table><thead><tr><th>xxxx</th><th style="text-align:center">xxxx</th><th style="text-align:center">xxxx</th><th style="text-align:center">xxxx</th><th style="text-align:right">xxxx</th></tr></thead><tbody><tr><td>20-17</td><td style="text-align:center">16-13</td><td style="text-align:center">12-9</td><td style="text-align:center">8-5</td><td style="text-align:right">4-1</td></tr></tbody></table><p>1-4: 表示当年有无闰年，有的话，为闰月的月份，没有的话，为0。<br>5-16：为除了闰月外的正常月份是大月还是小月，1为30天，0为29天。<br>注意：从1月到12月对应的是第16位到第5位。<br>17-20： 表示闰月是大月还是小月，仅当存在闰月的情况下有意义。</p><h5 id="例子1">例子1</h5><p>1980年的数据是（十六进制）：0x095b0<br>二进制：0000 1001 0101 1011 0000</p><table><thead><tr><th>0000</th><th style="text-align:center">1001</th><th style="text-align:center">0101</th><th style="text-align:center">1011</th><th style="text-align:right">0000</th></tr></thead><tbody><tr><td>20-17</td><td style="text-align:center">16-13</td><td style="text-align:center">12-9</td><td style="text-align:center">8-5</td><td style="text-align:right">4-1</td></tr><tr><td>无意义</td><td style="text-align:center">1-4月</td><td style="text-align:center">5-8月</td><td style="text-align:center">9-12月</td><td style="text-align:right">非闰年</td></tr></tbody></table><p>最右边的0000表示1980年没有闰月，所以最左边的0000也没有意义。从1月到12月的天数依次为：30、29、29、30、29、30、29、30、30、29、30、30，共12个月。分别对应16-5位的二进制数，1对30，0对29。</p><h5 id="例子2">例子2</h5><p>2017年的数据是：0x15176<br>二进制：0001 0101 0001 0111 0110</p><table><thead><tr><th>0001</th><th style="text-align:center">0101</th><th style="text-align:center">0001</th><th style="text-align:center">0111</th><th style="text-align:right">0110</th></tr></thead><tbody><tr><td>20-17</td><td style="text-align:center">16-13</td><td style="text-align:center">12-9</td><td style="text-align:center">8-5</td><td style="text-align:right">4-1</td></tr><tr><td>闰大月</td><td style="text-align:center">1-4月</td><td style="text-align:center">5-8月</td><td style="text-align:center">9-12月</td><td style="text-align:right">闰六月</td></tr></tbody></table><p>从1月到12月的天数依次为：29、30、29、30、29、29(六月)、30(闰六月)、29、30、29、30、30、30，共13个月。</p><h5 id="使用">使用</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//传回农历 y年的总天数</span></span><br><span class="line"><span class="comment">//例如2015年农历有354天</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">yearDays</span><span class="params">(<span class="type">int</span> y)</span> &#123;</span><br><span class="line"><span class="comment">//348为12个月都为平月29日的年总天数</span></span><br><span class="line">   <span class="type">int</span> i, sum = <span class="number">348</span>;</span><br><span class="line">   <span class="keyword">for</span> (i = <span class="number">0x8000</span>; i &gt; <span class="number">0x8</span>; i &gt;&gt;= <span class="number">1</span>) &#123;</span><br><span class="line">       <span class="comment">// i &gt;&gt;= 1 化为二进制向右移一位并赋给原值</span></span><br><span class="line">       <span class="comment">// 循环内从16-5位内找 有1则为大月 总天数+1</span></span><br><span class="line">       <span class="keyword">if</span> ((lunarInfo[y - <span class="number">1900</span>] &amp; i) != <span class="number">0</span>)</span><br><span class="line">           sum += <span class="number">1</span>;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> (sum + leapDays(y));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//传回农历 y年闰月的天数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">leapDays</span><span class="params">(<span class="type">int</span> y)</span> &#123;</span><br><span class="line">   <span class="keyword">if</span> (leapMonth(y) != <span class="number">0</span>) &#123;</span><br><span class="line">       <span class="keyword">if</span> ((lunarInfo[y - <span class="number">1900</span>] &amp; <span class="number">0x10000</span>) != <span class="number">0</span>)</span><br><span class="line">           <span class="comment">//闰大月</span></span><br><span class="line">           <span class="keyword">return</span> <span class="number">30</span>;</span><br><span class="line">       <span class="keyword">else</span></span><br><span class="line">       <span class="comment">//闰小月</span></span><br><span class="line">           <span class="keyword">return</span> <span class="number">29</span>;</span><br><span class="line">   &#125; <span class="keyword">else</span></span><br><span class="line">       <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//传回农历 y年闰哪个月 1-12 , 没闰传回 0</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">leapMonth</span><span class="params">(<span class="type">int</span> y)</span> &#123;</span><br><span class="line">   <span class="comment">//用4-1位与“1111”进行与运算</span></span><br><span class="line">   <span class="keyword">return</span> (<span class="type">int</span>) (lunarInfo[y - <span class="number">1900</span>] &amp; <span class="number">0xf</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//传回农历 y年m月的总天数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">monthDays</span><span class="params">(<span class="type">int</span> y, <span class="type">int</span> m)</span> &#123;</span><br><span class="line">   <span class="keyword">if</span> ((lunarInfo[y - <span class="number">1900</span>] &amp; (<span class="number">0x10000</span> &gt;&gt; m)) == <span class="number">0</span>)</span><br><span class="line">       <span class="keyword">return</span> <span class="number">29</span>;</span><br><span class="line">   <span class="keyword">else</span></span><br><span class="line">       <span class="keyword">return</span> <span class="number">30</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//传回农历 y年的生肖</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">animalsYear</span><span class="params">(<span class="type">int</span> year)</span> &#123;</span><br><span class="line">   <span class="keyword">final</span> String[] Animals = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;鼠&quot;</span>, <span class="string">&quot;牛&quot;</span>, <span class="string">&quot;虎&quot;</span>,</span><br><span class="line">   <span class="string">&quot;兔&quot;</span>, <span class="string">&quot;龙&quot;</span>, <span class="string">&quot;蛇&quot;</span>,<span class="string">&quot;马&quot;</span>, <span class="string">&quot;羊&quot;</span>, <span class="string">&quot;猴&quot;</span>, <span class="string">&quot;鸡&quot;</span>, <span class="string">&quot;狗&quot;</span>, <span class="string">&quot;猪&quot;</span>&#125;;</span><br><span class="line">   <span class="keyword">return</span> Animals[(year - <span class="number">4</span>) % <span class="number">12</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>日历重点是公历和阴历的转换，明后弄懂了再撰文。</p><p>转换弄懂了的话到时候结婚算八字都不用翻风水书了呢(￣▽￣)</p><p>课外扩展：<br><a href="http://blog.csdn.net/luozhuang/article/details/8654765">八字神煞合婚算法，看看自己中了几枪</a></p>]]></content>
      
      
      
        <tags>
            
            <tag> 算法 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>iOS 应用程序生命周期</title>
      <link href="/2015/11/03/iOS-application-life-cycle/"/>
      <url>/2015/11/03/iOS-application-life-cycle/</url>
      
        <content type="html"><![CDATA[<h2 id="应用生命周期">应用生命周期</h2><p>作为应用程序的委托对象，AppDelegate类在应用生命周期的不同阶段会回调不同的方法。</p><span id="more"></span><h3 id="五种状态">五种状态</h3><p>iOS的应用程序一共有5种状态:</p><ul><li>Not Running(非运行状态)。应用没有运行或被系统终止。</li><li>Inactive(前台非活动状态)。应用正在进入前台状态，但是还不能接受事件处理。</li><li>Active(前台活动状态)。应用进入前台状态，能接受事件处理。</li><li>Background(后台状态)。应用进入后台后，依然能够执行代码。如果有可执行的代码，就会执行代码，如果没有可执行的代码或者将可执行的代码执行完毕，应用会马上进入挂起状态。有的程序经过特殊的请求后可以长期处于Backgroud状态。</li><li>Suspended(挂起状态)。处于挂起的应用进入一种“冷冻”状态,不能执行代码。如果系统内存不够,系统就把挂起的程序清除掉，为前台程序提供更多的内存，应用会被终止。</li></ul><h3 id="iOS应用状态图">iOS应用状态图</h3><p><img src="http://img.frankorz.com/57ede16556a73.jpg" alt=""></p><h3 id="应用回调的方法和本地通知">应用回调的方法和本地通知</h3><p>以下是状态跃迁过程中六个应用回调的方法和本地通知：</p><p><img src="http://img.frankorz.com/57ede17d89308.jpg" alt=""></p><h3 id="应用程序的运行状态">应用程序的运行状态</h3><p>我们可以在“AppDelegate.m”文件中查看应用程序运行状态的方法，并为其添加日志输出，其中注释为官方注释，注意理解( ͒•ㅈ• ͒)</p><figure class="highlight mm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#import <span class="string">&quot;AppDelegate.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">AppDelegate</span> ()</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">AppDelegate</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="type">BOOL</span>)application:(<span class="built_in">UIApplication</span> *)application didFinishLaunchingWithOptions:</span><br><span class="line">(<span class="built_in">NSDictionary</span> *)launchOptions &#123;</span><br><span class="line">    <span class="comment">// Override point for customization after application launch.</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;application:didFinishLaunchingWithOptions:&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">YES</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)applicationWillResignActive:(<span class="built_in">UIApplication</span> *)application &#123;</span><br><span class="line">    <span class="comment">/* Sent when the application is about to move from active to inactive</span></span><br><span class="line"><span class="comment">    state. This can occur for certain types of temporary interruptions</span></span><br><span class="line"><span class="comment">    (such as an incoming phone call or SMS message) or when the user quits</span></span><br><span class="line"><span class="comment">    the application and it begins the transition to the background state.*/</span></span><br><span class="line">    <span class="comment">/* Use this method to pause ongoing tasks, disable timers,</span></span><br><span class="line"><span class="comment">    and throttle down OpenGL ES frame rates. Games should use this method</span></span><br><span class="line"><span class="comment">    to pause the game. */</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;applicationWillResignActive:&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)applicationDidEnterBackground:(<span class="built_in">UIApplication</span> *)application &#123;</span><br><span class="line">    <span class="comment">/* Use this method to release shared resources, save user data,</span></span><br><span class="line"><span class="comment">    invalidate timers, and store enough application state information to</span></span><br><span class="line"><span class="comment">    restore your application to its current state in case it</span></span><br><span class="line"><span class="comment">    is terminated later.*/</span></span><br><span class="line">    <span class="comment">/* If your application supports background execution,</span></span><br><span class="line"><span class="comment">    this method is called instead of applicationWillTerminate:</span></span><br><span class="line"><span class="comment">    when the user quits.*/</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;applicationDidEnterBackground:&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)applicationWillEnterForeground:(<span class="built_in">UIApplication</span> *)application &#123;</span><br><span class="line">    <span class="comment">/* Called as part of the transition from the background to the</span></span><br><span class="line"><span class="comment">    inactive state; here you can undo many of the changes made on</span></span><br><span class="line"><span class="comment">    entering the background.*/</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;applicationWillEnterForeground:&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)applicationDidBecomeActive:(<span class="built_in">UIApplication</span> *)application &#123;</span><br><span class="line">    <span class="comment">/* Restart any tasks that were paused (or not yet started) while</span></span><br><span class="line"><span class="comment">    the application was inactive. If the application was previously in</span></span><br><span class="line"><span class="comment">    the background, optionally refresh the user interface.*/</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;applicationDidBecomeActive:&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="type">void</span>)applicationWillTerminate:(<span class="built_in">UIApplication</span> *)application &#123;</span><br><span class="line">    <span class="comment">/* Called when the application is about to terminate.</span></span><br><span class="line"><span class="comment">    Save data if appropriate. See also</span></span><br><span class="line"><span class="comment">    applicationDidEnterBackground:.*/</span></span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;%@&quot;</span>, <span class="string">@&quot;applicationWillTerminate:&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure><p>打开程序：<br><img src="http://img.frankorz.com/57ede165e7a3a.jpg" alt=""></p><p>按下Home键：<br><img src="http://img.frankorz.com/57ede168c2bcd.jpg" alt=""></p><p>从界面重点图标打开程序：<br><img src="http://img.frankorz.com/57ede165d5aaf.jpg" alt=""></p><h2 id="非运行状态——应用启动场景">非运行状态——应用启动场景</h2><p>场景描述：用户点击应用图标的时候，可能是第一次启动这个应用，也可能是应用终止后再次启动。该场景的状态跃迁过程见下图，共经历两个阶段3个状态：Not running→Inactive→Active。</p><ul><li>在Not running→Inactive阶段。调用application:didFinishLaunchingWithOptions:方法，发出 UIApplicationDidFinishLaunchingNotification通知。</li><li>在Inactive→Active阶段。调用applicationDidBecomeActive:方法，发出UIApplicationDidBecomeActiveNotification通知。</li></ul><p><img src="http://img.frankorz.com/57ede1693df90.jpg" alt=""></p><h3 id="Active和Inactive的切换">Active和Inactive的切换</h3><p>应用程序在前台时有2种状态：Active和Inactive。大多数情况下，Inactive状态只是其他两个状态切换时出现的短暂状态（不是任意两个状态之间的切换都会进入Inactive），如打开应用，它会从Not Running先进入Inactive再进入Active；如前后台应用切换时，Inactive会在Active和Background之间短暂出现。</p><p>但是也有其他情况，Active和Inactive可以在前台运行时进行切换，比如系统弹出Alert，此时应用会从Active切换到Inactive，直到用户确认再返回Active；再如用户拉下通知页，也会发生Active和Inactive的切换；还有来电但拒接、双击Home键但返回原应用等都不进入Background，而只是在Active和Inactive切换，这个在下面会讲到~</p><p>在应用状态跃迁的过程中，iOS系统会回调AppDelegate中的一些方法，并且发送一些通知。实际上，在应用的生命周期中用到的方法和通知很多。</p><h2 id="点击Home键——应用退出场景">点击Home键——应用退出场景</h2><p>场景描述：应用处于运行状态(即Active状态)时，点击Home键或者有其他的应用导致当前应用中断。该场景的状态跃迁过程可以分成两种情况：可以在后台运行或者挂起，不可以在后台运行或者挂起。根据产品属性文件(Info.plist)中的相关属性 Application does not run in background 是与否可以控制这两种状态。如果采用文本编辑器打开Info.plist文件该设置项对应的键是UIApplicationExitsOnSuspend。</p><p><img src="http://img.frankorz.com/57ede17d0c6e9.jpg" alt=""></p><p>程序只要符合以下情况之一，只要进入后台或挂起状态就会终止：</p><ul><li>iOS4.0以前的系统</li><li>app是基于iOS4.0之前系统开发的。</li><li>设备不支持多任务</li><li>在Info.plist文件中，程序包含了UIApplicationExitsOnSuspend 键。</li></ul><p>app如果终止了，系统会调用app的代理的方法 <code>applicationWillTerminate:</code> 这样可以让你可以做一些清理工作，你可以保存一些数据或app的状态。这个方法也有5秒钟的限制，超时后方法会返回程序从内存中清除。<br>注意：用户可以手工关闭应用程序。</p><p>添加了UIApplicationExitsOnSuspend键，并设置Yes。打开应用后再按Home键退出：</p><p><img src="http://img.frankorz.com/57ede168d3047.jpg" alt=""></p><h3 id="状态跃迁的第一种情况">状态跃迁的第一种情况</h3><p>应用可以在后台运行或者挂起，共经历3个阶段4个状态:Active → Inactive → Background → Suspended。</p><ul><li>在Active→Inactive阶段。调用applicationWillResignActive:方法,发出UIApplicationWillResignActiveNotification通知。</li><li>在Inactive→Background阶段。应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。</li><li>在Background→Suspended阶段。调用applicationDidEnterBackground:方法，发出UIApplicationDidEnterBackgroundNotification通知。</li></ul><p><img src="http://img.frankorz.com/57ede16903018.jpg" alt=""></p><h4 id="当应用程序进入后台时，我们应该做些什么呢？">当应用程序进入后台时，我们应该做些什么呢？</h4><p>保存用户数据或状态信息，所有没写到磁盘的文件或信息，在进入后台时，最后都写到磁盘去，因为程序可能在后台被杀死，释放尽可能释放的内存。<br><code>applicationDidEnterBackgound: </code>方法有大概5秒的时间让你完成这些任务。如果超过时间还有未完成的任务，你的程序就会被终止而且从内存中清除。如果还需要长时间的运行任务，可以调用  <code>beginBackgroundTaskWithExpirationHandler</code>方法去请求后台运行时间和启动线程来运行长时间运行的任务。</p><h4 id="应用程序在后台时的内存使用">应用程序在后台时的内存使用</h4><p>在后台时，每个应用程序都应该释放最大的内存。系统努力的保持更多的应用程序在后台同时运行。不过当内存不足时，会终止一些挂起的程序来回收内存，那些内存最大的程序首先被终止。<br>事实上，应用程序应该的对象如果不再使用了，那就应该尽快的去掉强引用，这样编译器可以回收这些内存。如果你想缓存一些对象提升程序的性能，你可以在进入后台时，把这些对象去掉强引用。<br>下面这样的对象应该尽快的去掉强引用：</p><ul><li>图片对象</li><li>你可以重新加载的 大的视频或数据文件</li><li>任何没用而且可以轻易创建的对象</li></ul><p>在后台时，为了减少程序占用的内存，系统会自动在回收一些系统帮助你开辟的内存。比如：</p><ul><li>系统回收Core Animation的后备存储。</li><li>去掉任何系统引用的缓存图片</li><li>去掉系统管理数据缓存强引用</li></ul><h3 id="状态跃迁的第二种情况">状态跃迁的第二种情况</h3><p>应用不可以在后台运行或者挂起，共经历4个阶段5个状态:Active → Inactive → Background → Suspended→ Not running 。</p><ul><li>在Active→Inactivd阶段。应用由活动状态转为非活动状态(不涉及我们要重点说明的方法和通知)。</li><li>在Inactive→Background阶段。应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。</li><li>在Background→Suspended阶段。调用applicationDidEnterBackground:方法，发出UIApplicationDidEnterBackgroundNotification通知。</li><li>在Suspended→Not running阶段。调用applicationWillTerminate:方法，发出UIApplicationWillTerminateNotification通知。</li></ul><p><img src="http://img.frankorz.com/57ede16988cf1.jpg" alt=""></p><p>iOS在iOS 4之前不支持多任务，点击Home键时，应用会退出并中断；而在iOS 4之后(包括iOS 4)，操作系统能够支持多任务处理，点击Home键应用会进入后台但不会中断(内存不够的情况除外)。<br>应用在后台也可以进行部分处理工作，处理完成则进入挂起状态。</p><h2 id="挂起重新运行场景">挂起重新运行场景</h2><p>挂起状态的应用重新运行。该场景的状态跃迁过程如图2-26所示,共经历3个阶段4个状态:<br>Suspended → Background → Inactive → Active。</p><ul><li>Suspended→Background阶段。应用从挂起状态进入后台(不涉及我们讲述的这几个方法和通知)。</li><li>Background→Inactive阶段。调用applicationWillEnterForeground:方法，发出UIApplicationWillEnterForegroundNotification通知。</li><li>Inactive→Active阶段。调用applicationDidBecomeActive:方法,发出UIApplicationDidBecomeActiveNotification通知。</li></ul><p><img src="http://img.frankorz.com/57ede1691ad6f.jpg" alt=""></p><h2 id="内存清除——应用终止场景">内存清除——应用终止场景</h2><p>场景描述：应用在后台处理完成时进入挂起状态（这是一种休眠状态），如果这时发出低内存警告，为了满足其他应用对内存的需要，该应用就会被清除内存从而终止运行。</p><p><img src="http://img.frankorz.com/57ede16925455.jpg" alt=""></p><p>内存清除的时候应用终止运行。内存清除有两种情况，可能是系统强制清除内存，也可能是由使用者从任务栏中手动清除（即删掉应用）。内存清除后如果应用再次运行，上一次的运行状态不会被保存，相当于应用第一次运行。<br>在内存清除场景下，应用不会调用任何方法，也不会发出任何通知。</p><h2 id="参考文章">参考文章</h2><p><a href="http://blog.csdn.net/totogo2010/article/details/8048652">iOS应用程序生命周期(前后台切换,应用的各种状态)详解</a><br>《iOS开发指南：从零基础到App Store上架（第2版 ）》</p>]]></content>
      
      
      <categories>
          
          <category> iOS基础 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> iOS </tag>
            
            <tag> Objective-C </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>欢迎来到萤火之森</title>
      <link href="/2015/10/28/%E6%AC%A2%E8%BF%8E%E6%9D%A5%E5%88%B0%E8%90%A4%E7%81%AB%E4%B9%8B%E6%A3%AE/"/>
      <url>/2015/10/28/%E6%AC%A2%E8%BF%8E%E6%9D%A5%E5%88%B0%E8%90%A4%E7%81%AB%E4%B9%8B%E6%A3%AE/</url>
      
        <content type="html"><![CDATA[<p><img src="http://img.frankorz.com/57ede1f0d66eb.jpg" alt="1"></p><p>欢迎来到 <a href="http://frankorz.com">萤火之森</a>！</p><span id="more"></span><h2 id="欢迎👏">欢迎👏</h2><p>今天很荣幸的加入了全球最大的同性交友网站，并且很感谢hexo模板的作者给了我这个机会能让我在Github Pages上建立自己的博客，很感谢next主题的作者iissnan，这主题让我耳目一新，让我知道前端的无限能力。（我这里想输入颜文字，脑一抽去app store搜应用…真的有唉！！(▰˘◡˘▰) ~）<br>总而言之呢~ 微博广告多，微信自拍多，facebook没人看。<br>ヾ(◍’౪`◍)ﾉﾞ<br>人呐，总有那么一瞬间，想把身边所经历、所学习、所感悟给写下来，埋在某个地方。<br>既然进了程序员的坑，就是学到死的命 …&quot;〆´◡ฺ｀｡） ，萤火之森这名字蛮喜欢的~ 不做那为万世开太平的人，只为代码之神继之所学。乌拉！！！！！！！</p><h2 id="博客更改记录">博客更改记录</h2><p>16年10月13日 增加部署至Coding，国内直接走Coding Pages，速度提升。</p>]]></content>
      
      
      
    </entry>
    
    
  
  
</search>
