-
Notifications
You must be signed in to change notification settings - Fork 146
App organisation in Plain English
In this article we will see how three-mesh-ui works internally. It will be explained in Plain English, with however the assumption that you have at least a basic understanding of how three.js works and how to use it.
three-mesh-ui primary purpose is to provide an easy way to add user interfaces for virtual reality experiences built with three.js. It is designed to be integrated easily in an existing three.js project, as opposed to a framework like React360 or aFrame.
It doesn't make any assumption as to how the user interfaces can be interacted. That is, it is designed to produce THREE.Object3Ds, and from there the user can decide how to interact with this in a normal three.js workflow ( it can be tested for intersection with THREE.Raycaster ). This way, the user is free to integrate three-mesh-ui components in any kind of experience, involving any kind of controls (even VR hands controls).
A three-mesh-ui component is an object inheriting from THREE.Object3Ds. So far there is 4 of them : Block, InlineBlock, Text and Keyboard. Keyboard is different because it is the only component that is composed with other components. Therefore, it could be deemed a high-level component.
If you look at the code of these components, you will see that they are composed of several modules. For instance, this file is the main module of the Block component. Here, it composes the component with the several modules that are needed for its behaviour, over a THREE.Object3D instance. We will study these behaviour modules in the next chapter.
Every component has at at least three methods as its own properties : parseParams, updateLayout, and innerLayout. We talk about this more in length in the Components updates paragraph.
Components behaviour modules are all placed in this directory.
Behaviour of Block, InlineBlock and Keyboard, it is used for retrieving this component size (acccording to children dimension if undefined), and positioning children components inside itself. A BoxComponent (component with BoxComponent behaviour) is not responsible for positioning itself inside its parent, its parent is the only responsible for this.
At the moment it does not add any behaviour to a component, but an isInline = true
parameter, so that parents can know how to position this component. Used for the composition of InlineBlock and Text.
Responsible for positioning inline components children of a component. It only works if its children have a component.inlines
property. This property is set by the children themselves in their parseParams
function, it's an array containing objects with at least these properties : height
, width
, anchor
and lineBreak
.
width
and height
obviously contain the dimension of the element to position.
anchor
is the distance between the lowest point of the element to position, and the line in which it's positioned. Principally responsible for offsetting letter like "p" or "q".
lineBreak
is either null
or 'possible'
or 'mandatory'
, it indicates how much a line break is desirable on this element.
Responsible for assigning and updating materials to its component. Materials are created once, then their parameters (shader uniforms) are updated when necessary. Material clipping planes are updated every frame for each component, in order to dynamically hide every part of this component which is overflowing out of ancestor components with hiddenOverflow === true
.
Only used in the composition of Text, its methods are called by Text for :
- Determining the size of a given glyph
- Creating individual MSDFGlyph and merging them to create a single text THREE.Mesh
Most important behaviour module, it's common to all components in three-mesh-ui. This module is mostly a conductor, a getter-setter whose concern is to add/remove parameters (we should call them attributes) to a component, and register it for the right updates accordingly.
The user should not add properties directly to a component, instead they should call set
, whose concern is to determine what type of updates should be called for this component depending on the updated attributes.
There is three types of updates :
- parseParams, which parse input parameters than must be used by parents for a layout update
- updateLayout, which updates things affecting the positioning of children
- updateInner, which updates things that will affect only this component
We will elaborate on updates in the next paragraph.
When the user wants to repeatedly apply a given group of attributes to a component, they could use the set
method, but the easiest way is to use setupState
and setState
methods. setupState
stores predefined groups of attributes as states in the component, then the user can call setState
with the state name as argument to apply the predefined attributes.
We want to avoid useless duplicated update calls when the user adds/updates a lot of components in one go, which would lead to a huge drop in performance depending on how much components the user interface contains. Therefore, updates are not called immediately when the user calls set
or setState
. Instead, they are requested to UpdateManager.
UpdateManager is not used for module composition, it's a single module that knows all the components instantiated, since they are systematically registered to it.
When the user wishes to update all the components that where requested for updates, they call update, which is a function in the UpdateManager module assigned to the global three-mesh-ui object. It starts by calling all parseParams
functions of requested components, from parents to children. Then it does the same for updateLayout
functions, then updateInner
.