HT for Web 3D 手册

索引


概述

底层引擎

HT提供了基于WebGL3D技术的图形组件ht.graph3d.Graph3dViewWebGL基于OpenGL ES 2.0图形接口,因此WebGL属于底层的图形API接口, 二次开发还是有很高的门槛,HTGraph3dView组件通过对WebGL底层技术的封装,与HT其他组件一样, 基于HT统一的DataModel数据模型来驱动图形显示,极大降低了3D图形技术开发的门槛,在熟悉HT数据模型基础上, 一般程序员只需要1个小时的学习即可上手3D图形开发。

建模设计器

同时HT提供了强大的完全基于HTML5技术3D图形建模设计器,用户无需编码即可快速可视化搭建各种3D场景, 可以说HT3D开发模式完全打破了传统3D开发模式,绝大部分应用不再需要依赖精通3ds MaxMaya的专业3D设计师来建模, 也不需要整合Unity3d等引擎做图形渲染,HT一站式的提供了从建模到渲染,包括和2D组件呈现和数据融合的一站式解决方案。

浏览器支持

WebGL技术已被大部分最新浏览器支持,caniuse.com/webgl网站维护着最新桌面和移动浏览器对WebGL支持情况。 通过get.webgl.org可检测访问的浏览器是否支持WebGL

目前ChromeFirefoxSafariOpera的桌面也移动终端版本都已经支持WebGL标准, 采用iOS系统需要iOS8及以上版本,采用IE浏览器须IE11及以上版本才支持WebGL, 不管选择哪种类型浏览器,我们建议尽量采用最新版本。

如果你一定要使用IE6IE7IE8等老版IE浏览器,或者因采用HT for Web 3D,而老机器无法升级到IE11,则可以考虑安装 Google Chrome Frame插件, 在页面嵌入以下Tag代码片段,该页面就会采用Chrome来渲染。

<meta http-equiv="X-UA-Compatible" content="chrome=1">

使用Google Chrome Frame还需要注意一下几点:

可采用嵌入OBJECT元素的解决方案,绕开Google Chrome Frame不支持iframe的问题

<OBJECT ID="ChromeFrame" WIDTH=500 HEIGHT=500 CODEBASE="http://yourdomain/yourproject/"
        CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A">
    <PARAM NAME="src" VALUE="http://yourdomain/yourproject/">
    <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=500 NAME="ChromeFrame"
        SRC="http://yourdomain/yourproject/" TYPE="application/chromeframe">
    </embed>
</OBJECT>

Google Chrome Frame将于20141月停止支持和更新,目前Google Chrome Frame以发展到31的版本, 这个版本已满足HT2D3D所需的Canvas功能,因此HT的客户可以放心采用Google Chrome Frame解决老IE问题。 其他问题可参考Google Chrome FrameDeveloper GuideTroubleshooting


3D基础

3D坐标系

HT的三维坐标系由xyz三个轴线构成,x轴正方向朝右,y轴正方向朝上,z轴正方向朝向屏幕外。 HT系统的旋转采用右手螺旋法则

2D坐标关系

Graph3dView3D坐标系与GraphView2D坐标系既有关联又有差异,2D坐标系的x轴与3D坐标系的x轴对应, 2D坐标系的y轴与3D坐标系的z轴对应,2D坐标系的xy屏幕面相当于3D坐标系的xz面。

例如Node#getPosition()返回{x: 100, y: 200},则代表:

例如Node#getSize()返回{width: 300, height: 400},则代表:

3D坐标函数

3D坐标系的y轴则是与2D坐标系没有关联的新轴,ht.Node上增加了getElevation()setElevation(elevation)函数, 控制Node图元中心位置所在3D坐标系的y轴位置。同时增加了getTall()setTall(tall)函数,控制Node图元在y轴的长度。

为了避免2D3D坐标系的混乱,以及设置3D图元位置大小的方便,HTht.Node图元增加了以下新函数:

3D旋转函数

