tremble hace 6 años
padre
commit
9bbc7cf57f

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1163 - 0
assets/js/CanvasRenderer.js


+ 568 - 0
assets/js/MTLLoader.js

@@ -0,0 +1,568 @@
+/**
+ * Loads a Wavefront .mtl file specifying materials
+ *
+ * @author angelxuanchang
+ */
+
+THREE.MTLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.MTLLoader.prototype = {
+
+	constructor: THREE.MTLLoader,
+
+	/**
+	 * Loads and parses a MTL asset from a URL.
+	 *
+	 * @param {String} url - URL to the MTL file.
+	 * @param {Function} [onLoad] - Callback invoked with the loaded object.
+	 * @param {Function} [onProgress] - Callback for download progress.
+	 * @param {Function} [onError] - Callback for download errors.
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to load.
+	 */
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;
+
+		var loader = new THREE.FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	/**
+	 * Set base path for resolving references.
+	 * If set this path will be prepended to each loaded and found reference.
+	 *
+	 * @see setResourcePath
+	 * @param {String} path
+	 * @return {THREE.MTLLoader}
+	 *
+	 * @example
+	 *     mtlLoader.setPath( 'assets/obj/' );
+	 *     mtlLoader.load( 'my.mtl', ... );
+	 */
+	setPath: function ( path ) {
+
+		this.path = path;
+		return this;
+
+	},
+
+	/**
+	 * Set base path for additional resources like textures.
+	 *
+	 * @see setPath
+	 * @param {String} path
+	 * @return {THREE.MTLLoader}
+	 *
+	 * @example
+	 *     mtlLoader.setPath( 'assets/obj/' );
+	 *     mtlLoader.setResourcePath( 'assets/textures/' );
+	 *     mtlLoader.load( 'my.mtl', ... );
+	 */
+	setResourcePath: function ( path ) {
+
+		this.resourcePath = path;
+		return this;
+
+	},
+
+	setTexturePath: function ( path ) {
+
+		console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' );
+		return this.setResourcePath( path );
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	setMaterialOptions: function ( value ) {
+
+		this.materialOptions = value;
+		return this;
+
+	},
+
+	/**
+	 * Parses a MTL file.
+	 *
+	 * @param {String} text - Content of MTL file
+	 * @return {THREE.MTLLoader.MaterialCreator}
+	 *
+	 * @see setPath setResourcePath
+	 *
+	 * @note In order for relative texture references to resolve correctly
+	 * you must call setResourcePath() explicitly prior to parse.
+	 */
+	parse: function ( text, path ) {
+
+		var lines = text.split( '\n' );
+		var info = {};
+		var delimiter_pattern = /\s+/;
+		var materialsInfo = {};
+
+		for ( var i = 0; i < lines.length; i ++ ) {
+
+			var line = lines[ i ];
+			line = line.trim();
+
+			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+				// Blank line or comment ignore
+				continue;
+
+			}
+
+			var pos = line.indexOf( ' ' );
+
+			var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
+			key = key.toLowerCase();
+
+			var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
+			value = value.trim();
+
+			if ( key === 'newmtl' ) {
+
+				// New material
+
+				info = { name: value };
+				materialsInfo[ value ] = info;
+
+			} else {
+
+				if ( key === 'ka' || key === 'kd' || key === 'ks' ) {
+
+					var ss = value.split( delimiter_pattern, 3 );
+					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+
+				} else {
+
+					info[ key ] = value;
+
+				}
+
+			}
+
+		}
+
+		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
+		materialCreator.setCrossOrigin( this.crossOrigin );
+		materialCreator.setManager( this.manager );
+		materialCreator.setMaterials( materialsInfo );
+		return materialCreator;
+
+	}
+
+};
+
+/**
+ * Create a new THREE-MTLLoader.MaterialCreator
+ * @param baseUrl - Url relative to which textures are loaded
+ * @param options - Set of options on how to construct the materials
+ *                  side: Which side to apply the material
+ *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
+ *                  wrap: What type of wrapping to apply for textures
+ *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+ *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ *                                Default: false, assumed to be already normalized
+ *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ *                                  Default: false
+ * @constructor
+ */
+
+THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
+
+	this.baseUrl = baseUrl || '';
+	this.options = options;
+	this.materialsInfo = {};
+	this.materials = {};
+	this.materialsArray = [];
+	this.nameLookup = {};
+
+	this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
+	this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
+
+};
+
+THREE.MTLLoader.MaterialCreator.prototype = {
+
+	constructor: THREE.MTLLoader.MaterialCreator,
+
+	crossOrigin: 'anonymous',
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	setManager: function ( value ) {
+
+		this.manager = value;
+
+	},
+
+	setMaterials: function ( materialsInfo ) {
+
+		this.materialsInfo = this.convert( materialsInfo );
+		this.materials = {};
+		this.materialsArray = [];
+		this.nameLookup = {};
+
+	},
+
+	convert: function ( materialsInfo ) {
+
+		if ( ! this.options ) return materialsInfo;
+
+		var converted = {};
+
+		for ( var mn in materialsInfo ) {
+
+			// Convert materials info into normalized form based on options
+
+			var mat = materialsInfo[ mn ];
+
+			var covmat = {};
+
+			converted[ mn ] = covmat;
+
+			for ( var prop in mat ) {
+
+				var save = true;
+				var value = mat[ prop ];
+				var lprop = prop.toLowerCase();
+
+				switch ( lprop ) {
+
+					case 'kd':
+					case 'ka':
+					case 'ks':
+
+						// Diffuse color (color under white light) using RGB values
+
+						if ( this.options && this.options.normalizeRGB ) {
+
+							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+
+						}
+
+						if ( this.options && this.options.ignoreZeroRGBs ) {
+
+							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+
+								// ignore
+
+								save = false;
+
+							}
+
+						}
+
+						break;
+
+					default:
+
+						break;
+
+				}
+
+				if ( save ) {
+
+					covmat[ lprop ] = value;
+
+				}
+
+			}
+
+		}
+
+		return converted;
+
+	},
+
+	preload: function () {
+
+		for ( var mn in this.materialsInfo ) {
+
+			this.create( mn );
+
+		}
+
+	},
+
+	getIndex: function ( materialName ) {
+
+		return this.nameLookup[ materialName ];
+
+	},
+
+	getAsArray: function () {
+
+		var index = 0;
+
+		for ( var mn in this.materialsInfo ) {
+
+			this.materialsArray[ index ] = this.create( mn );
+			this.nameLookup[ mn ] = index;
+			index ++;
+
+		}
+
+		return this.materialsArray;
+
+	},
+
+	create: function ( materialName ) {
+
+		if ( this.materials[ materialName ] === undefined ) {
+
+			this.createMaterial_( materialName );
+
+		}
+
+		return this.materials[ materialName ];
+
+	},
+
+	createMaterial_: function ( materialName ) {
+
+		// Create material
+
+		var scope = this;
+		var mat = this.materialsInfo[ materialName ];
+		var params = {
+
+			name: materialName,
+			side: this.side
+
+		};
+
+		function resolveURL( baseUrl, url ) {
+
+			if ( typeof url !== 'string' || url === '' )
+				return '';
+
+			// Absolute URL
+			if ( /^https?:\/\//i.test( url ) ) return url;
+
+			return baseUrl + url;
+
+		}
+
+		function setMapForType( mapType, value ) {
+
+			if ( params[ mapType ] ) return; // Keep the first encountered texture
+
+			var texParams = scope.getTextureParams( value, params );
+			var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+
+			map.repeat.copy( texParams.scale );
+			map.offset.copy( texParams.offset );
+
+			map.wrapS = scope.wrap;
+			map.wrapT = scope.wrap;
+
+			params[ mapType ] = map;
+
+		}
+
+		for ( var prop in mat ) {
+
+			var value = mat[ prop ];
+			var n;
+
+			if ( value === '' ) continue;
+
+			switch ( prop.toLowerCase() ) {
+
+				// Ns is material specular exponent
+
+				case 'kd':
+
+					// Diffuse color (color under white light) using RGB values
+
+					params.color = new THREE.Color().fromArray( value );
+
+					break;
+
+				case 'ks':
+
+					// Specular color (color when light is reflected from shiny surface) using RGB values
+					params.specular = new THREE.Color().fromArray( value );
+
+					break;
+
+				case 'map_kd':
+
+					// Diffuse texture map
+
+					setMapForType( "map", value );
+
+					break;
+
+				case 'map_ks':
+
+					// Specular map
+
+					setMapForType( "specularMap", value );
+
+					break;
+
+				case 'norm':
+
+					setMapForType( "normalMap", value );
+
+					break;
+
+				case 'map_bump':
+				case 'bump':
+
+					// Bump texture map
+
+					setMapForType( "bumpMap", value );
+
+					break;
+
+				case 'map_d':
+
+					// Alpha map
+
+					setMapForType( "alphaMap", value );
+					params.transparent = true;
+
+					break;
+
+				case 'ns':
+
+					// The specular exponent (defines the focus of the specular highlight)
+					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+
+					params.shininess = parseFloat( value );
+
+					break;
+
+				case 'd':
+					n = parseFloat( value );
+
+					if ( n < 1 ) {
+
+						params.opacity = n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				case 'tr':
+					n = parseFloat( value );
+
+					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+
+					if ( n > 0 ) {
+
+						params.opacity = 1 - n;
+						params.transparent = true;
+
+					}
+
+					break;
+
+				default:
+					break;
+
+			}
+
+		}
+
+		this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
+		return this.materials[ materialName ];
+
+	},
+
+	getTextureParams: function ( value, matParams ) {
+
+		var texParams = {
+
+			scale: new THREE.Vector2( 1, 1 ),
+			offset: new THREE.Vector2( 0, 0 )
+
+		 };
+
+		var items = value.split( /\s+/ );
+		var pos;
+
+		pos = items.indexOf( '-bm' );
+
+		if ( pos >= 0 ) {
+
+			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+			items.splice( pos, 2 );
+
+		}
+
+		pos = items.indexOf( '-s' );
+
+		if ( pos >= 0 ) {
+
+			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		pos = items.indexOf( '-o' );
+
+		if ( pos >= 0 ) {
+
+			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+			items.splice( pos, 4 ); // we expect 3 parameters here!
+
+		}
+
+		texParams.url = items.join( ' ' ).trim();
+		return texParams;
+
+	},
+
+	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
+
+		var texture;
+		var loader = THREE.Loader.Handlers.get( url );
+		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
+
+		if ( loader === null ) {
+
+			loader = new THREE.TextureLoader( manager );
+
+		}
+
+		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+		texture = loader.load( url, onLoad, onProgress, onError );
+
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		return texture;
+
+	}
+
+};

+ 793 - 0
assets/js/OBJLoader.js

@@ -0,0 +1,793 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.OBJLoader = ( function () {
+
+	// o object_name | g group_name
+	var object_pattern = /^[og]\s*(.+)?/;
+	// mtllib file_reference
+	var material_library_pattern = /^mtllib /;
+	// usemtl material_name
+	var material_use_pattern = /^usemtl /;
+
+	function ParserState() {
+
+		var state = {
+			objects: [],
+			object: {},
+
+			vertices: [],
+			normals: [],
+			colors: [],
+			uvs: [],
+
+			materialLibraries: [],
+
+			startObject: function ( name, fromDeclaration ) {
+
+				// If the current object (initial from reset) is not from a g/o declaration in the parsed
+				// file. We need to use it for the first parsed g/o to keep things in sync.
+				if ( this.object && this.object.fromDeclaration === false ) {
+
+					this.object.name = name;
+					this.object.fromDeclaration = ( fromDeclaration !== false );
+					return;
+
+				}
+
+				var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
+
+				if ( this.object && typeof this.object._finalize === 'function' ) {
+
+					this.object._finalize( true );
+
+				}
+
+				this.object = {
+					name: name || '',
+					fromDeclaration: ( fromDeclaration !== false ),
+
+					geometry: {
+						vertices: [],
+						normals: [],
+						colors: [],
+						uvs: []
+					},
+					materials: [],
+					smooth: true,
+
+					startMaterial: function ( name, libraries ) {
+
+						var previous = this._finalize( false );
+
+						// New usemtl declaration overwrites an inherited material, except if faces were declared
+						// after the material, then it must be preserved for proper MultiMaterial continuation.
+						if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
+
+							this.materials.splice( previous.index, 1 );
+
+						}
+
+						var material = {
+							index: this.materials.length,
+							name: name || '',
+							mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
+							smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
+							groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
+							groupEnd: - 1,
+							groupCount: - 1,
+							inherited: false,
+
+							clone: function ( index ) {
+
+								var cloned = {
+									index: ( typeof index === 'number' ? index : this.index ),
+									name: this.name,
+									mtllib: this.mtllib,
+									smooth: this.smooth,
+									groupStart: 0,
+									groupEnd: - 1,
+									groupCount: - 1,
+									inherited: false
+								};
+								cloned.clone = this.clone.bind( cloned );
+								return cloned;
+
+							}
+						};
+
+						this.materials.push( material );
+
+						return material;
+
+					},
+
+					currentMaterial: function () {
+
+						if ( this.materials.length > 0 ) {
+
+							return this.materials[ this.materials.length - 1 ];
+
+						}
+
+						return undefined;
+
+					},
+
+					_finalize: function ( end ) {
+
+						var lastMultiMaterial = this.currentMaterial();
+						if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
+
+							lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
+							lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
+							lastMultiMaterial.inherited = false;
+
+						}
+
+						// Ignore objects tail materials if no face declarations followed them before a new o/g started.
+						if ( end && this.materials.length > 1 ) {
+
+							for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) {
+
+								if ( this.materials[ mi ].groupCount <= 0 ) {
+
+									this.materials.splice( mi, 1 );
+
+								}
+
+							}
+
+						}
+
+						// Guarantee at least one empty material, this makes the creation later more straight forward.
+						if ( end && this.materials.length === 0 ) {
+
+							this.materials.push( {
+								name: '',
+								smooth: this.smooth
+							} );
+
+						}
+
+						return lastMultiMaterial;
+
+					}
+				};
+
+				// Inherit previous objects material.
+				// Spec tells us that a declared material must be set to all objects until a new material is declared.
+				// If a usemtl declaration is encountered while this new object is being parsed, it will
+				// overwrite the inherited material. Exception being that there was already face declarations
+				// to the inherited material, then it will be preserved for proper MultiMaterial continuation.
+
+				if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
+
+					var declared = previousMaterial.clone( 0 );
+					declared.inherited = true;
+					this.object.materials.push( declared );
+
+				}
+
+				this.objects.push( this.object );
+
+			},
+
+			finalize: function () {
+
+				if ( this.object && typeof this.object._finalize === 'function' ) {
+
+					this.object._finalize( true );
+
+				}
+
+			},
+
+			parseVertexIndex: function ( value, len ) {
+
+				var index = parseInt( value, 10 );
+				return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+			},
+
+			parseNormalIndex: function ( value, len ) {
+
+				var index = parseInt( value, 10 );
+				return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
+
+			},
+
+			parseUVIndex: function ( value, len ) {
+
+				var index = parseInt( value, 10 );
+				return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
+
+			},
+
+			addVertex: function ( a, b, c ) {
+
+				var src = this.vertices;
+				var dst = this.object.geometry.vertices;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+				dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+				dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+			},
+
+			addVertexPoint: function ( a ) {
+
+				var src = this.vertices;
+				var dst = this.object.geometry.vertices;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+			},
+
+			addVertexLine: function ( a ) {
+
+				var src = this.vertices;
+				var dst = this.object.geometry.vertices;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+
+			},
+
+			addNormal: function ( a, b, c ) {
+
+				var src = this.normals;
+				var dst = this.object.geometry.normals;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+				dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+				dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+			},
+
+			addColor: function ( a, b, c ) {
+
+				var src = this.colors;
+				var dst = this.object.geometry.colors;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
+				dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
+				dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
+
+			},
+
+			addUV: function ( a, b, c ) {
+
+				var src = this.uvs;
+				var dst = this.object.geometry.uvs;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ] );
+				dst.push( src[ b + 0 ], src[ b + 1 ] );
+				dst.push( src[ c + 0 ], src[ c + 1 ] );
+
+			},
+
+			addUVLine: function ( a ) {
+
+				var src = this.uvs;
+				var dst = this.object.geometry.uvs;
+
+				dst.push( src[ a + 0 ], src[ a + 1 ] );
+
+			},
+
+			addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
+
+				var vLen = this.vertices.length;
+
+				var ia = this.parseVertexIndex( a, vLen );
+				var ib = this.parseVertexIndex( b, vLen );
+				var ic = this.parseVertexIndex( c, vLen );
+
+				this.addVertex( ia, ib, ic );
+
+				if ( ua !== undefined && ua !== '' ) {
+
+					var uvLen = this.uvs.length;
+					ia = this.parseUVIndex( ua, uvLen );
+					ib = this.parseUVIndex( ub, uvLen );
+					ic = this.parseUVIndex( uc, uvLen );
+					this.addUV( ia, ib, ic );
+
+				}
+
+				if ( na !== undefined && na !== '' ) {
+
+					// Normals are many times the same. If so, skip function call and parseInt.
+					var nLen = this.normals.length;
+					ia = this.parseNormalIndex( na, nLen );
+
+					ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
+					ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
+
+					this.addNormal( ia, ib, ic );
+
+				}
+
+				if ( this.colors.length > 0 ) {
+
+					this.addColor( ia, ib, ic );
+
+				}
+
+			},
+
+			addPointGeometry: function ( vertices ) {
+
+				this.object.geometry.type = 'Points';
+
+				var vLen = this.vertices.length;
+
+				for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+					this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+				}
+
+			},
+
+			addLineGeometry: function ( vertices, uvs ) {
+
+				this.object.geometry.type = 'Line';
+
+				var vLen = this.vertices.length;
+				var uvLen = this.uvs.length;
+
+				for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
+
+					this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
+
+				}
+
+				for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
+
+					this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
+
+				}
+
+			}
+
+		};
+
+		state.startObject( '', false );
+
+		return state;
+
+	}
+
+	//
+
+	function OBJLoader( manager ) {
+
+		this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+		this.materials = null;
+
+	}
+
+	OBJLoader.prototype = {
+
+		constructor: OBJLoader,
+
+		load: function ( url, onLoad, onProgress, onError ) {
+
+			var scope = this;
+
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( this.path );
+			loader.load( url, function ( text ) {
+
+				onLoad( scope.parse( text ) );
+
+			}, onProgress, onError );
+
+		},
+
+		setPath: function ( value ) {
+
+			this.path = value;
+
+			return this;
+
+		},
+
+		setMaterials: function ( materials ) {
+
+			this.materials = materials;
+
+			return this;
+
+		},
+
+		parse: function ( text ) {
+
+			console.time( 'OBJLoader' );
+
+			var state = new ParserState();
+
+			if ( text.indexOf( '\r\n' ) !== - 1 ) {
+
+				// This is faster than String.split with regex that splits on both
+				text = text.replace( /\r\n/g, '\n' );
+
+			}
+
+			if ( text.indexOf( '\\\n' ) !== - 1 ) {
+
+				// join lines separated by a line continuation character (\)
+				text = text.replace( /\\\n/g, '' );
+
+			}
+
+			var lines = text.split( '\n' );
+			var line = '', lineFirstChar = '';
+			var lineLength = 0;
+			var result = [];
+
+			// Faster to just trim left side of the line. Use if available.
+			var trimLeft = ( typeof ''.trimLeft === 'function' );
+
+			for ( var i = 0, l = lines.length; i < l; i ++ ) {
+
+				line = lines[ i ];
+
+				line = trimLeft ? line.trimLeft() : line.trim();
+
+				lineLength = line.length;
+
+				if ( lineLength === 0 ) continue;
+
+				lineFirstChar = line.charAt( 0 );
+
+				// @todo invoke passed in handler if any
+				if ( lineFirstChar === '#' ) continue;
+
+				if ( lineFirstChar === 'v' ) {
+
+					var data = line.split( /\s+/ );
+
+					switch ( data[ 0 ] ) {
+
+						case 'v':
+							state.vertices.push(
+								parseFloat( data[ 1 ] ),
+								parseFloat( data[ 2 ] ),
+								parseFloat( data[ 3 ] )
+							);
+							if ( data.length === 8 ) {
+
+								state.colors.push(
+									parseFloat( data[ 4 ] ),
+									parseFloat( data[ 5 ] ),
+									parseFloat( data[ 6 ] )
+
+								);
+
+							}
+							break;
+						case 'vn':
+							state.normals.push(
+								parseFloat( data[ 1 ] ),
+								parseFloat( data[ 2 ] ),
+								parseFloat( data[ 3 ] )
+							);
+							break;
+						case 'vt':
+							state.uvs.push(
+								parseFloat( data[ 1 ] ),
+								parseFloat( data[ 2 ] )
+							);
+							break;
+
+					}
+
+				} else if ( lineFirstChar === 'f' ) {
+
+					var lineData = line.substr( 1 ).trim();
+					var vertexData = lineData.split( /\s+/ );
+					var faceVertices = [];
+
+					// Parse the face vertex data into an easy to work with format
+
+					for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
+
+						var vertex = vertexData[ j ];
+
+						if ( vertex.length > 0 ) {
+
+							var vertexParts = vertex.split( '/' );
+							faceVertices.push( vertexParts );
+
+						}
+
+					}
+
+					// Draw an edge between the first vertex and all subsequent vertices to form an n-gon
+
+					var v1 = faceVertices[ 0 ];
+
+					for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
+
+						var v2 = faceVertices[ j ];
+						var v3 = faceVertices[ j + 1 ];
+
+						state.addFace(
+							v1[ 0 ], v2[ 0 ], v3[ 0 ],
+							v1[ 1 ], v2[ 1 ], v3[ 1 ],
+							v1[ 2 ], v2[ 2 ], v3[ 2 ]
+						);
+
+					}
+
+				} else if ( lineFirstChar === 'l' ) {
+
+					var lineParts = line.substring( 1 ).trim().split( " " );
+					var lineVertices = [], lineUVs = [];
+
+					if ( line.indexOf( "/" ) === - 1 ) {
+
+						lineVertices = lineParts;
+
+					} else {
+
+						for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
+
+							var parts = lineParts[ li ].split( "/" );
+
+							if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
+							if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
+
+						}
+
+					}
+					state.addLineGeometry( lineVertices, lineUVs );
+
+				} else if ( lineFirstChar === 'p' ) {
+
+					var lineData = line.substr( 1 ).trim();
+					var pointData = lineData.split( " " );
+
+					state.addPointGeometry( pointData );
+
+				} else if ( ( result = object_pattern.exec( line ) ) !== null ) {
+
+					// o object_name
+					// or
+					// g group_name
+
+					// WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
+					// var name = result[ 0 ].substr( 1 ).trim();
+					var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
+
+					state.startObject( name );
+
+				} else if ( material_use_pattern.test( line ) ) {
+
+					// material
+
+					state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
+
+				} else if ( material_library_pattern.test( line ) ) {
+
+					// mtl file
+
+					state.materialLibraries.push( line.substring( 7 ).trim() );
+
+				} else if ( lineFirstChar === 's' ) {
+
+					result = line.split( ' ' );
+
+					// smooth shading
+
+					// @todo Handle files that have varying smooth values for a set of faces inside one geometry,
+					// but does not define a usemtl for each face set.
+					// This should be detected and a dummy material created (later MultiMaterial and geometry groups).
+					// This requires some care to not create extra material on each smooth value for "normal" obj files.
+					// where explicit usemtl defines geometry groups.
+					// Example asset: examples/models/obj/cerberus/Cerberus.obj
+
+					/*
+					 * http://paulbourke.net/dataformats/obj/
+					 * or
+					 * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
+					 *
+					 * From chapter "Grouping" Syntax explanation "s group_number":
+					 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
+					 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
+					 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
+					 * than 0."
+					 */
+					if ( result.length > 1 ) {
+
+						var value = result[ 1 ].trim().toLowerCase();
+						state.object.smooth = ( value !== '0' && value !== 'off' );
+
+					} else {
+
+						// ZBrush can produce "s" lines #11707
+						state.object.smooth = true;
+
+					}
+					var material = state.object.currentMaterial();
+					if ( material ) material.smooth = state.object.smooth;
+
+				} else {
+
+					// Handle null terminated files without exception
+					if ( line === '\0' ) continue;
+
+					throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
+
+				}
+
+			}
+
+			state.finalize();
+
+			var container = new THREE.Group();
+			container.materialLibraries = [].concat( state.materialLibraries );
+
+			for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
+
+				var object = state.objects[ i ];
+				var geometry = object.geometry;
+				var materials = object.materials;
+				var isLine = ( geometry.type === 'Line' );
+				var isPoints = ( geometry.type === 'Points' );
+				var hasVertexColors = false;
+
+				// Skip o/g line declarations that did not follow with any faces
+				if ( geometry.vertices.length === 0 ) continue;
+
+				var buffergeometry = new THREE.BufferGeometry();
+
+				buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
+
+				if ( geometry.normals.length > 0 ) {
+
+					buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
+
+				} else {
+
+					buffergeometry.computeVertexNormals();
+
+				}
+
+				if ( geometry.colors.length > 0 ) {
+
+					hasVertexColors = true;
+					buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );
+
+				}
+
+				if ( geometry.uvs.length > 0 ) {
+
+					buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
+
+				}
+
+				// Create materials
+
+				var createdMaterials = [];
+
+				for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+					var sourceMaterial = materials[ mi ];
+					var material = undefined;
+
+					if ( this.materials !== null ) {
+
+						material = this.materials.create( sourceMaterial.name );
+
+						// mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
+						if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
+
+							var materialLine = new THREE.LineBasicMaterial();
+							materialLine.copy( material );
+							materialLine.lights = false; // TOFIX
+							material = materialLine;
+
+						} else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {
+
+							var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } );
+							materialLine.copy( material );
+							material = materialPoints;
+
+						}
+
+					}
+
+					if ( ! material ) {
+
+						if ( isLine ) {
+
+							material = new THREE.LineBasicMaterial();
+
+						} else if ( isPoints ) {
+
+							material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } );
+
+						} else {
+
+							material = new THREE.MeshPhongMaterial();
+
+						}
+
+						material.name = sourceMaterial.name;
+
+					}
+
+					material.flatShading = sourceMaterial.smooth ? false : true;
+					material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors;
+
+					createdMaterials.push( material );
+
+				}
+
+				// Create mesh
+
+				var mesh;
+
+				if ( createdMaterials.length > 1 ) {
+
+					for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
+
+						var sourceMaterial = materials[ mi ];
+						buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
+
+					}
+
+					if ( isLine ) {
+
+						mesh = new THREE.LineSegments( buffergeometry, createdMaterials );
+
+					} else if ( isPoints ) {
+
+						mesh = new THREE.Points( buffergeometry, createdMaterials );
+
+					} else {
+
+						mesh = new THREE.Mesh( buffergeometry, createdMaterials );
+
+					}
+
+				} else {
+
+					if ( isLine ) {
+
+						mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] );
+
+					} else if ( isPoints ) {
+
+						mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] );
+
+					} else {
+
+						mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] );
+
+					}
+
+				}
+
+				mesh.name = object.name;
+
+				container.add( mesh );
+
+			}
+
+			console.timeEnd( 'OBJLoader' );
+
+			return container;
+
+		}
+
+	};
+
+	return OBJLoader;
+
+} )();

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1042 - 0
assets/js/OrbitControls.js


