Este tutorial vai te guiar pelo processi básico de criação de um módulo personalizado, que pode ser completamente feito dentro do app web, sem necessidade de qualquer ambiente de desenvolvimento externo.
O recurso "Módulo" da Simulação de Óptica de Raios permite a criação de combinações modulares de objetos com parâmetros personalizados, pontos de controle personalizados, e malhas de objetos. Esse recurso extende a capacidade deste simulador ao combinar, especializar, ou reparametrizar objetos criados por ferramentas existentes para criar novas ferramentas. Por exemplo, o módulo CircleSource
(ver Ferramentas -> Outros -> Importar módulo) combina uma série de fontes pontuais criadas pela ferramenta existente "Fonte pontual (<360°)" ao redor de um círculo, para fazer uma ferramenta "fonte circular" que não existia no simulador. O módulo FresnelLens
especializa a ferramenta "Glass->Custom equation", para que a equação represente uma curva específica da lente Fresnel parametrizada pelo número de fatias, assim fazendo uma ferramenta especializada "Lente Fresnel", que tambem não existia anteriormente. Além de fazer novas ferramentas, esse recurso também pode fazer algumas demonstrações de óptica mais interativas. Por exemplo, ao arrastar o terceiro ponto de controle do módulo BeamExpander
, pode-se ver diretamente como a posição do ponto focal em comum das duas lentes afeta a largura do raio, sem necessitar ajustar os comprimentos focais das duas lentes individualmente.
Note que nem todos pontos de controle personalizados requerem um módulo. Alguns casos simples podem ser feitos com o recurso "ponto de mauseio" (ver a seção "Agrupar, rotacionar e ajustar escala de objetos" no popup de ajuda no canto inferior direito do simulador). Já que fazer um módulo é muito mais complicado que criar um ponto de manuseio, deve-se primeiro checar se o seu caso oise ser feito pelo recurso "ponto de manuseio" antes de considerar fazer um módulo. Veja aqui para um exemplo não trivial de um ponto de controle personalizado (mover dois sacos plásticos pra fora da água) sem utilizar um módulo.
Este app atualmente não possui uma interface visual para criação de módulos, então é preciso diretamente editar o JSON da cena.
Você pode habilitar o editor JSON incluso ao clicar no menu "configurações" no canto superior direito do app, e então marcar "Exibir editor JSON". O editor de código deve aparecer no lado esquerdo do app, com o código JSON da cena atual. Certifique-se que a sua tela é grande o suficiente, já que este recurso não funciona bem em dispositivos móveis.
Conforme você edita a cena utilizando o editor visual de cena, o código no editor JSON vai atualizar automaticamente, com a parte modificada destacada. Do mesmo modo, editar o código no editor JSON diretamente vai atualizar a cena automaticamente. Se você não for familiar com JSON ou qualquer tipo de formato de dados à base de texto, pode ser útil brincar com isso por um tempo.
Em particular, quando se adiciona um objeto à cena, ele é adicionado à lista objs
. E se você modificar algumas de suas propriedades para um valor não padrão, elas aparecem como pares chave-valor nesse objeto.
IMPORTANTE: Nessa página de tutorial, se você não ver o editor de código JSON nos iframes abaixo, por favor habilite-o e recarregue esta página, pois vocÊ precisará de ver o código para entender como funciona.
Vamos ver o nosso primeiro exemplo de módulo.
Você deve ver quatro linhas de texto. Olhando no editor JSON, você verá que as primeira duas estão diretamente na lista objs
de nível superior como normal, mas as últimas duas estão em modules.ExampleModule.objs
ao invés disso.
O module
é um dicionário onde a chave é o nome do módulo (neste caso ExampleModule
), e o valor é a definição do módulo. Em específico, a lista modules.ExampleModule.objs
descreve (o padrão de) objetos dentro desse módulo, que é diferente do objs
do nível superior que descreve objetos na cena.
To put the objects within the module to the scene, we need a "module object" in the top-level objs
array, which is objs[2]
in this example, whose type is ModuleObj
and whose module
property is the name of the module.
The module definition in the modules
dictionary is not editable by the visual scene editor. So when you click any of the last two texts in this example, you are just selecting the module object, and not the objects in the module. Since the coordinates of the texts in the module definition in this example are absolute coordinates, the last two texts are not draggable. We will learn how to make them draggable by using control points later.
If you select a module object, there is a "Demodulize" button on the object bar. Clicking it will "expand" the module object into its constituent, and objs
will now contain all the four texts. This operation is not reversible (but of course you can click "undo").
The suggested way of creating a module currently is to first create an empty module using the JSON editor, create some objects using the visual scene editor, and then cut and paste the objects from objs
to modules.ModuleName.objs
using the JSON editor.
The objects within the module can be defined by a set of parameters. Let’s look at a simple example
Here modules.ModuleName.params
is an array of strings "name=start:step:end:default"
defining the name of the variables and the range of the sliders. The sliders appear on the object bar when the module object is selected.
Within the modules.ExampleModule.objs
array, any values can be expressed using those parameters. Within a string (such as the text
property of a TextLabel
), the equations of the variables are enclosed by a pair of backticks. For number parameters (such as the fontSize
property of a TextLabel
), you need to make it a string so that you can use the backtick format in it, so each equation is sandwiched by a pair of backticks and a pair of quotes. The equation are evaluated with math.js (syntax). See there for the available syntax and functions you can use in the equations.
The actual values of the parameters are stored in the params
property of the module object, which, unlike the module definition, can be directly edited by the scene editor using the slider.
To make the module object draggable, we need to parametrize the objects within the module using a set of control points. Let’s look at the example
Here modules.ModuleName.numPoints
defines the number of control points. The coordinates of the control points are (x_1
, y_1
), (x_2
, y_2
), etc, and are used in the same ways as the parameters within modules.ExampleModule.objs
as described by the previous section. Note that the index starts from 1.
The actual values of the coordinates of the control points are stored in the points
property of the module object, which, unlike the hard-coded coordinates in Example 1, can be edited by the visual scene editor by dragging the control points, each shown as two concentric gray circles in the scene. If you drag elsewhere in the module object (such as dragging the text labels), all the control points will move together.
Since our module object can now move, it is now quite easy to create multiple instances as in usual tools. The name of the module is shown in the Tools -> Other menu, and you can select that and then click two points in the blank space in sequence for the two control points to create another instance of the module. You can also use the “duplicate” button on the object bar.
More complicated module can be built using arrays and conditionals. Let’s look at the example
Within modules.ExampleModule.objs
, any objects in an array can have two special keys: "for"
and "if"
. The value of the "for"
key is either a string of the format "name=start:step:end"
defining a loop variable, or an array of several strings of this format describing a multidimensional loop. Such an object in the array is duplicated several times according to the loop variables. The value of the "if"
key is a string representing a math.js expression that evaluates to a boolean, and such an object is included in the array if and only if the boolean is true.
To prevent accidental infinite loop, the total number of iteration of each "for"
loop is limited by the maxLoopLength
property of the module definition, whose default value is 1000. You can set this property to a larger value if needed.
For objects that already have custom equation input (such as Mirror -> Custom Equation), the equation property in the JSON is a string representing a LaTeX equation, rather than a math.js expression. To include custom parameters in the equation, you must use the same template syntax as if the LaTeX equation were a regular text. So the part enclosed by the backticks is in math.js expression, while the part outside is in LaTeX. The module parameters can only be accessed in the math.js part, and the independent variables of the custom equation (e.g. \(x\)) can only be accessed in the LaTeX part. Here is an example of generating a mirror with equation \(y=\cos(2\pi x+\phi)\), where \(\phi\) is a module parameter
In the future, there may be a way to unified the equation input.
For objects that already support different ways to define its shape (currently only Glass -> Spherical lens). There are special JSON syntax for such objects that can be used within the module definition, even if they are always defined by shape in the top level objs
array. Here is an example