ht.Node2D坐标系下由getRotation()setRotation(rotation)函数控制旋转,该参数对应于3D坐标系下沿y轴的负旋转值。 同时3D坐标系下增加了rotationXrotationZ两个分别沿着x轴和z轴的新旋转变量,同时增加以下新函数:

对于旋转还有个很重要的参数rotationMode,该参数通过getRotationMode()|setRotationMode('xzy')进行设置, 旋转的先后顺序会影响最终效果,该参数用于指定旋转的先后顺序,默认值为xzy,用户可通过设置以下六种参数改变旋转顺序模式:

3D简写函数

HT对常用函数有不少简写方式,例如getDataModel()|dm()getSelectionModel()|sm()等,同样3D也有不少便捷的简写函数:

3D投影

3D投影是一种将三维空间的点映射到二维平面的算法, 既3D空间的内容投影到2D屏幕坐标的过程,不同的投影算法会最终产生不同的屏幕内容显示效果, HT支持透视投影Perspective Projection 和正交投影Orthographic Projection这两种最主常用的投影算法。 Graph3dView组件默认采用透视投影,通过Graph3dView#setOrtho(true)可切换到正交投影。

透视投影

透视投影是为了获得接近真实三维物体的视觉效果而在二维的纸或者画布平面上绘图或者渲染的一种方法,它也称为透视图。 透视使得远的对象变小,近的对象变大,平行线会出现先交等更更接近人眼观察的视觉效果。

如上图所示,透视投影最终显示到屏幕上的内容只有截头锥体( View Frustum )部分的内容, 因此Graph3dView提供了eye, center, up, farnearfovyaspect参数来控制截头锥体的具体范围:

nearfar虽可根据实际场景任意调节,但建议在可接受的范围内,尽量让nearfar越接近越好, 有助于避免Z-fighting的精度问题。

正交投影

正交投影也叫正交视图,在这种投影方式下,不管远近物体看起来都是同样大小,屏幕成像让人感觉与人眼观察效果不一样。 正交投影在建模过程很有用,它提供了对场景更“技术”的视觉,让它易于绘制和判断比例。

正交投影具有和透视投影一样的大部分参数,但没有fovy参数,取而代之的是orthoWidth参数:


3D模型

HT提供了多种基础形体类型供用户建模使用,不同于传统的3D建模方式,HT的建模核心都是基于API的接口方式, 通过HT预定义的图元类型和参数接口,进行设置达到三维模型的构建,以下章节将介绍预定义的3D模型类型及设置参数, 另外可参考建模手册OBJ手册介绍的更多自定义扩展模型方式。

六面体

六面体是由六个面形成的立方体,是HT系统中最常被使用的基本图元类型,默认当构建一个ht.Node对象时显示的就是一个六面体, 六个面的整体参数可以通过style属性的all.*控制,例如all.color设置为red,那么所有六个面默认颜色都会变成红色, 如果需要具体面独立设置,则可以通过left.*|right.*|top.*|bottom.*|front.*|back.*具体面参数控制, 例如left.color设置为blue,那么左侧面将显示为红色,如果left.color设置为空,则HT会采用all.color的值。

默认情况下贴图会将整个图片填充满对应的面,但很多情况下贴图需要偏移一定位置,有时地板砖需要Tile方式平铺,有时贴图需要翻转, 甚至有时贴图的布局需要动态变化以达到流动等特效,这种情况下就需要定制UV Mapping, 通过uv参数告诉HT如何将图片根据需要自定义贴到面上:

shape3d

HTGraphView2D图形上,呈现各种图形是通过styleshape属性决定,类似的HT3D上提供了shape3d属性, 预定义了多种3D的形体。shape3d的默认值为undefined,图元显示为上章节介绍的六面立体图形,当shape3d指定值时, 则显示为shape3d指定的形体,其余的具体参数通过shape3d.*进行设置:

3d文字

HT可以通过加载对应的typeface字体,并指定nodeshape3d类型为text,来呈现3D文字。

