This is the second article of Three.js source code reading notes, let’s start directly.
Core::Object3D
Object3D seems to be the most important class in the Three.js framework. Quite a few other classes inherit from the Object3D class, such as scene classes, geometry classes, and cameras. Class, lighting class, etc.: They are all objects in 3D space, so they are called Object3D classes. The Object3D constructor is as follows:
THREE.Object3D = function () {
THREE.Object3DLibrary.push( this );
this.id = THREE.Object3DIdCount;
this.name = '';
this.properties = {};
this.parent = undefined;
this.children = [];
this.up = new THREE.Vector3( 0, 1, 0 );
this.position = new THREE.Vector3();
this .rotation = new THREE.Vector3();
this.eulerOrder = THREE.Object3D.defaultEulerOrder;
this.scale = new THREE.Vector3( 1, 1, 1 );
this.renderDepth = null ;
this.rotationAutoUpdate = true;
this.matrix = new THREE.Matrix4();
this.matrixWorld = new THREE.Matrix4();
this.matrixRotationWorld = new THREE.Matrix4( );
this.matrixAutoUpdate = true;
this.matrixWorldNeedsUpdate = true;
this.quaternion = new THREE.Quaternion();
this.useQuaternion = false;
this.boundRadius = 0.0;
this.boundRadiusScale = 1.0;
this.visible = true;
this.castShadow = false;
this.receiveShadow = false;
this.frustumCulled = true;
this._vector = new THREE.Vector3();
};
Before introducing the function, we need to introduce several important attributes of this class.
Description of attributes parent and children, it is usually necessary to use a tree to manage many Object3D objects. For example, a moving car is an Object3D object, and the logic that controls the car's driving route is implemented inside the object. Each vertex of the car is located in the correct position after being processed by the model matrix; but the wiper blade of the car swings not only with the It moves in the direction of the car, and it also swings left and right relative to the car. The logic of this swing cannot be implemented inside the object of the car. The solution is to set the wiper to the chidren of the car, and the logic inside the wiper is only responsible for its swing relative to the car. Under this tree structure, a scene Scene is actually the top Object3D, and its model matrix is the inverse matrix of the view matrix (depending on the camera).
The attributes matrix and matrixWorld are easy to understand. Matrix represents the local model matrix, which only represents the movement of the object, while matrixWorld needs to iterate to the parent node in sequence. Each iteration is left multiplied by the local value of the parent object. Model matrix, up to the Scene object - of course, actually left multiplied by the global model matrix of the parent object.
The attributes position, rotation, and scale represent the three transformation parts of the model matrix, which are explained in the Matrix4 class. rotation and eulerOrder jointly describe a rotation state, and quaternion can also describe a rotation state. The specific method to use depends on the Boolean value of useQuation.
You can see that the most important "transformation state" information about the Object3D object is actually stored in two "backups", one is the matrix object, and the other is the position and other attributes, two parts It should be consistent, if one backup is changed by some method, the other backup should also be updated in due course. There are some other properties whose meanings can be seen literally and type-wise, and are not listed separately. Let’s talk about the function below:
The function applyMatrix(matrix) multiplies the parameter matrix to the left by this.matrix. It actually performs a certain transformation on the Object3D object (this transformation may require several basic transformation steps, but it has been stored in parameter matrix). Note that after performing left multiplication on this.matrix, the values of parameters such as position are immediately updated. Compared with the following transformation functions, this function is more "advanced" and allows developers to freely specify the transformation matrix instead of saying "advance 5 units toward the x-axis."
applyMatrix: function ( matrix ) {
this. matrix.multiply( matrix, this.matrix );
this.scale.getScaleFromMatrix( this.matrix );
var mat = new THREE.Matrix4().extractRotation( this.matrix );
this. rotation.setEulerFromRotationMatrix( mat, this.eulerOrder );
this.position.getPositionFromMatrix( this.matrix );
},
The function translate(distance, axis) makes the object move distance distance in the direction specified by the axis axis. The functions translateX(distance), translateY(distance), and translateZ(distance) make it advance distance distance to the X, Y, and Z axes. Note that these functions only change the value of the position object, not the value of the matrix.
translate: function ( distance, axis ) {
this.matrix.rotateAxis( axis );
this.position.addSelf( axis.multiplyScalar( distance ) );
},
translateX: function ( distance ) {
this.translate( distance, this._vector.set( 1, 0, 0 ) );
},
The function localToWorld(vector) converts local coordinates into world coordinates, while the function worldToLocal does the opposite. Note that the local coordinates of the vector here refer to the coordinates before transformation, that is, the vertex coordinates of the wiper's default position.
The function lookAt(eye,center,up) executes the lookAt function of its matrix attribute object (as introduced before, the matrix4 object also has a lookAt function), which is generally used for camera objects. This function only changes the rotation state, so when the matrix attribute object is executed, if the attribute rotationAutoUpdate is true, the value of rotation or quaternion will be updated, which one is updated depends on the attribute useQuation.
The function add(object) and function remove(object) add a sub-object from the current Object3D object, or delete a sub-object. It is easy to understand that many Object3D objects in the scene are managed using trees. .
The function traverse(callback) traverses the caller and all descendants of the caller. The callback parameter is a function. The callee and each descendant object call callback(this).
traverse: function ( callback ) {
callback( this );
for ( var i = 0, l = this.children.length; i < l; i ) {
this.children[ i ].traverse( callback );
}
},
The function getChildByName(name, recursive) queries the caller's child element (recursive is false) or descendant element (recursive is true) through a string for an object that matches the attribute name and returns it.
The function getDescendants(array) pushes all the descendant objects of the caller into the array array.
The functions updateMatrix() and updateMatrixWorld(force) will update matrix and matrixWorld according to the position, rotation or quaternion, scale parameters. updateMatrixWorld also updates the matrixWorld of all descendant elements if the force value is true or the caller's own matrixWorldNeedsUpdate value is true. In the function applyMatrix(matrix), the position, rotation and other attributes are updated immediately after changing the matrix value, but in the function translate(distance, axis), the position and other variables are changed (or the position and other attributes are directly changed) and are not immediately updated. To update the matrix value, updateMatrix() should be called manually. These details are worth noting. You may think that you should add event listeners. Once one value changes, all others will be updated immediately, but I think it may be due to this consideration: updating at the appropriate time will bring higher results. Efficiency - for example, the rotation value may be changed frequently, but the matrix attribute is only updated before using it.
updateMatrix: function () {
this.matrix .setPosition( this.position );
if ( this.useQuaternion === false ) {
this.matrix.setRotationFromEuler( this.rotation, this.eulerOrder );
} else {
this .matrix.setRotationFromQuaternion( this.quaternion );
}
if ( this.scale.x !== 1 || this.scale.y !== 1 || this.scale.z !== 1 ) {
this.matrix.scale( this.scale );
this.boundRadiusScale = Math.max( this.scale.x, Math.max( this.scale.y, this.scale.z ) ) ;
}
this.matrixWorldNeedsUpdate = true;
},
updateMatrixWorld: function ( force ) {
if ( this.matrixAutoUpdate === true ) this.updateMatrix();
if ( this.matrixWorldNeedsUpdate === true || force === true ) {
if ( this.parent === undefined ) {
this.matrixWorld.copy( this.matrix );
} else {
this.matrixWorld.multiply( this.parent.matrixWorld, this.matrix );
}
this.matrixWorldNeedsUpdate = false;
force = true;
}
for ( var i = 0, l = this.children.length; i < l; i ) {
this.children[ i ].updateMatrixWorld( force );
}
},
The function deallocate manually releases the space occupied by the caller when the object is no longer needed.
Core::Projectors
A class that manages the projection matrix. The code is too complicated. I guess it will involve operations in the render class. Let’s look at it at the appropriate time.
Core::UV This constructor generates a material coordinate class - the coordinates on the material, which often correspond to the vertices. After rasterization, each pixel has a material coordinate. Then "pick color" from the material to achieve texture.
THREE.UV = function ( u, v ) {
this.u = u || 0;
this.v = v || 0;
};
The material coordinate class is a simplified vector2 class, except for the attribute name Just different.
Core::Ray Core::Rectangle Core:Spline Ray class, with origin, direction, far and near cutoff points. There should be applications in point light sources. Rectangular and curved types are relatively simple and not so "core". Let's look at them later.
Core::Geometry The Geometry class is also a very important class, representing a geometric shape composed of vertices and surfaces.
THREE.Geometry = function () {
THREE .GeometryLibrary.push( this );
this.id = THREE.GeometryIdCount;
this.name = '';
this.vertices = [];
this.colors = [];
this.normals = [];
this.faces = [];
this.faceUvs = [[]];
this.faceVertexUvs = [[]];
this.morphTargets = [];
this.morphColors = [];
this.morphNormals = [];
this.skinWeights = [];
this.skinIndices = [];
this.lineDistances = [];
this.boundingBox = null;
this.boundingSphere = null;
this.hasTangents = false;
this.dynamic = true;
this.verticesNeedUpdate = false;
this.elementsNeedUpdate = false;
this.uvsNeedUpdate = false;
this.normalsNeedUpdate = false;
this.tangentsNeedUpdate = false;
this.colorsNeedUpdate = false; false;
this.buffersNeedUpdate = false;
};
The following two sets of attributes are the most important
: The attribute vertics is an array, each element It is an object of vector3 type, representing a vertex coordinate. The attributes colors and normals represent the color values and discovery vectors corresponding to the vertices. They are only used in rare cases. In most cases, the colors and discovery vectors of the vertices are defined in the "surface" - if the 6 sides of the cube The colors are different, so each vertex is actually a different color on different faces.
The attribute faces is an array, and each element is an object of face4 or face3 type. When we introduced face3 before, we mentioned that face only stores the index value of the vertex, and the index value can be used in the array vertices. Get the coordinate value of the vertex.
The following is the function
: applyMatrix(matrix) function updates all vertex coordinates in the geometry and the normal vector of the surface. What it does is actually use the transformation matrix matrix to apply the geometry to the geometry. The shape undergoes spatial transformation. normalMatrix is the inverse transpose matrix of the 3×3 matrix in the upper left corner of the parameter matrix. This matrix is used to rotate the vector (normals, not vertex coordinates).
applyMatrix: function ( matrix ) {
var normalMatrix = new THREE.Matrix3();
normalMatrix.getInverse( matrix ).transpose();
for ( var i = 0, il = this.vertices.length; i < il; i ) {
var vertex = this.vertices[ i ];
matrix.multiplyVector3( vertex );
}
for ( var i = 0, il = this.faces.length; i < il; i ) {
var face = this.faces[ i ];
normalMatrix.multiplyVector3( face.normal ).normalize();
for ( var j = 0, jl = face.vertexNormals.length; j < ; jl; j ) {
normalMatrix.multiplyVector3( face.vertexNormals[ j ] ).normalize();
}
matrix.multiplyVector3( face.centroid );
}
},
The function ComputeCentroid() calculates the center of gravity of each surface in the geometry (not the center of gravity of the geometry itself). It seems that this function should be better placed on the prototype of the face class, but since the coordinates of the point cannot be obtained internally in the face class (unless a reference to the point coordinate array is passed into the constructor as a parameter, which is very costly), it is just Index value, so it has to be defined on the prototype of the geometry class. The following functions are in similar situations (in fact, the face class has almost no member functions).
The functions computeFaceNormals() and computeVertexNormals(areaWeight) calculate the normal vector. The former affects the normal attribute of each element in the face array, and there is only one face; the latter affects the vertexNormal of each element in the face array. Attributes, a face3 type object has 3, and a face4 type object has 4. However, it should be noted that a vertex shared by multiple surfaces has only one normal vector and is affected by multiple surfaces at the same time. For example, for a cube with the center at the origin and three sets of surfaces perpendicular to the axis, the normal vector of the vertex in the first quadrant is the normalization of (1,1,1). Although it seems incredible that the normals of the vertices of the plane are not perpendicular to the plane, this method of specifying normals has a good effect when using a plane to simulate a curved surface.
The function createMorphNormal creates normals for each morph. Morph should be used to display the deformation effect of fixed continuous animation.
The function mergeVertics removes points with the same coordinate values and updates the point index value in the face object.
Core::Quaternian The four-dimensional rotation class expresses a rotation transformation in another way. Compared with using rotation, it can avoid the gimbal deadlock problem.
THREE.Quaternion = function( x, y, z, w ) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = ( w != = undefined ) ? w : 1;
};
If you don’t talk about functions, Quaternian is a simple vector4 type object.
The function setFromEuler(v,order) sets a four-dimensional rotation through an Euler rotation.
The function setFromAxis(axis,angle) sets a four-dimensional rotation by rotating around any axis.
The function setFromRotationMatrix(matrix) sets the four-dimensional rotation through the rotation matrix.
There are also some functions that are the same as the vector4 class and are not listed here.