math 部分的大头看完了,就生下这四个文件了。
Color
color文件竟然是最多的,竟然有近600行!
颜色在three中,还是用 r , g , b 来表示。本身的结构比较简单,但是会有无数操作颜色的方法。
首先有三个颜色转换的helper function:
hue2rgb
HSV转RGB过程中的helper function
function hue2rgb( p, q, t ) { // TODOif ( t < 0 ) t += 1;if ( t > 1 ) t -= 1;if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;if ( t < 1 / 2 ) return q;if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );return p;}
SRGBToLinear LinearToSRGB
sRGB和线性的RGB转化,wiki上有说相关的转化公式:https://en.wikipedia.org/wiki/SRGB
setHex getHex
setHex: function ( hex ) {hex = Math.floor( hex );this.r = ( hex >> 16 & 255 ) / 255;this.g = ( hex >> 8 & 255 ) / 255;this.b = ( hex & 255 ) / 255;return this;}
getHex: function () {return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;}
0xffd03f之类的16进制与数字相互转换的方法。
setHSL getHSL
setHSL: function ( h, s, l ) {// h,s,l ranges are in 0.0 - 1.0h = _Math.euclideanModulo( h, 1 );s = _Math.clamp( s, 0, 1 );l = _Math.clamp( l, 0, 1 );if ( s === 0 ) {this.r = this.g = this.b = l;} else {var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );var q = ( 2 * l ) - p;this.r = hue2rgb( q, p, h + 1 / 3 );this.g = hue2rgb( q, p, h );this.b = hue2rgb( q, p, h - 1 / 3 );}return this;}
getHSL: function ( target ) {// h,s,l ranges are in 0.0 - 1.0var r = this.r, g = this.g, b = this.b;var max = Math.max( r, g, b );var min = Math.min( r, g, b );var hue, saturation;var lightness = ( min + max ) / 2.0;if ( min === max ) {hue = 0;saturation = 0;} else {var delta = max - min;saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );switch ( max ) {case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;case g: hue = ( b - r ) / delta + 2; break;case b: hue = ( r - g ) / delta + 4; break;}hue /= 6;}target.h = hue;target.s = saturation;target.l = lightness;return target;}
HSLRGB相互转换,是一个比较流行的做法吧,虽然有点看不懂,也不想推导:
https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
copyLinearToGamma copyGammaToLinear
把gamma次幂的颜色和线性的颜色相互转换,需要对gamma次幂的颜色了解下:https://en.wikipedia.org/wiki/Gamma_correction
主要是一些颜色的概念和转化有需要了解的地法,像颜色的计算,插值这些相对比较清楚,就不作讨论,但是写出来还是有好几百行的代码。
Frustum
定义一个FOV范围,由六个平面定义。它本身是一个四棱台。
数据结构就是包含6个 Plane 的数组,比较清楚。
setFromMatrix
setFromMatrix: function ( m ) {var planes = this.planes;var me = m.elements;var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();return this;}
从矩阵生成,这应该是就是投影矩阵了,当然不止是正交投影和透视投影,可以支持任意投影生成对应的视锥平面。
从这里可以找到参考资料:http://www8.cs.umu.se/kurser/5DV051/HT12/lab/plane_extraction.pdf
plane_extraction.pdf
所以,从这个资料来看,这六个面,应该分别对应的是:right,left,top,bottom,far,near
containsPoint
依次判断6个面,看是不是在平面的正面,还是相对比较清楚的。
然后,从这个函数可以看出,这六个平面的法线都是向内的。(hh,从其它的实现反推,来推测原来的结构,而不是用已有的结构推测这个函数的实现,真的是太好笑了)
intersectsSphere
也比较直观,判断球心与六个面的距离,如果有距离>-r,那么就一定会相交。
不像box,frustum六个面都有,因此也就判断起来用边界的距离就好了。
intersectsBox
intersectsBox: function ( box ) {
var planes = this.planes;
for ( var i = 0; i < 6; i ++ ) {
var plane = planes[ i ];
// corner at max distance
_vector.x = plane.normal.x > 0 ? box.max.x : box.min.x;
_vector.y = plane.normal.y > 0 ? box.max.y : box.min.y;
_vector.z = plane.normal.z > 0 ? box.max.z : box.min.z;
if ( plane.distanceToPoint( _vector ) < 0 ) {
return false;
}
}
}
这里还是需要注意一下,也是用距离判断,但是box会比sphere麻烦一点,需要找到可能的最远的点(正方向上),如果最远的点都在平面反面,那么就可以做拒绝操作,这里也用到了之前看到的求最大距离的角点的方法,根据平面的法线从8个点中选出最远的点。
intersectsSprite
intersectsSprite: function ( sprite ) {
_sphere.center.set( 0, 0, 0 );
_sphere.radius = 0.7071067811865476;
_sphere.applyMatrix4( sprite.matrixWorld );
return this.intersectsSphere( _sphere );
}
关于sprite自己了解的不多,先看下文档:
A sprite is a plane that always faces towards the camera, generally with a partially transparent texture applied.
TODO:就,等看到它之后再说吧。
intersectObject
intersectsObject: function ( object ) {
var geometry = object.geometry;
if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
_sphere.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
return this.intersectsSphere( _sphere );
}
也是类似的操作,先得到包围球再做处理,这个,主要是对这个 matrixWorld 不了解,也是先留个TODO吧。
Ray
光线的表示相对还是直观的,由原点和方向表示。两个的默认值分别是 Vec3(0, 0, 0) 和 Vec3(0, 0, -1) 即z轴负方向(向屏幕内射去)。
lookAt
lookAt: function ( v ) {
this.direction.copy( v ).sub( this.origin ).normalize();
return this;
}
使光线打到某个点上,就是调整这个光线的方向。
recast(t)
按照光路向前走t个单位,设为新的原点。
closestPointToPoint
光线上距离某点最近的点,就是射线到点的距离,需要在直线距离基础上,考虑原点的因素。
distanceSqToSegment
一个ray到线段的最短距离,感觉,好复杂的样子,其也是参考了一个现成的实现,而没有找到对应的参考资料,难呐,感觉越来越多的看不明白的地方了。TODO
**
distanceToPlane
非常有歧义,射线与平面相交。如果不相交,return null,相交了,是原点到平面的距离。感觉,这样的设定很反直觉,应该是相交了,距离没有意义,不相交才有距离可言呀。
应该是自己理解错了,是原点沿方向到达平面的距离,除以那个denominator就是算的是这个距离。
intersectSphere
由于射线的原因,需要分情况讨论,这个画个图,分三种情况:
- 不相交
- 相交两点
- 相交一点
讨论就OK了
intersectPlane
有了 distanceToPlane 函数之后,也就比较容易了,直接原点加上距离就OK了。
intersectBox
依次判断6个面,有一些early rejection的操作,所以代码看起来不是很清楚。
参数表示体现出了方便。
intersectTriangle
实现过于复杂,放弃放弃噗噗噗TODO
Interpolant
插值!这里有一个模板类,然后有具体的实现,有一点OO的东西在里边呢,应该是接触到的Three中的第一次出现原型继承了,来瞧一瞧。
首先是一个典型的原型继承关系,以 Interpolant 和 LinearInterpolant 为例说明。
参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Examples
JS是原型继承的,所以,其实是子类和父类原型上的关联,这个有点绕就不阐述了。
父类 Interpolant :
function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
this.parameterPositions = parameterPositions;
// ...
}
就是一个普通的函数,然后,用 this.xxx = yyy 来设置属性。
然后处理父类的原型:
Object.assign( Interpolant.prototype, {
evaluate: function ( t ) {
// ...
},
settings: null,
// ...
}
原型上添加父类相关的方法和属性。通过 Object.assign() 这样的方式,可以多次附加相应的属性。
对于子类 LinearInterpolant :
function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
}
没有自己的属性,但是调用了父类的,相当于是constructor函数,设置相应的属性,因为传了this,所以属性会被设定再子类对象中。
子类的原型:
LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
constructor: LinearInterpolant,
interpolate_: function ( i1, t0, t, t1 ) {
// ...
}
用这种 Object.assign( Object.create( Interpolant.prototype ), {...} 的方式,算是对父类原型的扩充,也就是所谓的继承了。
比如,这里覆盖了父类的 interpolate_ 方法。
另外值得注意的一点是设定了 constructor 属性,因为用这种方式,constructor属性并不会像 new Function() 这样自动添加,因此,手动设置下比较好,不然会向上边找,constructor就会变成 Interpolant 而不是 LinearInterpolant 了。
好了,了解了架构,看看功能吧。
看了下文档,感觉一点都不友好,不像是可以直接让用户用的,直接用的应该是 core 里的那个interpolant,这个再源码内部的animation中用到了。
看不懂啊!
大大的TODO