+ 38 - 0
assets/js/SceneUtils.js

@@ -0,0 +1,38 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneUtils = {
+
+	createMultiMaterialObject: function ( geometry, materials ) {
+
+		var group = new THREE.Group();
+
+		for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+			group.add( new THREE.Mesh( geometry, materials[ i ] ) );
+
+		}
+
+		return group;
+
+	},
+
+	detach: function ( child, parent, scene ) {
+
+		child.applyMatrix( parent.matrixWorld );
+		parent.remove( child );
+		scene.add( child );
+
+	},
+
+	attach: function ( child, scene, parent ) {
+
+		child.applyMatrix( new THREE.Matrix4().getInverse( parent.matrixWorld ) );
+
+		scene.remove( child );
+		parent.add( child );
+
+	}
+
+};

+ 486 - 0
assets/js/THREE.MeshLine.js

@@ -0,0 +1,486 @@
+;(function() {
+
+"use strict";
+
+var root = this
+
+var has_require = typeof require !== 'undefined'
+
+var THREE = root.THREE || has_require && require('three')
+if( !THREE )
+	throw new Error( 'MeshLine requires three.js' )
+
+function MeshLine() {
+
+	this.positions = [];
+
+	this.previous = [];
+	this.next = [];
+	this.side = [];
+	this.width = [];
+	this.indices_array = [];
+	this.uvs = [];
+	this.counters = [];
+	this.geometry = new THREE.BufferGeometry();
+
+	this.widthCallback = null;
+
+}
+
+MeshLine.prototype.setGeometry = function( g, c ) {
+
+	this.widthCallback = c;
+
+	this.positions = [];
+	this.counters = [];
+
+	if( g instanceof THREE.Geometry ) {
+		for( var j = 0; j < g.vertices.length; j++ ) {
+			var v = g.vertices[ j ];
+			var c = j/g.vertices.length;
+			this.positions.push( v.x, v.y, v.z );
+			this.positions.push( v.x, v.y, v.z );
+			this.counters.push(c);
+			this.counters.push(c);
+		}
+	}
+
+	if( g instanceof THREE.BufferGeometry ) {
+		// read attribute positions ?
+	}
+
+	if( g instanceof Float32Array || g instanceof Array ) {
+		for( var j = 0; j < g.length; j += 3 ) {
+			var c = j/g.length;
+			this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
+			this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] );
+			this.counters.push(c);
+			this.counters.push(c);
+		}
+	}
+
+	this.process();
+
+}
+
+MeshLine.prototype.compareV3 = function( a, b ) {
+
+	var aa = a * 6;
+	var ab = b * 6;
+	return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] );
+
+}
+
+MeshLine.prototype.copyV3 = function( a ) {
+
+	var aa = a * 6;
+	return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ];
+
+}
+
+MeshLine.prototype.process = function() {
+
+	var l = this.positions.length / 6;
+
+	this.previous = [];
+	this.next = [];
+	this.side = [];
+	this.width = [];
+	this.indices_array = [];
+	this.uvs = [];
+
+	for( var j = 0; j < l; j++ ) {
+		this.side.push( 1 );
+		this.side.push( -1 );
+	}
+
+	var w;
+	for( var j = 0; j < l; j++ ) {
+		if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) );
+		else w = 1;
+		this.width.push( w );
+		this.width.push( w );
+	}
+
+	for( var j = 0; j < l; j++ ) {
+		this.uvs.push( j / ( l - 1 ), 0 );
+		this.uvs.push( j / ( l - 1 ), 1 );
+	}
+
+	var v;
+
+	if( this.compareV3( 0, l - 1 ) ){
+		v = this.copyV3( l - 2 );
+	} else {
+		v = this.copyV3( 0 );
+	}
+	this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	for( var j = 0; j < l - 1; j++ ) {
+		v = this.copyV3( j );
+		this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+		this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	}
+
+	for( var j = 1; j < l; j++ ) {
+		v = this.copyV3( j );
+		this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+		this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	}
+
+	if( this.compareV3( l - 1, 0 ) ){
+		v = this.copyV3( 1 );
+	} else {
+		v = this.copyV3( l - 1 );
+	}
+	this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+	this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] );
+
+	for( var j = 0; j < l - 1; j++ ) {
+		var n = j * 2;
+		this.indices_array.push( n, n + 1, n + 2 );
+		this.indices_array.push( n + 2, n + 1, n + 3 );
+	}
+
+	if (!this.attributes) {
+		this.attributes = {
+			position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ),
+			previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ),
+			next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ),
+			side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ),
+			width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ),
+			uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ),
+			index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ),
+			counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 )
+		}
+	} else {
+		this.attributes.position.copyArray(new Float32Array(this.positions));
+		this.attributes.position.needsUpdate = true;
+		this.attributes.previous.copyArray(new Float32Array(this.previous));
+		this.attributes.previous.needsUpdate = true;
+		this.attributes.next.copyArray(new Float32Array(this.next));
+		this.attributes.next.needsUpdate = true;
+		this.attributes.side.copyArray(new Float32Array(this.side));
+		this.attributes.side.needsUpdate = true;
+		this.attributes.width.copyArray(new Float32Array(this.width));
+		this.attributes.width.needsUpdate = true;
+		this.attributes.uv.copyArray(new Float32Array(this.uvs));
+		this.attributes.uv.needsUpdate = true;
+		this.attributes.index.copyArray(new Uint16Array(this.indices_array));
+		this.attributes.index.needsUpdate = true;
+    }
+
+	this.geometry.addAttribute( 'position', this.attributes.position );
+	this.geometry.addAttribute( 'previous', this.attributes.previous );
+	this.geometry.addAttribute( 'next', this.attributes.next );
+	this.geometry.addAttribute( 'side', this.attributes.side );
+	this.geometry.addAttribute( 'width', this.attributes.width );
+	this.geometry.addAttribute( 'uv', this.attributes.uv );
+	this.geometry.addAttribute( 'counters', this.attributes.counters );
+
+	this.geometry.setIndex( this.attributes.index );
+
+}
+
+function memcpy (src, srcOffset, dst, dstOffset, length) {
+	var i
+
+	src = src.subarray || src.slice ? src : src.buffer
+	dst = dst.subarray || dst.slice ? dst : dst.buffer
+
+	src = srcOffset ? src.subarray ?
+	src.subarray(srcOffset, length && srcOffset + length) :
+	src.slice(srcOffset, length && srcOffset + length) : src
+
+	if (dst.set) {
+		dst.set(src, dstOffset)
+	} else {
+		for (i=0; i<src.length; i++) {
+			dst[i + dstOffset] = src[i]
+		}
+	}
+
+	return dst
+}
+
+/**
+ * Fast method to advance the line by one position.  The oldest position is removed.
+ * @param position
+ */
+MeshLine.prototype.advance = function(position) {
+
+	var positions = this.attributes.position.array;
+	var previous = this.attributes.previous.array;
+	var next = this.attributes.next.array;
+	var l = positions.length;
+
+	// PREVIOUS
+	memcpy( positions, 0, previous, 0, l );
+
+	// POSITIONS
+	memcpy( positions, 6, positions, 0, l - 6 );
+
+	positions[l - 6] = position.x;
+	positions[l - 5] = position.y;
+	positions[l - 4] = position.z;
+	positions[l - 3] = position.x;
+	positions[l - 2] = position.y;
+	positions[l - 1] = position.z;
+
+    // NEXT
+	memcpy( positions, 6, next, 0, l - 6 );
+
+	next[l - 6]  = position.x;
+	next[l - 5]  = position.y;
+	next[l - 4]  = position.z;
+	next[l - 3]  = position.x;
+	next[l - 2]  = position.y;
+	next[l - 1]  = position.z;
+
+	this.attributes.position.needsUpdate = true;
+	this.attributes.previous.needsUpdate = true;
+	this.attributes.next.needsUpdate = true;
+
+};
+
+function MeshLineMaterial( parameters ) {
+
+	var vertexShaderSource = [
+'precision highp float;',
+'',
+'attribute vec3 position;',
+'attribute vec3 previous;',
+'attribute vec3 next;',
+'attribute float side;',
+'attribute float width;',
+'attribute vec2 uv;',
+'attribute float counters;',
+'',
+'uniform mat4 projectionMatrix;',
+'uniform mat4 modelViewMatrix;',
+'uniform vec2 resolution;',
+'uniform float lineWidth;',
+'uniform vec3 color;',
+'uniform float opacity;',
+'uniform float near;',
+'uniform float far;',
+'uniform float sizeAttenuation;',
+'',
+'varying vec2 vUV;',
+'varying vec4 vColor;',
+'varying float vCounters;',
+'',
+'vec2 fix( vec4 i, float aspect ) {',
+'',
+'    vec2 res = i.xy / i.w;',
+'    res.x *= aspect;',
+'	 vCounters = counters;',
+'    return res;',
+'',
+'}',
+'',
+'void main() {',
+'',
+'    float aspect = resolution.x / resolution.y;',
+'	 float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);',
+'',
+'    vColor = vec4( color, opacity );',
+'    vUV = uv;',
+'',
+'    mat4 m = projectionMatrix * modelViewMatrix;',
+'    vec4 finalPosition = m * vec4( position, 1.0 );',
+'    vec4 prevPos = m * vec4( previous, 1.0 );',
+'    vec4 nextPos = m * vec4( next, 1.0 );',
+'',
+'    vec2 currentP = fix( finalPosition, aspect );',
+'    vec2 prevP = fix( prevPos, aspect );',
+'    vec2 nextP = fix( nextPos, aspect );',
+'',
+'	 float pixelWidth = finalPosition.w * pixelWidthRatio;',
+'    float w = 1.8 * pixelWidth * lineWidth * width;',
+'',
+'    if( sizeAttenuation == 1. ) {',
+'        w = 1.8 * lineWidth * width;',
+'    }',
+'',
+'    vec2 dir;',
+'    if( nextP == currentP ) dir = normalize( currentP - prevP );',
+'    else if( prevP == currentP ) dir = normalize( nextP - currentP );',
+'    else {',
+'        vec2 dir1 = normalize( currentP - prevP );',
+'        vec2 dir2 = normalize( nextP - currentP );',
+'        dir = normalize( dir1 + dir2 );',
+'',
+'        vec2 perp = vec2( -dir1.y, dir1.x );',
+'        vec2 miter = vec2( -dir.y, dir.x );',
+'        //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );',
+'',
+'    }',
+'',
+'    //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;',
+'    vec2 normal = vec2( -dir.y, dir.x );',
+'    normal.x /= aspect;',
+'    normal *= .5 * w;',
+'',
+'    vec4 offset = vec4( normal * side, 0.0, 1.0 );',
+'    finalPosition.xy += offset.xy;',
+'',
+'    gl_Position = finalPosition;',
+'',
+'}' ];
+
+	var fragmentShaderSource = [
+		'#extension GL_OES_standard_derivatives : enable',
+'precision mediump float;',
+'',
+'uniform sampler2D map;',
+'uniform sampler2D alphaMap;',
+'uniform float useMap;',
+'uniform float useAlphaMap;',
+'uniform float useDash;',
+'uniform float dashArray;',
+'uniform float dashOffset;',
+'uniform float dashRatio;',
+'uniform float visibility;',
+'uniform float alphaTest;',
+'uniform vec2 repeat;',
+'',
+'varying vec2 vUV;',
+'varying vec4 vColor;',
+'varying float vCounters;',
+'',
+'void main() {',
+'',
+'    vec4 c = vColor;',
+'    if( useMap == 1. ) c *= texture2D( map, vUV * repeat );',
+'    if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;',
+'    if( c.a < alphaTest ) discard;',
+'    if( useDash == 1. ){',
+'        c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));',
+'    }',
+'    gl_FragColor = c;',
+'    gl_FragColor.a *= step(vCounters, visibility);',
+'}' ];
+
+	function check( v, d ) {
+		if( v === undefined ) return d;
+		return v;
+	}
+
+	THREE.Material.call( this );
+
+	parameters = parameters || {};
+
+	this.lineWidth = check( parameters.lineWidth, 1 );
+	this.map = check( parameters.map, null );
+	this.useMap = check( parameters.useMap, 0 );
+	this.alphaMap = check( parameters.alphaMap, null );
+	this.useAlphaMap = check( parameters.useAlphaMap, 0 );
+	this.color = check( parameters.color, new THREE.Color( 0xffffff ) );
+	this.opacity = check( parameters.opacity, 1 );
+	this.resolution = check( parameters.resolution, new THREE.Vector2( 1, 1 ) );
+	this.sizeAttenuation = check( parameters.sizeAttenuation, 1 );
+	this.near = check( parameters.near, 1 );
+	this.far = check( parameters.far, 1 );
+	this.dashArray = check( parameters.dashArray, 0 );
+	this.dashOffset = check( parameters.dashOffset, 0 );
+	this.dashRatio = check( parameters.dashRatio, 0.5 );
+	this.useDash = ( this.dashArray !== 0 ) ? 1 : 0;
+	this.visibility = check( parameters.visibility, 1 );
+	this.alphaTest = check( parameters.alphaTest, 0 );
+	this.repeat = check( parameters.repeat, new THREE.Vector2( 1, 1 ) );
+
+	var material = new THREE.RawShaderMaterial( {
+		uniforms:{
+			lineWidth: { type: 'f', value: this.lineWidth },
+			map: { type: 't', value: this.map },
+			useMap: { type: 'f', value: this.useMap },
+			alphaMap: { type: 't', value: this.alphaMap },
+			useAlphaMap: { type: 'f', value: this.useAlphaMap },
+			color: { type: 'c', value: this.color },
+			opacity: { type: 'f', value: this.opacity },
+			resolution: { type: 'v2', value: this.resolution },
+			sizeAttenuation: { type: 'f', value: this.sizeAttenuation },
+			near: { type: 'f', value: this.near },
+			far: { type: 'f', value: this.far },
+			dashArray: { type: 'f', value: this.dashArray },
+			dashOffset: { type: 'f', value: this.dashOffset },
+			dashRatio: { type: 'f', value: this.dashRatio },
+			useDash: { type: 'f', value: this.useDash },
+			visibility: {type: 'f', value: this.visibility},
+			alphaTest: {type: 'f', value: this.alphaTest},
+			repeat: { type: 'v2', value: this.repeat }
+		},
+		vertexShader: vertexShaderSource.join( '\r\n' ),
+		fragmentShader: fragmentShaderSource.join( '\r\n' )
+	});
+
+	delete parameters.lineWidth;
+	delete parameters.map;
+	delete parameters.useMap;
+	delete parameters.alphaMap;
+	delete parameters.useAlphaMap;
+	delete parameters.color;
+	delete parameters.opacity;
+	delete parameters.resolution;
+	delete parameters.sizeAttenuation;
+	delete parameters.near;
+	delete parameters.far;
+	delete parameters.dashArray;
+	delete parameters.dashOffset;
+	delete parameters.dashRatio;
+	delete parameters.visibility;
+	delete parameters.alphaTest;
+	delete parameters.repeat;
+
+	material.type = 'MeshLineMaterial';
+
+	material.setValues( parameters );
+
+	return material;
+
+};
+
+MeshLineMaterial.prototype = Object.create( THREE.Material.prototype );
+MeshLineMaterial.prototype.constructor = MeshLineMaterial;
+
+MeshLineMaterial.prototype.copy = function ( source ) {
+
+	THREE.Material.prototype.copy.call( this, source );
+
+	this.lineWidth = source.lineWidth;
+	this.map = source.map;
+	this.useMap = source.useMap;
+	this.alphaMap = source.alphaMap;
+	this.useAlphaMap = source.useAlphaMap;
+	this.color.copy( source.color );
+	this.opacity = source.opacity;
+	this.resolution.copy( source.resolution );
+	this.sizeAttenuation = source.sizeAttenuation;
+	this.near = source.near;
+	this.far = source.far;
+	this.dashArray.copy( source.dashArray );
+	this.dashOffset.copy( source.dashOffset );
+	this.dashRatio.copy( source.dashRatio );
+	this.useDash = source.useDash;
+	this.visibility = source.visibility;
+	this.alphaTest = source.alphaTest;
+	this.repeat.copy( source.repeat );
+
+	return this;
+
+};
+
+if( typeof exports !== 'undefined' ) {
+	if( typeof module !== 'undefined' && module.exports ) {
+		exports = module.exports = { MeshLine: MeshLine, MeshLineMaterial: MeshLineMaterial };
+	}
+	exports.MeshLine = MeshLine;
+	exports.MeshLineMaterial = MeshLineMaterial;
+}
+else {
+	root.MeshLine = MeshLine;
+	root.MeshLineMaterial = MeshLineMaterial;
+}
+
+}).call(this);