呈现3D字体首先需要一个json格式的typeface字体,具体可使用网站facetype.js来生成,下载生成完毕的字体(json格式),并通过ht.Default.loadFontFace来加载字体到内存中。

文字具体的控制参数包含有:

ht.Edge

ht.EdgeGraphView的拓扑组件中作为连接节点的连线是重要的图元类型,同样在Graph3dView组件中依然具备2D连线的展示功能, 并且实现空间的三维连线效果,同时也增加了些针对3D的连线参数,连线在三维空间呈现默认是非立体方式, 通过设置stylecylinder可呈现为管状的立体线效果,构建连线的常用参数说明如下:

edge.typepoints类型时,设置到edge.points的拐点参数兼容2D{x: 100, y: 100}JSON格式, 同时3D上引入新的参数e代表elevation的海拔高度,因此3D支持{x: 10, y: 20, e: 30}的拐点参数格式, 此拐点代表在x10y30z20的位置点。

stylecylinder时连线呈现立体管线效果,这时候控制连线显示效果的参数都在shape3d.*上, 同时增加了repeat.uv.length参数,该参数默认为空,如果设置了长度值,则贴图会根据连线长度自动调节连线方向上的贴图倍数。

参见形状手册的空间管线章节

ht.Shape

参见形状手册

其他参数

整体

文字

2Dlabel.*文字属性对于3D依然试用,3D也支持内置的第二个label2.*文字,同时增加了以下针对3D空间摆放以及呈现特性的属性参数:

标注

2Dnote.*标注属性对于3D依然试用,3D也支持内置的第二个note.*标注,同时增加了以下针对3D空间摆放以及呈现特性的属性参数:

图标

iconsHT图元扩展上的一个非常有用的属性,利用其可以为图元增加任意多得图标附属部件。 icons内容为style上的icons属性json对象,该对象的结构示例代码如下:

data.setStyle('icons', {
   whateverName1: {
       position: 17,
       direction: 'north', // east, west, south, north
       gap: 1,
       names: ['icon1', 'icon2', 'icon3']
   },
   whateverName2: {
       position: 20,
       width: 16,
       height: 16,
       name: ['icon5']
   },
   whateverName3: ...
]);

icons可分为很多组,其中whateverName*可理解为组的名称,这个名称HT没有显示也不用于界面呈现效果,用户可以根据自己需要进行命名管理。 直接设置icons对象会冲掉已经设置的图标的副作用,为此HT提供了Data#addStyleIcon(name, json)Data#removeStyleIcon(name)的函数, 便于控制管理图标增删,因此以上代码也可通过下面代码实现:

data.addStyleIcon('whateverName1', {
       position: 17,
       direction: 'north', // east, west, south, north
       gap: 1,
       names: ['icon1', 'icon2', 'icon3']
});
data.addStyleIcon('whateverName2', {
       position: 20,
       width: 16,
       height: 16,
       name: ['icon5']
});

从以上示例代码可发现每组图标可为单个图标如whateverName2定义了icon5图标,也可为多个图标的排列, 如whateverName1['icon1', 'icon2', 'icon3']。而json的其他参数为如何摆放和显示这些图标相关:

吸附

吸附功能对于设计有层次关系的模型非常方便,例如设备面板吸附上设备机框,设备端口吸附上设备面板,这样从机框-面板-端口的层次关系吸附, 使得用户拖动整体机框时所有这个层次下的图元都会跟随移动。对于3D的场景下,吸附的概念更进一步延伸,当机框在三维空间进行任意位置偏移 以及任意角度旋转时,所有吸附的相关图元都会正确的跟随平移,并做出相应位置对应的旋转,以达到整体设备各个图形部分保持物理相对位置一致。

3D组件

组件基础

