深入理解three.js对svg的支持(三):svg转three对象

前言:SVG作为一种优秀的矢量图形格式在Web得到广泛应用,three.js作为知名的WebGL库自然也对其提供了支持。然而,官方文档中对此的说明十分单薄,网上与此相关的资源也不多。经过多次试验之后,在此分享我的一点理解,包括SVGLoader,SVGObject,SVGRenderer,svg和THREE对象的互相转化等内容。

3 svg图像转为THREE对象

难道就没有办法实现如引擎般顺滑地操作svg元素了么!查找和学习相关资源的时间都够自己写出来了——不,作为一个坚定的伸手党,不要退缩,手套会掉,坚决不自己写!我们要注重路上的风景!(一口齁咸的鸡汤下肚)

在第一部分中,载入的svg终究是svg文件,如果能够转为THREE的原生对象该多好啊,任何熟悉的THREE操作都可以用上了。在上一部分中我们见识到了SVGRenderer三维对象转svg的功能,本部分要做的事与它恰恰相反:将svg转为THREE.Shape对象。

3.1 svg到THREE形状

果然有人实现了这个功能1,《ThreeJS开发指南》这本书里也提到过这个,网上也有个人将之进行改进做了案例2,但案例的展示效果太烂,不仔细啥也看不见……

最原始的当然是1中的代码,作者自己写了一个svg到THREE.Shape,进而通过extrude拉伸成体的功能。不过这个代码已经非常obselete,用的很多THREE的API都过时了,但还是值得一看,只是作者实在是……所有的东西全都写得耦合到一起了。

为了更加方便操作,我们当然希望能够输入svg的path,输出THREE.Shape。于是需要对其进行修改(解码相关代码没有修改,不再赘述,有兴趣的阅读d3-threeD源码):

function d3threeD(exports) {
    ...

    exports.transformSVGPath =
    function transformSVGPath(pathStr) {

        //这里为了区分孔洞,没有采用源码中的THREE.Shape,而是用了ShapePath
        var path = new THREE.ShapePath();

        ... init variables

        //decode svg path strings
        function eatNum() {...}
        function nextIsNum() {...}

        var canRepeat;
        activeCmd = pathStr[0];
        while (idx <= len) {
            canRepeat = true;
            switch (activeCmd) {...}

            if (canRepeat && nextIsNum())
                continue;
            activeCmd = pathStr[idx++];
        }

        return path;//returns a single path
    }
}

var $d3g = {};
d3threeD($d3g);

3.2 形状的采样和连线

得到THREE.ShapePath之后,先通过其.toShapes方法转成THREE.Shape,然后便可以采样、连线转化为THREE.Line,在WebGLRenderer中进行渲染显示了,在主程序的初始化函数中调用addGeoObject即可。这一部分的代码参考了官方案例(这个案例好赞啊,全活了)。

var addGeoObject = function( group, svgObject ) {
    var i,j, len, len1;
    var path, mesh, amount, simpleShapes, simpleShape, shape3d, x, toAdd, results = [];
    var thePaths = svgObject.paths;

    len = thePaths.length;
    for (i = 0; i < len; ++i) {
        path = $d3g.transformSVGPath( thePaths[i] );

        //必须都是true,否则孔洞或外轮廓会丢失
        simpleShapes = path.toShapes(true,true);    
        //simpleShapes = path;                  

        len1 = simpleShapes.length;
        for (j = 0; j < len1; ++j) {
            simpleShape = simpleShapes[j];
            simpleShape.autoClose = true;

//==========
//如果关注带填充颜色的图案,则应该使用THREE.ShapeBufferGeometry(详见上文提到的官方案例)创建几何体。本文关注轮廓
//==========

            //获取点序列的采样方法来自虚基类CurvePath,默认参数12段
            //P.S样条曲线也是这样采样连线显示的(官方文档搜splinecurve)
            var points = simpleShape.createPointsGeometry();
            //var geometry = new THREE.ShapeGeometry(simpleShape);
            var line = new THREE.Line( points, new THREE.LineBasicMaterial( { color: 0xff0000, linewidth: 1 } ) );
            group.add(line);
        }
    }
};

3.3 转化后的变换

现在svg是转化成Object3D了,终于可以使用熟悉的变换——基准从svg图像的左上角变为几何中心。但是没有想象的那么简单!

svg转化为Object3D,必然涉及到坐标系的问题。那么对于这个Object3D对象,其局部坐标系和世界坐标有什么关系呢?这里将世界坐标和此对象的局部坐标都画出来。

不过要注意的由于DX一些无法抗拒的因素,Windows下的WebGLRenderer不支持linewidth线宽设置,因此为了区分世界坐标和局部坐标,这里将渲染器换成了CanvasRenderer,如下图(换成CanvasRenderer需要额外包含CanvasRenderer.js和Projector.js)

深入理解three.js对svg的支持(三):svg转three对象_第1张图片

可以看到,局部坐标和世界坐标是重合的,svg原图中的x,y方向和三维场景是世界坐标x,y方向相同。等等,说好的局部坐标原点在物体中心呢?这样变换不就又成svg相对左上角的机制了吗!费了老鼻子劲结果……

深入理解three.js对svg的支持(三):svg转three对象_第2张图片

不行,要改anchor!至少要设置到图案的中心。然而threejs不支持设定变换的anchor,例如Object3D旋转方法的基准只能是局部坐标系,即使通过rotateOnAxis指定了轴线方向,也是过局部原点的基准轴——这样一来,想要绕局部原点外的轴旋转就没有原生方法支持了。想要实现这种变换,有三种办法:

  • 使用矩阵.applyMarix方法)。没必要细说,不懂的童鞋去补习下计算机图形学基础。毕竟矩阵爸爸,还有什么实现不了的?但是这种方法需要你先计算,且可读性不好。
  • 通过子物体实现。将目标作为子物体,通过调整在父物体坐标系中的位置,使子物体变换的基准参考(如某个棱,某个顶点)和父物体的局部坐标原点重合。变换父物体,就可以实现子物体的变换。限于篇幅不再具体说明,详见3。
  • Geometry上做文章4。将Geometry的变换和Mesh的变换分开,对Geometry的变换相当于变了anchor,这样Mesh的原点就不是几何中心了。如果又要使用常规的变换,再把Geometry变换回来即可。此方法和第二种方法类似,可见Geometry和Mesh的关系有一点点类似子物体和父物体的关系。

本例采用了第二种方法,因为所有的LINE都在一个group里。先通过Box3获取group的包围盒,然后对group的每个子物体平移。代码如下:

var box;

...

box = new THREE.Box3();
box.setFromObject(group);

//set anchor to geometry center
for(var i=0;i

深入理解three.js对svg的支持(三):svg转three对象_第3张图片

由于BoxHelper只能绘图,而不能获取到Box3实体,因此只能新建一个Box3对象来获取包围盒。需要注意的是这个包围盒更新变换没问题,但获取必须是静态的,因为Box3的包围盒是AABB包围盒而不是OBB包围盒,因此不应该动态获取,否则做循环变换后是无法回到原位的。

这样,svg的anchor就到几何中心了,转为常规变换,done。

3.4 参考资料

1 https://github.com/asutherland/d3-threeD/blob/master/lib/d3-threeD.js
2 https://codepen.io/januff/pen/avajMa
3 http://blog.csdn.net/srk19960903/article/details/68925004
4 http://rwoodley.org/?p=1073

你可能感兴趣的:(webgl,svg,three-js,Web)