2D 物理引擎基础之碰撞检测

一、前言 本篇是“2D物理引擎基础”的第二篇文章,将主要讲解碰撞检测的基本原理和算法。 碰撞检测是物理引擎十分重要的组成部分。碰撞检测及后续的碰撞约束反映了物质的不可入性,是物理模拟真实性的基础。另外碰撞检测也能有效用于游戏逻辑的实现,如触发陷阱、探测障碍等等。 二、碰撞检测的基本概念 碰撞检测的目标就是判断两个几何体是否存在重合部分,如果存在重合部分,则得到接触点、碰撞法线和穿透深度。 接触点(Contact Point)指的是两个几何体相接触的位置。当然对于碰撞检测而言,接触的部分并非点而是区域,但是我们可以通过算法从中近似地取出接触点,一般位于其中一个几何体的顶点或边上。 碰撞法线指示了一个物体与另一个物体发生碰撞后,后者为了分离而应该采取的运动方向。这一法线大多是几何体中某一条边的法线 穿透深度是沿碰撞法线方向最终分离所需的距离,这一数值常用来修正碰撞后两几何体的位置,避免两几何体在视觉上发生重叠。 三、分离轴定理(SAT,Separating Axis Theorem) 分离轴定理是如下内容:若两个凸物体没有重合,则总会存在一条直线,能将两个物体分离。这样的直线称为分离轴。对于高维物体来说这同样适用,只不过分离轴将变为分离超平面。 利用分离轴定理可以实现凸几何体的碰撞检测。对于多边形,算法是这样的: 对两个多边形的每一条边,做垂直于该边的直线 对两个多边形的每一个顶点,分别做到步骤1中所得直线的投影 如果存在一条直线,两个多边形对这条直线的投影并不重合,则两个多边形不重合 如果不存在步骤3中的直线,则两个多边形重合。 完全按照算法思路得到代码如下 def sat(body_a, body_b): return not find_separate_axis(body_a, body_a, body_b) and not find_separate_axis(body_b, body_a, body_b) def find_separate_axis(lines_body, body_a, body_b): for i in range(len(lines_body) - 1): p1 = lines_body[i] p2 = lines_body[i] + get_normal(lines_body[i+1] - lines_body[i]) a_min, a_max = body_cast(p1, p2, body_a) b_min, b_max = body_cast(p1, p2, body_b) if p1[0] != p2[0]: max_min = a_min if a_min[0] > b_min[0] else b_min min_max = a_max if a_max[0] < b_max[0] else b_max if max_min[0] < min_max[0]: continue else: max_min = a_min if a_min[1] > b_min[1] else b_min min_max = a_max if a_max[1] < b_max[1] else b_max if max_min[1] < min_max[1]: continue return True return False def body_cast(a, b, body): body_min = body_max = point_cast(a, b, body[0]) for p in body: p_cast = point_cast(a, b, p) if p_cast[0] < body_min[0] or (p_cast[0] == body_min[0] and p_cast[1] < body_min[1]): body_min = p_cast if p_cast[0] > body_max[0] or (p_cast[0] == body_max[0] and p_cast[1] > body_max[1]): body_max = p_cast return body_min, body_max def point_cast(a, b, p): u = p - a v = b - a p_cast = a + v * (np....

二月 8, 2023 · 3 分钟 · 585 字 · Wokron

2D 物理引擎基础之物理属性

一、前言 本系列是我学习物理引擎相关知识的总结。虽说最初的目标是自己实现一个完整的物理引擎,但是随着学习的深入我认识到,为了让引擎更好地模拟实际,所需要的各种数学原理和处理技巧远远超出了“涉猎”的程度。因此我决定就此止步,但是还是留下了自己的实践成果和经验总结,就在这系列文章中。 本文是“2D物理引擎基础”的第一篇,将介绍物理引擎中物体所需的物理属性 二、物理引擎中的物体 在物理引擎中,模拟的物体对象被假定为刚体。刚体是如此致密坚硬的一种物体,以至于任何碰撞都无法改变其形状。同时物理引擎还常常假定对象的密度均匀,这样对象的质心将只由其几何形状决定。通过这样的假设,物理引擎将能够通过算法优化高效地进行计算模拟: 刚体说明物体不会改变形状,这样当物体受到力的作用时,力的作用效果就不会造成改变物体运动之外的其他影响,如质心改变。 密度均匀使得质心的求解更为方便,将质心位置和偏转角度作为物体属性,就可以完全表示物体上各点的位置信息 接下来所说的物理属性也是在如上约定的基础上的。 三、运动学属性 形状的表示 物理引擎中的形状用描述物体边界的属性表示。不同形状的物体通过不同的几何参数进行表示,如矩形可用长宽表示,圆可用半径长度表示,这些较为简单。更一般地我们考虑多边形,多边形可用顶点坐标表示,但是我们希望形状是位置无关的,那么多边形的顶点坐标也不能在坐标轴上随意选取。虽然 $(0, 0), (1, 0), (1, 1), (0, 1)$ 和 $(1, 1), (2, 1), (2, 2), (1, 2)$ 表示相同形状的多边形,但我们需要选择与位置无关的那一个。我们可以选择使多边形的质心在坐标原点的顶点集合,因为我们后续需要通过质心确定运动学属性。对于均质物体来说,这也就是多边形的几何中心在原点。 我们不能人为地在设置形状时就限制顶点围成的形状中心必须在原点,这对于复杂的形状来说十分困难。我们可以根据顶点集合先确定几何中心位置,再对每一顶点减去中心坐标以将物体平移到集合中心为原点的位置。 对于多边形,计算几何中心的公式为 $$ A = \frac{1}{2} \sum_{i=0}^{N-1}(x_iy_{i+1} - x_{i+1}y_i) $$ $$ c_x = \frac{1}{6A} \sum_{i=0}^{N-1}(x_i + x_{i+1})(x_iy_{i+1} - x_{i+1}y_i) $$ $$ c_y = \frac{1}{6A} \sum_{i=0}^{N-1}(y_i + y_{i+1})(x_iy_{i+1} - x_{i+1}y_i) $$ 其中 $A$ 为多边形面积, $(c_x, c_y)$ 即几何中心位置。需要注意的是,在这个公式中,顶点序列为逆时针,且第一个和最后一个应是同一个坐标,以暗示这是一个闭合图形并且方便公式表示。$N$ 是不重合的顶点个数/边数。 如下是求集合中心的 python 代码 def get_geometric_center(points): area = 0 c_x = c_y = 0 n = points....

二月 8, 2023 · 3 分钟 · 615 字 · Wokron

用 Pytorch 实现简单循环神经网络

一、歌词生成项目 想要在 nlp 方面深入,于是选择训练生成一个 RNN 网络,主要目标是自动生成歌词。在这里受到了 最浅显易懂的 PyTorch 深度学习入门 的启发,并利用 up 主提供的 源码 中的数据集。 相关代码的编写也有参考该 up 主的部分,但均为在理解内容的基础上自行编写的。另外也有对该 up 主代码中的疏漏进行修改的地方。 二、数据的获取 (1)编写数据集 原作者用爬虫获取的歌词数据被保存在 lyrics.txt 文件中。我们要将数据按可供训练的模型加载。具体来说 我们希望每一次获取数据,都能得到输入和目标输出(对本项目来说就是两段有一个文字偏差的序列) 并且将文字数字化,即 nlp 的 tokenize 为了实现批量训练,需要每次获取定长的序列 为了实现第一点,我们要继承 dataset;实现第二点,需要根据数据建立字符表;实现第三点,需要定长截取歌词句子的一部分。 另外,为了减少每次加载数据所用的时间,还需要将数据集的信息持久化。 我们的 LyricsDataset 具体实现如下。首先,我们在构造函数中通过传入的路径加载数据,判断是否已经存在处理过后的数据,如存在则加载;如不存在则读入原始数据并处理 class LyricsDataset(Dataset): def __init__(self, root_path, seq_size): self.seq_size = seq_size processed_name = "/processed/lyrics.pth" raw_name = "/raw/lyrics.txt" if os.path.exists(root_path + processed_name): print("find processed data") self.__load_processed_data(root_path + processed_name) else: print("processed data not found, will process raw data") self....

一月 17, 2023 · 4 分钟 · 642 字 · Wokron

机器学习 NLP 基本知识

一、自然语言处理(nlp)简介 一份思绪奔驰的前言: 语言的边界就是思想的边界。如果从人类所具有的一切中挑出一个事物,让它来显示出人与其他生物的不同之处,那一定是语言。语言是我们用来思考和交流的方式,我们的一切文明都构筑在这简单的一维序列中,可我们却几乎不曾深入了解过它。 有人说,我们不曾真正的理解语言,我们所有对语言的运用和理解,不过建立在婴儿及此后对他人的声音与书本上符号的猜测上。或许正是如此,但是我们依旧要做出猜测,向着语言的神秘进发,这是我们的信念,是我们认识我们的认识的开始。 (1)作为语言学的 nlp 早在计算机的上古时代,深度学习还未诞生的时候,自然语言处理作为语言学的一个领域就已诞生了,这个领域也被语言学家们称为计算机语言学。两个名称在一起,才表达出 nlp 的真正含义——通过机器处理语言。nlp 最早的研究方向是机器翻译,那时人们人为地总结语言的规律,对词汇进行标注,对语句进行句法分析。结果是人为的规则覆盖面不足,所设计的系统无法扩展。 (2)作为机器学习研究领域的 nlp 随着计算机的发展,出现了基于传统统计学习模型的自然语言处理方式。这些原始的模型较之之前有所进步,但受限于计算机性能,统计方法也遭遇了瓶颈。 直到近年来算力的发展,使得深度神经网络成为可能。深度神经网络结构中潜在的学习能力,在 nlp 领域发挥了作用。通过多维数据表示语言和含义,深度学习以高效且与人类认知过程相似的方式发挥了巨大的效果。 二、词向量 词汇作为符号,其形象是离散的;但词汇的所指作为定义,其含义却是丰富而连续的。比如说“母亲”这个词汇,既表示了这个概念所对应的事物是在一种血缘关系中的一方(她是孩子的母亲);又表示了这个事物是能繁殖者(母鸡);在一定程度上,同样表示了非血缘关系,但具有类似血缘关系的行为的个体(大地是母亲)。 偏个题,《来自深渊》中有对生骸语的类似的描述. 因此,我们就不能再将词汇只作为离散的符号看待了,不能认为词汇之间是相互排斥的关系了。我们需要将词汇看做某些元含义在不同程度下的集合,或者从机器学习的角度,把这些元含义称作特征。那么也就是说,我们将每个词汇都看成一定维度的向量。 但是我们要如何确定特征呢?特征的数量又有多少?如果人工地确定特征为“存在、含义、物质、精神”等等,这一过程将耗费精力且永无止境。实际上按照机器学习的一般策略,我们只需要通过统计文本,自发的构建词汇向量即可。 这一方面有许多算法,如 N-gram 算法,GloVe 算法等等。另外也可以在深度学习的过程中利用反向传播自发的调整词向量,在 pytorch 中这通过 Embedding 层来实现。 三、循环神经网络(RNN)及其变体 (1)朴素 RNN 考虑我们说话或写作时的基本逻辑。对于一段语言序列,在之后的词汇总是和之前的词汇有关,未表达的部分总是已表达部分的补全或补充。循环神经网络的机制类似,我们需要用一个或多个隐藏变量作为对之前语句含义的表示,在输出下一个词汇时,会让隐藏变量参与决策;同时每多说完一个词汇,这个词汇也会更新隐藏变量,以实现表达含义的更新。 具体来说,我们用 $t$ 表示时间序列,对某一时间 $t$,$x_t$ 表示输入,$y_t$ 表示输出,$h_t$ 表示隐含状态。那么朴素的 RNN 网络即: $$ h_t = tanh(W^{(hx)} x_t + W^{(hh)} h_{t-1}) $$ $$ y_t = W^{(S)} h_t $$ 容易看出 RNN 和 Moore 自动机有相似之处。 其中 $W^{(hx)}, W^{(hh)}, W^{(S)} h_t$ 分别为三个不同的矩阵。RNN 的激活函数也可以选择 ReLU。同时可以为激活函数中的部分添加偏置(bias)$b^{(hx)}, b^{(hh)}$ 等。...

一月 17, 2023 · 2 分钟 · 328 字 · Wokron

Spring 面向切面

一、面向切面(AOP) 有时,在按数据的处理流程编写程序时,我们不得不关心流程之外的情况,比如异常处理安全或日志。这些部分与主要事务交织在一起,使得代码功能不清,造成了强耦合。 面向切片(Aspect-Oriented Programming)就是为解决这样的问题产生的技术。该技术把那些横向影响了应用多处的功能从被其影响的主要事物流程中分离开来,作为切面。使流程只需要关注其本身,而切片则通过其他方式织入程序。 二、面向切面的术语 正如面向对象有其术语一样,面向切面也有用于描述其技术的相关概念,在介绍 Spring 的面向切面前需要加以解释。 通知(Advice) 通知是切面所具有的行为,也就是不采用面向切面编程时,那些与主要事务无关,应该被抽离出来的代码段。 连接点(Join Point) 连接点是可以应用通知的地方,也就是能够执行切面所具有的行为的地方。 切点(Poincut) 切点是真正应用通知的地方,切点一定是连接点。 切面(Aspect) 切面是通知和切点的总和。当程序执行到切点所在的位置时,就会执行对应的通知。 引入(Introduction) 引入是作为切面的类作用到处理主要事务的类的过程。这一过程为处理主要事物的类引入了新的方法和属性,但却没有对这个类本身进行修改。 织入(Weaving) 织入是为了实现切面的引入而采取的操作。织入将切面引入目标对象,创建了融合切面和目标的代理对象。这一操作可以发生在编译期、类加载期和运行期,需要看具体的实现。Spring 会在运行期完成切面的织入。 三、Spring的AOP:利用切点表达式选择切点 通知和切点共同组成了切面,在这一部分将讲述如何确定切点的位置。我们使用的是称为切点表达式的语法规范,用这一表达式确定我们所指定的切点,以便之后通知的编写。 Spring AOP 使用的是 AspectJ 切点指示器中的一部分,包括如下的内容 AspectJ指示器 描述 arg() 限制连接点匹配参数为指定类型的执行方法 @args() 限制连接点匹配参数由指定注解标注的执行方法 execution() 用于匹配是连接点的执行方法 this() 限制连接点匹配AOP代理的bean引用为指定类型的类 target 限制连接点匹配目标对象为指定类型的类 @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 within() 限制连接点匹配指定的类型 @within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) @annotation 限定匹配带有指定注解的连接点 通过组合使用这些类似于函数的指示器的,就可以确定我们想要引入通知的连接点究竟在哪里。接下来我们通过一些示例展示切点表达式的使用。如 execution(* concert.Performance.perform(..)) 表示匹配一个在方法执行时触发的连接点,这个方法是 concert 包下的 Performance 类中名为 perform 的方法,不考虑方法的返回值和参数(匹配所有同名的 perform 方法)。 切点表达式中还可以使用与(&&)或(||)非(!)操作。如 execution(* concert.Performance.perform(..)) && within(concert.*) 表示同时满足是 concert 包下的类方法调用,并且满足上一条的条件的切点。...

