本教程将引导您完成建立自定义模块的基本步骤。这个功能可完全在本模拟器网页程序中实现,而不需要其他的开发环境。
「模块」是「线光学模拟」的一个目前处于实验阶段的功能,它允许将物件以模块化的方式组合在一起,其中可包含自定义参数、自定义控制点与物件数组。本功能透过将本模拟器中现有的工具所建立的物件进行组合、特化或重新参数化,来制作新的工具,以扩充本模拟器的功能。例如,CircleSource
模块(请参见工具->其他->导入模块)将现有的「点光源(<360°)」工具所建立的一系列点光源沿着一个圆形组合在一起,成为一个「圆形光源」工具,这在模拟器中原本并不存在。FresnelLens
模块则是将「透光物->自定义函数」工具特化,使函数表示菲涅耳透镜的特定曲线,由切片数参数化,以制作一个特化的「菲涅耳透镜」工具,这在模拟器中原本也不存在。除了制作新工具外,这个功能还可以使一些光学演示更具互动性。例如,通过拖曳BeamExpander
模块的第三个控制点,可以直接观察两个透镜的共同焦点位置如何影响光束宽度,而无需分别调整两个透镜的焦距。
注意并非所有的自定义控制点都需要模块。一些简单的情况可能可以通过「控制杆」功能实现(请参见模拟器右下角的帮助弹出窗口中的「群组、旋转和缩放物件」部分)。由于制作模块比建立控制杆复杂得多,您应该在考虑制作模块之前检查您的情况是否可以通过「控制杆」功能实现。比如这个例子展示了一个相对复杂但不必使用模块的自定义控制点(将两个塑料袋从水中移开)。
本应用目前没有用于建立模块的视觉界面,因此您需要直接编辑场景的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来计算(https://mathjs.org/docs/reference/functions/evaluate.html)。请参见该网页以了解您可以在数学式中使用的语法和函数。
参数的实际值位于模块物件的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
数组中这类的物件总是以形状来定义。以下是一个例子:
欲贡献您的模块,请参见貢獻模块。
见GitHub上的Discussion #145(请使用英文)。