# 「射线光学模拟」模块制作教程

本教程将引导您完成建立自定义模块的基本步骤。这个功能可完全在本模拟器网页程序中实现，而不需要其他的开发环境。

「线光学模拟」的「模块」功能允许将物件以模块化的方式组合在一起，其中可包含自定义参数、自定义控制点与物件数组。本功能透过将本模拟器中现有的工具所建立的物件进行组合、特化或重新参数化，来制作新的工具，以扩充本模拟器的功能。例如，`CircleSource`模块（请参见工具->其他->导入模块）将现有的「点光源（<360°）」工具所建立的一系列点光源沿着一个圆形组合在一起，成为一个「圆形光源」工具，这在模拟器中原本并不存在。`FresnelLens`模块则是将「透光物->自定义函数」工具特化，使函数表示菲涅耳透镜的特定曲线，由切片数参数化，以制作一个特化的「菲涅耳透镜」工具，这在模拟器中原本也不存在。除了制作新工具外，这个功能还可以使一些光学演示更具互动性。例如，通过拖曳`BeamExpander`模块的第三个控制点，可以直接观察两个透镜的共同焦点位置如何影响光束宽度，而无需分别调整两个透镜的焦距。

注意并非所有的自定义控制点都需要模块。一些简单的情况可能可以通过「控制杆」功能实现（请参见模拟器右下角的帮助弹出窗口中的「群组、旋转和缩放物件」部分）。由于制作模块比建立控制杆复杂得多，您应该在考虑制作模块之前检查您的情况是否可以通过「控制杆」功能实现。比如这个例子展示了一个相对复杂但不必使用模块的自定义控制点（将两个塑料袋从水中移开）。

## 内置的JSON编辑器

本应用目前没有用于建立模块的视觉界面，因此您需要直接编辑场景的JSON原始码。

您可以通过点击应用程序右上角的「设置」下拉菜单，然后勾选「显示JSON编辑器」来启用内置的JSON编辑器。原始码编辑器会出现在界面的左侧，并显示目前场景的JSON原始码。请确保您有足够大的屏幕，因为这个功能在行动装置上效果不佳。

当您使用可视化场景编辑器修改场景时，JSON编辑器中的原始码将自动更新，且更改的部分会突出显示。反之，直接在JSON编辑器中编辑原始码将相应更新场景。如果您不熟悉JSON或任何基于文字的数据格式，您可能会希望花一些时间来熟悉它。

特别是，当您将一个物件新增到场景中时，它将被新增到`objs`数组中。如果您将某些属性修改为非预设值，它们将作为该物件的键值对出现。

注意：如果您在本教程页面中看不到下面的iframe中的JSON编辑器，请启用它并重新加载本页，因为您需要查看原始码以了解它的运作方式。

## 模块的基本概念

让我们看第一个例子。

您会看到四段文字。通过查看JSON编辑器，您将看到前两段直接位于顶层的`objs`数组中，但后两段位于`modules.ExampleModule.objs`中。

`module`是一个字典，其中键是模块的名称（在本例中是`ExampleModule`），值是该模块的定义。特别是，`modules.ExampleModule.objs`数组描述了该模块中的物件（模板），这与描述场景中的物件的顶层`objs`数组不同。

要将模块中的物件放到场景中，我们需要一个「模块物件」，它位于顶层`objs`数组中，本例中是`objs[2]`，其类型是`ModuleObj`，其`module`属性是模块的名称。

`modules`字典中的模块定义无法由可视化场景编辑器编辑。因此，当您点选本例中的后两段文字时，您只选择了模块物件，而不是模块中的物件。由于本例中模块定义中的文字坐标是绝对坐标，因此后两段文字无法被拖曳。我们将在后面学习到如何使用控制点来使它们可被拖曳。

当您提取模块物件时，物件栏上会有一个「取消模块化」按钮。点选后会将模块物件「展开」为构成此模块的物件，此时`objs`将包含所有四段文字。这个操作是不可逆的（除非点击「复原」）。

