让我们设定一个纯粹的实际目标-实现一个能够用鼠标移动和缩放的无尽画布。 例如,这样的画布可以用作图形编辑器中的移动坐标系。 我们的想法的实现并不那么复杂,但是理解它的过程与基本的数学和物理对象相关联,我们将在开发过程中考虑这些基本对象。
结果问题陈述
在我们的画布上,我们只想执行两个功能:移动鼠标和移动鼠标,以及滚动时缩放。 我们转型的舞台将选择浏览器。 在这种情况下,武器不必选择。
状态机
这种系统的行为可以方便地通过其状态之间的转换来描述。 状态机 在哪里 -状态之间的转换函数,本身显示许多状态。
在我们的例子中,状态图如下所示:

在实现过渡功能时,使事件相关变得很方便。 将来这将变得显而易见。 同样,能够订阅计算机状态的更改很方便。
type State = string | number; type Transition<States = State> = { to: States, where: (event: Event) => Array<boolean>, }; type Scheme<States = State> = { [key: States]: Array<Transition<States>> }; interface FSM<States = State> { constructor(state: States, scheme: Scheme<States>): void; // Returns true if the scheme had a transition, false otherwise get isActive(): boolean; // Dispatch event and try to do transition dispatch(event: Event): void; // subscribe on state change on(state: States, cb: (event: Event) => any): FSM<States>; // remove subscriber removeListener(state: States, cb: (event: Event) => any): void; };
我们将执行推迟一会儿,然后进行构成我们任务基础的几何变换。
几何形状
如果移动画布的问题非常明显,我们将不再赘述,那么应该更详细地考虑拉伸。 首先,我们需要拉伸使单个点保持静止-鼠标光标。 还必须满足可逆性条件,即 用户操作的相反顺序应将画布恢复到原始位置。 哪种几何适合于此? 我们将考虑平面到其自身的一些点转换 ,通常通过引入新变量来表示 定义为旧功能:
根据数学对偶原理,这种变换可以解释为坐标系的变化,也可以解释为空间本身在固定的情况下的变换。 第二种解释对于我们的目的很方便。
对几何学的现代理解不同于对古代人的理解。 根据F. Klein的说法,-几何研究某些变换组的不变量。 因此,在一组动作中 不变量是两点之间的距离 。 它包括并行连字符 在向量上 轮换 相对于原点成一定角度 和思考 相对于某条线 。 这种运动称为基本运动。 这两个运动的组成属于我们的团队,有时可以归结为基本运动。 因此,例如,相对于直线的两个连续镜面反射 和 围绕某个中心以某个角度旋转(请检查一下自己):
当然您已经猜到这样的动作 形成欧几里得几何。 但是,拉伸并不能保留两点之间的距离,而可以保留它们之间的关系。 因此,一组动作虽然应该包含在我们的方案中,但只能作为一个子组。
适合我们的几何形状基于拉伸组 除了上述动作外,还增加了同质性 按系数 。
好吧,最后。 反向元素必须存在于组中。 因此有一个中立的 (或单个)不会更改任何内容。 举个例子
意味着先伸入
次然后在
。
现在,我们可以用组理论语言描述拉伸,使鼠标光标固定不动:
注1在一般情况下,动作的重新安排不是可交换的(您可以先脱下外套,然后脱下衬衫,反之亦然)。
表达运动 和 及其在代码中作为向量函数的组成
type Scalar = number; type Vec = [number, number]; type Action<A = Vec | Scalar> = (z: Vec) => (v: A) => Vec;
疼痛1奇怪的是,在JavaScript中没有运算符重载。 似乎矢量和栅格图形的如此广泛使用,以“经典”形式处理矢量或复数要方便得多。 动作的概念将取代算术运算。 例如,围绕某个向量旋转 在拐角处 将以简单的方式表达:
不幸的是,至少与Python不同,JS并没有遵循Pythagorean奇妙的“世界就是数字”思想的发展。
请注意,到目前为止,我们一直在处理一组连续的转换。 但是,计算机不能处理连续量,因此,在Poincare之后,我们将连续组理解为无限的离散操作组。 现在我们已经弄清楚了几何形状,我们应该转向运动的相对性。
画布的宇宙学。 模块化网格
一个世纪以来,作为人类, 已经知道宇宙的膨胀 。 观察遥远的物体-星系和类星体,我们记录电磁频谱向更长波的移动-所谓的宇宙学红移。 任何测量都将观察者,被观察者以及与我们进行测量有关的测量方法联系起来。 如果没有测量仪器,就不可能在自然界中建立不变的关系,即确定宇宙的几何形状。 但是,几何体失去了它的意义而无法观察。 因此,在我们的任务中,最好有与银河系相关的地标,与之相关的光线可以确定画布运动的相对性。 这样的结构可以是周期性的格子,每次当空间扩展两次时,它就会分叉。
由于晶格是周期性的,因此采用模块化代数很方便。 因此,我们将作为一个团体 也在圆环上 。 由于监视器屏幕不是连续平面,而是整数晶格 (我们现在忽略它是有限的),然后是该组的作用 必须考虑在整数圆环上 在哪里 -正方形边缘的大小 :

因此,一劳永逸地将圆环固定在原点附近,我们将对其进行所有进一步的计算。 然后使用标准画布库方法传播它。 这是一个像素移动的样子:

显然,采用模块x%p的标准操作不适合我们,因为它将参数的负值转换为负值,但整数环上没有任何值。 写你的功能 :
const mod = (x, p) => x >= 0 ? Math.round(x) % p : p + Math.round(x) % p;
现在回到最终状态机
定义它FSM类方便地继承自EventEmitter,它将为我们提供订阅功能。
class FSM<States> extends EventEmitter { static get TRANSITION() { return '__transition__'; } state: States; scheme: Scheme<States>; constructor(state: States, scheme: Scheme<States>) { super(); this.state = state; this.scheme = scheme; this.on(FSM.TRANSITION, event => this.emit(this.state, event)); } get isActive(): boolean { return typeof(this.scheme[this.state]) === 'object'; } dispatch(event: Event) { if (this.isActive) { const transition = this.scheme[this.state].find(({ where }) => where(event).every(domen => domen) ); if (transition) { this.state = transition.to; this.emit(FSM.TRANSITION, event); } } } }
接下来,定义过渡方案,创建画布并
初始化一切。 canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d');
然后,您应该确定渲染功能,设置必要的初始值并订阅状态更改。 考虑一下代码中最有趣的部分:
fsm.on('zooming', (event: WheelEvent) => {
首先,我们不按系数k进行扩展,而是按nk / k的比例进行扩展。 这是由于以下事实:我们的映射在固定点处的m步 表示为
或相对于初始值
显然是产品 迭代步骤有一个非线性函数,要么很快收敛到零,要么以很小的初始偏差逃到无穷大。
我们引入变量g,这是将画布加倍的一种度量。 显然,它在一定间隔内取一个恒定值。 实现线性 我们使用齐次替换
然后,除第一个和最后一个成员外,工作中的所有成员将减少:
此外,相位跳变g降低了膨胀率,使得在我们之前展开的分形结构始终线性移动。 因此,我们获得了宇宙膨胀的哈勃幂定律的近似变化。
仍然需要了解我们模型准确性的局限性。
量子波动。 2 adic数字字段
了解测量过程导致了实数的概念。 海森堡的不确定性原则指出了其局限性。 现代计算机不能使用实数,而可以使用机器字,其长度取决于处理器的容量。 机器字构成2进制数的字段 并表示为 在哪里 -字长。 在这种情况下,测量过程由计算过程代替,并且与非阿基米德度量标准关联:
因此,我们的模型具有计算限制。 这些限制在IEEE_754标准中进行了描述。 从某个点开始,我们的基本大小将超出准确性的限制,并且获取模块的操作将开始生成类似于伪随机序列的错误。 只需删除该行即可验证
if (g < min || g > max) return;
由于我们使用多个参数,因此本例中的最终限制是通过半经验方法计算的。
结论
因此,乍看起来似乎很遥远,但理论却在浏览器的画布上联系在一起。 动作,度量和计算的概念彼此紧密相关。 合并它们的问题仍然没有解决。
结果聚苯乙烯很明显,源代码很小,所以我在普通的index.html中领导开发。 在撰写本文时,我添加了在TypeScript Playground中测试的类型。