It is best practice to create a project folder outside of Hedron. This is advantageous, because:
- The project can exist as it's own repository
- You can install dependencies from NPM without polluting Hedron's own dependencies
Inside the project folder, you'll want to have a "sketches" folder, this is what you'll point Hedron to.
This directory contains sketch folders. Sketch folders can be grouped into directories to keep things neat, with as many levels of organisation as you need. However, you can't have a sketch folder inside another sketch folder.
Sketches live in the sketches directory. A sketch is itself a directory with two required files:
- config.js
- index.js
This is where the params and shots are defined as an object literal.
defaultTitle
- Initial title of the sketch when first added to a scene (can be renamed by user)category
- Can be set to help organise sketchesauthor
- Can be set to help organise sketchesparams
- Params are the values you have control over in a sketch. This setting is an array of objects, each with the following properties:key
- A unique string for the param (Required)title
- A human readable title for the param (defaults tokey
)valueType
- Can befloat
(default),boolean
orenum
. The look and behaviour of the param control will change depending on what type you are using dropdown forenum
, button forboolean
)defaultValue
- Default value for the param when the sketch is first loaded. If not set, the default depends on the value type.defaultMin
- Used withfloat
value type. If set, will interpolate the slider value to this minimum (defaults to 0)defaultMax
- Used withfloat
value type. If set, will interpolate the slider value to this maximum (defaults to 1)options
- Used withenum
value type. An array of objects with these properties:value
- The value of the param if this option is selected (Required)label
- A human readable label for the option (defaults tovalue
)
hidden
- Hides the param from the user. One example for using this would be that you might have some param in your sketch that is controlled by a shot, but not available to edit for the user. (Defaults tofalse
)
shots
- Shots are methods you have control of in your sketch. This setting is an array of objects, each with the following properties:method
- The name of the method to control in your sketchtitle
- A human readable name for the shot (defaults tomethod
)
Example of a config file:
module.exports = {
defaultTitle: 'Solid',
category: 'Simple',
author: 'Laurence Ipsum',
params: [
{
key: 'rotSpeedX',
defaultValue: 0,
title: 'Rotation Speed X',
valueType: 'float',
defaultMin: -1,
defaultMax: 1,
},
{
key: 'isWireframe',
title: 'Wireframe',
valueType: 'boolean',
defaultValue: true,
},
{
key: 'geomName',
title: 'Geometry',
valueType: 'enum',
defaultValue: 'icosa',
options: [
{
value: 'cube',
label: 'Cube',
},
{
value: 'tetra',
label: 'Tetra',
},
{
value: 'octa',
label: 'Octa',
},
{
value: 'icosa',
label: 'Icosa',
},
{
value: 'dodeca',
label: 'Dodeca',
},
],
},
],
shots: [
{
method: 'shapeShift',
title: 'Shape Shift',
}
]
}
This is the main file for the sketch. Essentially, it's a Javascript class, with some important properties and methods. You can require
other modules from here, so don't feel restricted to a single file. However, you should use the Hedron global version of THREE
and not import this yourself (window.HEDRON.dependencies
), see below.
root
- This should be set in the constructor as aTHREE.Group
. It is the top-level 3D object for the sketch. Hedron takes this root object and places it in the scene. Not required if your sketch is only for post processing (see below).
constructor
- Where the sketch setup happens. It has a single object literal as an argument, with the following properties:params
- A key/value pair of all the params in the sketchscene
- The three.js scene that this sketch is added tocamera
- The three.js camera the scene is usingrenderer
- The three.js renderersketchesDir
- The location of the top level sketches directory. Useful for loading in external assets.outputSize
- An object withwidth
andheight
properties that match the image output resolution
update
- This method is called every frame. It has a single object literal as an argument, with the following properties:params
- A key/value pair of all the params in the sketchelapsedTimeMs
- Elapsed time in milliseconds, since Hedron was startedelapsedFrames
- Elapsed frames since Hedron was started. This is an ideal value based on 60FPS, instead of actual frames that have been seen.deltaMs
- Milliseconds that have passed since the last update was fireddeltaFrame
- How many should have passed since the last update was fired. Ideally, this will always be at 1. If the program experiences some lag and the FPS drops below 60, this will be some number greater than 1. Use this to multiply with any incremental values to keep animation speeds consistenttick
- Raw number of updates that have actually been fired since Hedron startedallParams
- A key/value pair of all the sketches in the scene, aech with their own key/value pair of paramsoutputSize
- An object withwidth
andheight
properties that match the image output resolution
destructor
- Fires when a sketch is removed. Use this to clean up anything that may continue to run afterwards. Not usually needed. Has the same argument object as the construtor, except for theparams
property.
Any custom method in your sketch class can be defined as a shot in the config. Shot methods have a single object literal as an argument, with the following properties:
params
- A key/value pair of all the params in the sketch
Hedron provides a single global variable with some useful libraries to make use of.
window.HEDRON.dependencies
THREE
- It is strongly recommended you use this instance ofTHREE
and not your own installed package. Also are the following extras have been added to this property as child properties:GLTFLoader
OrbitControls
TWEEN
- A very simple tweening library. The update method is called interally in Hedron, so there's no need to do that inside of a sketch.postprocessing
- The post processing library Hedron usesglslify
- Module system for GLSL
Using this variable means that VS Code Intellisense won't work by default. See below on how to fix this.
This is the minimum you need to do in order to use Hedron.
// Hedron exposes three.js via the HEDRON global
// It is highly advised you use this and don't install your own version of THREE
const { THREE } = window.HEDRON.dependencies
// All sketches are a class
class MyFirstSketch {
constructor () {
// You must define a sketch root object
this.root = new THREE.Group()
// Create a cube, add it to the root of the scene
const mat = new THREE.MeshNormalMaterial()
const geom = new THREE.BoxGeometry(1, 1, 1)
this.cube = new THREE.Mesh(geom, mat)
this.root.add(this.cube)
}
update ({ params }) {
// params.rotSpeedX is a value that is controlled by the user,
// in order to change the rotation speed of the cube
this.cube.rotation.x += params.rotSpeedX
}
}
module.exports = MyFirstSketch
This example shows many more features of Hedron
// Hedron exposes three.js via the HEDRON global
// It is highly advised you use this and don't install your own version of THREE
const { THREE } = window.HEDRON.dependencies
class Solid {
constructor ({ scene, renderer, camera, params, meta }) {
// Sketches must set this root property so Hedron can place the sketch in the scene
// Everything should be added to this root
this.root = new THREE.Group()
// Empty object to be populated with meshes
this.meshes = {}
// Defining a single material for all the polyhedra
this.mat = new THREE.MeshBasicMaterial(
{ wireframe: true, color: 0xffffff }
)
const size = 1
// Array of geometries (the platonic solids!)
const geoms = {
cube: new THREE.BoxGeometry(size, size, size),
tetra: new THREE.TetrahedronGeometry(size),
octa: new THREE.OctahedronGeometry(size),
icosa: new THREE.IcosahedronGeometry(size),
dodeca: new THREE.DodecahedronGeometry(size),
}
// Keep an array of the geom names
this.geomNames = Object.keys(geoms)
// Loop through meshes
for (const geomName in geoms) {
// Create a mesh for each solid
const mesh = new THREE.Mesh(geoms[geomName], this.mat)
// Add to meshes object
this.meshes[geomName] = mesh
// Add to scene
this.root.add(mesh)
// Hide the mesh
mesh.visible = false
}
}
// The update method gets called every frame and passes in params defined in the config
update ({ params, deltaFrame }) {
// Solids spin too fast at 1
const baseSpeed = 0.15
// Using the rotSpeed params to affect the rotation of the group
// We multiply by deltaFrame to keep the rotation speed consistent
this.root.rotation.x += params.rotSpeedX * baseSpeed * deltaFrame
this.root.rotation.y += params.rotSpeedY * baseSpeed * deltaFrame
this.root.rotation.z += params.rotSpeedZ * baseSpeed * deltaFrame
// Change scale using params.scale
this.root.scale.set(params.scale, params.scale, params.scale)
// Change material wireframe option using boolean param
this.mat.wireframe = params.isWireframe
if (this.currGeomName !== params.geomName) {
if (this.currGeomName) this.meshes[this.currGeomName].visible = false
this.meshes[params.geomName].visible = true
this.currGeomName = params.geomName
}
}
// All non-special methods of the class are exposed as "shots", defined in the config.
// These are single functions that can fire rather than paramaters than slowly change.
randomGeom ({ params }) {
// Shot can be used to directly alter something in the sketch
// or as in this example, to manipulate the params
const i = Math.floor(Math.random() * this.geomNames.length)
const geomName = this.geomNames[i]
// In order to tell Hedron params have changed inside a shot, return the updated params
return { geomName }
}
/** HEDRON TIP **
Use the destructor method to do anything when the sketch is deleted
**/
destructor ({ scene, renderer, camera, params, meta }) {
console.log('Solid sketch deleted!')
}
}
/** HEDRON TIP **
Class must be exported as a default.
**/
module.exports = Solid
Custom post processing, such as pixel shaders, are handled inside of sketches. Hedron's post processing system is a thin wrapper around the postprocessing library, so it's a good idea to understand how that works. Multiple passes can be created inside of initiatePostProcessing
and returned as an array. initiatePostProcessing
has a similar argument object to the class constructor (see above), except renderer
is replaced with composer
(for experimental usage).
All values are updated with the usual update
method.
Please note that this feature is still very much under development and so will most likely see many changes to the API in future.
Below is a simple example of how to achieve a bloom effect. A config file is also needed, which is exactly the same as a normal sketch config file.
// THe postprocessing library is available under the HEDRON global variable
const { EffectPass, BloomEffect, BlendFunction, KernelSize } = window.HEDRON.dependencies.postprocessing
class Bloom {
initiatePostProcessing () {
// Create a bloom effect
this.bloomEffect = new BloomEffect({
blendFunction: BlendFunction.SCREEN,
kernelSize: KernelSize.LARGE,
useLuminanceFilter: true,
luminanceThreshold: 0.825,
luminanceSmoothing: 0.075,
height: 480,
})
// Create your passes and return as an array
const pass = new EffectPass(null, this.bloomEffect)
return [ pass ]
}
update ({ params }) {
this.bloomEffect.blurPass.scale = params.scale
this.bloomEffect.luminanceMaterial.threshold = params.lumThreshold
this.bloomEffect.luminanceMaterial.smoothing = params.lumSmoothing
this.bloomEffect.blendMode.opacity.value = params.opacity
}
}
module.exports = Bloom
There are plenty of other examples that can be found in the example sketches folder.
If you have the "Watch sketches" setting enabled, Hedron will automatically refresh your sketches. However, if you don't have this enabled or something went wrong with the file watch (e.g. your sketch imports a file outside of its own folder) you'll need to click "Reload File" to see changes made to sketch files.
This refresh will remove the sketch from the scene, import any new params or shots, remove and old params and shots, and then add the new sketch back into the scene.
Please note: File change detection may not work with all text editors. (e.g. Atom on OSX is reported to be inconsistent).
Using the HEDRON
global variable is recommended, but it does come back with a drawback by default. VS Code Intellisense (e.g. autocomplete, paramater info) won't work with these libraries. To remedy this, Hedron exposes these dependencies, which means they can be imported with the right config files. In the root of your project folder, you'll need jsconfig.json
and globals.d.ts
. Below is information on these, examples can also be found in the example-projects
directory.
{
"compilerOptions": {},
"exclude": ["node_modules", "**/node_modules/*"]
}
This file does not need to be edited and can be uses as above.
import { dependencies } from 'path/to/hedron/src/electron/renderer/globalVars'
export as namespace HEDRON;
export { dependencies }
The import path in this file will need updating, depending on your operating system and if you are using a compiled version of Hedron or the source code. Below are some examples (which you may still need to edit to your needs).
Installed (Windows):
C:/Users/alex/AppData/Local/Programs/Hedron/resources/static/globalVars
Installed (OSX):
/Applications/Hedron.app/Contents/Resources/static/globalVars
From source:
/path/to/hedron/src/electron/renderer/globalVars
You can get extra functionality by adding dev.config.js
to /config
(from the root directory of the Hedron repo).
// config/dev.config.js
module.exports = {
defaultProject: false
}
Setting defaultProject
to the path of a saved project (e.g. /Users/alex/Desktop/foo.json
) can help improve your workflow when developing by automatically loading that project when the app compiles. This is particularly useful when developing Hedron itself, so that you can test changes made to the app immediately, without having to manually load in a project each time.