+ 627 - 0
assets/js/d3_threeD.js

@@ -0,0 +1,627 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var transformSVGPathExposed;
+
+function d3threeD(exports) {
+
+    const DEGS_TO_RADS = Math.PI / 180,
+        UNIT_SIZE = 1;
+
+    const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46,
+        MINUS = 45;
+
+
+
+    function transformSVGPath(pathStr) {
+
+        var paths = [];
+        var path = new THREE.Shape();
+
+        var idx = 1, len = pathStr.length, activeCmd,
+            x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
+            x1 = 0, x2 = 0, y1 = 0, y2 = 0,
+            rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy;
+
+        function eatNum() {
+            var sidx, c, isFloat = false, s;
+            // eat delims
+            while (idx < len) {
+                c = pathStr.charCodeAt(idx);
+                if (c !== COMMA && c !== SPACE)
+                    break;
+                idx++;
+            }
+            if (c === MINUS)
+                sidx = idx++;
+            else
+                sidx = idx;
+            // eat number
+            while (idx < len) {
+                c = pathStr.charCodeAt(idx);
+                if (DIGIT_0 <= c && c <= DIGIT_9) {
+                    idx++;
+                    continue;
+                }
+                else if (c === PERIOD) {
+                    idx++;
+                    isFloat = true;
+                    continue;
+                }
+
+                s = pathStr.substring(sidx, idx);
+                return isFloat ? parseFloat(s) : parseInt(s);
+            }
+
+            s = pathStr.substring(sidx);
+            return isFloat ? parseFloat(s) : parseInt(s);
+        }
+
+        function nextIsNum() {
+            var c;
+            // do permanently eat any delims...
+            while (idx < len) {
+                c = pathStr.charCodeAt(idx);
+                if (c !== COMMA && c !== SPACE)
+                    break;
+                idx++;
+            }
+            c = pathStr.charCodeAt(idx);
+            return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9));
+        }
+
+        var canRepeat;
+        var enteredSub = false;
+        var zSeen = false;
+        activeCmd = pathStr[0];
+
+        while (idx <= len) {
+            canRepeat = true;
+            switch (activeCmd) {
+                // moveto commands, become lineto's if repeated
+                case 'M':
+                    enteredSub = false;
+                    x = eatNum();
+                    y = eatNum();
+                    path.moveTo(x, y);
+                    activeCmd = 'L';
+                    break;
+                case 'm':
+                    x += eatNum();
+                    y += eatNum();
+                    path.moveTo(x, y);
+                    activeCmd = 'l';
+                    break;
+                case 'Z':
+                case 'z':
+                    // z is a special case. This ends a segment and starts
+                    // a new path. Since the three.js path is continuous
+                    // we should start a new path here. This also draws a
+                    // line from the current location to the start location.
+                    canRepeat = false;
+                    if (x !== firstX || y !== firstY)
+                        path.lineTo(firstX, firstY);
+
+                    paths.push(path);
+
+                    // reset the elements
+                    firstX = null;
+                    firstY = null;
+
+                    // avoid x,y being set incorrectly
+                    enteredSub = true;
+
+                    path = new THREE.Shape();
+
+                    zSeen = true;
+
+                    break;
+                // - lines!
+                case 'L':
+                case 'H':
+                case 'V':
+                    nx = (activeCmd === 'V') ? x : eatNum();
+                    ny = (activeCmd === 'H') ? y : eatNum();
+                    path.lineTo(nx, ny);
+                    x = nx;
+                    y = ny;
+                    break;
+                case 'l':
+                case 'h':
+                case 'v':
+                    nx = (activeCmd === 'v') ? x : (x + eatNum());
+                    ny = (activeCmd === 'h') ? y : (y + eatNum());
+                    path.lineTo(nx, ny);
+                    x = nx;
+                    y = ny;
+                    break;
+                // - cubic bezier
+                case 'C':
+                    x1 = eatNum(); y1 = eatNum();
+                case 'S':
+                    if (activeCmd === 'S') {
+                        x1 = 2 * x - x2; y1 = 2 * y - y2;
+                    }
+                    x2 = eatNum();
+                    y2 = eatNum();
+                    nx = eatNum();
+                    ny = eatNum();
+                    path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
+                    x = nx; y = ny;
+                    break;
+                case 'c':
+                    x1 = x + eatNum();
+                    y1 = y + eatNum();
+                case 's':
+                    if (activeCmd === 's') {
+                        x1 = 2 * x - x2;
+                        y1 = 2 * y - y2;
+                    }
+                    x2 = x + eatNum();
+                    y2 = y + eatNum();
+                    nx = x + eatNum();
+                    ny = y + eatNum();
+                    path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
+                    x = nx; y = ny;
+                    break;
+                // - quadratic bezier
+                case 'Q':
+                    x1 = eatNum(); y1 = eatNum();
+                case 'T':
+                    if (activeCmd === 'T') {
+                        x1 = 2 * x - x1;
+                        y1 = 2 * y - y1;
+                    }
+                    nx = eatNum();
+                    ny = eatNum();
+                    path.quadraticCurveTo(x1, y1, nx, ny);
+                    x = nx;
+                    y = ny;
+                    break;
+                case 'q':
+                    x1 = x + eatNum();
+                    y1 = y + eatNum();
+                case 't':
+                    if (activeCmd === 't') {
+                        x1 = 2 * x - x1;
+                        y1 = 2 * y - y1;
+                    }
+                    nx = x + eatNum();
+                    ny = y + eatNum();
+                    path.quadraticCurveTo(x1, y1, nx, ny);
+                    x = nx; y = ny;
+                    break;
+                // - elliptical arc
+                case 'A':
+                    rx = eatNum();
+                    ry = eatNum();
+                    xar = eatNum() * DEGS_TO_RADS;
+                    laf = eatNum();
+                    sf = eatNum();
+                    nx = eatNum();
+                    ny = eatNum();
+                    if (rx !== ry) {
+                        console.warn("Forcing elliptical arc to be a circular one :(",
+                            rx, ry);
+                    }
+                    // SVG implementation notes does all the math for us! woo!
+                    // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+                    // step1, using x1 as x1'
+                    x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2;
+                    y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2;
+                    // step 2, using x2 as cx'
+                    var norm = Math.sqrt(
+                        (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) /
+                            (rx*rx * y1*y1 + ry*ry * x1*x1));
+                    if (laf === sf)
+                        norm = -norm;
+                    x2 = norm * rx * y1 / ry;
+                    y2 = norm * -ry * x1 / rx;
+                    // step 3
+                    cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
+                    cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;
+
+                    var u = new THREE.Vector2(1, 0),
+                        v = new THREE.Vector2((x1 - x2) / rx,
+                            (y1 - y2) / ry);
+                    var startAng = Math.acos(u.dot(v) / u.length() / v.length());
+                    if (u.x * v.y - u.y * v.x < 0)
+                        startAng = -startAng;
+
+                    // we can reuse 'v' from start angle as our 'u' for delta angle
+                    u.x = (-x1 - x2) / rx;
+                    u.y = (-y1 - y2) / ry;
+
+                    var deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
+                    // This normalization ends up making our curves fail to triangulate...
+                    if (v.x * u.y - v.y * u.x < 0)
+                        deltaAng = -deltaAng;
+                    if (!sf && deltaAng > 0)
+                        deltaAng -= Math.PI * 2;
+                    if (sf && deltaAng < 0)
+                        deltaAng += Math.PI * 2;
+
+                    path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf);
+                    x = nx;
+                    y = ny;
+                    break;
+
+                case ' ':
+                    // if it's an empty space, just skip it, and see if we can find a real command
+                    break;
+
+                default:
+                    throw new Error("weird path command: " + activeCmd);
+            }
+            if (firstX === null && !enteredSub) {
+                firstX = x;
+                firstY = y;
+            }
+
+            // just reissue the command
+            if (canRepeat && nextIsNum())
+                continue;
+            activeCmd = pathStr[idx++];
+        }
+
+        if (zSeen) {
+            return paths;
+        } else {
+            paths.push(path);
+            return paths;
+        }
+    }
+
+    transformSVGPathExposed = transformSVGPath;
+
+    function applySVGTransform(obj, tstr) {
+
+
+        var idx = tstr.indexOf('('), len = tstr.length,
+            cmd = tstr.substring(0, idx++);
+        function eatNum() {
+            var sidx, c, isFloat = false, s;
+            // eat delims
+            while (idx < len) {
+                c = tstr.charCodeAt(idx);
+                if (c !== COMMA && c !== SPACE)
+                    break;
+                idx++;
+            }
+            if (c === MINUS)
+                sidx = idx++;
+            else
+                sidx = idx;
+            // eat number
+            while (idx < len) {
+                c = tstr.charCodeAt(idx);
+                if (DIGIT_0 <= c && c <= DIGIT_9) {
+                    idx++;
+                    continue;
+                }
+                else if (c === PERIOD) {
+                    idx++;
+                    isFloat = true;
+                    continue;
+                }
+
+                s = tstr.substring(sidx, idx);
+                return isFloat ? parseFloat(s) : parseInt(s);
+            }
+
+            s = tstr.substring(sidx);
+            return isFloat ? parseFloat(s) : parseInt(s);
+        }
+        switch (cmd) {
+            case 'translate':
+                obj.position.x = Math.floor(eatNum() * UNIT_SIZE);
+                obj.position.y = Math.floor(eatNum() * UNIT_SIZE);
+                break;
+            case 'scale':
+                obj.scale.x = Math.floor(eatNum() * UNIT_SIZE);
+                obj.scale.y = Math.floor(eatNum() * UNIT_SIZE);
+                break;
+            default:
+                console.warn("don't understand transform", tstr);
+                break;
+        }
+    }
+
+    applySVGTransformExposed = applySVGTransform;
+
+    function wrap_setAttribute(name, value) {
+    }
+    function wrap_setAttributeNS(namespace, name, value) {
+    }
+
+
+
+
+
+    var extrudeDefaults = {
+        amount: 20,
+        bevelEnabled: true,
+        material: 0,
+        extrudeMaterial: 0,
+    };
+
+
+
+
+
+    function commonSetAttribute(name, value) {
+        switch (name) {
+            case 'x':
+                this.position.x = Math.floor(value * UNIT_SIZE);
+                break;
+
+            case 'y':
+                this.position.y = Math.floor(value * UNIT_SIZE);
+                break;
+
+            case 'class':
+                this.clazz = value;
+                break;
+
+            case 'stroke':
+            case 'fill':
+                if (typeof(value) !== 'string')
+                    value = value.toString();
+                this.material.color.setHex(parseInt(value.substring(1), 16));
+                break;
+
+            case 'transform':
+                applySVGTransform(this, value);
+                break;
+
+            case 'd':
+                var shape = transformSVGPath(value),
+                    geom = shape.extrude(extrudeDefaults);
+                this.geometry = geom;
+                this.geometry.boundingSphere = {radius: 3 * UNIT_SIZE};
+                this.scale.set(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
+
+                break;
+
+            default:
+                throw new Error("no setter for: " + name);
+        }
+    }
+    function commonSetAttributeNS(namespace, name, value) {
+        this.setAttribute(name, value);
+    }
+
+    function Group(parentThing) {
+        THREE.Object3D.call(this);
+
+        this.d3class = '';
+
+        parentThing.add(this);
+    };
+    Group.prototype = new THREE.Object3D();
+    Group.prototype.constructor = Group;
+    Group.prototype.d3tag = 'g';
+    Group.prototype.setAttribute = commonSetAttribute;
+    Group.prototype.setAttributeNS = commonSetAttributeNS;
+
+    function fabGroup() {
+        return new Group(this);
+    }
+
+    function Mesh(parentThing, tag, geometry, material) {
+        THREE.Mesh.call(this, geometry, material);
+
+        this.d3tag = tag;
+        this.d3class = '';
+
+        parentThing.add(this);
+    }
+    Mesh.prototype = new THREE.Mesh();
+    Mesh.prototype.constructor = Mesh;
+    Mesh.prototype.setAttribute = commonSetAttribute;
+    Mesh.prototype.setAttributeNS = commonSetAttributeNS;
+
+
+    const SPHERE_SEGS = 16, SPHERE_RINGS = 16,
+        DEFAULT_COLOR = 0xcc0000;
+
+    var sharedSphereGeom = null,
+        sharedCubeGeom = null;
+
+    function fabSphere() {
+        if (!sharedSphereGeom)
+            sharedSphereGeom = new THREE.SphereGeometry(
+                UNIT_SIZE / 2, SPHERE_SEGS, SPHERE_RINGS);
+        var material = new THREE.MeshLambertMaterial({
+            color: DEFAULT_COLOR,
+        });
+        return new Mesh(this, 'sphere', sharedSphereGeom, material);
+    }
+
+    function fabCube() {
+        if (!sharedCubeGeom)
+            sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
+        var material = new THREE.MeshLambertMaterial({
+            color: DEFAULT_COLOR,
+        });
+        return new Mesh(this, 'cube', sharedCubeGeom, material);
+    }
+
+    function fabPath() {
+        // start with a cube that we will replace with the path once it gets created
+        if (!sharedCubeGeom)
+            sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
+        var material = new THREE.MeshLambertMaterial({
+            color: DEFAULT_COLOR,
+        });
+        return new Mesh(this, 'path', sharedCubeGeom, material);
+    }
+
+    function Scene() {
+        THREE.Scene.call(this);
+        this.renderer = null;
+        this.camera = null;
+        this.controls = null;
+        this._d3_width = null;
+        this._d3_height = null;
+    }
+    Scene.prototype = new THREE.Scene();
+    Scene.prototype.constructor = Scene;
+    Scene.prototype._setBounds = function() {
+        this.renderer.setSize(this._d3_width, this._d3_height);
+        var aspect = this.camera.aspect;
+        this.camera.position.set(
+            this._d3_width * UNIT_SIZE / 2,
+            this._d3_height * UNIT_SIZE / 2,
+            Math.max(this._d3_width * UNIT_SIZE / Math.sqrt(2),
+                this._d3_height * UNIT_SIZE / Math.sqrt(2)));
+        this.controls.target.set(this.camera.position.x, this.camera.position.y, 0);
+        console.log("camera:", this.camera.position.x, this.camera.position.y,
+            this.camera.position.z);
+
+
+
+        //this.camera.position.z = 1000;
+    };
+    Scene.prototype.setAttribute = function(name, value) {
+        switch (name) {
+            case 'width':
+                this._d3_width = value;
+                if (this._d3_height)
+                    this._setBounds();
+                break;
+            case 'height':
+                this._d3_height = value;
+                if (this._d3_width)
+                    this._setBounds();
+                break;
+        }
+    };
+
+
+
+    function fabVis() {
+        var camera, scene, controls, renderer;
+
+        // - scene
+        scene = new Scene();
+        threeJsScene = scene;
+
+        // - camera
+        camera = scene.camera = new THREE.PerspectiveCamera(
+            75,
+            window.innerWidth / window.innerHeight,
+            1, 100000);
+        /*
+         camera = scene.camera = new THREE.OrthographicCamera(
+         window.innerWidth / -2, window.innerWidth / 2,
+         window.innerHeight / 2, window.innerHeight / -2,
+         1, 50000);
+         */
+        scene.add(camera);
+
+        // - controls
+        // from misc_camera_trackball.html example
+        controls = scene.controls = new THREE.TrackballControls(camera);
+        controls.rotateSpeed = 1.0;
+        controls.zoomSpeed = 1.2;
+        controls.panSpeed = 0.8;
+
+        controls.noZoom = false;
+        controls.noPan = false;
+
+        controls.staticMoving = true;
+        controls.dynamicDampingFactor = 0.3;
+
+        controls.keys = [65, 83, 68];
+
+        controls.addEventListener('change', render);
+
+        // - light
+        /*
+         var pointLight = new THREE.PointLight(0xFFFFFF);
+         pointLight.position.set(10, 50, 130);
+         scene.add(pointLight);
+         */
+
+        var spotlight = new THREE.SpotLight(0xffffff);
+        spotlight.position.set(-50000, 50000, 100000);
+        scene.add(spotlight);
+
+        var backlight = new THREE.SpotLight(0x888888);
+        backlight.position.set(50000, -50000, -100000);
+        scene.add(backlight);
+
+        /*
+         var ambientLight = new THREE.AmbientLight(0x888888);
+         scene.add(ambientLight);
+         */
+
+        function helperPlanes(maxBound) {
+            var geom = new THREE.PlaneGeometry(maxBound, maxBound, 4, 4);
+            for (var i = 0; i < 4; i++) {
+                var color, cx, cy;
+                switch (i) {
+                    case 0:
+                        color = 0xff0000;
+                        cx = maxBound / 2;
+                        cy = maxBound / 2;
+                        break;
+                    case 1:
+                        color = 0x00ff00;
+                        cx = maxBound / 2;
+                        cy = -maxBound / 2;
+                        break;
+                    case 2:
+                        color = 0x0000ff;
+                        cx = -maxBound / 2;
+                        cy = -maxBound / 2;
+                        break;
+                    case 3:
+                        color = 0xffff00;
+                        cx = -maxBound / 2;
+                        cy = maxBound / 2;
+                        break;
+                }
+                var material = new THREE.MeshLambertMaterial({ color: color });
+                var mesh = new THREE.Mesh(geom, material);
+                mesh.position.set(cx, cy, -1);
+
+                scene.add(mesh);
+            }
+        }
+        //helperPlanes(UNIT_SIZE * 225);
+
+        // - renderer
+        renderer = scene.renderer = new THREE.WebGLRenderer({
+            // too slow...
+            //antialias: true,
+        });
+        this.appendChild( renderer.domElement );
+
+        // - stats
+        var stats = new Stats();
+        stats.domElement.style.position = 'absolute';
+        stats.domElement.style.top = '0px';
+        stats.domElement.style.zIndex = 100;
+        this.appendChild( stats.domElement );
+
+        function animate() {
+            requestAnimationFrame(animate, renderer.domElement);
+            controls.update();
+        }
+
+        function render() {
+            renderer.render(scene, camera);
+            stats.update();
+        }
+
+        animate();
+
+        return scene;
+    };
+}
+
+var $d3g = {};
+d3threeD($d3g);

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 14 - 0
assets/js/dat.gui.min.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 5 - 0
assets/js/stats.min.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 47349 - 0
assets/js/three.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 892 - 0
assets/js/three.min.js


+ 519 - 0
assets/js/threeBSP.js

