new FLine()
An FLine
is a container for geometry. You can construct an FLine
with new FLine()
:
stage.name = myLine
stage.add(myLine)
stage
is a place to add FLine
once they’ve been made. If you add an FLine
to the stage that’s already there it gets updated. If you fail to add an FLine
to a stage, you’ll never see it.
fline.moveTo(x,y,z=0)
Adds a ‘moveTo’ instruction to a line — this moves the ‘pen’ without drawing. z
defaults to 0
if you don’t include it. .moveTo
can also consume vectors: fline.moveTo(vec(1,2,3) + vec(3,4,5))
. Instructions can be chained together thus: fline.moveTo(0,0,0).lineTo(1,1,1)
.
fline.lineTo(x,y,z=0)
Adds a ‘lineTo’ instruction to a line — this moves the ‘pen’ without drawing. z
defaults to 0
if you don’t include it. .lineTo
can also consume vectors: fline.lineTo(vec(1,2,3) + vec(3,4,5))
. Instructions can be chained together thus: fline.moveTo(0,0,0).lineTo(1,1,1)
.
fline.cubicTo(cx1, cy1, cz1, cx2, cy2, cz2, x,y,z)
Draws a cubic spline curve to a new position using control points c1
and c2
. Vector3
and Vector2
are also accepted. Returns this FLine so you can chain calls together.
fline.filled = false
Controls whether an FLine
is filled in (that is: turned into solid shapes) when it is drawn. Defaults to false
.
fline.stroked = true
Controls whether an FLine
is filled in (that is: turned into solid shapes) when it is drawn. Defaults to true
.
fline.color = vec(1,0,0.5, 0.25)
Sets the color of a line. Colors in Field are Vector4
, that is vec(Red, Green, Blue, Opacity)
with each component ranging from 0.0 (black / transparent) to 1.0 (white / opaque). This is an additive color space. You can also find a good additive color picker built into OS X:
fline.fillColor = vec(1,0,0.5, 0.25)
Sets the color of a fill of a line (which otherwise defaults to fline.color
. Colors in Field are Vector4
, that is vec(Red, Green, Blue, Opacity)
with each component ranging from 0.0 (black / transparent) to 1.0 (white / opaque). This is an additive color space.
fline.strokeColor = vec(1,0,0.5, 0.25)
Sets the color of a stroke of a line (which otherwise defaults to fline.color
. Colors in Field are Vector4
, that is vec(Red, Green, Blue, Opacity)
with each component ranging from 0.0 (black / transparent) to 1.0 (white / opaque). This is an additive color space.
fline.byTransforming( (x,y,z) => vec(y,x,z*x) )
Returns a new, independent FLine
that has had all of it’s positions transformed by the supplied function.
fline + vec(1,0,0)
Return a new FLine
translated by the added vector.
fline - vec(1,0,0)
Return a new FLine
translated by the subtracted vector.
fline * vec(1,0,0)
, fline * 0.2
Return a new FLine
scaled by the added vector or scalar
fline * rotate3(vec(0,1,0), 0.2)
Return a new FLine
rotated by the supplied transform.
fline.fill -> Mesh
After a line has been added to stage
the fill
property contains the Three.js Mesh object that holds the geometry. You can use this to do all kinds of advanced Three.js tricks.
fline.stroke -> Mesh
After a line has been added to stage
the stroke
property contains the Three.js Mesh object that holds the line segment geometry of the outline. You can use this to do all kinds of advanced Three.js tricks.
fline.mouseDown.myhandler = (event) => {}
FLine
can have mouseDown
handlers associated with them:
This means that you can make buttons that do things:
This will send a button that changes color when you ‘click’ it and scurries slightly to the right.
print("hello world")
Print sends something back from the web-browser back into the text editor (typically at the bottom or near where you executed something from). If print
gets a complex JavaScript object it will try really hard to format it correctly in a way that you can explore using the mouse. Sometimes, if the object is too long or complex it will give up. When this works this is a great way to investigate complex Three.js objects.
For browsers where print
is hardwired into a request to actually print the page, you can use log
instead.
clearAll()
Removes everything Field related from the scene.
clearAFrame()
Removes everything A-Frame related from the scene.
_.banana = 10
You can set properties that are conceptually ‘inside’ boxes using the _
symbol. For example _.banana=10
sets banana
equal to 10
. This is a lot like var banana = 10
however properties set on _
can be seen by boxes that are ‘downstream’ of this box. That is: they have arrows coming from this box. You have already seen one example of this. Our AR / in-web-browser boxes are ‘downstream’ of our web-server box.
__.banana = 10
You can make properties globally available by using double underscore __
. Once __.banana=10
has been set somewhere, then _.banana==10
in all boxes everywhere.
_.begin('peach')
[syntax might change]This is equivalent to ‘beginning’ a box called ‘peach’. That is, option clicking on it. You can use this to trigger boxes in callbacks for example.
_.end('peach')
[syntax might change]This stops a box called ‘peach’.
launch( function*, name)
Starts a generator function running. This is a little like if you inserted a _r
into a new box, but of course doesn’t require you to make a new box and feed all of the variables you need for it to run into a new place. By far the most common use for launch
is actually inside over
— making little animations that smooth over changes.
But, by way of example, this code here launches a little function that animates the opacity of a line f
:
The key difference here is the yield keyword and the asterix after the function
declaration. This makes a different kind of function one that, when it’s called again, execution picks up from after the most recent yield
rather than starting again at the top. Now we have a function that runs 100 times and then ends, smoothly animating the opacity of f
. An easier way to do this code though is to use over
.
The name
parameter requires some explaination — only one launched function with a particular name can be running in a box at a time. This helps prevent the case where your code starts spawning long running launched functions that all stomp over each other in strange ways. But, if you want to run two at a time, give them different names.
over(seconds, name, callback)
Calls callback(a)
with a
moving from 0 to 1 (inclusive) over the next seconds
. For example, the example given for launch
above is better written as:
mapDirectory("/Users/marc/Desktop", "/assets")
The mapDirectory
function exposes some part of your desktop computer (the machine running Field) to devices connected to our webserver. For example, if we had a file on our desktop called ‘fantastic.gltf’ then this works:
On Windows you might want something like:
loadFBX(url, name [, callback])
Loads an FBX file stored on the server from url
and adds it to the scene. After the fbx file is loaded an object called "name"
is filled in with name.animations
as a list of data in the file and name.objects
representing all of the loaded geometries. This object also gives you animation control over the material that’s loaded. Furthermore the root node of the object tree you load will be called ‘name’.
You can call:
Since almost nobody remembers to wait for the model to load (and it’s a pain to architect your code such that this happens) there’s an alternative syntax using a callback
. Think of this a little like mouseDown
and friends - a way of arranging for some code to be called later.
If you don’t mind a little more syntax the above code is much clearer about making sure that things are executed at the right time.
loadJSONScene(url, loaded)
Loads an JSON Scene file (exported from the editor) stored on the server from url
and adds it to the scene. After the json scene file is loaded an object called "loaded"
is filled in with loaded.animations
as a list of data in the file and loaded.objects
representing all of the loaded geometries.
You can call:
loadJSONScene(url, loaded)
Loads an GLTF Scene file stored on the server from url
and adds it to the scene. After the gltf file is loaded an object called "loaded"
is filled in with loaded.animations
as a list of data in the file and loaded.objects
representing all of the loaded geometries.
You can call:
findByName(name)
Finds the first Three.js object in the Scene called ‘name’. Case sensitive, ‘Scene’ is not the same as an object called ‘sceNE’.
findBySearchingFor(pattern)
Finds the first Three.js object in the Scene with a name containing ‘pattern’. E.g findBySearchingFor('ear
) matches an object called Pear
addPointLight(name)
Adds a point light to the scene (called “name”). You can move this around with position
, e.g:
Asking for completion (ctrl-period) on light.
will give you all kinds of interesting things to set (the color of the light for example…)
addVideoPlane(url, name)
Adds a plane with video on it into the scene called ‘name’ (replacing anything else called ‘name’ that’s already there). The video is preloaded, but not started. On iOS video and sound can only play as a result of user interaction. Therefore, currently, you need something like:
addVideoPlane
returns an object containing mesh
, video
and material
which refer to the Three.js and HTML5 video objects. It also contains a mouseDown
array which you can use to wrong code when people press on the plane containing the video (see the example for planes below).
Multiple video playback on iOS is supported only on videos that don’t contain sound (or, better yet, explicitly marked as muted=true
).
Combining this with some judicious use of __.
and _.
gives you something like an editing system inside Field.
For example, in one box we’ll have:
That box is going to load a video and put it on a plane in space.
If we connect that box to another:
Then that box can access _.v
:
addImagePlane(url, name)
Very similar to addVideoPlane
above (but lacking the play
and pause
) functionality. Also includes mouseDown
:
addBox(name, size=1)
Adds a box to the scene of a certain size. For when you just need to mark a spot in space. It returns something very similar to addImagePlane
and addVideoPlane
. For example, to set the position of the box:
Field installs some handy uniform event handlers so you can run code when certain events happen. Information about the event is passed to your code as an argument. Note event handlers only fire when there’s nothing selected in the editor.
Event handlers are combined using the same kind of style as the FLine
stage
:
mouseDown.myHandler = (event) => { ... }
dblClick.myHandler = (event) => { ... }
mouseUp.myHandler = (event) => { ... }
mouseMove.myHandler = (event) => { ... }
Called when the mouse (or finder) is pressed, “double-clicked”, released or moved. As well as globally you can also find more focused even handlers on video planes, image planes and FLine
instances. Note that dblClick
can’t go back in time and prevent the first mouseDown
from having happened.
event
contains:
A Vector2(x,y)
ranging from -1.0 to 1.0 from left to right, bottom to top.
The position of the camera
The direction of the mouse click / finger from the camera position.
If this isn’t undefined
, this is the position in world space where the event hits a AR plane
This is a list of Three.js objects that this mouseDown / touch intersects. The list contains elements like:
getCurrentCamera()
Just like the name suggests, this function returns all of the information that you need to know about the current ‘camera’
e.g:
Use this inside your callbacks and _r
to control things based on where the camera is.
For example:
Yields:
The following code gives you an event callback that’s fired when your phone sees an image in the world for the first time:
You have to turn it on in the settings page of the iOS app: “reset world sensing” and “always enable world sensing” must both be on. You’ll have to do this dance every time the IP address of your laptop/desktop changes. This is, again, a “security” feature — your camera is no longer merely building a fairly anonymous dot-map of the world, now it is actively hunting for images; hence the new escalation.
createImageAnchor(name, url, width, callback)
Creates an image anchor (overwriting any other with name name
) from an image stored at url
. Width is in meters (and it’s very important, it will overwrite the phone’s idea of how far way from the screen the image is). The callback is supplied with a scene-graph node anchored at the image.
updateWorldMapPoints()
Requests that Field updates its copy of the world map. This operates asynchronously, getWorldMapPoints()
won’t always immediately change, because the operation takes a while (~50ms). Once it finishes, if there even is a world map (it doesn’t exist early on in the AR ‘startup ritual’), a new copy of the world map will end up stored locally.
worldMapPoints
worldMapPoints
is simply an array-of-arrays representing the points in your phone’s current model of the world. e.g
Spatial audio sources are created using makeSource(name, url)
— like video, images and 3d models you’ll have to ‘map’ part of your computer to the outside world using mapDirectory
.
For example:
mysource = makeSource(name, url)
Creates a sound source, returning an object that lets you control playback, position, and range of a source as well a lets you gain access to analysis information concerning the source.
mySource.play()
mySource.pause()
mySource.stop()
mySource.volume()
Returns the instantaneous volume of a source (when measured at the source) — in dB.
mySource.waveformData()
Returns an array (typically, but always, 1024 elements long) consisting of the instantaneous raw waveform of the sound.
mySource.frequencyData()
Returns an array (typically, but always, 1024 elements long) consisting of the instantaneous raw frequency analysis of the sound (in some-kind-of-dB)
For example, the following draws a live spectrum of a sound
Field’s web rendering engine is actually a wrapper around Three.js — strictly, the live-coding, running, animation, variables, linear-algebra and FLine
part is Field and the actually drawing things is Three.js. Often parts of Three.js show through, and this is deliberate. This offers a trapdoor, away from Field into a bigger, broader more complex graphics system. We (or you) can wrap these things up in tighter, simpler functions if they end up being useful.
Trapdoors include fline.fill
and fline.stroke
which return THREE.Mesh
objects, addVideoPlane( ... )
and addImagePlane( ... )
functions that return objects that contain mesh
, material
etc. and the findByX( ... )
series of methods which return whatever THREE.Object3d
happens to match that name. Sometimes when somebody asks how to do some certain rendering thing the answer really ought to be, look at this Three.js code. This section compiles these miscellanous recipes.
Missing a texture map on an object (perhaps because your FBX file isn’t quite loading textures correctly):
Remember: that’s a URL not a desktop path — it goes through the mapDirectory
logic.
We can extend this syntax to other image parts of the material. This is how you alpha mask a piece of geometry — useful for videos!
For a video plane v
:
As you know Object3D
s exist in a scene-graph / hierarchy. To remove parts of the scene call .remove
on their parent:
Removes “Lamp” from it’s parent, and thus from the scene itself.
Remember the Ah Ha video? The key there is that there’s a magic window through which a 3d scene is visible. This can be easily achieved using ‘stenciling’ by this inscrutable late 1980s low-level code. This code breaks through not only the layer between Field and Three.js, but the layer between Three.js and WebGL, the lower-level graphics library used by web-browsers for hardware accelerated drawing.
Gives us:
We can understand this code by thinking about a hidden ‘stencil’ image that goes along with the image that we see. In the first function the plane ALWAYS
draws into this stencil image the value ‘1’ (it starts off all zeros); in the third function we only draw if the image is EQUAL
to 1
. The second and fourth functions simply return your graphics card to the state it found it as a curtesy to anything else trying to get drawn. You can extend this to multiple stenciling and stenciled objects if you like.