math 部分的大头看完了,就生下这四个文件了。

Color

color文件竟然是最多的,竟然有近600行!

颜色在three中,还是用 r , g , b 来表示。本身的结构比较简单,但是会有无数操作颜色的方法。

首先有三个颜色转换的helper function:

hue2rgb
HSV转RGB过程中的helper function

  1. function hue2rgb( p, q, t ) { // TODO
  2. if ( t < 0 ) t += 1;
  3. if ( t > 1 ) t -= 1;
  4. if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
  5. if ( t < 1 / 2 ) return q;
  6. if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
  7. return p;
  8. }

SRGBToLinear LinearToSRGB
sRGB和线性的RGB转化,wiki上有说相关的转化公式:https://en.wikipedia.org/wiki/SRGB

setHex getHex

  1. setHex: function ( hex ) {
  2. hex = Math.floor( hex );
  3. this.r = ( hex >> 16 & 255 ) / 255;
  4. this.g = ( hex >> 8 & 255 ) / 255;
  5. this.b = ( hex & 255 ) / 255;
  6. return this;
  7. }
  1. getHex: function () {
  2. return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
  3. }

0xffd03f之类的16进制与数字相互转换的方法。

setHSL getHSL

  1. setHSL: function ( h, s, l ) {
  2. // h,s,l ranges are in 0.0 - 1.0
  3. h = _Math.euclideanModulo( h, 1 );
  4. s = _Math.clamp( s, 0, 1 );
  5. l = _Math.clamp( l, 0, 1 );
  6. if ( s === 0 ) {
  7. this.r = this.g = this.b = l;
  8. } else {
  9. var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
  10. var q = ( 2 * l ) - p;
  11. this.r = hue2rgb( q, p, h + 1 / 3 );
  12. this.g = hue2rgb( q, p, h );
  13. this.b = hue2rgb( q, p, h - 1 / 3 );
  14. }
  15. return this;
  16. }
  1. getHSL: function ( target ) {
  2. // h,s,l ranges are in 0.0 - 1.0
  3. var r = this.r, g = this.g, b = this.b;
  4. var max = Math.max( r, g, b );
  5. var min = Math.min( r, g, b );
  6. var hue, saturation;
  7. var lightness = ( min + max ) / 2.0;
  8. if ( min === max ) {
  9. hue = 0;
  10. saturation = 0;
  11. } else {
  12. var delta = max - min;
  13. saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
  14. switch ( max ) {
  15. case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
  16. case g: hue = ( b - r ) / delta + 2; break;
  17. case b: hue = ( r - g ) / delta + 4; break;
  18. }
  19. hue /= 6;
  20. }
  21. target.h = hue;
  22. target.s = saturation;
  23. target.l = lightness;
  24. return target;
  25. }

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

  1. setFromMatrix: function ( m ) {
  2. var planes = this.planes;
  3. var me = m.elements;
  4. var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];
  5. var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];
  6. var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];
  7. var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];
  8. planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
  9. planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
  10. planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
  11. planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
  12. planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
  13. planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
  14. return this;
  15. }

从矩阵生成,这应该是就是投影矩阵了,当然不止是正交投影和透视投影,可以支持任意投影生成对应的视锥平面。

从这里可以找到参考资料: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

由于射线的原因,需要分情况讨论,这个画个图,分三种情况:

  1. 不相交
  2. 相交两点
  3. 相交一点

讨论就OK了

intersectPlane

有了 distanceToPlane 函数之后,也就比较容易了,直接原点加上距离就OK了。

intersectBox

依次判断6个面,有一些early rejection的操作,所以代码看起来不是很清楚。
参数表示体现出了方便。

intersectTriangle

实现过于复杂,放弃放弃噗噗噗TODO

Interpolant

插值!这里有一个模板类,然后有具体的实现,有一点OO的东西在里边呢,应该是接触到的Three中的第一次出现原型继承了,来瞧一瞧。

首先是一个典型的原型继承关系,以 InterpolantLinearInterpolant 为例说明。
参考: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