@@ -0,0 +1,519 @@
+// Generated by CoffeeScript 1.6.3
+(function() {
+    var BACK, COPLANAR, EPSILON, FRONT, SPANNING, returning,
+      __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+      __slice = [].slice,
+      __hasProp = {}.hasOwnProperty,
+      __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+  
+    EPSILON = 1e-5;
+  
+    COPLANAR = 0;
+  
+    FRONT = 1;
+  
+    BACK = 2;
+  
+    SPANNING = 3;
+  
+    returning = function(value, fn) {
+      fn();
+      return value;
+    };
+  
+    window.ThreeBSP = (function() {
+      function ThreeBSP(treeIsh, matrix) {
+        this.matrix = matrix;
+        this.intersect = __bind(this.intersect, this);
+        this.union = __bind(this.union, this);
+        this.subtract = __bind(this.subtract, this);
+        this.toGeometry = __bind(this.toGeometry, this);
+        this.toMesh = __bind(this.toMesh, this);
+        this.toTree = __bind(this.toTree, this);
+        if (this.matrix == null) {
+          this.matrix = new THREE.Matrix4();
+        }
+        this.tree = this.toTree(treeIsh);
+      }
+  
+      ThreeBSP.prototype.toTree = function(treeIsh) {
+        var face, geometry, i, polygons, _fn, _i, _len, _ref,
+          _this = this;
+        if (treeIsh instanceof ThreeBSP.Node) {
+          return treeIsh;
+        }
+        polygons = [];
+        geometry = treeIsh instanceof THREE.Geometry ? treeIsh : treeIsh instanceof THREE.Mesh ? (treeIsh.updateMatrix(), this.matrix = treeIsh.matrix.clone(), treeIsh.geometry) : void 0;
+        _ref = geometry.faces;
+        _fn = function(face, i) {
+          var faceVertexUvs, idx, polygon, vIndex, vName, vertex, _j, _len1, _ref1, _ref2;
+          faceVertexUvs = (_ref1 = geometry.faceVertexUvs) != null ? _ref1[0][i] : void 0;
+          if (faceVertexUvs == null) {
+            faceVertexUvs = [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()];
+          }
+          polygon = new ThreeBSP.Polygon();
+          _ref2 = ['a', 'b', 'c', 'd'];
+          for (vIndex = _j = 0, _len1 = _ref2.length; _j < _len1; vIndex = ++_j) {
+            vName = _ref2[vIndex];
+            if ((idx = face[vName]) != null) {
+              vertex = geometry.vertices[idx];
+              vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0], new THREE.Vector2(faceVertexUvs[vIndex].x, faceVertexUvs[vIndex].y));
+              vertex.applyMatrix4(_this.matrix);
+              polygon.vertices.push(vertex);
+            }
+          }
+          return polygons.push(polygon.calculateProperties());
+        };
+        for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
+          face = _ref[i];
+          _fn(face, i);
+        }
+        return new ThreeBSP.Node(polygons);
+      };
+  
+      ThreeBSP.prototype.toMesh = function(material) {
+        var geometry, mesh,
+          _this = this;
+        if (material == null) {
+          material = new THREE.MeshNormalMaterial();
+        }
+        geometry = this.toGeometry();
+        return returning((mesh = new THREE.Mesh(geometry, material)), function() {
+          mesh.position.getPositionFromMatrix(_this.matrix);
+          return mesh.rotation.setFromRotationMatrix(_this.matrix);
+        });
+      };
+  
+      ThreeBSP.prototype.toGeometry = function() {
+        var geometry, matrix,
+          _this = this;
+        matrix = new THREE.Matrix4().getInverse(this.matrix);
+        return returning((geometry = new THREE.Geometry()), function() {
+          var face, idx, polyVerts, polygon, v, vertUvs, verts, _i, _len, _ref, _results;
+          _ref = _this.tree.allPolygons();
+          _results = [];
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            polygon = _ref[_i];
+            polyVerts = (function() {
+              var _j, _len1, _ref1, _results1;
+              _ref1 = polygon.vertices;
+              _results1 = [];
+              for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
+                v = _ref1[_j];
+                _results1.push(v.clone().applyMatrix4(matrix));
+              }
+              return _results1;
+            })();
+            _results.push((function() {
+              var _j, _ref1, _results1;
+              _results1 = [];
+              for (idx = _j = 2, _ref1 = polyVerts.length; 2 <= _ref1 ? _j < _ref1 : _j > _ref1; idx = 2 <= _ref1 ? ++_j : --_j) {
+                verts = [polyVerts[0], polyVerts[idx - 1], polyVerts[idx]];
+                vertUvs = (function() {
+                  var _k, _len1, _ref2, _ref3, _results2;
+                  _results2 = [];
+                  for (_k = 0, _len1 = verts.length; _k < _len1; _k++) {
+                    v = verts[_k];
+                    _results2.push(new THREE.Vector2((_ref2 = v.uv) != null ? _ref2.x : void 0, (_ref3 = v.uv) != null ? _ref3.y : void 0));
+                  }
+                  return _results2;
+                })();
+                face = (function(func, args, ctor) {
+                  ctor.prototype = func.prototype;
+                  var child = new ctor, result = func.apply(child, args);
+                  return Object(result) === result ? result : child;
+                })(THREE.Face3, __slice.call((function() {
+                  var _k, _len1, _results2;
+                  _results2 = [];
+                  for (_k = 0, _len1 = verts.length; _k < _len1; _k++) {
+                    v = verts[_k];
+                    _results2.push(geometry.vertices.push(v) - 1);
+                  }
+                  return _results2;
+                })()).concat([polygon.normal.clone()]), function(){});
+                geometry.faces.push(face);
+                _results1.push(geometry.faceVertexUvs[0].push(vertUvs));
+              }
+              return _results1;
+            })());
+          }
+          return _results;
+        });
+      };
+  
+      ThreeBSP.prototype.subtract = function(other) {
+        var them, us, _ref;
+        _ref = [this.tree.clone(), other.tree.clone()], us = _ref[0], them = _ref[1];
+        us.invert().clipTo(them);
+        them.clipTo(us).invert().clipTo(us).invert();
+        return new ThreeBSP(us.build(them.allPolygons()).invert(), this.matrix);
+      };
+  
+      ThreeBSP.prototype.union = function(other) {
+        var them, us, _ref;
+        _ref = [this.tree.clone(), other.tree.clone()], us = _ref[0], them = _ref[1];
+        us.clipTo(them);
+        them.clipTo(us).invert().clipTo(us).invert();
+        return new ThreeBSP(us.build(them.allPolygons()), this.matrix);
+      };
+  
+      ThreeBSP.prototype.intersect = function(other) {
+        var them, us, _ref;
+        _ref = [this.tree.clone(), other.tree.clone()], us = _ref[0], them = _ref[1];
+        them.clipTo(us.invert()).invert().clipTo(us.clipTo(them));
+        return new ThreeBSP(us.build(them.allPolygons()).invert(), this.matrix);
+      };
+  
+      return ThreeBSP;
+  
+    })();
+  
+    ThreeBSP.Vertex = (function(_super) {
+      __extends(Vertex, _super);
+  
+      function Vertex(x, y, z, normal, uv) {
+        this.normal = normal != null ? normal : new THREE.Vector3();
+        this.uv = uv != null ? uv : new THREE.Vector2();
+        this.interpolate = __bind(this.interpolate, this);
+        this.lerp = __bind(this.lerp, this);
+        Vertex.__super__.constructor.call(this, x, y, z);
+      }
+  
+      Vertex.prototype.clone = function() {
+        return new ThreeBSP.Vertex(this.x, this.y, this.z, this.normal.clone(), this.uv.clone());
+      };
+  
+      Vertex.prototype.lerp = function(v, alpha) {
+        var _this = this;
+        return returning(Vertex.__super__.lerp.apply(this, arguments), function() {
+          _this.uv.add(v.uv.clone().sub(_this.uv).multiplyScalar(alpha));
+          return _this.normal.lerp(v, alpha);
+        });
+      };
+  
+      Vertex.prototype.interpolate = function() {
+        var args, _ref;
+        args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
+        return (_ref = this.clone()).lerp.apply(_ref, args);
+      };
+  
+      return Vertex;
+  
+    })(THREE.Vector3);
+  
+    ThreeBSP.Polygon = (function() {
+      function Polygon(vertices, normal, w) {
+        this.vertices = vertices != null ? vertices : [];
+        this.normal = normal;
+        this.w = w;
+        this.subdivide = __bind(this.subdivide, this);
+        this.tessellate = __bind(this.tessellate, this);
+        this.classifySide = __bind(this.classifySide, this);
+        this.classifyVertex = __bind(this.classifyVertex, this);
+        this.invert = __bind(this.invert, this);
+        this.clone = __bind(this.clone, this);
+        this.calculateProperties = __bind(this.calculateProperties, this);
+        if (this.vertices.length) {
+          this.calculateProperties();
+        }
+      }
+  
+      Polygon.prototype.calculateProperties = function() {
+        var _this = this;
+        return returning(this, function() {
+          var a, b, c, _ref;
+          _ref = _this.vertices, a = _ref[0], b = _ref[1], c = _ref[2];
+          _this.normal = b.clone().sub(a).cross(c.clone().sub(a)).normalize();
+          return _this.w = _this.normal.clone().dot(a);
+        });
+      };
+  
+      Polygon.prototype.clone = function() {
+        var v;
+        return new ThreeBSP.Polygon((function() {
+          var _i, _len, _ref, _results;
+          _ref = this.vertices;
+          _results = [];
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            v = _ref[_i];
+            _results.push(v.clone());
+          }
+          return _results;
+        }).call(this), this.normal.clone(), this.w);
+      };
+  
+      Polygon.prototype.invert = function() {
+        var _this = this;
+        return returning(this, function() {
+          _this.normal.multiplyScalar(-1);
+          _this.w *= -1;
+          return _this.vertices.reverse();
+        });
+      };
+  
+      Polygon.prototype.classifyVertex = function(vertex) {
+        var side;
+        side = this.normal.dot(vertex) - this.w;
+        switch (false) {
+          case !(side < -EPSILON):
+            return BACK;
+          case !(side > EPSILON):
+            return FRONT;
+          default:
+            return COPLANAR;
+        }
+      };
+  
+      Polygon.prototype.classifySide = function(polygon) {
+        var back, front, tally, v, _i, _len, _ref, _ref1,
+          _this = this;
+        _ref = [0, 0], front = _ref[0], back = _ref[1];
+        tally = function(v) {
+          switch (_this.classifyVertex(v)) {
+            case FRONT:
+              return front += 1;
+            case BACK:
+              return back += 1;
+          }
+        };
+        _ref1 = polygon.vertices;
+        for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
+          v = _ref1[_i];
+          tally(v);
+        }
+        if (front > 0 && back === 0) {
+          return FRONT;
+        }
+        if (front === 0 && back > 0) {
+          return BACK;
+        }
+        if ((front === back && back === 0)) {
+          return COPLANAR;
+        }
+        return SPANNING;
+      };
+  
+      Polygon.prototype.tessellate = function(poly) {
+        var b, count, f, i, j, polys, t, ti, tj, v, vi, vj, _i, _len, _ref, _ref1, _ref2,
+          _this = this;
+        _ref = {
+          f: [],
+          b: [],
+          count: poly.vertices.length
+        }, f = _ref.f, b = _ref.b, count = _ref.count;
+        if (this.classifySide(poly) !== SPANNING) {
+          return [poly];
+        }
+        _ref1 = poly.vertices;
+        for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
+          vi = _ref1[i];
+          vj = poly.vertices[(j = (i + 1) % count)];
+          _ref2 = (function() {
+            var _j, _len1, _ref2, _results;
+            _ref2 = [vi, vj];
+            _results = [];
+            for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
+              v = _ref2[_j];
+              _results.push(this.classifyVertex(v));
+            }
+            return _results;
+          }).call(this), ti = _ref2[0], tj = _ref2[1];
+          if (ti !== BACK) {
+            f.push(vi);
+          }
+          if (ti !== FRONT) {
+            b.push(vi);
+          }
+          if ((ti | tj) === SPANNING) {
+            t = (this.w - this.normal.dot(vi)) / this.normal.dot(vj.clone().sub(vi));
+            v = vi.interpolate(vj, t);
+            f.push(v);
+            b.push(v);
+          }
+        }
+        return returning((polys = []), function() {
+          if (f.length >= 3) {
+            polys.push(new ThreeBSP.Polygon(f));
+          }
+          if (b.length >= 3) {
+            return polys.push(new ThreeBSP.Polygon(b));
+          }
+        });
+      };
+  
+      Polygon.prototype.subdivide = function(polygon, coplanar_front, coplanar_back, front, back) {
+        var poly, side, _i, _len, _ref, _results;
+        _ref = this.tessellate(polygon);
+        _results = [];
+        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+          poly = _ref[_i];
+          side = this.classifySide(poly);
+          switch (side) {
+            case FRONT:
+              _results.push(front.push(poly));
+              break;
+            case BACK:
+              _results.push(back.push(poly));
+              break;
+            case COPLANAR:
+              if (this.normal.dot(poly.normal) > 0) {
+                _results.push(coplanar_front.push(poly));
+              } else {
+                _results.push(coplanar_back.push(poly));
+              }
+              break;
+            default:
+              throw new Error("BUG: Polygon of classification " + side + " in subdivision");
+          }
+        }
+        return _results;
+      };
+  
+      return Polygon;
+  
+    })();
+  
+    ThreeBSP.Node = (function() {
+      Node.prototype.clone = function() {
+        var node,
+          _this = this;
+        return returning((node = new ThreeBSP.Node()), function() {
+          var p, _ref, _ref1, _ref2;
+          node.divider = (_ref = _this.divider) != null ? _ref.clone() : void 0;
+          node.polygons = (function() {
+            var _i, _len, _ref1, _results;
+            _ref1 = this.polygons;
+            _results = [];
+            for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
+              p = _ref1[_i];
+              _results.push(p.clone());
+            }
+            return _results;
+          }).call(_this);
+          node.front = (_ref1 = _this.front) != null ? _ref1.clone() : void 0;
+          return node.back = (_ref2 = _this.back) != null ? _ref2.clone() : void 0;
+        });
+      };
+  
+      function Node(polygons) {
+        this.clipTo = __bind(this.clipTo, this);
+        this.clipPolygons = __bind(this.clipPolygons, this);
+        this.invert = __bind(this.invert, this);
+        this.allPolygons = __bind(this.allPolygons, this);
+        this.isConvex = __bind(this.isConvex, this);
+        this.build = __bind(this.build, this);
+        this.clone = __bind(this.clone, this);
+        this.polygons = [];
+        if ((polygons != null) && polygons.length) {
+          this.build(polygons);
+        }
+      }
+  
+      Node.prototype.build = function(polygons) {
+        var _this = this;
+        return returning(this, function() {
+          var poly, polys, side, sides, _i, _len, _results;
+          sides = {
+            front: [],
+            back: []
+          };
+          if (_this.divider == null) {
+            _this.divider = polygons[0].clone();
+          }
+          for (_i = 0, _len = polygons.length; _i < _len; _i++) {
+            poly = polygons[_i];
+            _this.divider.subdivide(poly, _this.polygons, _this.polygons, sides.front, sides.back);
+          }
+          _results = [];
+          for (side in sides) {
+            if (!__hasProp.call(sides, side)) continue;
+            polys = sides[side];
+            if (polys.length) {
+              if (_this[side] == null) {
+                _this[side] = new ThreeBSP.Node();
+              }
+              _results.push(_this[side].build(polys));
+            } else {
+              _results.push(void 0);
+            }
+          }
+          return _results;
+        });
+      };
+  
+      Node.prototype.isConvex = function(polys) {
+        var inner, outer, _i, _j, _len, _len1;
+        for (_i = 0, _len = polys.length; _i < _len; _i++) {
+          inner = polys[_i];
+          for (_j = 0, _len1 = polys.length; _j < _len1; _j++) {
+            outer = polys[_j];
+            if (inner !== outer && outer.classifySide(inner) !== BACK) {
+              return false;
+            }
+          }
+        }
+        return true;
+      };
+  
+      Node.prototype.allPolygons = function() {
+        var _ref, _ref1;
+        return this.polygons.slice().concat(((_ref1 = this.front) != null ? _ref1.allPolygons() : void 0) || []).concat(((_ref = this.back) != null ? _ref.allPolygons() : void 0) || []);
+      };
+  
+      Node.prototype.invert = function() {
+        var _this = this;
+        return returning(this, function() {
+          var flipper, poly, _i, _j, _len, _len1, _ref, _ref1, _ref2;
+          _ref = _this.polygons;
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+            poly = _ref[_i];
+            poly.invert();
+          }
+          _ref1 = [_this.divider, _this.front, _this.back];
+          for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
+            flipper = _ref1[_j];
+            if (flipper != null) {
+              flipper.invert();
+            }
+          }
+          return _ref2 = [_this.back, _this.front], _this.front = _ref2[0], _this.back = _ref2[1], _ref2;
+        });
+      };
+  
+      Node.prototype.clipPolygons = function(polygons) {
+        var back, front, poly, _i, _len;
+        if (!this.divider) {
+          return polygons.slice();
+        }
+        front = [];
+        back = [];
+        for (_i = 0, _len = polygons.length; _i < _len; _i++) {
+          poly = polygons[_i];
+          this.divider.subdivide(poly, front, back, front, back);
+        }
+        if (this.front) {
+          front = this.front.clipPolygons(front);
+        }
+        if (this.back) {
+          back = this.back.clipPolygons(back);
+        }
+        return front.concat(this.back ? back : []);
+      };
+  
+      Node.prototype.clipTo = function(node) {
+        var _this = this;
+        return returning(this, function() {
+          var _ref, _ref1;
+          _this.polygons = node.clipPolygons(_this.polygons);
+          if ((_ref = _this.front) != null) {
+            _ref.clipTo(node);
+          }
+          return (_ref1 = _this.back) != null ? _ref1.clipTo(node) : void 0;
+        });
+      };
+  
+      return Node;
+  
+    })();
+  
+  }).call(this);

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1289 - 0
assets/js/webgl-utils.js


+ 74 - 0
css/style.css

@@ -0,0 +1,74 @@
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section {
+	display: block;
+}
+body {
+	line-height: 1;
+}
+ol, ul {
+	list-style: none;
+}
+blockquote, q {
+	quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+html,body{
+  width: 100%;
+  height: 100%;
+}
+.body{
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+.left{
+  width: 50%;
+  height: 100%;
+  float: left;
+  position: relative;
+}
+.left #myCanvas{
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%,-50%)
+}
+.right{
+  width: 50%;
+  height: 100%;
+  background: #ccc;
+  float: right;
+}
+.clear{
+  clear: both;
+}

BIN
img/panorama_demo.input.jpg


BIN
img/panorama_demo.result.jpg


+ 42 - 0
index.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <title>Page Title</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <script src="https://cdn.bootcss.com/jquery/2.2.3/jquery.min.js"></script>
+    <script src="https://cdn.bootcss.com/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
+    <script src="https://cdn.bootcss.com/numbers.js/0.7.0/numbers.min.js"></script>
+    <link rel="stylesheet" href="./css/style.css" />
+  </head>
+  <body>
+    <div class="body">
+      <div class="left">
+        <canvas
+          id="myCanvas"
+          width="500"
+          height="300"
+          style="border:1px solid #d3d3d3;background:#ffffff;"
+        >
+          Your browser does not support the HTML5 canvas tag.
+        </canvas>
+      </div>
+      <div id="geo-cav" class="right">
+      </div>
+      <div class="clear"></div>
+    </div>
+    <!------------------------ 方奕卓 -------------------------->
+    <script src="assets/js/three.js"></script>
+    <script src="assets/js/stats.min.js"></script>
+    <script src="assets/js/OrbitControls.js"></script>
+    <script src="assets/js/THREE.MeshLine.js"></script>
+    <script src="assets/js/CanvasRenderer.js"></script>
+    <script src="./js/util.js"></script>
+    <script src="./js/main.js"></script>
+    <script src="model_data/data.js"></script>
+    <script src="js/determineFace.js"></script>
+    <script src="js/drawGeometry.js"></script>
+    <!----------------------- 方奕卓 end ----------------------->
+  </body>
+</html>

+ 218 - 0
js/PanoramaCamera.cpp

