WARNING TO POTENTIAL USERS: This is a fairly complex set of A-Frame components, at an early stage of development
Interfaces and implementation details are likely to change going forwards, potentially in non-back-compatible ways. And there will be bugs…
A set of components to support connecting together A-Frame entities based on a configurable set of plugs and sockets.
The system is configurable, but the general principles are that:
dynamic-snap
component)socket-fabric
component resolves these conflicts (e.g. by prioritizing the majority and rejecting the others)Example applications of this system include
The system consists of several elements.
An entity that has plugs or sockets configured on it must have the socket-fabric
component configured on it, for example like this:
<a-box position = "-1 1.1 0"
width = 2
socket-fabric
color="red">
</a-box>
Plugs and sockets are then be configured as child entities beneath the socket-fabric
entity, like this:
<a-box position = "-1 1.1 0"
width = 2
socket-fabric
color="red">
<a-entity socket position = "-0.5 -0.5 0">
</a-entity>
<a-entity socket position = "0.5 -0.5 0">
</a-entity>
<a-entity plug position = "-0.5 0.5 0">
</a-entity>
<a-entity plug position = "0.5 0.5 0">
</a-entity>
</a-box>
The above example configures a 2 x 1 x 1 block, with two evenly-spaced plugs on its top, and two evenly-spaced sockets on its base, as shown in this image (sockets are invisible by default, but can be shown in debug mode.
Overall configuration for the overall plug/socket system can be specified on the A-Frame scene like this:
<a-scene socket="snapDistance: 0.2; debug: true">
(see below for schema, which details all the options)
Each entity in a scene that is able to connect to other entities in the scene must have a socket-fabric
component configured on it.
Each such entity can then have any number of plug and socket entities configured as children, each with its own position and rotation relative to the parent entity.
The socket system keeps track of all the plugs and sockets in the scene, including which of them are bound to another plug / socket, and which of them are free.
For each free plug, the socket system continuously monitors whether it comes into range & alignment with a free socket. Plugs seek to connect to sockets that are close by, and in reasonably close alignment (plugs always ignore sockets on the same fabric as them). To join together, plugs and sockets must align their y-axes, but there is potential for rotation about this y-axis (currently this can only be discrete number of fixed positions - see rotationIncrement
property below for more details).
When a free plug and socket are in close range & close alignment, requests are made to the socket fabric to re-align with this position, so that the plug & socket can align exactly. This re-alignment can either be:
dynamic-snap
)Sockets and plugs have “inertia” which controls which one of them moves, in order to snap together (currently the inertia logic is very simple, but it may be extended in future).
A socket fabric with multiple plugs & sockets may receive multiple simultaneous requests to re-align, which may be in conflict with each other. The socket fabric resolves these conflicts (currently using a simple majority - more flexibility may come in future), and determines which of the requests are honored, and which are rejected.
The system does not yet implement any logic to maintain relative positions of entities that are bound together by plug/socket connections. When modifying the transform of an entity that is bound via plugs or sockets to another entity, applications must take care of updating the other entities (or explicitly break the binds of the entity before moving it).
Property | Type | Description | Default |
---|---|---|---|
snapDistance | number | The maximum distance, in world space units, that a plug and socket will snap together | 0.1 |
snapRotation | number | The maximum angle, in degrees, that a plug and socket may be offset by, relative to a snappable orientation, in order for that plug and socket to snap together. | 50 |
rotationIncrement | number | Plugs and sockets must align exactly on their x and z rotations, but there is flexibility in the rotation of the y axis. This parameter specifies the angle (in degrees) between positions at which a plug and socket can be fixed together. 90 degrees results in 4 possible positions of the plug & socket (like a square plug into a square socket). 60 degrees would result in 6 possible positions (like a hexagonal plug in a hexagonal socket). In theory, 0 degrees would result in completely flexible rotation of the plug in the socket (a cylindrical plug in a cylindrical socket). However the current implementation searches linearly through possible positions, and so will not perform well for low values of rotationIncrement - as a guideline, values below 30 degrees are not recommended. |
90 |
debug | boolean | Enables debug visualization of the positions of plugs & sockets. Also enables some console logging. | false |
Property | Type | Description | Default |
---|---|---|---|
snap | string | One of: auto or events .In “auto” mode, the socket fabric moves immediately when plugs or sockets request to bind to peer sockets or plugs. In “events” mode, the socket fabric does not move itself, but emits snapStart and snapEnd events, allowing movements to be controlled by an externa component such as dynamic-snap . |
auto |
Property | Type | Description | Default |
---|---|---|---|
type | string | One of: plug or socket .The socket component implements both plug & socket functionality. This property should not be set explicitly. Rather, sockets should be created using the socket component (with no properties), and plugs should be created using the plug component (again, with no properties) |
socket |
The plug component currently has no properties on its schema.
Note that the plug
component is in fact just a thin wrapper that configures a socket
component of type: plug
The socket
component exposes the following binding data, which is used when handling binding requests:
Property | Description |
---|---|
isSocket |
true for a socket, false for a plug. |
bindingState |
binding state of the plug or socket. Takes one of the following values: - PS_STATE_FREE - available for binding- PS_STATE_BINDING - requested binding to a peer socket or plug- PS_STATE_BOUND - bound to a peer socket or plug- PS_STATE_TARGET - a peer socket or plug has requested binding (or is bound to) this socket or plug- PS_STATE_FAILED - a binding request failed. This is a transient state used while cleaning up a failed binding attempt. Following clean-up, state returns to PS_STATE_FREE |
worldSpaceObject |
An Object3D representing the transform of this plug or socket in world space. This is maintained every frame via the socket component’s tick() method. |
peer |
The plug or socket that is being bound to. This refers to the world-space Object3D for the peer plug or socket. From this, the plug/socket entity can be accessed as peer.el , and its socket component as peer.el.components.socket |
adjustmentTransform |
An Object3D representing a proposed transform for this plug or socket, in order to bind to a peer socket or plug. This is a transform to be applied in world space. This is set prior to sending a binding-request event to the fabric. |
fabricAdjustmentTransform |
An Object3D made available by the socket component to it’s parent fabric, which uses it in computations. This is allocated by, and made available by, the socket component, as a convenience for the socket-fabric component. |
Binding events flow internally between plug/sockets entities and their parent socket fabric as follows. Binding is asymmetric, and happens in one direction, either plug to socket or socket to plug, depending on the “inertia” of their parent fabrics.
The binding plug or socket moves (or attempts to move), while the socket or plug that is bound-to remains stationary.
Event | Emitted by | Consumed by | Description |
---|---|---|---|
binding-request | plug/socket | fabric | Indicates that the plug (or socket) would like to bind to a corresponding socket (or plug). The event is emitted on the plug or socket that is binding (i.e. the one that will move). The event does not contain any additional detail. However prior to emitting the event, the plug or socket sets it’s adjustmentTransform property to indicate the adjustment required to achieve the proposed binding. |
binding-cancel | plug/socket | fabric | Indicates that any binding involving this plug or socket should be cleared. This can be emitted at any time, by either peer involved in a binding. Whereas binding-request may succeed or fail, a binding-cancel always takes effect immediately.!! MIGHT BE BETTER TO NAME THIS binding-clear ??? |
binding-success | fabric | plug/socket | This is emitted by the socket-fabric component, on the plug / socket entity as the binding-request . It indicates that requested binding has taken place successfully. This event should only be emitted once the socket or plug that requested the binding has been moved into the requested position. |
binding-failed | fabric | plug/socket | This is emitted by the socket-fabric component, on the plug / socket entity as the binding-request . It indicates that requested binding has not occurred, and will not occur. Typically this is because conflicting binding requests made to the socket fabric have been prioritized over this request. |
When the socket system matches a socket to a plug, inertia is used to determine which of them should move (in the case of a tie, the plug moves to meet the socket).
The socket
component offers a function, getInertia()
, which computes and returns the inertia of this socket.
It can be useful to break all bonds for a given socket-fabric
The socket-fabric
component provides a breakBonds()
function that does this.
(!! currently, this function is triggered automatically when a mouseGrab
event is received on the socket-fabric
entity - but this needs cleaning up!!)
Note that calling breakBonds()
, without moving the entity, will immediately result in a new set of binding requests, as the plugs and sockets that are aligned will detect each other, and trigger binding again. Viable approaches for breaking the bindings of a socket fabric include:
breakBonds()
snapStart
events, but unless that event is responded to with a snappedTo
event, the new binding requests won’t succeed.When operating in “events” mode, plug-socket
implements the Snap Position Interface, which allows it to be used with the dynamic-snap
component
It emits snapStart
and snapEnd
events, based on , and listens for the snappedTo
event generated by dynamic-snap
when an entity snaps to a position.
Via CDN
<script src="https://cdn.jsdelivr.net/npm/aframe-plug-socket@0.0.1/dist/plug-socket.min.js"></script>
Or via npm
npm install aframe-plug-socket
then in your code…
if (!AFRAME.components['socket']) require('aframe-plug-socket')
(the if
test avoids conflicts with other npm packages that may also use aframe-plug-socket
)
Examples to follow…
See also: tests
A basic set of QUnit Unit Tests exist, which can be found here: https://diarmidmackenzie.github.io/aframe-components/components/plug-socket/test/unit-tests.html
Currently these can only be run in the browser, not at the command line.
A brief list of some ways in which the plug/socket system might be developed in future:
The code used to efficiently identify the set of sockets in close proximity to a given plug is based on code originally developed for Virtonomy, re-used here with their kind permission.