diff --git a/README.md b/README.md index d2b14e7ab..c33046f48 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Shadow Editor * 名称:Shadow Editor -* 版本:v0.0.9(开发中) +* 版本:v0.0.9 * 说明:基于`three.js`的场景编辑器。 * 源码一:https://gitee.com/tengge1/ShadowEditor @@ -81,12 +81,25 @@ npm run build-docs ## 项目截图 -![image](images/scene20181007.png) +![image](images/scene20181125.png) [点击此处](images/README.md)查看更多截图 ## 开发日志 +**v0.0.9** + +* 发布日期:2018年11月25日 +* 更新日志: + +1. 新增布料带动画。 +2. gltf模型导入带动画。 +3. skinned morph(*.js)模型导入带动画。(新版three.js示例中已经移除该模型。) +4. 平面画点工具。 +5. 平面画线工具。 +6. 平面贴花工具。 +7. 选中物体效果优化。 + **v0.0.8** * 发布日期:2018年10月27日 @@ -97,7 +110,7 @@ npm run build-docs 3. 所有场景一键发布静态网站,便于部署到`GitHub Pages`服务上。 4. 柏林地形组件、序列化和反序列化,并可在播放器中展示。 5. 上传mp4视频贴图,并可以设置到材质上,在三维场景中播放视频。 -6. 增加液体组件(测试)。 +6. 增加水组件。 **v0.0.7** diff --git a/ShadowEditor.Web/src/editor/Toolbar.js b/ShadowEditor.Web/src/editor/Toolbar.js index 7fcf4576b..b2e4e1ad4 100644 --- a/ShadowEditor.Web/src/editor/Toolbar.js +++ b/ShadowEditor.Web/src/editor/Toolbar.js @@ -1,5 +1,6 @@ import UI from '../ui/UI'; import AddObjectCommand from '../command/AddObjectCommand'; +import Earcut from '../utils/Earcut'; /** * 工具栏 @@ -70,14 +71,16 @@ Toolbar.prototype.render = function () { icon: 'icon-line', title: '画线', onClick: this.onAddLine.bind(this) - }, { - xtype: 'iconbutton', - id: 'addPolygonBtn', - scope: this.id, - icon: 'icon-polygon', - title: '画面', - onClick: this.onAddPolygon.bind(this) - }, { + }, + // { + // xtype: 'iconbutton', + // id: 'addPolygonBtn', + // scope: this.id, + // icon: 'icon-polygon', + // title: '画面', + // onClick: this.onAddPolygon.bind(this) + // }, + { xtype: 'iconbutton', id: 'sprayBtn', scope: this.id, @@ -167,7 +170,7 @@ Toolbar.prototype.onAddPointIntersect = function (obj, event) { return; } - var geometry = new THREE.CircleBufferGeometry(0.1, 32, 0, Math.PI * 2); + var geometry = new THREE.CircleBufferGeometry(0.4, 32, 0, Math.PI * 2); var material = new THREE.PointsMaterial({ color: 0xffffff * Math.random(), @@ -208,9 +211,11 @@ Toolbar.prototype.onAddLine = function () { var material = new THREE.LineMaterial({ color: 0xffffff, - linewidth: 5, // in pixels + linewidth: 8, // in pixels vertexColors: THREE.VertexColors, - dashed: false + dashed: false, + polygonOffset: true, + polygonOffsetFactor: -40, }); var renderer = this.app.editor.renderer; @@ -271,29 +276,39 @@ Toolbar.prototype.onAddPolygon = function () { this.app.on(`intersect.${this.id}AddPolygon`, this.onAddPolygonIntersect.bind(this)); this.app.on(`dblclick.${this.id}AddPolygon`, this.onAddPolygonDblClick.bind(this)); - var shape = new THREE.Shape(); - var geometry = new THREE.ShapeBufferGeometry(shape); + var geometry = new THREE.BufferGeometry(); + + geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(300), 3)); + geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(300), 3)); + geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(200), 2)); + + geometry.attributes.position.count = 0; + geometry.attributes.normal.count = 0; + geometry.attributes.uv.count = 0; var material = new THREE.MeshBasicMaterial({ color: 0xffffff * Math.random(), polygonOffset: true, polygonOffsetFactor: -40, + side: THREE.DoubleSide, }); this.polygon = new THREE.Mesh(geometry, material); this.polygon.name = '面'; + this.polygon.drawMode = THREE.TriangleStripDrawMode; this.app.editor.execute(new AddObjectCommand(this.polygon)); - this.polygonVertices = []; + this.polygonPoints = []; } else { addPolygonBtn.unselect(); this.app.on(`intersect.${this.id}AddPolygon`, null); this.app.on(`dblclick.${this.id}AddPolygon`, null); this.polygon = null; - this.polygonVertices = null; + + this.polygonPoints = null; } }; @@ -302,32 +317,32 @@ Toolbar.prototype.onAddPolygonIntersect = function (obj) { return; } - this.polygonVertices.push(obj.point.x, obj.point.y, obj.point.z); + this.polygonPoints.push(obj.point); - var shape = new THREE.Shape(); - shape.autoClose = true; + var position = this.polygon.geometry.attributes.position; + var normal = this.polygon.geometry.attributes.normal; + var uv = this.polygon.geometry.attributes.uv; - for (var i = 0; i < this.polygonVertices.length / 3; i++) { - var x = this.polygonVertices[i * 3]; - var y = this.polygonVertices[i * 3 + 1]; - var z = this.polygonVertices[i * 3 + 2]; + var index = position.count; - if (i === 0) { - shape.moveTo(x, y, z); - } else { - shape.lineTo(x, y, z); - } - } + position.setXYZ( + index, + obj.point.x, + obj.point.y, + obj.point.z, + ); + + normal.setXYZ(index, obj.face.normal.x, obj.face.normal.y, obj.face.normal.z); - var geometry = this.polygon.geometry; - geometry.dispose(); + uv.setXY(index, obj.uv.x, obj.uv.y); - geometry = new THREE.ShapeBufferGeometry(shape, this.polygonVertices.length); - geometry.attributes.position.needsUpdate = true; - geometry.attributes.normal.needsUpdate = true; - geometry.attributes.uv.needsUpdate = true; + position.count++; + normal.count++; + uv.count++; - this.polygon.geometry = geometry; + position.needsUpdate = true; + normal.needsUpdate = true; + uv.needsUpdate = true; }; Toolbar.prototype.onAddPolygonDblClick = function (obj) { diff --git a/ShadowEditor.Web/src/editor/menubar/ComponentMenu.js b/ShadowEditor.Web/src/editor/menubar/ComponentMenu.js index 96a04331c..f38595f2f 100644 --- a/ShadowEditor.Web/src/editor/menubar/ComponentMenu.js +++ b/ShadowEditor.Web/src/editor/menubar/ComponentMenu.js @@ -179,6 +179,9 @@ ComponentMenu.prototype.onAddCloth = function () { var editor = this.app.editor; var cloth = new Cloth(); + + cloth.name = '布'; + editor.execute(new AddObjectCommand(cloth)); }; diff --git a/ShadowEditor.Web/src/event/TransformControlsEvent.js b/ShadowEditor.Web/src/event/TransformControlsEvent.js index d695a205a..cae1bdb63 100644 --- a/ShadowEditor.Web/src/event/TransformControlsEvent.js +++ b/ShadowEditor.Web/src/event/TransformControlsEvent.js @@ -131,9 +131,11 @@ TransformControlsEvent.prototype.onObjectSelected = function (object) { return; } - if (object && !(object instanceof THREE.Scene) && !(object instanceof THREE.PerspectiveCamera && object.userData.isDefault === true)) { - this.app.editor.transformControls.attach(object); + if (!object || object === this.app.editor.scene || object === this.app.editor.camera) { + return; } + + this.app.editor.transformControls.attach(object); }; /** diff --git a/ShadowEditor.Web/src/utils/Earcut.js b/ShadowEditor.Web/src/utils/Earcut.js new file mode 100644 index 000000000..d1d3e212d --- /dev/null +++ b/ShadowEditor.Web/src/utils/Earcut.js @@ -0,0 +1,810 @@ +/** + * @author Mugen87 / https://github.com/Mugen87 + * Port from https://github.com/mapbox/earcut (v2.1.2) + */ + +var Earcut = { + + triangulate: function ( data, holeIndices, dim ) { + + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length, + outerNode = linkedList( data, 0, outerLen, dim, true ), + triangles = []; + + if ( ! outerNode ) return triangles; + + var minX, minY, maxX, maxY, x, y, invSize; + + if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + + if ( data.length > 80 * dim ) { + + minX = maxX = data[ 0 ]; + minY = maxY = data[ 1 ]; + + for ( var i = dim; i < outerLen; i += dim ) { + + x = data[ i ]; + y = data[ i + 1 ]; + if ( x < minX ) minX = x; + if ( y < minY ) minY = y; + if ( x > maxX ) maxX = x; + if ( y > maxY ) maxY = y; + + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + + invSize = Math.max( maxX - minX, maxY - minY ); + invSize = invSize !== 0 ? 1 / invSize : 0; + + } + + earcutLinked( outerNode, triangles, dim, minX, minY, invSize ); + + return triangles; + + } + +}; + +// create a circular doubly linked list from polygon points in the specified winding order + +function linkedList( data, start, end, dim, clockwise ) { + + var i, last; + + if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { + + for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + + } else { + + for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + + } + + if ( last && equals( last, last.next ) ) { + + removeNode( last ); + last = last.next; + + } + + return last; + +} + +// eliminate colinear or duplicate points + +function filterPoints( start, end ) { + + if ( ! start ) return start; + if ( ! end ) end = start; + + var p = start, again; + + do { + + again = false; + + if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { + + removeNode( p ); + p = end = p.prev; + if ( p === p.next ) break; + again = true; + + } else { + + p = p.next; + + } + + } while ( again || p !== end ); + + return end; + +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) + +function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { + + if ( ! ear ) return; + + // interlink polygon nodes in z-order + + if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); + + var stop = ear, prev, next; + + // iterate through ears, slicing them one by one + + while ( ear.prev !== ear.next ) { + + prev = ear.prev; + next = ear.next; + + if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + + // cut off the triangle + triangles.push( prev.i / dim ); + triangles.push( ear.i / dim ); + triangles.push( next.i / dim ); + + removeNode( ear ); + + // skipping the next vertice leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + + if ( ear === stop ) { + + // try filtering points and slicing again + + if ( ! pass ) { + + earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); + + // if this didn't work, try curing all small self-intersections locally + + } else if ( pass === 1 ) { + + ear = cureLocalIntersections( ear, triangles, dim ); + earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); + + // as a last resort, try splitting the remaining polygon into two + + } else if ( pass === 2 ) { + + splitEarcut( ear, triangles, dim, minX, minY, invSize ); + + } + + break; + + } + + } + +} + +// check whether a polygon node forms a valid ear with adjacent nodes + +function isEar( ear ) { + + var a = ear.prev, + b = ear, + c = ear.next; + + if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; + + while ( p !== ear.prev ) { + + if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) { + + return false; + + } + + p = p.next; + + } + + return true; + +} + +function isEarHashed( ear, minX, minY, invSize ) { + + var a = ear.prev, + b = ear, + c = ear.next; + + if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + + var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ), + minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ), + maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ), + maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y ); + + // z-order range for the current triangle bbox; + + var minZ = zOrder( minTX, minTY, minX, minY, invSize ), + maxZ = zOrder( maxTX, maxTY, minX, minY, invSize ); + + // first look for points inside the triangle in increasing z-order + + var p = ear.nextZ; + + while ( p && p.z <= maxZ ) { + + if ( p !== ear.prev && p !== ear.next && + pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && + area( p.prev, p, p.next ) >= 0 ) return false; + p = p.nextZ; + + } + + // then look for points in decreasing z-order + + p = ear.prevZ; + + while ( p && p.z >= minZ ) { + + if ( p !== ear.prev && p !== ear.next && + pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && + area( p.prev, p, p.next ) >= 0 ) return false; + + p = p.prevZ; + + } + + return true; + +} + +// go through all polygon nodes and cure small local self-intersections + +function cureLocalIntersections( start, triangles, dim ) { + + var p = start; + + do { + + var a = p.prev, b = p.next.next; + + if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + + triangles.push( a.i / dim ); + triangles.push( p.i / dim ); + triangles.push( b.i / dim ); + + // remove two nodes involved + + removeNode( p ); + removeNode( p.next ); + + p = start = b; + + } + + p = p.next; + + } while ( p !== start ); + + return p; + +} + +// try splitting polygon into two and triangulate them independently + +function splitEarcut( start, triangles, dim, minX, minY, invSize ) { + + // look for a valid diagonal that divides the polygon into two + + var a = start; + + do { + + var b = a.next.next; + + while ( b !== a.prev ) { + + if ( a.i !== b.i && isValidDiagonal( a, b ) ) { + + // split the polygon in two by the diagonal + + var c = splitPolygon( a, b ); + + // filter colinear points around the cuts + + a = filterPoints( a, a.next ); + c = filterPoints( c, c.next ); + + // run earcut on each half + + earcutLinked( a, triangles, dim, minX, minY, invSize ); + earcutLinked( c, triangles, dim, minX, minY, invSize ); + return; + + } + + b = b.next; + + } + + a = a.next; + + } while ( a !== start ); + +} + +// link every hole into the outer loop, producing a single-ring polygon without holes + +function eliminateHoles( data, holeIndices, outerNode, dim ) { + + var queue = [], i, len, start, end, list; + + for ( i = 0, len = holeIndices.length; i < len; i ++ ) { + + start = holeIndices[ i ] * dim; + end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; + list = linkedList( data, start, end, dim, false ); + if ( list === list.next ) list.steiner = true; + queue.push( getLeftmost( list ) ); + + } + + queue.sort( compareX ); + + // process holes from left to right + + for ( i = 0; i < queue.length; i ++ ) { + + eliminateHole( queue[ i ], outerNode ); + outerNode = filterPoints( outerNode, outerNode.next ); + + } + + return outerNode; + +} + +function compareX( a, b ) { + + return a.x - b.x; + +} + +// find a bridge between vertices that connects hole with an outer ring and and link it + +function eliminateHole( hole, outerNode ) { + + outerNode = findHoleBridge( hole, outerNode ); + + if ( outerNode ) { + + var b = splitPolygon( outerNode, hole ); + + filterPoints( b, b.next ); + + } + +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon + +function findHoleBridge( hole, outerNode ) { + + var p = outerNode, + hx = hole.x, + hy = hole.y, + qx = - Infinity, + m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + + do { + + if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { + + var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); + + if ( x <= hx && x > qx ) { + + qx = x; + + if ( x === hx ) { + + if ( hy === p.y ) return p; + if ( hy === p.next.y ) return p.next; + + } + + m = p.x < p.next.x ? p : p.next; + + } + + } + + p = p.next; + + } while ( p !== outerNode ); + + if ( ! m ) return null; + + if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + var stop = m, + mx = m.x, + my = m.y, + tanMin = Infinity, + tan; + + p = m.next; + + while ( p !== stop ) { + + if ( hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { + + tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential + + if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) { + + m = p; + tanMin = tan; + + } + + } + + p = p.next; + + } + + return m; + +} + +// interlink polygon nodes in z-order + +function indexCurve( start, minX, minY, invSize ) { + + var p = start; + + do { + + if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + + } while ( p !== start ); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked( p ); + +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + +function sortLinked( list ) { + + var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1; + + do { + + p = list; + list = null; + tail = null; + numMerges = 0; + + while ( p ) { + + numMerges ++; + q = p; + pSize = 0; + + for ( i = 0; i < inSize; i ++ ) { + + pSize ++; + q = q.nextZ; + if ( ! q ) break; + + } + + qSize = inSize; + + while ( pSize > 0 || ( qSize > 0 && q ) ) { + + if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { + + e = p; + p = p.nextZ; + pSize --; + + } else { + + e = q; + q = q.nextZ; + qSize --; + + } + + if ( tail ) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + + } + + p = q; + + } + + tail.nextZ = null; + inSize *= 2; + + } while ( numMerges > 1 ); + + return list; + +} + +// z-order of a point given coords and inverse of the longer side of data bbox + +function zOrder( x, y, minX, minY, invSize ) { + + // coords are transformed into non-negative 15-bit integer range + + x = 32767 * ( x - minX ) * invSize; + y = 32767 * ( y - minY ) * invSize; + + x = ( x | ( x << 8 ) ) & 0x00FF00FF; + x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; + x = ( x | ( x << 2 ) ) & 0x33333333; + x = ( x | ( x << 1 ) ) & 0x55555555; + + y = ( y | ( y << 8 ) ) & 0x00FF00FF; + y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; + y = ( y | ( y << 2 ) ) & 0x33333333; + y = ( y | ( y << 1 ) ) & 0x55555555; + + return x | ( y << 1 ); + +} + +// find the leftmost node of a polygon ring + +function getLeftmost( start ) { + + var p = start, leftmost = start; + + do { + + if ( p.x < leftmost.x ) leftmost = p; + p = p.next; + + } while ( p !== start ); + + return leftmost; + +} + +// check if a point lies within a convex triangle + +function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { + + return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 && + ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 && + ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0; + +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) + +function isValidDiagonal( a, b ) { + + return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && + locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ); + +} + +// signed area of a triangle + +function area( p, q, r ) { + + return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + +} + +// check if two points are equal + +function equals( p1, p2 ) { + + return p1.x === p2.x && p1.y === p2.y; + +} + +// check if two segments intersect + +function intersects( p1, q1, p2, q2 ) { + + if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) || + ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true; + + return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 && + area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0; + +} + +// check if a polygon diagonal intersects any polygon segments + +function intersectsPolygon( a, b ) { + + var p = a; + + do { + + if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects( p, p.next, a, b ) ) { + + return true; + + } + + p = p.next; + + } while ( p !== a ); + + return false; + +} + +// check if a polygon diagonal is locally inside the polygon + +function locallyInside( a, b ) { + + return area( a.prev, a, a.next ) < 0 ? + area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : + area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; + +} + +// check if the middle point of a polygon diagonal is inside the polygon + +function middleInside( a, b ) { + + var p = a, + inside = false, + px = ( a.x + b.x ) / 2, + py = ( a.y + b.y ) / 2; + + do { + + if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && + ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) { + + inside = ! inside; + + } + + p = p.next; + + } while ( p !== a ); + + return inside; + +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring + +function splitPolygon( a, b ) { + + var a2 = new Node( a.i, a.x, a.y ), + b2 = new Node( b.i, b.x, b.y ), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + +} + +// create a node and optionally link it with previous one (in a circular doubly linked list) + +function insertNode( i, x, y, last ) { + + var p = new Node( i, x, y ); + + if ( ! last ) { + + p.prev = p; + p.next = p; + + } else { + + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + + } + + return p; + +} + +function removeNode( p ) { + + p.next.prev = p.prev; + p.prev.next = p.next; + + if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; + if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; + +} + +function Node( i, x, y ) { + + // vertice index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertice nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = null; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; + +} + +function signedArea( data, start, end, dim ) { + + var sum = 0; + + for ( var i = start, j = end - dim; i < end; i += dim ) { + + sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); + j = i; + + } + + return sum; + +} + +export default Earcut; diff --git a/images/README.md b/images/README.md index c17e1bdb3..17f7e2862 100644 --- a/images/README.md +++ b/images/README.md @@ -1,3 +1,7 @@ +* 2018年11月25日 + +![image](scene20181125.png) + * 2018年10月7日 ![image](scene20181007.png) diff --git a/images/scene20181125.png b/images/scene20181125.png new file mode 100644 index 000000000..6ae293599 Binary files /dev/null and b/images/scene20181125.png differ