目前建议制作模块的方式为，首先使用JSON编辑器建立一个空的模块，然后使用可视化场景编辑器新增一些物件，最后使用JSON编辑器将这些物件从`objs`剪下并粘贴到`modules.ModuleName.objs`中。

## 新增参数

模块中的物件可以由一组参数来定义。让我们看一个简单的例子

这里`modules.ModuleName.params`是一个由形如`"名称=起始值：增量：终止值：预设值"`的字串构成的数组，定义了变数的名称和数值滑杆的范围。当提取模块物件时，滑杆将出现在物件栏上。

在`modules.ExampleModule.objs`数组中，任何值都可以使用这些参数来表示。在字串中（例如`TextLabel`的`text`属性）中，带有参数的数学式被反引号包围。对于数值参数（例如`TextLabel`的`fontSize`属性），您需要将其改为字串，以便您可以在其中使用反引号格式。因此每条数学式都被一对反引号和一对引号包围。这些数学式将使用math.js来计算（语法）。请参见该网页以了解您可以在数学式中使用的语法和函数。

参数的实际值位于模块物件的`params`属性中。这部分与模块定义不同，其可以通过滑杆直接由可视化场景编辑器编辑。

## 新增控制点

为了使模块物件可被拖曳，我们需要使用一组控制点来对模块中的物件位置进行参数化。让我们看一个例子

这里`modules.ModuleName.numPoints`描述了控制点的数量。控制点坐标之符号为 (`x_1`, `y_1`)、(`x_2`, `y_2`) 等等。其用在`modules.ExampleModule.objs`中的方式与前一节所述之参数相同。请注意索引从1开始。

控制点的实际值位于模块物件的`controlPoints`属性中，其可以直接由可视化场景编辑器编辑，而非如第一个例子中写死在模块定义中的坐标。每个控制点在场景中显示为两个同心的灰色圆形，并且可以被拖曳。若您拖曳模块物件的其他地方（如文字标签），则所有的控制点会一起移动。

由于我们的模块物件已经可以移动，我们可以很容易地建立多个实例，就像在本模拟器中其他的工具中一样。模块的名称显示在工具->其他菜单中，您可以选择它，然后按两个点来指定两个控制点的位置，就可建立此模块的另一个实例。您也可以使用物件栏上的「复制」按钮。

## 数组与条件

使用数组和条件语法，可以建立更复杂的模块。让我们看一个例子

在`modules.ExampleModule.objs`中，任何数组中的物件都可以有两个特殊的键：`"for"`和`"if"`。`"for"`键的值是一个描述循环变数的字串，格式为`"名称=起始值：增量：终止值"`，或者是一个包含多个这种格式的字串的数组，描述多维循环。这样的物件在数组中根据循环变数被复制多次。`"if"`键的值是则是一个表示布尔值的字串，使得该物件只在布尔值为真时才会包含在数组中。

为了防止意外的无穷循环，每个`"for"`循环的总迭代次数由模块定义中的`maxLoopLength`属性限制，其预设值为1000。如果需要，您可以将此属性设定为更大的值。

## 内置自定义数学式功能的物件

对于已经具有自定义数学式输入的物件（例如镜子->自定义函数），JSON中的数学式属性是一个表示LaTeX数学式的字串，而不是math.js表达式。要在数学式中包含自定义参数，您必须将LaTeX数学式视为普通字串，并使用普通字串的模板语法。因此，反引号括起来的部分是math.js表达式，而反引号外部的则是LaTeX数学式。模块参数只能在math.js的部分中使用，而物件内置的自定义函数的自变量（例如\(x\)）只能在LaTeX的部分中使用。以下例子产生一个形状为\(y=\cos(2\pi x+\phi)\)的镜子，其中\(\phi\)为模块参数

未来可能会有一种统一的方式来输入数学式。

## 内置形状参数化功能的物件

对于已经支持使用不同方式定义其形状的物件（目前仅有透光物->球面透镜），有特殊的JSON语法可用于在模块定义中用这些方式来定义该物件，即使在顶层`objs`数组中这类的物件总是以形状来定义。以下是一个例子