一月 10, 2023 · 3 分钟 · 433 字 · Wokron

Spring 高级装配

上一篇文章已经讲解了 spring 依赖注入装配的方法。但是为了处理一些特殊的问题,或者为了更好地实现某些功能,spring 还提供了更多的装配设置。 一、条件化声明 Bean 让我们回到上一篇第一节的例子,假设这次我们需要在开发时使用 SQLite,而在生产环境使用 Oracle 要怎么办呢? 或许可以这样,我们在生产环境注意将 DBUtil Bean 改成 Oracle,把原来的部分注释掉,就像是这样。 @Bean public DBUtil databaseUtil() { // return new SQLiteUtil(); // for dev return new OracleUtil(); // for prod } 但这样做很明显是不合适的。如果许多组件都需要进行调整的话,修改上就会十分复杂,且很容易出错。 我们可以使用注释 @Profile 实现不同环境条件下选择不同的装配方式。这需要给带有 @Bean 或 @Configuration 注释的方法或类添加注释 @Profile("某某环境")。于是数据库的选择就可以改为如下形式: @Bean @Profile("dev") public DBUtil sqliteUtil() { return new SQLiteUtil(); } @Bean @Profile("prod") public DBUtil oracleUtil() { return new OracleUtil(); } 想要启用某个 Bean 需要设置环境。具体来说,这通过两个环境变量来实现,spring.profiles.default 和 spring.profiles.active。这两个值可以在许多地方定义。一种方式是在 properties 或 yaml 文件中定义,如...

一月 10, 2023 · 3 分钟 · 502 字 · Wokron