HT显示3D视图的组件为ht.graph3d.Graph3dView,其中ht.graph3d为和3D组件相关的类包,Graph3dView为呈现3D视图的组件类。 可类比于2D视图组件ht.graph.GraphViewGraphViewGraph3dView两者可共享同一数据模型DataModel, 在2D3D的API设计上HT保持了很多一致性。

Graph3dView的界面DOM结构是由最底层的DIV元素,以及渲染层CANVAS元素组合而成,通过getView()可得到最底层的DIV元素, 通过getCanvas()可得到渲染层CANVAS元素,HT默认的交互事件都是添加在底层DIV元素上, 用户做自定义交互扩展也可通过直接对getView()返回的元素添加事件监听的方式。

颜色参数在HTML领域是比较灵活,可为#F0F1F2的十六进制格式,可为字符串名redblack等,可为rgb(255,128,32)格式, 可为rgba(255,128,32,0.5)包含透明度的格式。然而WebGL的API接口对于颜色参数的格式,一般要求rgba四个参数的取值范围为0~1的数字格式, 因此Graph3dView组件上的颜色参数默认值为[r,g,b,a]数字数组格式,而考虑到DataModel上的Data数据与2D兼容性, HT内部会自动对颜色参数进行转换,因此data.s('all.color','red')data.s('all.color',[1,0,0,1])的方式都是一样的效果。

灯光设置

灯光和雾化等效果请参见灯光手册

网格轴线

为了提供三维空间的坐标参考,Graph3dView预置了现实xz面网格,xyz三个方向轴,以及中心点位置的显示功能, 默认这些参数都是关闭不显示状态,可根据需要打开开关,并改变显示参数。

3D交互

交互基础

默认模式

3D的交互与2D有很大的区别,默认Graph3dView提供的是围绕Graph3dView#getCenter()中心点旋转的操作模式, 这种模式下进行Drag操作时会改变Graph3dView#getEye()的眼睛观察点位置,鼠标滚轮或触屏pinch缩放的效果, 实质也是改变eye位置,使其更接近或者更远离center中心位置,最终达到视觉缩放或者走近和远离物体的效果。

第一人称模式

Graph3dView还提供了第一人称的漫游交互模型,该模式同时改变eyecenter的位置, 通过Graph3dView#setFirstPersonMode(true)可切换为第一人称模式,这种模式下操作就行人或车在行进的效果:

在第一人称模式下,还可设置Graph3dView#setMouseRoamable(true|false)参数,该参数默认值为true,如果设置为false, 则鼠标左键右键都不支持前进后退的操作功能,但左键可拖拽编辑图元,右键可改变视角方向,采用这样的方式一般会结合键盘w|s|a|d按键进行漫游操作。

交互函数

HT的交互函数一般都有是否起动画的参数anim,该参数可为boolean类型的简单true|false,也可为json对象, 当为json对象时则代表启动动画,同时json对象上的属性则用于控制动画相关的参数,以下示例代码片段供参考:

g3d.walk(distance, {
    frames: 50,
    interval: 30,
    easing: function(t) {return t; },
    finishFunc: function() {
        forwardIndex += 1;
        if (points.length - 2 > forwardIndex) {
            g3d.setCenter([point2.x, 1600, point2.y]);
            setTimeout(function() {
                g3d.rotate(Math.PI / 2, 0, {
                    frames: 30,
                    interval: 30,
                    easing: function(t) {return t;},
                    finishFunc:function() {forward();}
                });
            }, 60);
        } else {
            var lastPoint = points[points.length  - 1];
            g3d.setCenter([lastPoint.x, 1400, lastPoint.y]);
            setTimeout(function() {
                g3d.rotate(-Math.PI / 2, 0, {
                    frames: 30,
                    interval: 30,
                    finishFunc: function() {
                        window.isAnimationRunning = false;
                    }
                });

            }, 60);
        }
    }
});

虽然3D交互本质主要就是改变eyecenter这两个位置参数,但直接操作三维坐标点还是太原始晦涩, Graph3dView提供了以下跟简单直观的函数操作方式:

交互开关