@@ -0,0 +1,218 @@
+// #include "../util.h"
+
+#include <csc_images.hpp.opencv.inl>
+
+using namespace CSC ;
+
+/*
+	@info: 'VAL64'是浮点数'double'
+	@info: 'LENGTH'和'INDEX'都是整形'int64'
+	@info:'ARRAY2<T>是Array<T ,ARGC<2>>,既'T[2]'
+	@info: 'Array'是动态定长容器
+	@info: 'Queue'是动态变长容器
+	@info: 'VAR_NONE'指总是无效的元素索引
+*/
+namespace panorama_demo {
+/*
+	@function: 相机在坐标原点上,返回空间点在球目图片上的投影点,和'panorama_xyz_from_txy'互逆
+	@param {xyz}: 空间中的3D点,不需要先进行归一化
+	@return {ret}: 纹理空间的2D点,范围为[0 ,1]
+*/
+inline static ARRAY2<VAL64> panorama_txy_from_xyz (const ARRAY3<VAL64> &xyz) {
+	ARRAY2<VAL64> ret ;
+	//@info: 这一步是为了兼容我们所用的球目相机的正方向,并且进行归一化
+	const auto r1x = Vector<VAL64> {-xyz[1] ,-xyz[0] ,xyz[2] ,0}.normalize () ;
+	//@info: 分别计算投影点坐标
+	//@info: 反三角函数,缩放,平移
+	ret[0] = _ATAN_ (r1x[1] ,r1x[0]) / VAL64 (VALX_PI * 2) + VAL64 (0.5) ;
+	ret[1] = _ACOS_ (r1x[2]) / VAL64 (VALX_PI * 2) * 2 ;
+	return std::move (ret) ;
+}
+
+/*
+	@function: 相机在坐标原点上,返回球目图片上的投影点所在单位球上的原点,和'panorama_txy_from_xyz'互逆
+	@param {txy}: 纹理空间的2D点,范围为[0 ,1]
+	@return {ret}: 空间中的3D点,在单位球上
+*/
+inline static ARRAY3<VAL64> panorama_xyz_from_txy (const ARRAY2<VAL64> &txy) {
+	ARRAY3<VAL64> ret ;
+	//@info: 分别计算反投影点坐标
+	//@info: 平移,缩放
+	const auto r1x = txy[1] / 2 * VAL64 (VALX_PI * 2) ;
+	const auto r2x = (txy[0] - VAL64 (0.5)) * VAL64 (VALX_PI * 2) ;
+	//@info: 对应的三角函数
+	ret[0] = -_SIN_ (r1x) * _SIN_ (r2x) ;
+	ret[1] = -_SIN_ (r1x) * _COS_ (r2x) ;
+	ret[2] = _COS_ (r1x) ;
+	return std::move (ret) ;
+}
+
+/*
+	@function: 计算栅格化所有线段后最大生成点的数量
+	@param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
+	@param {gap}: 栅格化所需的步长,既每多长生成一个点
+	@return {ret}: 最大生成点的数量
+*/
+inline static LENGTH projection_line_max_count (const Array<ARRAY2<ARRAY3<VAL64>>> &line ,VAL64 gap) {
+	LENGTH ret = 0 ;
+	//@info: 循环所有的线段
+	for (auto &i : line) {
+		//@info: 转成向量
+		const auto r1x = Vector<VAL64> {i[0] ,1} ;
+		const auto r2x = Vector<VAL64> {i[1] ,1} ;
+		//@info: 计算每条线段需要栅格化多少个点
+		//@info: 向量差,求模,除步长,向上取整
+		ret += LENGTH ((r1x - r2x).magnitude () / gap + 0.5) ;
+	}
+	printf('ret',ret)
+	return std::move (ret) ;
+}
+
+/*
+	@function: 计算3D线段在球目图片上的栅格化投影
+	@param {camera_pose_matrix}: 相机位置,外参矩阵
+	@param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
+	@param {gap}: 栅格化所需的步长,既每多长生成一个点
+	@return {ret}: 栅格化投影投影后的2D线段,点坐标属于纹理空间,范围为[0 ,1]
+*/
+inline static Queue<ARRAY2<ARRAY2<VAL64>>> panorama_projection_line (const Matrix<VAL64> &camera_pose_matrix ,const Array<ARRAY2<ARRAY3<VAL64>>> &line ,VAL64 gap) {
+	_DEBUG_ASSERT_ (gap > 0) ;
+	//@info: 计算栅格化所有线段后最大生成点的数量
+	const auto r4x = projection_line_max_count (line ,gap) ;
+	//@info: 创建容器
+	Queue<ARRAY2<ARRAY2<VAL64>>> ret = Queue<ARRAY2<ARRAY2<VAL64>>> (r4x) ;
+	//@info: 预分配元素索引,'ix'指当前线段,'iy'指上一条线段
+	INDEX ix = ret.insert () ;
+	INDEX iy = VAR_NONE ;
+	const auto r10x = camera_pose_matrix.inverse () ;
+	//@info: 循环所有的线段
+	for (auto &i : line) {
+		//@info: 转成向量,并应用相机外参矩阵的反变换
+		const auto r1x = r10x * Vector<VAL64> {i[0] ,1} ;
+		const auto r2x = r10x * Vector<VAL64> {i[1] ,1} ;
+		//@info: 计算栅格化的点数量
+		const auto r3x = (r2x - r1x).magnitude () / gap ;
+		//@info: 计算栅格化的步长向量
+		const auto r5x = (r2x - r1x).normalize () * gap ;
+		for (VAL64 j = 0 ; j < r3x ; j += 1) {
+			//@info: 计算循环栅格化的每一个3D点的坐标
+			const auto r6x = r1x + r5x * j ;
+			//@info: 计算3D点对应的2D投影
+			const auto r7x = panorama_txy_from_xyz (r6x.xyz ()) ;
+			//@info: 记录'ix'的第一个点和'iy'的第二个点
+			ret[ix][0] = r7x ;
+			if (iy != VAR_NONE)
+				ret[iy][1] = r7x ;
+			//@info: 预分配元素索引
+			iy = ix ;
+			ix = ret.insert () ;
+		}
+		//@info: 跳过一个点都没有的情况
+		if (iy == VAR_NONE)
+			continue ;
+		//@info: 对栅格化向上取整,补上最后一条线段的第二个点
+		const auto r8x = r1x + r5x * _CEIL_ (r3x ,VAL64 (1)) ;
+		const auto r9x = panorama_txy_from_xyz (r8x.xyz ()) ;
+		
+		ret[iy][1] = r9x ;
+		iy = VAR_NONE ;
+	}
+	//@info: 因为使用了预分配元素索引,最后需要弹出末尾未实际使用的元素
+	ret.pop () ;
+	return std::move (ret) ;
+}
+
+/*
+	@function: 操作图片,需要根据平台自行实现
+*/
+static const PhanRef<const AbstractImage<COLOR_BGR>::Engine> &ABSTRACTIMAGE_ENGINE = PhanRef<const AbstractImage<COLOR_BGR>::Engine>::make (_CACHE_ ([] () {
+	return AutoRef<AbstractImage_Engine_OPENCV<COLOR_BGR>>::make () ;
+})) ;
+
+/*
+	@function: 绘制线段,需要根据平台自行实现
+	@warn: 一个很重要的点,球目图片是横向循环的,既'x(width)=x(0)',因此'[{w-2,0} ,{1 ,0}]'和'[{1 ,0} ,{w-2,0}]'不同,不能直接绘制,需要变换成'[{0 ,0} ,{1 ,0}]'和'[{w-2 ,0} ,{w-1 ,0}]'进行绘制
+*/
+inline static void image_draw_line (AbstractImage<COLOR_BGR> &image ,const Queue<ARRAY2<ARRAY2<VAL64>>> &line ,LENGTH thickness ,const COLOR_BGR &color) {
+	const auto r4x = image.native<cv::Mat> () ;
+	const auto r7x = ARRAY2<VAL64> {VAL64 (_XVALUE_<cv::Mat &> (r4x).cols) ,VAL64 (_XVALUE_<cv::Mat &> (r4x).rows)} ;
+	const auto r3x = cv::Scalar {cv::Scalar::value_type (color[0]) ,cv::Scalar::value_type (color[1]) ,cv::Scalar::value_type (color[2])} ;
+	for (auto &i : line) {
+		const auto r10x = i[0][0] * r7x[0] ;
+		const auto r11x = i[0][1] * r7x[1] ;
+		const auto r12x = i[1][0] * r7x[0] ;
+		const auto r13x = i[1][1] * r7x[1] ;
+		const auto r14x = _SQE_ (r12x - r10x) ;
+		const auto r15x = _SQE_ (_ABS_ (r12x - r10x) - r7x[0]) ;
+		if (r15x >= r14x) {
+			const auto r1x = cv::Point {cv::Point::value_type (r10x) ,cv::Point::value_type (r11x)} ;
+			const auto r2x = cv::Point {cv::Point::value_type (r12x) ,cv::Point::value_type (r13x)} ;
+			cv::line (_XVALUE_<cv::Mat &> (r4x) ,r1x ,r2x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
+		} else {
+			const auto r16x = r10x < r12x ? ARRAY2<VAL64> {r12x - r7x[0] ,r13x} : ARRAY2<VAL64> {r10x - r7x[0] ,r11x} ;
+			const auto r17x = r10x < r12x ? ARRAY2<VAL64> {r10x ,r11x} : ARRAY2<VAL64> {r12x ,r13x} ;
+			const auto r18x = r10x < r12x ? ARRAY2<VAL64> {r12x ,r13x} : ARRAY2<VAL64> {r10x ,r11x} ;
+			const auto r19x = r10x < r12x ? ARRAY2<VAL64> {r10x + r7x[0] ,r11x} : ARRAY2<VAL64> {r12x + r7x[0] ,r13x} ;
+			const auto r20x = cv::Point {cv::Point::value_type (r16x[0]) ,cv::Point::value_type (r16x[1])} ;
+			const auto r21x = cv::Point {cv::Point::value_type (r17x[0]) ,cv::Point::value_type (r17x[1])} ;
+			const auto r22x = cv::Point {cv::Point::value_type (r18x[0]) ,cv::Point::value_type (r18x[1])} ;
+			const auto r23x = cv::Point {cv::Point::value_type (r19x[0]) ,cv::Point::value_type (r19x[1])} ;
+			cv::line (_XVALUE_<cv::Mat &> (r4x) ,r20x ,r21x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
+			cv::line (_XVALUE_<cv::Mat &> (r4x) ,r22x ,r23x ,r3x ,VAR32 (thickness) ,cv::LineTypes::FILLED) ;
+		}
+	}
+}
+
+/*
+	@function: 加载3D线段,需要根据平台自行实现
+	@info: 这里生成一个3D单位CUBE的所有线段
+*/
+inline static Array<ARRAY2<ARRAY3<VAL64>>> line_of_demo_cube () {
+	const auto r1x = VAL64 (0.5) ;
+	const auto r2x = ARRAY8<ARRAY3<VAL64>> ({
+		{-r1x ,-r1x ,+r1x} ,
+		{-r1x ,+r1x ,+r1x} ,
+		{+r1x ,+r1x ,+r1x} ,
+		{+r1x ,-r1x ,+r1x} ,
+		{-r1x ,-r1x ,-r1x} ,
+		{-r1x ,+r1x ,-r1x} ,
+		{+r1x ,+r1x ,-r1x} ,
+		{+r1x ,-r1x ,-r1x}}) ;
+	const auto r3x = Array<ARRAY2<INDEX>> ({
+		{0 ,1} ,
+		{1 ,2} ,
+		{2 ,3} ,
+		{3 ,0} ,
+		{4 ,5} ,
+		{5 ,6} ,
+		{6 ,7} ,
+		{7 ,4} ,
+		{0 ,4} ,
+		{1 ,5} ,
+		{2 ,6} ,
+		{3 ,7}}) ;
+	Array<ARRAY2<ARRAY3<VAL64>>> ret = Array<ARRAY2<ARRAY3<VAL64>>> (r3x.length ()) ;
+	for (INDEX i = 0 ; i < r3x.length () ; i++) {
+		ret[i][0] = r2x[r3x[i][0]] ;
+		ret[i][1] = r2x[r3x[i][1]] ;
+	}
+	return std::move (ret) ;
+}
+} ;
+
+int main () {
+	//@info: 加载3D线段
+	const auto r1x = panorama_demo::line_of_demo_cube () ;
+	//@info: 计算3D线段栅格化投影后的2D线段
+	const auto r2x = panorama_demo::panorama_projection_line (Matrix<VAL64>::make_identity () ,r1x ,0.1) ;
+	if (TRUE) {
+		auto rax = AbstractImage<COLOR_BGR> (panorama_demo::ABSTRACTIMAGE_ENGINE) ;
+		//@info: 加载图片,文件'panorama_demo.input.jpg'
+		rax.load_file (_PCSTR_ ("panorama_demo.input.jpg")) ;
+		//@info: 绘制线段
+		panorama_demo::image_draw_line (rax ,r2x ,30 ,{0 ,0 ,255}) ;
+		//@info: 保存图片,文件'panorama_demo.result.jpg'
+		rax.save_file (_PCSTR_ ("panorama_demo.result.jpg") ,AnyRef<std::vector<int>>::make (std::vector<int> {cv::IMWRITE_PNG_COMPRESSION ,5})) ;
+	}
+	return 0 ;
+}

+ 343 - 0
js/determineFace.js

@@ -0,0 +1,343 @@
+let faceAry = [];
+let keys = ['2d_point1', '2d_point2'];
+let lineKey = 'panorama_line_2d';
+let startPoint = [];
+let face = [];
+let negativeXVec = [-0.01, 0] // 以相同点为起点, 0.01为单位长度, 沿x轴负半轴求出单位向量
+let num = 0;
+let startLine = null;
+var rx1 = _3dLine(r2x, r3x, ret);
+
+var line_2d = _3dTo2d(
+    make_matrix_from_quat([
+      0.008515,
+      -0.014279,
+      0.016179,
+      0.999731,
+      -5.438891,
+      2.167653,
+      0.165233
+    ]),
+    rx1,
+    0.05
+  )['to3d']
+  console.log('====================================');
+  console.log('line_2d',line_2d);
+  console.log('====================================');
+/**
+ * 寻找所有通过point相交的线段
+ * @param {array} pointAry 
+ */
+function findIntersectLine() {
+    let lineLen = line_2d.panorama_line_2d.length;
+    console.time();
+    // 对数组中的每条线段进行循环, 从第一个点开始
+    for (let i = 0; i < lineLen; i++) {
+        let curline = line_2d[lineKey][i];
+        if (startLine && curline['3d_id'] === startLine['3d_id']) {
+            continue;
+        }
+        // console.log(i);
+        startLine = curline;
+        let curPoint = startPoint = curline['2d_point1'];
+        let nextPoint = null;
+        let faceLine = [];
+
+        while (true) {
+            let data = find2dSamePoint(curPoint);
+            let samePointNum = data.samePointNum; // 记录相同点的个数;
+            let samePointLineAry = data.samePointLineAry; // 具有相同点的线段集合
+            if (samePointNum === 1) {
+                // faceLine.indexOf(curline['3d_id']) < 0 && faceLine.push(curline['3d_id']); // 记录最后一条线段的3d_id
+                // !arrayOnly(faceAry, faceLine) && faceAry.push(faceLine);
+                // console.log(faceLine)
+                break;
+            }
+            if (samePointNum === 2) { // 仅有两条线段相交于该点
+                faceLine.indexOf(curline['3d_id']) < 0 && faceLine.push(curline['3d_id']);
+                curline = findLineBesideTwoId(samePointLineAry, curline['2d_id']); // 寻找下一线段, 并设为当前线段
+                // curPoint = curline['2d_point2'];  // 下一线段终点
+                curPoint = findNextPoint(curline, curPoint); // 查找线段中与当前点不同的另一端点
+                // console.log(curline);
+                if (arrayEquals(startPoint, curPoint)) { // 如果下一线段的终点为起始点, 则已形成闭环, 本次查找结束
+                    faceLine.indexOf(curline['3d_id']) < 0 && faceLine.push(curline['3d_id']); // 记录最后一条线段的3d_id
+                    !arrayOnly(faceAry, faceLine) && faceAry.push(faceLine);
+                    // console.log(faceLine)
+                    break;
+                }
+            }
+            if (samePointNum >= 3) { // 有3条及3条以上的线段相交于该点
+                let standardLine = findLineContainKey(samePointLineAry, curline['3d_id']); // 获取反向线段并作为基准线段
+                let endPoint = findNextPoint(standardLine, curPoint); // 查找线段中与当前点不同的另一端点
+                // if (arrayEquals(startPoint, curPoint)) { // 如果下一线段的终点为起始点, 则已形成闭环, 本次查找结束
+                //     faceLine.indexOf(curline['3d_id']) < 0 && faceLine.push(curline['3d_id']); // 记录最后一条线段的3d_id
+                //     console.log(faceLine)
+                //     break;
+                // }
+                let standardVec = getVector(standardLine, curPoint);
+                let standardAngle = getAngle(negativeXVec, standardVec, standardLine);
+                let positiveLines = findLineBesideKey(samePointLineAry, standardLine['3d_id']); // 获取正向线段的集合
+                let min_s_c_angle = 500; // 最小的标准线与比较线夹角
+                let rightLine = null; // 最右线段
+                for (let j = 0; j < positiveLines.length; j++) { // 分别求出正向线段的向量
+                    let postiveVec = getVector(positiveLines[j], curPoint);
+                    let postiveAngle = getAngle(negativeXVec, postiveVec);
+                    if (standardAngle < postiveAngle) {
+                        // let s_c_angle = 360 - (postiveAngle - standardAngle);
+                        let s_c_angle = 360 - (postiveAngle - standardAngle);
+                        // 查找最大夹角并记录最右线段
+                        s_c_angle < min_s_c_angle && (min_s_c_angle = s_c_angle, rightLine = positiveLines[j]);
+                    }
+                    if (standardAngle > postiveAngle) {
+                        // let s_c_angle = 360 - (standardAngle - postiveAngle);
+                        let s_c_angle = standardAngle - postiveAngle;
+                        s_c_angle < min_s_c_angle && (min_s_c_angle = s_c_angle, rightLine = positiveLines[j]);
+                    }
+                }
+                faceLine.indexOf(rightLine['3d_id']) < 0 && faceLine.push(rightLine['3d_id']);
+                curline = rightLine; // 寻找下一线段, 并设为当前线段
+                // curPoint = curline['2d_point2'];  // 下一线段终点
+                curPoint = findNextPoint(curline, curPoint); // 查找线段中与当前点不同的另一端点
+
+                // console.log(standardVec);
+                // return;
+            }
+
+        }
+        // num++;
+        // console.log(num)
+        // console.log(faceLine)
+        // console.log(num)
+        if (num === 500) {
+            console.log(faceLine);
+            return;
+        }
+    }
+    console.log('faceAry',faceAry);
+    console.timeEnd();
+}
+
+/**
+ * 求两向量之间的夹角
+ * @param {array} standardVec 标准向量
+ * @param {array} comparativeVec 被比较向量
+ * @param {object} comparativeLine 被比较线段, 用于判断线段属于第几象限
+ */
+function getAngle(standardVec, comparativeVec, comparativeLine) {
+    if (!standardVec || !comparativeVec) {
+        console.log('standardVec或comparativeVec未定义, 请检查');
+        return;
+    }
+    if (standardVec.length < 2 || comparativeVec.length < 2) {
+        console.log('standardVec或comparativeVec长度小于2, 请检查');
+        return;
+    }
+    let s_x = standardVec[0];
+    let s_y = standardVec[1];
+    let c_x = comparativeVec[0];
+    let c_y = comparativeVec[1];
+    // 求内积
+    let productValue = s_x * c_x + s_y * c_y;
+    // 求向量的模
+    let vs_val = Math.sqrt(s_x * s_x + s_y * s_y);
+    let vc_val = Math.sqrt(c_x * c_x + c_y * c_y);
+    // 求两向量间的cos值
+    let cos_val = productValue / (vs_val * vc_val);
+    let minAngle = Math.acos(cos_val) * 180 / Math.PI; // 求出的是与x轴负半轴之间的最小夹角
+    // console.log(angle);
+    comparativeVec[1] < 0 && (minAngle = 360 - minAngle);
+    return minAngle;
+}
+
+/**
+ * 获取线段象限
+ * @param {object} line 
+ * @returns {Boolean} true-表示正半轴, false-表示负半轴
+ */
+function getQuadrant(line) {
+    if (!line) {
+        console.log('line未定义, 请检查');
+        return;
+    }
+    (line.reversed = null || typeof line.reversed === 'undefined') && (line.reversed = false);
+    if ((line.reversed && line[keys[0]][1] > line[keys[1]][1]) || (!line.reversed && line[keys[1]][1] > line[keys[0]][1])) { // 翻转, 为point1-point2
+        return true; // true-表示正半轴
+    }
+    if ((line.reversed && line[keys[0]][1] < line[keys[1]][1]) || (!line.reversed && line[keys[1]][1] < line[keys[0]][1])) {
+        return false; // false-表示负半轴
+    }
+}
+
+/**
+ * 根据标准线段, 比较线段所处的象限来翻转角度(即360 - 最小angle)
+ * @param {object} standardLine 标准线段
+ * @param {object} comparativeLine 比较线段
+ * @param {array} samePoint 相同点
+ */
+function angleReverse(standardLine, comparativeLine, samePoint) {
+    let origin_x = samePoint[0]; // 取相同点作为原点
+    let origin_y = samePoint[1];
+    let s_x = standardLine[keys[0]][0]; // 标准线段的终点是point1
+    let s_y = standardLine[keys[0]][1];
+    let c_x = comparativeLine[keys[1]][0]; // 比较线段的终点是point2
+    let c_y = comparativeLine[keys[1]][1];
+
+    // 标准线段位于第一象限, 或位于x轴负半轴
+    if ((s_x < origin_x && s_y < origin_y) || (s_x < origin_x && s_y === origin_y)) {
+        // 比较线段位于第一象限标准线的上方, 或位于第二象限, 或位于第三象限-s_y的上面
+        if (c_y > s_y || (c_x > origin_x) || (c_x > origin_x && c_y > -s_y)) {
+            return false; // 不用进行角度翻转
+        }
+        return true;
+    }
+
+}
+
+/**
+ * 根据相同点samePoint在线段中的位置来求对应的向量
+ * @param {object} line 
+ * @param {array} samePoint 
+ */
+function getVector(line, samePoint) {
+    if (!line) {
+        console.log('line未定义, 请检查');
+        return;
+    }
+    if (!samePoint || samePoint.length < 2) {
+        console.log('samePoint未定义或长度小于2, 请检查');
+        return;
+    }
+    for (let i = 0; i < keys.length; i++) {
+        let key = keys[i];
+        // 相同点是线段的第一个点, 则 point2 - point1 为该线段向量
+        if (key === keys[0] && arrayEquals(line[key], samePoint)) {
+            let vec_x = line[keys[1]][0] - line[keys[0]][0];
+            let vec_y = line[keys[1]][1] - line[keys[0]][1];
+            let vec = [vec_x, vec_y];
+            line['vector2'] = vec;
+            return vec;
+        }
+        // 相同点是线段的第二个点, 则 point1 - point2 为该线段向量
+        if (key === keys[1] && arrayEquals(line[key], samePoint)) {
+            let vec_x = line[keys[0]][0] - line[keys[1]][0];
+            let vec_y = line[keys[0]][1] - line[keys[1]][1];
+            let vec = [vec_x, vec_y];
+            line['vector2'] = vec;
+            return vec;
+        }
+    }
+}
+
+/**
+ * 查找线段除point外的另一定点
+ * @param {object} line 
+ * @param {array} point 
+ */
+function findNextPoint(line, point) {
+    for (let i = 0; i < keys.length; i++) {
+        if (!arrayEquals(line[keys[i]], point)) {
+            return line[keys[i]];
+        }
+    }
+}
+
+/**
+ * 仅有两个相同点的情况下调用, 查找含指定twoId之外的线段
+ * @param {*} lineAry 
+ * @param {*} twoDId 
+ */
+function findLineBesideTwoId(lineAry, twoDId) {
+    if (!lineAry) {
+        console.log('lineAry未定义, 请检查');
+        return null;
+    }
+    if (typeof twoDId !== 'number') {
+        console.log('threeDId未定义, 请检查');
+        return null;
+    }
+    for (let i = 0; i < lineAry.length; i++) {
+        if (lineAry[i]['2d_id'] !== twoDId) {
+            return lineAry[i];
+        }
+    }
+
+}
+
+/**
+ * 在集合中寻找除了含threeDId之外的其他线段
+ * @param {array} lineAry 线段集合
+ * @param {number} threeDId 表示寻找正向还是反向线段
+ */
+function findLineBesideKey(lineAry, threeDId) {
+    if (!lineAry) {
+        console.log('lineAry未定义, 请检查');
+        return null;
+    }
+    if (typeof threeDId !== 'number') {
+        console.log('threeDId未定义, 请检查');
+        return null;
+    }
+    let positiveLines = [];
+    for (let i = 0; i < lineAry.length; i++) {
+        if (lineAry[i]['3d_id'] === threeDId) {
+            continue;
+        } else {
+            positiveLines.push(lineAry[i]);
+        }
+        // if (reversed && lineAry[i].reversed === reversed) {
+        //     return lineAry[i];
+        // }
+        // if (!reversed && lineAry[i].reversed === reversed) {
+        //     positiveLines.push(lineAry[i]);
+        // }
+    }
+    return positiveLines;
+}
+
+/**
+ * 在集合中查找含有threeDId的线段
+ * @param {array} lineAry 线段集合
+ * @param {number} threeDId 
+ */
+function findLineContainKey(lineAry, threeDId) {
+    if (!lineAry) {
+        console.log('lineAry未定义, 请检查');
+        return null;
+    }
+    if (typeof threeDId !== 'number') {
+        console.log('threeDId未定义, 请检查');
+        return null;
+    }
+    for (let i = 0; i < lineAry.length; i++) {
+        if (lineAry[i]['3d_id'] === threeDId) {
+            return lineAry[i];
+        }
+    }
+}
+
+/**
+ * 寻找2D线段中的相同点
+ * @param {array} curPoint 寻找与该点相交的其他点
+ */
+function find2dSamePoint(curPoint) {
+    let lineLen = line_2d.panorama_line_2d.length;
+    let samePointNum = 0; // 记录相同点的个数;
+    let samePointLineAry = []; // 具有相同点的线段集合
+    // 对于指定点, 在集合的所有线段中寻找相同点
+    for (let j = 0; j < lineLen; j++) {
+        let comparativeLine = line_2d[lineKey][j];
+        // 在线段中的两个点中寻找是否有相同点
+        for (let k = 0; k < keys.length; k++) {
+            let key = keys[k]
+            if (arrayEquals(comparativeLine[key], curPoint)) {
+                key === keys[0] ? comparativeLine.reversed = false : comparativeLine.reversed = true;
+                samePointNum++;
+                samePointLineAry.push(comparativeLine); // 记录具有相同点的线段
+            }
+        }
+    }
+    return {
+        samePointNum: samePointNum,
+        samePointLineAry: samePointLineAry
+    }
+}

+ 188 - 0
js/drawGeometry.js

@@ -0,0 +1,188 @@
+let scene, camera, renderer, controls;
+let stats = initStats();
+let key = 'points', id = 'id';
+let verticalLineAry = [];
+
+/* 初始加载 */
+(function () {
+    console.log("three start...");
+    init();
+    animate();
+    console.log("three end...");
+})();
+
+/**
+ * 初始化函数
+ */
+function init() {
+    initRenderer();
+    initScene();
+    initCamera();
+    initLight();
+    initControls();
+    drawGeometry();
+}
+
+function drawGeometry() {
+    if (!panorama_line_3d) {
+        let geometry = new THREE.geometry
+        console.log('panorama_line_3d未定义, 请检查');
+        return;
+    }
+
+    let material = new THREE.LineBasicMaterial({
+        color: 'red'
+    })
+    let geometry = new THREE.Geometry();
+
+    // 第一层的面循环
+    for (let i = 0; i < panorama_line_3d.length; i++) {
+        let face = panorama_line_3d[i];
+
+        if (face.length < 4) {
+            console.log('构成面的线段数小于4, 请检查');
+            return;
+        }
+        let sta = false;   // 用于控制寻找相同点还是不同点
+        // 第二层面中的线循环
+        for (let j = 0; j < face.length; j++) {
+            if (!face[j][key] || face[j][key].length < 2) {
+                console.log('线段' + face[j][id] + '的点数据有误, 请检查');
+                return;
+            }
+            // 绘制第一条线段
+            if (j === 0) {
+                // 第一条线段, 先寻找不同点
+                let firstPoint = findPoint(face[j][key], face[j + 1][key], false);
+                verticalLineAry.push(findverticalLine(firstPoint));
+                firstPoint && geometry.vertices.push(new THREE.Vector3(firstPoint[0], firstPoint[1], firstPoint[2]));
+                // 再寻找相同点
+                let secondPoint = findPoint(face[j][key], face[j + 1][key], true);
+                verticalLineAry.push(findverticalLine(secondPoint));
+                secondPoint && geometry.vertices.push(new THREE.Vector3(secondPoint[0], secondPoint[1], secondPoint[2]));
+            } else if (j === face.length - 1) {  // 最后一条线段, 寻找与第一条线段相交的点
+                let lastPoint = findPoint(face[j][key], face[0][key], true);
+                verticalLineAry.push(findverticalLine(lastPoint));
+                lastPoint && geometry.vertices.push(new THREE.Vector3(lastPoint[0], lastPoint[1], lastPoint[2]));
+            } else {  // 寻找与上一条线段相交的点
+                let point = findPoint(face[j][key], face[j + 1][key], true);
+                verticalLineAry.push(findverticalLine(point));
+                point && geometry.vertices.push(new THREE.Vector3(point[0], point[1], point[2]));
+            }
+        }
+
+    }
+    let line = new THREE.Line(geometry, material);
+    scene.add(line);
+    findIntersectLine();
+}
+
+/**
+ * 
+ * @param {*} point 
+ */
+function findverticalLine(point) {
+    let lineAry = [];
+    for(let i = 0; i < panorama_line_3d.length; i++) {
+        for(let j = 0; j < panorama_line_3d[i].length; j++) {
+            for(let k = 0; k < panorama_line_3d[i][j]['points'].length; k++) {
+                let o_point = panorama_line_3d[i][j]['points'][k];
+                if(arrayEquals(o_point, point)) {
+                    lineAry.push(panorama_line_3d[i][j]['id']);
+                }
+            }
+        }
+    }
+    // console.log(lineAry);
+    return lineAry;
+}
+
+
+
+/**
+ * 初始化渲染器
+ */
+function initRenderer() {
+    renderer = new THREE.WebGLRenderer({
+        antialias: true
+    });
+    renderer.setSize(window.innerWidth / 2, window.innerHeight);
+    renderer.setClearColor("#ccc");
+    renderer.shadowMap.enabled = true;
+    let rightCav = document.getElementById('geo-cav');
+    rightCav.appendChild(renderer.domElement);
+    // canvas = renderer.domElement;
+}
+
+/**
+ * 初始化场景
+ */
+function initScene() {
+    scene = new THREE.Scene();
+    // initContents();
+}
+
+/**
+ * 初始化相机
+ */
+function initCamera() {
+    camera = new THREE.PerspectiveCamera(
+        45,
+        0.5* window.innerWidth / window.innerHeight,
+        1,
+        100
+    );
+    // camera = new THREE.OrthographicCamera(3 / - 2, 3 / 2, 3 / 2, 3 / - 2, 1, 1000);
+    camera.position.set(2.5, -2, 1);
+    camera.up.x = 0;
+    camera.up.y = 0;
+    camera.up.z = 1;
+    camera.lookAt(new THREE.Vector3(0, 0, 0));
+}
+
+/**
+ * 初始化灯光
+ */
+function initLight() {
+    let ambientLight = new THREE.AmbientLight(0x333333);
+    let directionalLight = new THREE.DirectionalLight("#FFFAFA", 1);
+    directionalLight.position.set(100, -200, 200);
+    directionalLight.castShadow = true;
+    scene.add(ambientLight);
+    scene.add(directionalLight);
+}
+
+/**
+ * 初始化控制器
+ */
+function initControls() {
+    // TODO: 3D状态下启动控制器
+    controls = new THREE.OrbitControls(camera, renderer.domElement);
+}
+
+
+/**
+ * 初始化性能插件
+ */
+function initStats() {
+    let stats = new Stats();
+    stats.setMode(0);
+    stats.domElement.style.position = "absolute";
+    stats.domElement.style.left = "0";
+    stats.domElement.style.top = "0";
+    document.body.appendChild(stats.domElement);
+    return stats;
+}
+
+/* 循环调用 */
+function animate() {
+    requestAnimationFrame(animate);
+    renderer.render(scene, camera);
+    update();
+}
+
+/* 更新数据 */
+function update() {
+    stats.update();
+    controls.update();
+}

+ 239 - 0
js/main.js

@@ -0,0 +1,239 @@
+// 3d点坐标
+var r2x = [
+  // 俯视面
+  [-1.92, 0.034, 1.5],
+  [-2.92, 0.284, 1.5],
+  [-6.02, 0.284, 1.5],
+  [-6.02, 3.934, 1.5],
+  [2.53, 3.934, 1.5],
+  [2.53, 1.684, 1.5],
+  [3.13, 1.684, 1.5],
+  [3.13, 0.584, 1.5],
+  [2.23, 0.584, 1.5],
+  [2.23, 0.034, 1.5],
+
+  // 仰视面
+  [-1.92, 0.034, -1.5],
+  [-2.92, 0.284, -1.5],
+  [-6.02, 0.284, -1.5],
+  [-6.02, 3.934, -1.5],
+  [2.53, 3.934, -1.5],
+  [2.53, 1.684, -1.5],
+  [3.13, 1.684, -1.5],
+  [3.13, 0.584, -1.5],
+  [2.23, 0.584, -1.5],
+  [2.23, 0.034, -1.5]
+];
+// 顶点索引
+var r10x = 10;
+var r3x = [
+  [0, 1],
+  [1, 2],
+  [2, 3],
+  [3, 4],
+  [4, 5],
+  [5, 6],
+  [6, 7],
+  [7, 8],
+  [8, 9],
+  [9, 0],
+
+  [r10x + 0, r10x + 1],
+  [r10x + 1, r10x + 2],
+  [r10x + 2, r10x + 3],
+  [r10x + 3, r10x + 4],
+  [r10x + 4, r10x + 5],
+  [r10x + 5, r10x + 6],
+  [r10x + 6, r10x + 7],
+  [r10x + 7, r10x + 8],
+  [r10x + 8, r10x + 9],
+  [r10x + 9, r10x + 0],
+
+  [0, r10x + 0],
+  [1, r10x + 1],
+  [2, r10x + 2],
+  [3, r10x + 3],
+  [4, r10x + 4],
+  [5, r10x + 5],
+  [6, r10x + 6],
+  [7, r10x + 7],
+  [8, r10x + 8],
+  [9, r10x + 9]
+];
+// 线段
+var ret = [
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  [],
+  []
+];
+var canvas = document.getElementById("myCanvas");
+var img = new Image();
+// var identity = numbers.matrix.identity(4);
+var accuracy = 0.02;
+// 缩放精度
+var scaleAccuracy = 0.05;
+var maxX = 0;
+var faceLineArr = [];
+var isScale = false;
+var scaleTime;
+var scaleTimeH;
+
+// 各个面的数组集合
+var faceArr = [
+  [1, 22, 11, 21],
+  [12, 2, 23, 22],
+  [23, 13, 24, 3],
+  [26, 16, 27, 6],
+  [27, 17, 28, 7],
+  [28, 18, 29, 8],
+  [29, 19, 20, 9],
+  // [11,21,1,2,3,4,25,14,13,12]
+];
+
+var selectArray;
+var selectArr;
+var selectArrIdx;
+img.onload = function() {
+  draw();
+};
+img.src = "./img/panorama_demo.input.jpg";
+
+function draw() {
+  var w = img.width / 6;
+  var h = img.height / 6;
+  var deltaFactor = 1;
+  var distance = 0;
+  canvas.width = w;
+  canvas.height = h;
+  scaleTime = w;
+  scaleTimeH = h;
+  var context = canvas.getContext("2d");
+  context.strokeStyle = "#ff0000";
+  context.lineWidth = w / 250;
+  var rx1 = _3dLine(r2x, r3x, ret);
+
+  var lineArr = _3dTo2d(
+    make_matrix_from_quat([
+      0.008515,
+      -0.014279,
+      0.016179,
+      0.999731,
+      -5.438891,
+      2.167653,
+      0.165233
+    ]),
+    rx1,
+    0.1
+  )['drawArr'];
+
+  $("#myCanvas").click(function(e) {
+    // 获取当前的坐标点
+    var current = [e.offsetX, e.offsetY];
+    // 计算当前坐标点到墙面中心店的距离
+    // selectArr = selectWhichFace(lineArr,faceArr);
+    selectArray = selectWhichFace(
+      lineArr,
+      faceArr,
+      current,
+      scaleTime,
+      scaleTimeH
+    );
+    selectArr = selectArray["arr"];
+    selectArrIdx = faceArr[selectArray["id"]];
+    // selectArr = [lineArr[1],lineArr[22],lineArr[11],lineArr[21]]
+
+    // 判断当前点在墙面之内时,选中改墙体区域
+    if (isInFace(current, selectArr, scaleTime, scaleTimeH)) {
+      // if (true) {
+      isScale = true;
+      // 选中的墙进行画线
+      selectDragLine(context, w, h, scaleTime, scaleTimeH, lineArr, selectArr);
+    } else {
+      // 未选中墙体时还原线段
+      context.strokeStyle = "#ff0000";
+      drawSingleLine(context, w, h, scaleTime, scaleTimeH, lineArr, true);
+      isScale = false;
+    }
+  });
+  $("#myCanvas").mousewheel(function(e) {
+    (function() {
+      if (isScale) {
+        distance = deltaFactor * e.deltaY * scaleAccuracy;
+        lineArr = updateLineArr(distance, lineArr, r2x, selectArray["id"]);
+        selectArrIdx = faceArr[selectArray["id"]];
+        selectArr = updateSelectArr(lineArr, selectArrIdx);
+        selectDragLine(
+          context,
+          w,
+          h,
+          scaleTime,
+          scaleTimeH,
+          lineArr,
+          selectArr
+        );
+      }
+    })();
+  });
+  // 监听键盘按下时间 (上键为放大,下键为缩小)
+  $("body").keydown(function(e) {
+    if (isScale) {
+      if (e.keyCode == 38) {
+        distance = deltaFactor * 1 * scaleAccuracy;
+        lineArr = updateLineArr(distance, lineArr, r2x, selectArray["id"]);
+        selectArrIdx = faceArr[selectArray["id"]];
+        selectArr = updateSelectArr(lineArr, selectArrIdx);
+        selectDragLine(
+          context,
+          w,
+          h,
+          scaleTime,
+          scaleTimeH,
+          lineArr,
+          selectArr
+        );
+      } else if (e.keyCode == 40) {
+        distance = deltaFactor * -1 * scaleAccuracy;
+        lineArr = updateLineArr(distance, lineArr, r2x, selectArray["id"]);
+        selectArrIdx = faceArr[selectArray["id"]];
+        selectArr = updateSelectArr(lineArr, selectArrIdx);
+        selectDragLine(
+          context,
+          w,
+          h,
+          scaleTime,
+          scaleTimeH,
+          lineArr,
+          selectArr
+        );
+      }
+    }
+  });
+  drawSingleLine(context, w, h, scaleTime, scaleTimeH, lineArr, true);
+}

+ 779 - 0
js/util.js

@@ -0,0 +1,779 @@
+function isTwoLine(arr) {
+    var max = 0,
+      min = 1;
+    for (var i = 0; i < arr.length; i++) {
+      if (arr[i][0] > max) {
+        max = arr[i][0];
+      }
+      if (arr[i][0] < min) {
+        min = arr[i][0];
+      }
+    }
+    return [min === 0, max];
+  }
+  
+  /*
+      @function: 加载3D线段,需要根据平台自行实现
+      @info: 这里生成一个3D单位CUBE的所有线段
+  */
+  function _3dLine(r2x, r3x, ret) {
+    // 坐标点长度
+    var r1x = 0.5;
+    // 矩形的各个定点
+    var r2x_t = [
+      [-r1x, -r1x, +r1x],
+      [-r1x, +r1x, +r1x],
+      [+r1x, +r1x, +r1x],
+      [+r1x, -r1x, +r1x],
+      [-r1x, -r1x, -r1x],
+      [-r1x, +r1x, -r1x],
+      [+r1x, +r1x, -r1x],
+      [+r1x, -r1x, -r1x]
+    ];
+    // 线段的坐标
+    // z轴
+    var r3x_t = [
+      [0, 1],
+      [1, 2],
+      [2, 3],
+      [3, 0],
+      [4, 5],
+      [5, 6],
+      [6, 7],
+      [7, 4],
+      [0, 4],
+      [1, 5],
+      [2, 6],
+      [3, 7]
+    ];
+  
+    // 各个线段的坐标
+    var ret_t = [
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""],
+      ["", ""]
+    ];
+    ret[0][0] = r2x[r3x[0][0]];
+    for (var i = 0; i < r3x.length; i++) {
+        ret[i] = {
+            "3d_id":i,
+            "3d_point1":r2x[r3x[i][0]],
+            "3d_point2":r2x[r3x[i][1]]
+        }
+    //   ret[i][0] = r2x[r3x[i][0]];
+    //   ret[i][1] = r2x[r3x[i][1]];
+    }
+    ret_t[0][0] = r2x_t[r3x_t[0][0]];
+    for (var i = 0; i < r3x_t.length; i++) {
+      ret_t[i][0] = r2x_t[r3x_t[i][0]];
+      ret_t[i][1] = r2x_t[r3x_t[i][1]];
+    }
+  
+    console.log(ret)
+    return ret;
+  }
+  
+  // 归一化
+  function normalize(xyz, isFixed) {
+    var _mo_ = Math.sqrt(xyz[0] * xyz[0] + xyz[1] * xyz[1] + xyz[2] * xyz[2]);
+    var x = xyz[0] / _mo_;
+    var y = xyz[1] / _mo_;
+    var z = xyz[2] / _mo_;
+  
+    var r1x;
+    if (isFixed) {
+      r1x = [-y, -x, z];
+    } else {
+      r1x = [x, y, z];
+    }
+  
+    return r1x;
+  }
+  
+  /*
+      @function: 相机在坐标原点上,返回空间点在球目图片上的投影点,和'_calculate3dPoint'互逆
+      @param {xyz}: 空间中的3D点,不需要先进行归一化
+      @return {ret}: 纹理空间的2D点,范围为[0 ,1]
+  */
+  
+  function _calculate2dPoint(xyz) {
+    var ret = ["", ""];
+    //@info: 这一步是为了兼容我们所用的球目相机的正方向,并且进行归一化
+    // const auto r1x = Vector<VAL64> {-xyz[1] ,-xyz[0] ,xyz[2] ,0}.normalize () ;
+  
+    var r1x = normalize(xyz, true);
+    // var r1x = xyz
+    ret[0] = Math.atan2(r1x[1], r1x[0]) / (Math.PI * 2) + 0.5;
+  
+    ret[1] = (Math.acos(r1x[2]) / (Math.PI * 2)) * 2;
+  
+    return ret;
+  
+    // ARRAY2<VAL64> ret ;
+    // //@info: 这一步是为了兼容我们所用的球目相机的正方向,并且进行归一化
+    // const auto r1x = Vector<VAL64> {-xyz[1] ,-xyz[0] ,xyz[2] ,0}.normalize () ;
+    // //@info: 分别计算投影点坐标
+    // //@info: 反三角函数,缩放,平移
+    // ret[0] = _ATAN_ (r1x[1] , r1x[0]) / VAL64 (VALX_PI * 2) + VAL64 (0.5) ;
+    // ret[1] = _ACOS_ (r1x[2]) / VAL64 (VALX_PI * 2) * 2 ;
+    // return std::move (ret) ;
+  }
+  
+  /*
+      @function: 相机在坐标原点上,返回球目图片上的投影点所在单位球上的原点,和'_calculate2dPoint'互逆
+      @param {txy}: 纹理空间的2D点,范围为[0 ,1]
+      @return {ret}: 空间中的3D点,在单位球上
+  */
+  
+  function _calculate3dPoint(txy) {
+    var ret = ["", "", ""];
+  
+    var r1x = (txy[1] / 2) * (Math.PI * 2);
+    var r2x = (txy[0] - 0.5) * (Math.PI * 2);
+  
+    ret[0] = -Math.sin(r1x) * Math.sin(r2x);
+    ret[1] = -Math.sin(r1x) * Math.cos(r2x);
+    ret[2] = Math.cos(r1x);
+  
+    return ret;
+  }
+  
+  /*
+      @function: 计算栅格化所有线段后最大生成点的数量
+      @param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
+      @param {gap}: 栅格化所需的步长,既每多长生成一个点
+      @return {ret}: 最大生成点的数量
+  */
+  
+  function _calculateMaxPoint(line, gap) {
+    var ret = 0;
+  
+    for (var i = 0; i < line.length; i++) {
+      var r1x = line[i][0];
+      var r2x = line[i][1];
+      // Math.hypot(x1 - x0, y1 - y0);
+      ret += Math.ceil(
+        Math.hypot(r1x[0] - r2x[0], r1x[1] - r2x[1], r1x[2] - r2x[2]) / gap + 0.5
+      );
+    }
+  
+    return ret;
+  }
+  
+  /*
+      @function: 计算3D线段在球目图片上的栅格化投影
+      @param {camera_pose_matrix}: 相机位置,外参矩阵
+      @param {line}: 线段的数组,line[i][0]为第一个点,line[i][1]为第二个点
+      @param {gap}: 栅格化所需的步长,既每多长生成一个点
+      @return {ret}: 栅格化投影投影后的2D线段,点坐标属于纹理空间,范围为[0 ,1]
+  */
+  function _3dTo2d(camera_pose_matrix, line, gap) {
+    var ret = [];
+    var ret_t = [];
+    var item;
+    var r10x = numbers.matrix.inverse(camera_pose_matrix);
+  
+    for (var i = 0; i < line.length; i++) {
+      ret_t.push({ line: [] });
+      item = []
+      var r1x_temp = [[line[i]["3d_point1"][0]], [line[i]['3d_point1'][1]], [line[i]['3d_point1'][2]], [1]];
+      var r2x_temp = [[line[i]['3d_point2'][0]], [line[i]['3d_point2'][1]], [line[i]['3d_point2'][2]], [1]];
+      var r1x = numbers.matrix.multiply(r10x, r1x_temp);
+      var r2x = numbers.matrix.multiply(r10x, r2x_temp);
+      var r3x =
+          Math.hypot(r2x[0] - r1x[0], r2x[1] - r1x[1], r2x[2] - r1x[2]) / gap
+        ;
+  
+      var r2r1 = [r2x[0] - r1x[0], r2x[1] - r1x[1], r2x[2] - r1x[2]];
+      var hasNormalize = normalize(r2r1, false);
+  
+      var r5x = [
+        hasNormalize[0] * gap,
+        hasNormalize[1] * gap,
+        hasNormalize[2] * gap
+      ];
+  
+      for (var j = 0; j < r3x; j++) {
+        var r6x = [
+          parseFloat(r1x[0]) + parseFloat(r5x[0]) * j,
+          parseFloat(r1x[1]) + parseFloat(r5x[1]) * j,
+          parseFloat(r1x[2]) + parseFloat(r5x[2]) * j
+        ];
+        var r7x = _calculate2dPoint(r6x);
+        if (r7x[0] > maxX) {
+          maxX = r7x[0];
+        }
+        ret_t[i].line.push(toDecimal(r7x));
+        item.push(toDecimal(r7x))
+      }
+      var r6x_t = [
+        parseFloat(r1x[0]) + parseFloat(r5x[0]) * r3x,
+        parseFloat(r1x[1]) + parseFloat(r5x[1]) * r3x,
+        parseFloat(r1x[2]) + parseFloat(r5x[2]) * r3x
+      ];
+      var r7x_t = _calculate2dPoint(r6x_t);
+      ret_t[i].line.push(toDecimal(r7x_t));
+      item.push(toDecimal(r7x_t))
+    ret.push({
+            "3d_id":[line[i]["3d_id"]][0],  
+            '2d_arr':fixLine(item)
+        })
+    }
+
+    // ret2dObj(ret)
+    return {
+        drawArr:ret_t,
+        to3d:ret2dObj(ret)
+    };
+    // return ret_t;
+
+  }
+
+  function ret2dObj(arr) {
+      var ret =[];
+      var item;
+      var count = 0
+      for (var i = 0; i < arr.length; i++) {
+          for (var j = 0; j < arr[i]["2d_arr"].length; j++) {
+            ret.push({
+                "2d_id":count,
+                "3d_id":i,
+                "2d_point1":arr[i]["2d_arr"][j]["2d_point1"],
+                "2d_point2":arr[i]["2d_arr"][j]["2d_point2"]
+            })
+            count++
+          }
+      }
+      return {
+        panorama_line_2d:ret
+      }
+  }
+  
+  function fixLine(arr) {
+    var obj,
+      t_arr = [];
+    for (var i = 0; i < arr.length; i++) {
+      obj = {
+        "2d_point1": arr[i],
+        "2d_point2": arr[i + 1]
+      };
+      if (obj["2d_point2"]) {
+        t_arr.push(obj);
+      }
+    }
+    return t_arr;
+  }
+  
+  /**
+   * 四舍五入保留6位小数
+   * @param {数组} arr 
+   */
+  function toDecimal(arr) {
+    var xf = parseFloat(arr[0]);
+    var yf = parseFloat(arr[1]);
+  
+    if (isNaN(xf) || isNaN(yf)) {
+      return;
+    }
+    tx = Math.round(xf * 10e5) / 10e5;
+    ty = Math.round(yf * 10e5) / 10e5;
+  
+    return [tx, ty];
+  }
+  
+  function make_matrix_from_quat(quat) {
+    var ret = numbers.matrix.identity(4);
+    var r1x =
+      quat[0] * quat[0] +
+      quat[1] * quat[1] +
+      quat[2] * quat[2] +
+      quat[3] * quat[3];
+    if (r1x != 0) {
+      var x2 = (2 / r1x) * quat[0];
+      var y2 = (2 / r1x) * quat[1];
+      var z2 = (2 / r1x) * quat[2];
+      var xx = quat[0] * x2;
+      var xy = quat[0] * y2;
+      var xz = quat[0] * z2;
+      var yy = quat[1] * y2;
+      var yz = quat[1] * z2;
+      var zz = quat[2] * z2;
+      var wx = quat[3] * x2;
+      var wy = quat[3] * y2;
+      var wz = quat[3] * z2;
+  
+      ret[0][0] = 1 - (yy + zz);
+      ret[1][0] = xy - wz;
+      ret[2][0] = xz + wy;
+      ret[0][1] = xy + wz;
+      ret[1][1] = 1 - (xx + zz);
+      ret[2][1] = yz - wx;
+      ret[0][2] = xz - wy;
+      ret[1][2] = yz + wx;
+      ret[2][2] = 1 - (xx + yy);
+    }
+    // ret = numbers.matrix.multiply([[1, 0, 0], [0, 1, 0], [0, 0, 1]], transpose(ret))
+  
+    var tran = [
+      [1, 0, 0, quat[4]],
+      [0, 1, 0, quat[5]],
+      [0, 0, 1, quat[6]],
+      [0, 0, 0, 1]
+    ];
+  
+    ret = numbers.matrix.multiply(tran, transpose(ret));
+  
+    return ret;
+    //   [
+    //     [1, 0, 0, quat[4]],
+    //     [0, 1, 0, quat[5]],
+    //     [0, 0, 1, quat[6]],
+    //     [0 ,0 ,0 ,1]
+    //   ]
+  }
+  /**
+   * 矩阵转置
+   * @param {矩阵} arr
+   */
+  function transpose(arr) {
+    var arr2 = [];
+    for (var i = 0; i < arr[0].length; i++) {
+      arr2[i] = [];
+    }
+    for (var i = 0; i < arr.length; i++) {
+      for (var j = 0; j < arr[i].length; j++) {
+        arr2[j][i] = arr[i][j];
+      }
+    }
+    return arr2;
+  }
+  
+  /**
+   * 画线
+   * @param {*画布} context
+   * @param {*画布宽度 } w
+   * @param {*画布高度} h
+   * @param {*水平缩放倍数} scaleTime
+   * @param {*垂直缩放倍数} scaleTimeH
+   * @param {*线条数组} lineArr
+   * @param {*是否重画} isclear
+   */
+  function drawSingleLine(
+    context,
+    w,
+    h,
+    scaleTime,
+    scaleTimeH,
+    lineArr,
+    isclear
+  ) {
+  
+    context.beginPath();
+  
+    if (isclear) {
+      context.clearRect(0, 0, w, h);
+      context.drawImage(img, 0, 0, w, h);
+    }
+    context.moveTo(
+      lineArr[0].line[0][0] * scaleTime,
+      lineArr[0].line[0][1] * scaleTimeH
+    );
+  
+    for (var i = 0; i < lineArr.length; i++) {
+      if (isTwoLine(lineArr[i].line)[0]) {
+        for (var k = 0; k < lineArr[i].line.length; k++) {
+          if (lineArr[i].line[k][0] < 0.5) {
+            var temp = lineArr[i].line[k][0] + isTwoLine(lineArr[i].line)[1];
+            if (temp <= 1) {
+              lineArr[i].line.push([temp, lineArr[i].line[k][1]]);
+            }
+          }
+        }
+      }
+      context.moveTo(
+        lineArr[i].line[0][0] * scaleTime,
+        lineArr[i].line[0][1] * scaleTimeH
+      );
+      for (var j = 0; j < lineArr[i].line.length; j++) {
+        if (j + 1 < lineArr[i].line.length) {
+          // Math.hypot(
+          //   lineArr[i].line[j+1][0] - lineArr[i].line[j][0],
+          //   lineArr[i].line[j+1][1] - lineArr[i].line[j][1]
+          // ) < 0.05
+          if (Math.abs(lineArr[i].line[j + 1][0] - lineArr[i].line[j][0]) > 0.5) {
+            context.moveTo(
+              lineArr[i].line[j + 1][0] * scaleTime,
+              lineArr[i].line[j + 1][1] * scaleTimeH
+            );
+          } else {
+            context.lineTo(
+              lineArr[i].line[j][0] * scaleTime,
+              lineArr[i].line[j][1] * scaleTimeH
+            );
+          }
+        }
+        if (j + 1 === lineArr[i].line.length) {
+          context.lineTo(
+            lineArr[i].line[j][0] * scaleTime,
+            lineArr[i].line[j][1] * scaleTimeH
+          );
+        }
+      }
+    }
+  
+    context.stroke();
+  }
+  
+  /**
+   * 选中伸缩的墙进行画线
+   * @param {画布} context
+   * @param {画布宽度} w
+   * @param {画布高度} h
+   * @param {水平缩放倍数} scaleTime
+   * @param {垂直缩放倍数} scaleTimeH
+   * @param {线条数组} lineArr
+   */
+  function selectDragLine(context, w, h, scaleTime, scaleTimeH, lineArr, test) {
+    context.strokeStyle = "#ff0000";
+    drawSingleLine(context, w, h, scaleTime, scaleTimeH, lineArr, true);
+    context.strokeStyle = "#00ff00";
+    drawSingleLine(context, w, h, scaleTime, scaleTimeH, test, false);
+  }
+  
+  /**
+   * 找到面得中心坐标
+   * @param {线段数组} lineArr
+   * @param {水平缩放倍数} scaleTime
+   * @param {垂直缩放倍数} scaleTimeH
+   */
+  function _calculateMiddlePoint(test, scaleTime, scaleTimeH) {
+    var minX = 1,
+      minY = 1,
+      maxX = 0,
+      maxY = 0;
+    for (var i = 0; i < test.length; i++) {
+      for (var j = 0; j < test[i].line.length; j++) {
+        minX = Math.min(minX, test[i].line[j][0]);
+        minY = Math.min(minY, test[i].line[j][1]);
+        maxX = Math.max(maxX, test[i].line[j][0]);
+        maxY = Math.max(maxY, test[i].line[j][1]);
+      }
+    }
+    return [
+      ((maxX - minX) / 2 + minX) * scaleTime,
+      ((maxY - minY) / 2 + minY) * scaleTimeH
+    ];
+  }
+  
+  /**
+   * 计算墙体中心和鼠标坐标点的距离
+   * @param {当前鼠标点击的坐标点} current
+   * @param {墙体的中心点坐标} middle
+   */
+  function _calculateMiddlePointDistance(current, middle) {
+    return _calculateDistance(current, middle);
+  }
+  
+  /**
+   * 计算两点之间的距离
+   * @param {坐标点} p1
+   * @param {坐标点} p2
+   */
+  function _calculateDistance(p1, p2) {
+    return Math.sqrt(Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[0] - p1[0], 2));
+  }
+  
+  /**
+   * 判断点是否在墙体上
+   * @param {鼠标当前坐标点} current
+   * @param {墙体线段} lineArr
+   * @param {水平缩放倍数} scaleTime
+   * @param {垂直缩放倍数} scaleTimeH
+   */
+  function isInFace(current, test, scaleTime, scaleTimeH) {
+    var fixArr = [];
+    var selectYArr = [];
+    var minX = 1,
+      minY = 1,
+      maxX = 0,
+      maxY = 0;
+    for (var i = 0; i < test.length; i++) {
+      for (var j = 0; j < test[i].line.length; j++) {
+        minX = Math.min(minX, test[i].line[j][0]);
+        // minY = Math.min(minY, test[i].line[j][1]);
+        maxX = Math.max(maxX, test[i].line[j][0]);
+        // maxY = Math.max(maxY, test[i].line[j][1]);
+        fixArr.push({
+          id: parseFloat(test[i].line[j][0]).toFixed(1),
+          y: test[i].line[j][1]
+        });
+      }
+    }
+    for (var i = 0; i < fixArr.length; i++) {
+      if (fixArr[i].id == parseFloat(current[0] / scaleTime).toFixed(1)) {
+        selectYArr.push(fixArr[i].y);
+      }
+    }
+    for (var i = 0; i < selectYArr.length; i++) {
+      minY = Math.min(minY, selectYArr[i]);
+      maxY = Math.max(maxY, selectYArr[i]);
+    }
+    return (
+      current[0] > minX * scaleTime &&
+      current[0] < maxX * scaleTime &&
+      current[1] > minY * scaleTimeH &&
+      current[1] < maxY * scaleTimeH
+    );
+  }
+  
+  /**
+   * 更新墙体线段的位置
+   * @param {缩放的大小} distance
+   * @param {墙体线段} lineArr
+   */
+  function updateLineArr(distance, lineArr,_3dPoint,tag) {
+    // deltaX:值为负的(-1),则表示滚轮向左滚动。值为正的(1),则表示滚轮向右滚动。
+    // deltaY:值为负的(-1),则表示滚轮向下滚动。值为正的(1),则表示滚轮向上滚动。
+    // deltaFactor:增量因子。通过 deltaFactor * deltaX 或者 deltaFactor * deltaY 可以得到浏览器实际的滚动距离。
+    // distance = -10
+    if (tag) {
+      _3dPoint[2][0]+=distance
+      _3dPoint[3][0]+=distance
+      _3dPoint[((_3dPoint.length)/2+2)][0]+=distance
+      _3dPoint[((_3dPoint.length)/2+3)][0]+=distance
+    } else{
+      _3dPoint[1][1]+=distance
+      _3dPoint[2][1]+=distance
+      _3dPoint[((_3dPoint.length)/2+1)][1]+=distance
+      _3dPoint[((_3dPoint.length)/2+2)][1]+=distance
+    }
+    rx1 = _3dLine(r2x, r3x, ret);
+    lineArr = _3dTo2d(
+      make_matrix_from_quat([
+        0.008515,
+        -0.014279,
+        0.016179,
+        0.999731,
+        -5.438891,
+        2.167653,
+        0.165233
+      ]),
+      rx1,
+      0.05
+    )["drawArr"];
+    return lineArr;
+  }
+  
+  function updateSelectArr(lineArr,idxArr) {
+    var ret = [];
+    for (var i = 0; i < idxArr.length; i++) {
+      ret.push(lineArr[idxArr[i]])
+    }
+  
+    return ret
+  }
+  
+  function _culaculateFaceArr(lineArr, faceArr) {
+    var ret = [];
+    for (var i = 0; i < faceArr.length; i++) {
+      var item = [];
+      for (var j = 0; j < faceArr[i].length; j++) {
+        item.push(lineArr[faceArr[i][j]]);
+      }
+      ret.push({
+        id:i,
+        arr:item
+      });
+    }
+    
+    return ret;
+  }
+  
+  function selectWhichFace(lineArr, faceArr, current, scaleTime, scaleTimeH) {
+    var allFaceArr = _culaculateFaceArr(lineArr, faceArr);
+    var ret = 0;
+    var tempRet = 0;
+    var minDist =
+      faceArr &&
+      _calculateMiddlePointDistance(
+        current,
+        _calculateMiddlePoint(allFaceArr[0].arr, scaleTime, scaleTimeH)
+      );
+    for (var i = 0; i < allFaceArr.length; i++) {
+      if (isInFace(current, allFaceArr[i].arr, scaleTime, scaleTimeH)) {
+        // tempRet = i
+        ret = i
+        if (
+          _calculateMiddlePointDistance(
+            current,
+            _calculateMiddlePoint(allFaceArr[i].arr, scaleTime, scaleTimeH)
+          ) < minDist
+        ) {
+          minDist = _calculateMiddlePointDistance(
+            current,
+            _calculateMiddlePoint(allFaceArr[i].arr, scaleTime, scaleTimeH)
+          );
+        }else{
+          ret = i
+        }
+      }
+    }
+    return  allFaceArr[ret];
+  }
+  
+  
+/**
+ * 根据sta查找点
+ * @param {array} line1 第一条线段的点坐标
+ * @param {array} line2 第二条线段的点坐标
+ * @param {Boolean} sta 决定查找相同的点或不相同的点 
+ */
+function findPoint(line1, line2, sta) {
+    if (!line1 || !line2) {
+        console.log('线段未定义' + line1 + '  ' + line2);
+        return null;
+    }
+    if (line1.length < 2 || line2.length < 2) {
+        console.log('线段点坐标个数小于3, 请检查')
+        return
+    }
+    if (sta === null || typeof sta === 'undefined') {
+        sta = true;
+    }
+    // 根据sta值寻找当前线段与下一线段中符合要求的点
+    for (let i = 0; i < line1.length; i++) {
+        // sta为false, 寻找与下一线段都不相同的点
+        if (!sta && sta === arrayEquals(line1[i], line2[0]) && sta === arrayEquals(line1[i], line2[1])) {
+            
+            return line1[i];
+        }
+        // sta为true, 寻找与下一线段相交的点
+        if (sta && (sta === arrayEquals(line1[i], line2[0]) || sta === arrayEquals(line1[i], line2[1]))) {
+            return line1[i];
+        }
+    }
+    return null;
+}
+
+/**
+ * 判断两个数组是否相等
+ * @param {array} array1 
+ * @param {array} array2 
+ */
+function arrayEquals(array1, array2) {
+    if (!array1 || !array2) {
+        return false;
+    }
+    if (array1.length !== array2.length) {
+        return false;
+    }
+    for (let i = 0, len = array1.length; i < len; i++) {
+        if (array1[i] instanceof Array && array2[i] instanceof Array) {
+            if (!equals(array1[i], array2[2])) {
+                return false;
+            }
+        } else if (array1[i] !== array2[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * 判断一个数组中的元素是否都包含在另一个数组中
+ * @param {array} arrays
+ * @param {array} array2 
+ * @returns {boolean} true-表示数组元素唯一, false表示数组元素不唯一
+ */
+function arrayOnly(array1, array2) {
+    if(!array1 || !array2) {
+        console.log('数组未定义, 请检查');
+        return;
+    }
+    if(!array1.length || !array2.length || array1.length === 0) {
+        return false;
+    }
+    for(let i = 0; i < array1.length; i++) {
+        let num = 0;
+        for(let j = 0; j < array1[i].length; j++) {
+            if(array2.indexOf(array1[i][j]) >= 0) {
+                // return false;
+                num++;
+            }
+        }
+        if(num === array1[i].length) {
+            return true;
+        }
+    }
+    return false;
+}
+
+function fix3dData(arr) {
+    var ret = []
+    var idxItem
+    var idxArr = []
+   
+  
+    for (var i = 0; i < arr.length; i++) {
+        idxItem = []
+        for (var j = 0; j < arr[i].length; j++) {
+            ret.push(arr[i][j].points[0])
+            idxItem.push(arr[i][j].id)
+        }
+        idxArr.push(idxItem)
+    }
+
+    // return idxArr;
+
+    var r2x_t = [
+        [-0.5, -0.5, +0.5],
+        [-0.5, +0.5, +0.5],
+        [+0.5, +0.5, +0.5],
+        [+0.5, -0.5, +0.5],
+        [-0.5, -0.5, -0.5],
+        [-0.5, +0.5, -0.5],
+        [+0.5, +0.5, -0.5],
+        [+0.5, -0.5, -0.5]
+      ];
+      // 线段的坐标
+      // z轴
+      var r3x_t = [
+        [0, 1],
+        [1, 2],
+        [2, 3],
+        [3, 0],
+        [4, 5],
+        [5, 6],
+        [6, 7],
+        [7, 4],
+        [0, 4],
+        [1, 5],
+        [2, 6],
+        [3, 7]
+      ];
+    
+      // 各个线段的坐标
+      var ret_t = [
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""],
+        ["", ""]
+      ];
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 8248 - 0
model_data/data.js


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 8264 - 0
model_data/panorama_demo.json


+ 652 - 0
model_data/panorama_demo_simple.json

@@ -0,0 +1,652 @@
+{
+	"panorama_line_3d":[{
+		"3d_id":0,
+		"3d_point1":[-0.5 ,-0.5 ,0.5],
+		"3d_point2":[-0.5 ,0.5 ,0.5]
+	},{
+		"3d_id":1,
+		"3d_point1":[-0.5 ,0.5 ,0.5],
+		"3d_point2":[0.5 ,0.5 ,0.5]
+	},{
+		"3d_id":2,
+		"3d_point1":[0.5 ,0.5 ,0.5],
+		"3d_point2":[0.5 ,-0.5 ,0.5]
+	},{
+		"3d_id":3,
+		"3d_point1":[0.5 ,-0.5 ,0.5],
+		"3d_point2":[-0.5 ,-0.5 ,0.5]
+	},{
+		"3d_id":4,
+		"3d_point1":[-0.5 ,-0.5 ,-0.5],
+		"3d_point2":[-0.5 ,0.5 ,-0.5]
+	},{
+		"3d_id":5,
+		"3d_point1":[-0.5 ,0.5 ,-0.5],
+		"3d_point2":[0.5 ,0.5 ,-0.5]
+	},{
+		"3d_id":6,
+		"3d_point1":[0.5 ,0.5 ,-0.5],
+		"3d_point2":[0.5 ,-0.5 ,-0.5]
+	},{
+		"3d_id":7,
+		"3d_point1":[0.5 ,-0.5 ,-0.5],
+		"3d_point2":[-0.5 ,-0.5 ,-0.5]
+	},{
+		"3d_id":8,
+		"3d_point1":[-0.5 ,-0.5 ,0.5],
+		"3d_point2":[-0.5 ,-0.5 ,-0.5]
+	},{
+		"3d_id":9,
+		"3d_point1":[-0.5 ,0.5 ,0.5],
+		"3d_point2":[-0.5 ,0.5 ,-0.5]
+	},{
+		"3d_id":10,
+		"3d_point1":[0.5 ,0.5 ,0.5],
+		"3d_point2":[0.5 ,0.5 ,-0.5]
+	},{
+		"3d_id":11,
+		"3d_point1":[0.5 ,-0.5 ,0.5],
+		"3d_point2":[0.5 ,-0.5 ,-0.5]
+	}],
+	"panorama_line_2d":[{
+		"2d_id":0,
+		"3d_id":0,
+		"2d_point1":[0.625 ,0.304087],
+		"2d_point2":[0.642612 ,0.288971]
+	},{
+		"2d_id":1,
+		"3d_id":0,
+		"2d_point1":[0.642612 ,0.288971],
+		"2d_point2":[0.66399 ,0.274373]
+	},{
+		"2d_id":2,
+		"3d_id":0,
+		"2d_point1":[0.66399 ,0.274373],
+		"2d_point2":[0.689441 ,0.2618]
+	},{
+		"2d_id":3,
+		"3d_id":0,
+		"2d_point1":[0.689441 ,0.2618],
+		"2d_point2":[0.718584 ,0.253121]
+	},{
+		"2d_id":4,
+		"3d_id":0,
+		"2d_point1":[0.718584 ,0.253121],
+		"2d_point2":[0.75 ,0.25]
+	},{
+		"2d_id":5,
+		"3d_id":0,
+		"2d_point1":[0.75 ,0.25],
+		"2d_point2":[0.781416 ,0.253121]
+	},{
+		"2d_id":6,
+		"3d_id":0,
+		"2d_point1":[0.781416 ,0.253121],
+		"2d_point2":[0.810559 ,0.2618]
+	},{
+		"2d_id":7,
+		"3d_id":0,
+		"2d_point1":[0.810559 ,0.2618],
+		"2d_point2":[0.83601 ,0.274373]
+	},{
+		"2d_id":8,
+		"3d_id":0,
+		"2d_point1":[0.83601 ,0.274373],
+		"2d_point2":[0.857388 ,0.288971]
+	},{
+		"2d_id":9,
+		"3d_id":0,
+		"2d_point1":[0.857388 ,0.288971],
+		"2d_point2":[0.875 ,0.304087]
+	},{
+		"2d_id":10,
+		"3d_id":1,
+		"2d_point1":[0.875 ,0.304087],
+		"2d_point2":[0.892612 ,0.288971]
+	},{
+		"2d_id":11,
+		"3d_id":1,
+		"2d_point1":[0.892612 ,0.288971],
+		"2d_point2":[0.91399 ,0.274373]
+	},{
+		"2d_id":12,
+		"3d_id":1,
+		"2d_point1":[0.91399 ,0.274373],
+		"2d_point2":[0.939441 ,0.2618]
+	},{
+		"2d_id":13,
+		"3d_id":1,
+		"2d_point1":[0.939441 ,0.2618],
+		"2d_point2":[0.968584 ,0.253121]
+	},{
+		"2d_id":14,
+		"3d_id":1,
+		"2d_point1":[0.968584 ,0.253121],
+		"2d_point2":[0 ,0.25]
+	},{
+		"2d_id":15,
+		"3d_id":1,
+		"2d_point1":[0 ,0.25],
+		"2d_point2":[0.031416 ,0.253121]
+	},{
+		"2d_id":16,
+		"3d_id":1,
+		"2d_point1":[0.031416 ,0.253121],
+		"2d_point2":[0.060559 ,0.2618]
+	},{
+		"2d_id":17,
+		"3d_id":1,
+		"2d_point1":[0.060559 ,0.2618],
+		"2d_point2":[0.08601 ,0.274373]
+	},{
+		"2d_id":18,
+		"3d_id":1,
+		"2d_point1":[0.08601 ,0.274373],
+		"2d_point2":[0.107388 ,0.288971]
+	},{
+		"2d_id":19,
+		"3d_id":1,
+		"2d_point1":[0.107388 ,0.288971],
+		"2d_point2":[0.125 ,0.304087]
+	},{
+		"2d_id":20,
+		"3d_id":2,
+		"2d_point1":[0.125 ,0.304087],
+		"2d_point2":[0.142612 ,0.288971]
+	},{
+		"2d_id":21,
+		"3d_id":2,
+		"2d_point1":[0.142612 ,0.288971],
+		"2d_point2":[0.16399 ,0.274373]
+	},{
+		"2d_id":22,
+		"3d_id":2,
+		"2d_point1":[0.16399 ,0.274373],
+		"2d_point2":[0.189441 ,0.2618]
+	},{
+		"2d_id":23,
+		"3d_id":2,
+		"2d_point1":[0.189441 ,0.2618],
+		"2d_point2":[0.218584 ,0.253121]
+	},{
+		"2d_id":24,
+		"3d_id":2,
+		"2d_point1":[0.218584 ,0.253121],
+		"2d_point2":[0.25 ,0.25]
+	},{
+		"2d_id":25,
+		"3d_id":2,
+		"2d_point1":[0.25 ,0.25],
+		"2d_point2":[0.281416 ,0.253121]
+	},{
+		"2d_id":26,
+		"3d_id":2,
+		"2d_point1":[0.281416 ,0.253121],
+		"2d_point2":[0.310559 ,0.2618]
+	},{
+		"2d_id":27,
+		"3d_id":2,
+		"2d_point1":[0.310559 ,0.2618],
+		"2d_point2":[0.33601 ,0.274373]
+	},{
+		"2d_id":28,
+		"3d_id":2,
+		"2d_point1":[0.33601 ,0.274373],
+		"2d_point2":[0.357388 ,0.288971]
+	},{
+		"2d_id":29,
+		"3d_id":2,
+		"2d_point1":[0.357388 ,0.288971],
+		"2d_point2":[0.375 ,0.304087]
+	},{
+		"2d_id":30,
+		"3d_id":3,
+		"2d_point1":[0.375 ,0.304087],
+		"2d_point2":[0.392612 ,0.288971]
+	},{
+		"2d_id":31,
+		"3d_id":3,
+		"2d_point1":[0.392612 ,0.288971],
+		"2d_point2":[0.41399 ,0.274373]
+	},{
+		"2d_id":32,
+		"3d_id":3,
+		"2d_point1":[0.41399 ,0.274373],
+		"2d_point2":[0.439441 ,0.2618]
+	},{
+		"2d_id":33,
+		"3d_id":3,
+		"2d_point1":[0.439441 ,0.2618],
+		"2d_point2":[0.468584 ,0.253121]
+	},{
+		"2d_id":34,
+		"3d_id":3,
+		"2d_point1":[0.468584 ,0.253121],
+		"2d_point2":[0.5 ,0.25]
+	},{
+		"2d_id":35,
+		"3d_id":3,
+		"2d_point1":[0.5 ,0.25],
+		"2d_point2":[0.531416 ,0.253121]
+	},{
+		"2d_id":36,
+		"3d_id":3,
+		"2d_point1":[0.531416 ,0.253121],
+		"2d_point2":[0.560559 ,0.2618]
+	},{
+		"2d_id":37,
+		"3d_id":3,
+		"2d_point1":[0.560559 ,0.2618],
+		"2d_point2":[0.58601 ,0.274373]
+	},{
+		"2d_id":38,
+		"3d_id":3,
+		"2d_point1":[0.58601 ,0.274373],
+		"2d_point2":[0.607388 ,0.288971]
+	},{
+		"2d_id":39,
+		"3d_id":3,
+		"2d_point1":[0.607388 ,0.288971],
+		"2d_point2":[0.625 ,0.304087]
+	},{
+		"2d_id":40,
+		"3d_id":4,
+		"2d_point1":[0.625 ,0.695913],
+		"2d_point2":[0.642612 ,0.711029]
+	},{
+		"2d_id":41,
+		"3d_id":4,
+		"2d_point1":[0.642612 ,0.711029],
+		"2d_point2":[0.66399 ,0.725627]
+	},{
+		"2d_id":42,
+		"3d_id":4,
+		"2d_point1":[0.66399 ,0.725627],
+		"2d_point2":[0.689441 ,0.7382]
+	},{
+		"2d_id":43,
+		"3d_id":4,
+		"2d_point1":[0.689441 ,0.7382],
+		"2d_point2":[0.718584 ,0.746879]
+	},{
+		"2d_id":44,
+		"3d_id":4,
+		"2d_point1":[0.718584 ,0.746879],
+		"2d_point2":[0.75 ,0.75]
+	},{
+		"2d_id":45,
+		"3d_id":4,
+		"2d_point1":[0.75 ,0.75],
+		"2d_point2":[0.781416 ,0.746879]
+	},{
+		"2d_id":46,
+		"3d_id":4,
+		"2d_point1":[0.781416 ,0.746879],
+		"2d_point2":[0.810559 ,0.7382]
+	},{
+		"2d_id":47,
+		"3d_id":4,
+		"2d_point1":[0.810559 ,0.7382],
+		"2d_point2":[0.83601 ,0.725627]
+	},{
+		"2d_id":48,
+		"3d_id":4,
+		"2d_point1":[0.83601 ,0.725627],
+		"2d_point2":[0.857388 ,0.711029]
+	},{
+		"2d_id":49,
+		"3d_id":4,
+		"2d_point1":[0.857388 ,0.711029],
+		"2d_point2":[0.875 ,0.695913]
+	},{
+		"2d_id":50,
+		"3d_id":5,
+		"2d_point1":[0.875 ,0.695913],
+		"2d_point2":[0.892612 ,0.711029]
+	},{
+		"2d_id":51,
+		"3d_id":5,
+		"2d_point1":[0.892612 ,0.711029],
+		"2d_point2":[0.91399 ,0.725627]
+	},{
+		"2d_id":52,
+		"3d_id":5,
+		"2d_point1":[0.91399 ,0.725627],
+		"2d_point2":[0.939441 ,0.7382]
+	},{
+		"2d_id":53,
+		"3d_id":5,
+		"2d_point1":[0.939441 ,0.7382],
+		"2d_point2":[0.968584 ,0.746879]
+	},{
+		"2d_id":54,
+		"3d_id":5,
+		"2d_point1":[0.968584 ,0.746879],
+		"2d_point2":[0 ,0.75]
+	},{
+		"2d_id":55,
+		"3d_id":5,
+		"2d_point1":[0 ,0.75],
+		"2d_point2":[0.031416 ,0.746879]
+	},{
+		"2d_id":56,
+		"3d_id":5,
+		"2d_point1":[0.031416 ,0.746879],
+		"2d_point2":[0.060559 ,0.7382]
+	},{
+		"2d_id":57,
+		"3d_id":5,
+		"2d_point1":[0.060559 ,0.7382],
+		"2d_point2":[0.08601 ,0.725627]
+	},{
+		"2d_id":58,
+		"3d_id":5,
+		"2d_point1":[0.08601 ,0.725627],
+		"2d_point2":[0.107388 ,0.711029]
+	},{
+		"2d_id":59,
+		"3d_id":5,
+		"2d_point1":[0.107388 ,0.711029],
+		"2d_point2":[0.125 ,0.695913]
+	},{
+		"2d_id":60,
+		"3d_id":6,
+		"2d_point1":[0.125 ,0.695913],
+		"2d_point2":[0.142612 ,0.711029]
+	},{
+		"2d_id":61,
+		"3d_id":6,
+		"2d_point1":[0.142612 ,0.711029],
+		"2d_point2":[0.16399 ,0.725627]
+	},{
+		"2d_id":62,
+		"3d_id":6,
+		"2d_point1":[0.16399 ,0.725627],
+		"2d_point2":[0.189441 ,0.7382]
+	},{
+		"2d_id":63,
+		"3d_id":6,
+		"2d_point1":[0.189441 ,0.7382],
+		"2d_point2":[0.218584 ,0.746879]
+	},{
+		"2d_id":64,
+		"3d_id":6,
+		"2d_point1":[0.218584 ,0.746879],
+		"2d_point2":[0.25 ,0.75]
+	},{
+		"2d_id":65,
+		"3d_id":6,
+		"2d_point1":[0.25 ,0.75],
+		"2d_point2":[0.281416 ,0.746879]
+	},{
+		"2d_id":66,
+		"3d_id":6,
+		"2d_point1":[0.281416 ,0.746879],
+		"2d_point2":[0.310559 ,0.7382]
+	},{
+		"2d_id":67,
+		"3d_id":6,
+		"2d_point1":[0.310559 ,0.7382],
+		"2d_point2":[0.33601 ,0.725627]
+	},{
+		"2d_id":68,
+		"3d_id":6,
+		"2d_point1":[0.33601 ,0.725627],
+		"2d_point2":[0.357388 ,0.711029]
+	},{
+		"2d_id":69,
+		"3d_id":6,
+		"2d_point1":[0.357388 ,0.711029],
+		"2d_point2":[0.375 ,0.695913]
+	},{
+		"2d_id":70,
+		"3d_id":7,
+		"2d_point1":[0.375 ,0.695913],
+		"2d_point2":[0.392612 ,0.711029]
+	},{
+		"2d_id":71,
+		"3d_id":7,
+		"2d_point1":[0.392612 ,0.711029],
+		"2d_point2":[0.41399 ,0.725627]
+	},{
+		"2d_id":72,
+		"3d_id":7,
+		"2d_point1":[0.41399 ,0.725627],
+		"2d_point2":[0.439441 ,0.7382]
+	},{
+		"2d_id":73,
+		"3d_id":7,
+		"2d_point1":[0.439441 ,0.7382],
+		"2d_point2":[0.468584 ,0.746879]
+	},{
+		"2d_id":74,
+		"3d_id":7,
+		"2d_point1":[0.468584 ,0.746879],
+		"2d_point2":[0.5 ,0.75]
+	},{
+		"2d_id":75,
+		"3d_id":7,
+		"2d_point1":[0.5 ,0.75],
+		"2d_point2":[0.531416 ,0.746879]
+	},{
+		"2d_id":76,
+		"3d_id":7,
+		"2d_point1":[0.531416 ,0.746879],
+		"2d_point2":[0.560559 ,0.7382]
+	},{
+		"2d_id":77,
+		"3d_id":7,
+		"2d_point1":[0.560559 ,0.7382],
+		"2d_point2":[0.58601 ,0.725627]
+	},{
+		"2d_id":78,
+		"3d_id":7,
+		"2d_point1":[0.58601 ,0.725627],
+		"2d_point2":[0.607388 ,0.711029]
+	},{
+		"2d_id":79,
+		"3d_id":7,
+		"2d_point1":[0.607388 ,0.711029],
+		"2d_point2":[0.625 ,0.695913]
+	},{
+		"2d_id":80,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.304087],
+		"2d_point2":[0.625 ,0.336132]
+	},{
+		"2d_id":81,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.336132],
+		"2d_point2":[0.625 ,0.372279]
+	},{
+		"2d_id":82,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.372279],
+		"2d_point2":[0.625 ,0.41226]
+	},{
+		"2d_id":83,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.41226],
+		"2d_point2":[0.625 ,0.455281]
+	},{
+		"2d_id":84,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.455281],
+		"2d_point2":[0.625 ,0.5]
+	},{
+		"2d_id":85,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.5],
+		"2d_point2":[0.625 ,0.544719]
+	},{
+		"2d_id":86,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.544719],
+		"2d_point2":[0.625 ,0.58774]
+	},{
+		"2d_id":87,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.58774],
+		"2d_point2":[0.625 ,0.627721]
+	},{
+		"2d_id":88,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.627721],
+		"2d_point2":[0.625 ,0.663868]
+	},{
+		"2d_id":89,
+		"3d_id":8,
+		"2d_point1":[0.625 ,0.663868],
+		"2d_point2":[0.625 ,0.695913]
+	},{
+		"2d_id":90,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.304087],
+		"2d_point2":[0.875 ,0.336132]
+	},{
+		"2d_id":91,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.336132],
+		"2d_point2":[0.875 ,0.372279]
+	},{
+		"2d_id":92,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.372279],
+		"2d_point2":[0.875 ,0.41226]
+	},{
+		"2d_id":93,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.41226],
+		"2d_point2":[0.875 ,0.455281]
+	},{
+		"2d_id":94,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.455281],
+		"2d_point2":[0.875 ,0.5]
+	},{
+		"2d_id":95,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.5],
+		"2d_point2":[0.875 ,0.544719]
+	},{
+		"2d_id":96,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.544719],
+		"2d_point2":[0.875 ,0.58774]
+	},{
+		"2d_id":97,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.58774],
+		"2d_point2":[0.875 ,0.627721]
+	},{
+		"2d_id":98,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.627721],
+		"2d_point2":[0.875 ,0.663868]
+	},{
+		"2d_id":99,
+		"3d_id":9,
+		"2d_point1":[0.875 ,0.663868],
+		"2d_point2":[0.875 ,0.695913]
+	},{
+		"2d_id":100,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.304087],
+		"2d_point2":[0.125 ,0.336132]
+	},{
+		"2d_id":101,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.336132],
+		"2d_point2":[0.125 ,0.372279]
+	},{
+		"2d_id":102,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.372279],
+		"2d_point2":[0.125 ,0.41226]
+	},{
+		"2d_id":103,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.41226],
+		"2d_point2":[0.125 ,0.455281]
+	},{
+		"2d_id":104,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.455281],
+		"2d_point2":[0.125 ,0.5]
+	},{
+		"2d_id":105,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.5],
+		"2d_point2":[0.125 ,0.544719]
+	},{
+		"2d_id":106,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.544719],
+		"2d_point2":[0.125 ,0.58774]
+	},{
+		"2d_id":107,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.58774],
+		"2d_point2":[0.125 ,0.627721]
+	},{
+		"2d_id":108,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.627721],
+		"2d_point2":[0.125 ,0.663868]
+	},{
+		"2d_id":109,
+		"3d_id":10,
+		"2d_point1":[0.125 ,0.663868],
+		"2d_point2":[0.125 ,0.695913]
+	},{
+		"2d_id":110,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.304087],
+		"2d_point2":[0.375 ,0.336132]
+	},{
+		"2d_id":111,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.336132],
+		"2d_point2":[0.375 ,0.372279]
+	},{
+		"2d_id":112,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.372279],
+		"2d_point2":[0.375 ,0.41226]
+	},{
+		"2d_id":113,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.41226],
+		"2d_point2":[0.375 ,0.455281]
+	},{
+		"2d_id":114,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.455281],
+		"2d_point2":[0.375 ,0.5]
+	},{
+		"2d_id":115,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.5],
+		"2d_point2":[0.375 ,0.544719]
+	},{
+		"2d_id":116,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.544719],
+		"2d_point2":[0.375 ,0.58774]
+	},{
+		"2d_id":117,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.58774],
+		"2d_point2":[0.375 ,0.627721]
+	},{
+		"2d_id":118,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.627721],
+		"2d_point2":[0.375 ,0.663868]
+	},{
+		"2d_id":119,
+		"3d_id":11,
+		"2d_point1":[0.375 ,0.663868],
+		"2d_point2":[0.375 ,0.695913]
+	}]
+}