FourJS is a library that adds a physics engine (CannonJS) and component functionality to ThreeJS and streamlines the workflow with the framework.
Note: These docs assume basic familiarity with ThreeJS and javascript.
Note: FourJS is a work in progress.
A component is a class that is connected to a ThreeJS object and executes code every frame (and whan the object is activated/deactivated).
For example, a component could be a movement system that handles player input and moves an object accordingly.
The beauty of a component system is its' scalability. once you write a component once, you can add it to how many objects you want.
In addition, it provites abstraction, as an object can have multiple components where every component is in charge of a spacific niche in the behavior of the object.
component related methods and attributes:
FourJS sits on top of ThreeJS. That means that you can choose when to extend a ThreeJS obejct with FourJS functionality.
in order to extend a ThreeJS object with FourJS functionality you use the Four.include(object) method.
This method adds (in runtime) all the quality of life methods that FourJS provides, such as the component system, to the object.
Note: all the following methods only work if you previously called Four.include with the ThreeJS object in question.
This method starts the "frame cycle", the frame loop that calls each component every frame. Usualy called once all the objects were set up with the correct components.
You pass all the objects that are needed in order to render things to the screen (these objects should be created prior to calling this function using the ThreeJS methods).
You can think of this method like a "play button" - it starts the actual core frame loop of the program.
This method is called from the object with the class name of the component you're instrested in adding (Not an instance of it). You can also pass additional arguments the are passed to the component constructor.
For example: box.addComponent(Physics) would add the Physics component to the box object.
Once this method is called the component can run code whenever it wants (though usualy it is every frame/on program start).
After you added a component to an object you can access its' properties with Object.CompName.
Usualy most of a compnents' "settings" are under this attributes object.
A proparty that dictates whether a component can run code. If a component is not active it is as if it doesn't exist. You shuold generaly prefer to deactivate a component over deleting it.
When a component is deactivated it can execute code to prep its' deacivation.
returns a copy (by value) of the component list of an objects' component list.
removes a component from an object by its' index in the component list.
Gets a component class name and returns True or False depending on whether the object has a component of that type.
gets a component class name and returns the first component found of that type.
gets a component class name and returns a list of all components found of that type. If none are found returns an empty list.
returns a component by its' index in the component list.
A component should be a class that extends the Four.Component class.
Every component has a few "keyword" functions it can use in order to execute code in cirtain times.
These functions are:
These method runs immediately after the component is added to an object (thus, usualy before the frame cycle has bagun), you can look at it as a constructor.
It should be used to create proparties and prepare the structure of the component for frame cycle.
This method runs once when the frame cycle has begun.
Use it to initialize the component and do all oparations that are needed to be done before any frames are rendered.
This method runs every frame. Usualy the "main" method of the component. Use can use it to change the object or collect information or anything else that is needed to be done on a frame-by-frame basis.
This method is called once every constant amound of time (you can access and change this amound of time with Four.FixedUpdateTimeStep).
You should use this method for calculations and oparations that should not be framerate dependent (for example moving an object, since if that would be framerate dependent the object would move faster for people who have faster machines, and thus higher framerates).
If you wanna implament a spacial case of this method for a specific component with a custom timestep, we suggest using setInterval(this.lazyUpdate.bind(this), timestep). that would create a lazyUpdate method with a custom timestep for that specific component.
some builtin proparties and methods we provide are:
An array proparty that should be used for all the "settings" of the component. For example if the component moves an object, an attribute could be movement speed.
should the user be allowed to add multiple insatnces of this component to a single object.
An event fired when the object is activated. You can subscribe to it with this.activated.add(this.customOnActivatedMethod, this).
An event fired when the object is deactivated. You can subscribe to it with this.deactivated.add(this.customOnDeactivatedMethod, this).
In addition, we provite a quality of life methods such as:
A QOL method that can be used when you want to add to the atttributes array a subset of a diffrent settings object. The subset argument is an array/set that spacifies the proparties the subset obejct will have.
It returns a proxy that redirects to the original settings object for the "allowed" proparties from the original.
In order for the Physics system to work, you first need to create an object with the PhysicsManager component. We suggest not thinking much of it and implamenting it as such (note that this workflow might change in the future):
const manager = new THREE.Object3D();
FOUR.include(manager);
manager.addComponent(PhysicsManager);
The PhysicsManager is important since it handles the Physics calls every frame. In addition, it provites an attribute that can help you make your program more efficient:
This is an attribute you can use if you want you physics to only be calculated in a certain scope. For example if an object gets far from the camera, you might not want its' physics to be calculated anymore. Thus, you can set the bindingObj as the camera, and if an object gets to a certain range away from it, its' physics will no longer be calculated.
You can set the range of the bindingObj with physicsMangerObject.PhysicsManger.attributes.scope.range.
You can add Physics to an object with Object.addComponent(Physics).
currently, you have these attributes in the physics component:
sets the mass of the object.
The type of physics calculations; Dynamic/Kinematic/Static.
You can read more about the diffrent types in the cannonJS docs here: https://pmndrs.github.io/cannon-es/docs
Example of setting a type:
import { Body } from '../base_components/Physics.js';
object.Physics.attributes.type = Body.STATIC;
This attribute provides full access to the CannonJS methods of manipulating objects with physics. You can use the the CannonJS docs (https://pmndrs.github.io/cannon-es/docs) in order to find out more about the different ways of doing so. Our physics component will make sure the CannonJS position and data are always synced with the ThreeJS position and data.