默认情况下以下参数都是开启状态,即可通过鼠标或键盘进行以下交互操作,可根据需求进行开关设置:

键盘操作

Graph3dView预置了很多键盘操作功能

Graph3dView默认情况下移动图元是沿着xz平面移动,当按住以下键时将改变移动模式:

通过键盘改变移动模式的默认实现逻辑在getMoveMode(event, data)函数里,该函数默认实现逻辑如下, 如果最后选中的图元的style属性3d.move.mode指定了值,则不再考虑键盘状态而采用该设置值:

getMoveMode: function(event, data){
    var movemode = data.s('3d.move.mode');
    if(movemode){
        return movemode;
    }
    var map = ht.Default.getCurrentKeyCodeMap(),
        x = '88',
        y = '89',
        z = '90';
    if(event.shiftKey || (map[x] && map[y] && map[z])) return 'xyz';
    if(map[x] && map[y]) return 'xy';
    if(map[x] && map[z]) return 'xz';
    if(map[y] && map[z]) return 'yz';
    if(map[x]) return 'x';
    if(map[y]) return 'y';
    if(map[z]) return 'z';
    return 'xz';
},

可参考吸附章节例子中,将移动模式设置为沿xyz三维空间移动的代码:

g3d.getMoveMode = function(event){
    return 'xyz';
};

图元编辑

默认情况下图元在三维场景即可拖拽沿着xz平面移动,或结合键盘实现任意空间方向移动,但结合键盘毕竟不够直观易用, 同时图元还有三个轴方向的旋转角度,三个轴方向的大小尺寸等参数需要可控,即需要修改p3s3r3参数的编辑功能。

为此HT提供了直观的解决方案,当Graph3dView#setEditable(true)处于编辑状态时,最后一个选中的图元将呈现如下的, 由图元中心延伸出来的三个轴方向的标示条,每个轴方向的标示条又分为三段:

参见过滤器章节,控制图元是否允许移动、旋转和改变大小。

交互监听

通过Graph3dView#addInteractorListener可监听交互过程:

g3d.addInteractorListener(function(e){
    console.log(e.kind);
});

其中回调事件e.kind参数类型如下:

当单击或双击在图元上时,返回事件除了e.kind外,还有e.part参数提供了具体点击图元哪个部位的信息:

碰撞检测

HT不但提供第一人称漫游交互模式,还支持漫游过程对墙面等阻挡物的碰撞检测功能。 通过碰撞检测可以限制第一人称漫游的允许范围。漫游操作一般沿着xz平面进行,因此HT提供了定义xz平面上的多线, 用来描述不可超越的漫游边界。

通过Graph3dView#setBoundaries(boundaries)可指定碰撞边界,boundaries的格式如下:

g3d.setBoundaries([
    [
        p0.x, p0.y,
        p1.x, p1.y,
        p2.x, p2.y,
        p3.x, p3.y
    ],
    [
        p4.x, p4.y,
        p5.x, p5.y,
        p6.x, p6.y
    ]
]);

以上代码设置了两条折线p0-p1-p2-p3p4-p5-p6,每条折线由一个数组描述所有端点,数组的第一和第二元素表示起始点的x,z坐标, 接下来依次是第二、第三等端点的x,z坐标,可理解为第一个端点为MoveTo其他图元依次进行LineTo围成的边界。

以下示例代码用到了ht.Default.toBoundaries(data.getPoints(), data.getSegments())的函数, 该函数可将不连续的曲线转化成微分的直线线段。代码利用GraphView#addTopPainter, 将3Deyecenter的位置方向信息实时绘制在2D,以便直观理解当前第一人称所在位置和朝向。

选中状态

Graph3dView中被选中的图元会显示为较暗的状态,变暗系数是由图元stylebrightnessselect.brightness属性决定, select.brightness属性默认值为0.7,最终返回值大于1变亮,小于1变暗,等于1或为空则不变化。

