This project is being imported as submodule in several other repositories. The hierarchy works as a NodeJS module.
Graphical Programming with ThreeJS
- Main classes for graphics pipeline
- WebGL project using ThreeJS, HTML5 and OOJS (object oriented javasctipt) for exploring several computer-graphics techniques:
- Geometry and Normals calculation for complex models
- UV coordinates calculation
- Surface Smoothing by using Vertices Normals
- Lighting and Shadows
- Skybox and reflections
- User Interface (sliders, toggles, buttons)
- Finite State Machine to handle "shooting robot"
- Collision detection using AABB
- Particles System with Nebula-Threejs
- three-global.js
- It is used to create a global object
THREEand add functionalities to it- Imports
three.jslibrary as module from npm - Names it
THREE(following the nomenclature used in other modules) - Exports the object
THREEfrom this script / module to be imported into the rest (ex: in OrbitControls.js) - In OrbitControls.js, or other scripts, new functionality is added to
THREEobject
- Imports
- It is used to create a global object
- Graphical Programming with Threejs libgptjs
- The classes at
libgptjs/are importingthree-global.js - Wrapper / Library to facilitate re-use of code and organize the graphics pipeline
- It contains several objects (classes) for wrapping all the logic required for creating an scene with threejs
- This allows modularity and we can reuse code creating instances of those clases
- The classes at
- GPT_Coords
- Gets vertices (Float32Array) and edges array (Uint32Array)
- Calculates the normal vector for each triangle
- Provides a method for calculating the UV coordinates for each triangle
- GPT_Model
- Simple class to integrate mesh + geometry + material
- Provides method for cleaning gl buffers that were reserved
- GPT_LinkedModel
- Model formed of joining several
THREE.Object3Din order to create articulated models like robot arms - Provides method for adding a new link between two Object3D and finally linking all of them in sequence
- Model formed of joining several
- GPT_ModelCollider
- Attaches an AABB (axis aligned bounding box) to an existign Mesh
- Provides a method for detecting collision with another AABB
- GPT_Scene
- List of
GPT_Models andGPT_Light - Provides abstract methods for initial configuration and updates in every frame
- These methods have to be overriden when creating the instance of the
GPT_Scene
- These methods have to be overriden when creating the instance of the
- Provides methods for adding and removing models at runtime
- List of
- GPT_Render
- It initializes the camera and camera-handler
- This is the main object that creates a
webgl-rendererand invokes methods ofGPT_Scene
- GPT_App
- Top-level object that configures the
windowand usesGPT_Render - It contains the main loop for animation in which the
updateandrenderare being invoked
- Top-level object that configures the
- Common.js
- Contains all constants to be re-used in several points in the code
- CoordsDragon.js
- Stores arrays of dragon model (vertices and edges)
- Since it inherits from
GPT_Coordsit provides methods for computing normals and UVs coordinates
- CoordsGripper.js
- Stores arrays of gripper model (vertices and edges)
- Since it inherits from
GPT_Coordsit provides methods for computing normals and UVs coordinates
- ModelSkybox.js
- Creates a big cube and maps the texture to simulate environment
- These skybox images will be reflected on the Dragon surface and Gripper surface
- ModelDragon.js
- Inherits from
GPT_Modeland overridesget_geometryandget_materialmethods - Creates and initializes
geometryandmaterialobjects to be inserted into amesh - Computes
UVcoordinates per face (triangle) in order to simulate reflections of the skybox onto the dragon surface - Contains a
GPT_ModelCollider
- Inherits from
- ModelGripper.js
- Idem to ModelDragon
- ModelRobot.js
- Inherits from
GPT_LinkedModel - Creates separately the parts of the robot (base, arm, forearm, hand and gripper). Then links them all in sequence
- Inherits from
- ModelTrajectory.js
- Given 2 initial points to be used as direction vector
- It computes the control points (
p1, p2, p3, peak and end) to be used later into the spline points calculation - Control points form a triangle with one of the edges following the
p1andp2directionPeakpoint is in the middle of triangle and is the highest pointEndpoint is on the floor
- Spline points are calculated using the control points and
catmullromwith N (30) segments - Final spline points are used to create line geometry to be rendered
- It computes the control points (
- Given 2 initial points to be used as direction vector
- ModelBullet.js
- Creates the geometry, material, mesh, and GPT_ModelCollider
- Needs a trajectory and a starting point3D
- Provides a method for moving the bullet between 2 consecutive points3D of the trajectory based on time passed since last frame
- InputManager.js
- Checks if it is running on mobile device or desktop
- Creates the UI (sliders, toggles, etc.) and installs the
onChangecallbacks to be executed when a value is updated by the user - Creates html button for "shoot" and attaches the corresponding callback
- FSM_Robot.js
- Defines a finite state machine for robot shooter
- Defines
States,EventsandTransitions - Defines Transitions as a dictionary of allowed state-event pairs
- Provides methods for
transitingfrom one state to other depending on the "Event" - Provides method for
updatingthe current state based on timers expiration
- SceneDragon.js
- Contains the handling of main interactions: InputManager, animation (update) of objects, etc.
- Inherits from
GPT_Sceneand overridescreateObjects,createLights,updateObjectsandupdateLightsmethods - Performs all setting up of models and lights: floor, dragon, skybox, robot, trajectory, etc.
- Performs periodic updates of models and lights: translate, rotate, destroy and create new trajectory, etc.
- Contains a method where actions are triggered depending on the change of state of
FSM_Robot- Any change of state is reflected into the UI
Idle- Rotate Dragon, update AABB
loading_bullet- Rotate shooting arm
- Increase power while user keeps clicking and update UI slider
- When power increased set shooting arm to red color
bullet_traveling- Draw the trajectory
- Move the bullet along the trajectory
- Rotate while traveling
hit- Blink dragon to red
- Stop bullet at collision point
- Limits the reaction to the incoming "shoot events" by checking if current robot state is
idle
- A triangle is the basic polygone
- It is formed of 3 vertices, each vertex has 3 components float (x, y, z)
- Its vertices are defined clockwise by default. This is taken into account when computing the face (triangle) normal vector
- A
geometryin threejs is formed of several arraysposition- It is a
Float32Arrayin which all the coordinates of all vertices of all triangles are packed together - Array lenght is
3 * num vertices - Example forming the first 2 triangles
CoordsGripper.prototype.getArrayVertices = function () { return new Float32Array([ 0, 0, 0, 0, 20, 0, 19, 20, 0, 19, 0, 0, 0, 20, 4, 0, 0, 4,
itemSize3 because there are 3 components per vertexModelDragon.prototype.get_geometry = function () { const _geom = new THREE.BufferGeometry(); _geom.setAttribute( "position", new THREE.BufferAttribute(this.coords.vertices_coordinates, 3) );
- It is a
normal- It is a
Float32Arraycontaining all the normal vectors for all triangles - Array lenght is
3 * num triangles - It is computed at GPT_Coords
calculateNormals - Idem to
positions
- It is a
indices- It is a
UInt32Arraycontaining all the sequence of indices (ofpositionsarray) to form triangles - Example forming the first 2 triangles
CoordsGripper.prototype.getArrayEdges = function () { return new Uint32Array([ 2, 0, 1, 3, 0, 2,
itemSize1 because there are 1 component per vertex-index_geom.setIndex(new THREE.BufferAttribute(this.coords.edges_indices, 1));
- It is a
uv- It is a
Float32Arraycontaining the UV coordinates for all vertices of all triangles - Each vertex will have 2 UV components (texture coordinates)
- UV coordinate values are in range [0.0, 1.0]
- Array lenght is
6 * num triangles - It is computed at GPT_Coords.js
getUVs itemSize2 because each vertex has 2 UV componets_geom.setAttribute( "uv", new THREE.BufferAttribute(uvs, 2) );
- It is a
- A
Mesh Phong Materialin threejs is needed to define the rendering of the geometryModelDragon.prototype.get_material = function () { // loading TextureCube as skybox const _mat = new THREE.MeshPhongMaterial( { color: 0xe5ffe5, emissive: 0xb4ef3e, flatShading: true, // initially per-triangle normals specular: 0x003300, shininess: 70, side: THREE.FrontSide, transparent: true, opacity: 0.75, envMap: Common.SKYBOX_CUBE_TEXTURE } );
- A mesh in threejs is formed of a geometry and a material
this.mesh = new THREE.Mesh(this.geometry, this.material);
GPT_Coords.js getUVs
- Calculates UV for planar surface (x, y, z) where z = 0
- Depends on geometry bounding box
- Computes the UV values for each face (triangle)
- Stores UV coordinates for each triangle (6 float components)
- First you need to have per-face (triangle) normals
- GPT_Coords.js
calculateNormals- Creates
points3Darray by grouping 3 values frompositionsarray - Creates triagles array by grouping 3 values from
points3Darray - Computes normals for each triangle clockwise
- v1 = p2 - p1
- v2 = p3 - p2
- cross_product(v1, v2)
- Applies modulus
- Stores normal (3 float components)
- Creates
- GPT_Coords.js
- Then you can invoke
computeVertexNormalsin order to make the transition between faces (triangles) smoother when computing the lightingSceneDragon.prototype.createDragon = function () { // pre-calculated for surface smoothing this.dragon_model.geometry.computeVertexNormals();
- You must update the material to be
smooth shading(flatShading= false)_cbs.on_change_dragon_smoothing = (new_val_) => { const _dragon = this.gpt_models.get("dragon"); // boolean if (new_val_) { _dragon.material.flatShading = false; } else { _dragon.material.flatShading = true; } _dragon.material.needsUpdate = true; };
SceneDragon.js createLights
- Creates an
ambient lightthat will be added when shading the models surface- 5% white
- It doesn't need a position into the Scene
- Creates a
point light- 75% white
- Emits in all directions
- Creates a
directional light- 75% white
- Emits only in the direction vector provided (-200, 200, 0)
- Creates a
focal light- 75% white
- Emits light in a cone volume
- Direction of the central lighting vector is pointing to the center of the floor (0,0,0)
- Defines
angleanddistanceto make afading lightingfrom the center of the cone to the exterior - Defines the cone like shape by defining parameters of shador:
near,farandfov
- ModelSkyBox.js
- Creates a
BoxGeometry - Attaches a texture (material) per face
- The texture images must be specifically for a cube texture
- They need to be mapped properly ordered
posx, negx, posy, negy, posz, negz
- Makes the inner of the box visible instead of the outside
ModelSkybox.prototype.get_material = function () { ... _cubeFacesMaterials.push( new THREE.MeshBasicMaterial({ map: _loader.load(_img_path), color: 0xffffff, // white side: THREE.BackSide // inside the cube }) ...
- Creates a
- Reflections on ModelDragon.js
- Sets
transparent,opacity, andshininesto simulate "glass" - Sets the
Skybox Textures ArrayasenvMap
ModelDragon.prototype.get_material = function () { const _mat = new THREE.MeshPhongMaterial( { color: 0xe5ffe5, emissive: 0xb4ef3e, flatShading: true, // initially per-triangle normals specular: 0x003300, shininess: 70, side: THREE.FrontSide, transparent: true, opacity: 0.75, envMap: Common.SKYBOX_CUBE_TEXTURE } );
- Sets
- Idem for hand of the robot ModelGripper.js
- It creates a
dat.guiobject- dat.gui assumes the GUI type based on the target's initial value type:
- boolean:
checkbox - int / float:
slider - string:
text input - function:
button
- boolean:
- When user updates a value with the UI we store the new value in
effectvariables
- dat.gui assumes the GUI type based on the target's initial value type:
- It attaches the corresponding
onChangecallbacks to be executed when a new value is set using the UI - Saves references to UI controllers, so we can reflect updates on the UI
- Reflects text of
robot_state - Reflects value of
robot_power
- Reflects text of
- Creates a custom html button for
shoot, which is separated from the rest of the panel for better usability - Creates a
statswidget at the bottom of the canvas container. This reflects the frames per second
- Defines
States,Eventsand allowedTransitions - A transition is defined as
destination stategiven a pairState-Event - Updates the current state based on the expiration of timers
IDLE--> shoot -->LOADING_BULLETLOADING_BULLET--> timer.expired -->BULLET_TRAVELINGBULLET_TRAVELING--> collision -->HITBULLET_TRAVELING--> timer.expired -->NO_HITHIT/NO_HIT--> timer.expired -->IDLE
- Main concept can be read at link
- Attaches an AABB (axis aligned bounding box)
- Updates the dimensions of the AABB at runtime
- Checks if intersects with other AABB (collided)
- Follow tutorial at three-nebula.org
- Nebula is a particle system engine that works with threejs
- It provides an editor to create manually and save to json file
- Adapted manually for our SceneDragon scale:
{ "type": "Radius", "properties": { "width": 20, "height": 80, "isEnabled": true }, { "type": "RadialVelocity", "properties": { "radius": 400, "x": 0, "y": 0, "z": 1, "theta": 10, "isEnabled": true } - The rest of values (color, sprite, life cycle, etc.) were edited using the Nebula editor (windows)
- Loads the particle system from json file and creates an instance of
nebulathat will be used to renderNebula.SpriteRendererneeds the mainTHREE.Scene
- Provides a method for updating the
dragon fire particlesaccording to dragon mouth positionDragonFire.update_to_dragon_mouthperforms a sequence of translations and rotations to place / update properly the particles emitter while dragon is rotating