Graph3dView#getBrightness函数控制最终图元亮度,因此也可以通过重载覆盖该函数自定义选中图元亮度,以下为默认逻辑:

getBrightness: function(data){
    var brightness = data.s('brightness'),
        selectBrightness = this.isSelected(data) ? data.s('select.brightness') : null;

    if(brightness == null){
        return selectBrightness;
    }
    if(selectBrightness == null){
        return brightness;
    }
    return brightness * selectBrightness;
},

Graph3dView#getWireframe函数用于定义图元立体线框效果,默认实现代码如下, 由实现代码可知通过控制wf.*wfwireframe的简称)相关参数即可实现显示选中线框的效果。

getWireframe: function(data){
    var visible = data.s('wf.visible');
    if(visible === true || (visible === 'selected' && this.isSelected(data))){
        return {
            color: data.s('wf.color'),
            width: data.s('wf.width'),
            short: data.s('wf.short'),
            mat: data.s('wf.mat')
        };
    }
},

过滤器

选择过滤器

默认情况所有图元都是可选中,用户可通过设置选择过滤器取消部分图元的可选中功能, 可否选中的最终控制在SelectionModel模型的filterFunc过滤器上,也可通过重载GraphViewisSelectable函数, 或调用GraphView.setSelectableFunc(func)的封装函数控制,示例代码如下:

graph3dView.setSelectableFunc(function(data){
    return data.a('selectable');
});

可见过滤器

默认情况图元都是可见,用户可通过设置可见过滤器隐藏部分图元,示例代码如下:

graph3dView.setVisibleFunc(function(data){
    return data.s('all.transparent') === true;
});

该示例代码逻辑为:只显示all.transparenttrue的图元。 Graph3dView#isVisible函数最终决定图元是否可见,因此也可通过直接重载覆盖该函数自定义:

graph3dView.isVisible = function(data){
    return data.s('all.transparent') === true;
};

移动过滤器

默认情况图元都是可移动,用户可通过设置移动过滤器固定部分图元,示例代码如下:

graph3dView.setMovableFunc(function(data){
    return movableItem.selected;
});

该示例代码逻辑为:当movableItemselectedtrue时图元才允许移动。 Graph3dView#isMovable函数最终决定图元可否移动,因此也可通过直接重载覆盖该函数自定义:

graph3dView.isMovable = function(data){
    return movableItem.selected;
};

旋转过滤器

Graph3dView#setEditable(true)设置为可编辑的情况下,默认选中图元允许旋转,可通过如下代码禁止部分图元旋转:

graph3dView.setRotationEditableFunc(function(data){
    return data instanceof ht.Shape;
});

以上代码的逻辑为:只允许ht.Shape类型的图元可以旋转。 Graph3dView#isRotationEditable函数最终决定图元可否旋转,因此也可通过直接重载覆盖该函数自定义:

graph3dView.isRotationEditable: function(data){
    return data instanceof ht.Shape;
},

改变大小过滤器

Graph3dView#setEditable(true)设置为可编辑的情况下,默认选中图元允许改变大小,可通过如下代码禁止部分图元旋转:

graph3dView.setSizeEditableFunc(function(data){
    return data instanceof ht.Shape;
});

以上代码的逻辑为:只允许ht.Shape类型的图元可以改变大小。 Graph3dView#isSizeEditable函数最终决定图元可否改变大小,因此也可通过直接重载覆盖该函数自定义:

graph3dView.isSizeEditable: function(data){
    return data instanceof ht.Shape;
},

除在视图组件上设置过滤器外,GraphViewGraph3dView的内置过滤机制也参考了以下style属性,用户可直接改变以下style达到对单个图元的控制效果:

调试信息

导出

导出例子片段:

{
    label: 'Export Image',
    action: function(){
        var w = window.open();
        w.document.open();
        w.document.write("<img src='" + g3d.toDataURL(g3d.getView().style.background) + "'/>");
        w.document.close();
    }
}

欢迎交流 service@hightopo.com