dingtalk_zqunll 2 лет назад
Сommit
dbdf0b5825

+ 58 - 0
.gitignore

@@ -0,0 +1,58 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env

Разница между файлами не показана из-за своего большого размера
+ 1544 - 0
build/js.blend.js


+ 1042 - 0
example/orbit_controls.js

@@ -0,0 +1,1042 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
+ */
+
+// This set of controls performs orbiting, dollying (zooming), and panning.
+// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+//
+//    Orbit - left mouse / touch: one finger move
+//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
+//    Pan - right mouse, or arrow keys / touch: three finger swipe
+
+THREE.OrbitControls = function ( object, domElement ) {
+
+	this.object = object;
+
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	// Set to false to disable this control
+	this.enabled = true;
+
+	// "target" sets the location of focus, where the object orbits around
+	this.target = new THREE.Vector3();
+
+	// How far you can dolly in and out ( PerspectiveCamera only )
+	this.minDistance = 0;
+	this.maxDistance = Infinity;
+
+	// How far you can zoom in and out ( OrthographicCamera only )
+	this.minZoom = 0;
+	this.maxZoom = Infinity;
+
+	// How far you can orbit vertically, upper and lower limits.
+	// Range is 0 to Math.PI radians.
+	this.minPolarAngle = 0; // radians
+	this.maxPolarAngle = Math.PI; // radians
+
+	// How far you can orbit horizontally, upper and lower limits.
+	// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+	this.minAzimuthAngle = - Infinity; // radians
+	this.maxAzimuthAngle = Infinity; // radians
+
+	// Set to true to enable damping (inertia)
+	// If damping is enabled, you must call controls.update() in your animation loop
+	this.enableDamping = false;
+	this.dampingFactor = 0.25;
+
+	// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+	// Set to false to disable zooming
+	this.enableZoom = true;
+	this.zoomSpeed = 1.0;
+
+	// Set to false to disable rotating
+	this.enableRotate = true;
+	this.rotateSpeed = 1.0;
+
+	// Set to false to disable panning
+	this.enablePan = true;
+	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
+
+	// Set to true to automatically rotate around the target
+	// If auto-rotate is enabled, you must call controls.update() in your animation loop
+	this.autoRotate = false;
+	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+
+	// Set to false to disable use of the keys
+	this.enableKeys = true;
+
+	// The four arrow keys
+	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
+
+	// Mouse buttons
+	this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
+
+	// for reset
+	this.target0 = this.target.clone();
+	this.position0 = this.object.position.clone();
+	this.zoom0 = this.object.zoom;
+
+	//
+	// public methods
+	//
+
+	this.getPolarAngle = function () {
+
+		return spherical.phi;
+
+	};
+
+	this.getAzimuthalAngle = function () {
+
+		return spherical.theta;
+
+	};
+
+	this.saveState = function () {
+
+		scope.target0.copy( scope.target );
+		scope.position0.copy( scope.object.position );
+		scope.zoom0 = scope.object.zoom;
+
+	};
+
+	this.reset = function () {
+
+		scope.target.copy( scope.target0 );
+		scope.object.position.copy( scope.position0 );
+		scope.object.zoom = scope.zoom0;
+
+		scope.object.updateProjectionMatrix();
+		scope.dispatchEvent( changeEvent );
+
+		scope.update();
+
+		state = STATE.NONE;
+
+	};
+
+	// this method is exposed, but perhaps it would be better if we can make it private...
+	this.update = function () {
+
+		var offset = new THREE.Vector3();
+
+		// so camera.up is the orbit axis
+		var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+		var quatInverse = quat.clone().inverse();
+
+		var lastPosition = new THREE.Vector3();
+		var lastQuaternion = new THREE.Quaternion();
+
+		return function update() {
+
+			var position = scope.object.position;
+
+			offset.copy( position ).sub( scope.target );
+
+			// rotate offset to "y-axis-is-up" space
+			offset.applyQuaternion( quat );
+
+			// angle from z-axis around y-axis
+			spherical.setFromVector3( offset );
+
+			if ( scope.autoRotate && state === STATE.NONE ) {
+
+				rotateLeft( getAutoRotationAngle() );
+
+			}
+
+			spherical.theta += sphericalDelta.theta;
+			spherical.phi += sphericalDelta.phi;
+
+			// restrict theta to be between desired limits
+			spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
+
+			// restrict phi to be between desired limits
+			spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+
+			spherical.makeSafe();
+
+
+			spherical.radius *= scale;
+
+			// restrict radius to be between desired limits
+			spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+
+			// move target to panned location
+			scope.target.add( panOffset );
+
+			offset.setFromSpherical( spherical );
+
+			// rotate offset back to "camera-up-vector-is-up" space
+			offset.applyQuaternion( quatInverse );
+
+			position.copy( scope.target ).add( offset );
+
+			scope.object.lookAt( scope.target );
+
+			if ( scope.enableDamping === true ) {
+
+				sphericalDelta.theta *= ( 1 - scope.dampingFactor );
+				sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+
+			} else {
+
+				sphericalDelta.set( 0, 0, 0 );
+
+			}
+
+			scale = 1;
+			panOffset.set( 0, 0, 0 );
+
+			// update condition is:
+			// min(camera displacement, camera rotation in radians)^2 > EPS
+			// using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+			if ( zoomChanged ||
+				lastPosition.distanceToSquared( scope.object.position ) > EPS ||
+				8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+				scope.dispatchEvent( changeEvent );
+
+				lastPosition.copy( scope.object.position );
+				lastQuaternion.copy( scope.object.quaternion );
+				zoomChanged = false;
+
+				return true;
+
+			}
+
+			return false;
+
+		};
+
+	}();
+
+	this.dispose = function () {
+
+		scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+		scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+		scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+		scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+		scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+		scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+
+		window.removeEventListener( 'keydown', onKeyDown, false );
+
+		//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+	};
+
+	//
+	// internals
+	//
+
+	var scope = this;
+
+	var changeEvent = { type: 'change' };
+	var startEvent = { type: 'start' };
+	var endEvent = { type: 'end' };
+
+	var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
+
+	var state = STATE.NONE;
+
+	var EPS = 0.000001;
+
+	// current position in spherical coordinates
+	var spherical = new THREE.Spherical();
+	var sphericalDelta = new THREE.Spherical();
+
+	var scale = 1;
+	var panOffset = new THREE.Vector3();
+	var zoomChanged = false;
+
+	var rotateStart = new THREE.Vector2();
+	var rotateEnd = new THREE.Vector2();
+	var rotateDelta = new THREE.Vector2();
+
+	var panStart = new THREE.Vector2();
+	var panEnd = new THREE.Vector2();
+	var panDelta = new THREE.Vector2();
+
+	var dollyStart = new THREE.Vector2();
+	var dollyEnd = new THREE.Vector2();
+	var dollyDelta = new THREE.Vector2();
+
+	function getAutoRotationAngle() {
+
+		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+	}
+
+	function getZoomScale() {
+
+		return Math.pow( 0.95, scope.zoomSpeed );
+
+	}
+
+	function rotateLeft( angle ) {
+
+		sphericalDelta.theta -= angle;
+
+	}
+
+	function rotateUp( angle ) {
+
+		sphericalDelta.phi -= angle;
+
+	}
+
+	var panLeft = function () {
+
+		var v = new THREE.Vector3();
+
+		return function panLeft( distance, objectMatrix ) {
+
+			v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+			v.multiplyScalar( - distance );
+
+			panOffset.add( v );
+
+		};
+
+	}();
+
+	var panUp = function () {
+
+		var v = new THREE.Vector3();
+
+		return function panUp( distance, objectMatrix ) {
+
+			v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
+			v.multiplyScalar( distance );
+
+			panOffset.add( v );
+
+		};
+
+	}();
+
+	// deltaX and deltaY are in pixels; right and down are positive
+	var pan = function () {
+
+		var offset = new THREE.Vector3();
+
+		return function pan( deltaX, deltaY ) {
+
+			var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+			if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+				// perspective
+				var position = scope.object.position;
+				offset.copy( position ).sub( scope.target );
+				var targetDistance = offset.length();
+
+				// half of the fov is center to top of screen
+				targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
+
+				// we actually don't use screenWidth, since perspective camera is fixed to screen height
+				panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+				panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+			} else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+				// orthographic
+				panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+				panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+			} else {
+
+				// camera neither orthographic nor perspective
+				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+				scope.enablePan = false;
+
+			}
+
+		};
+
+	}();
+
+	function dollyIn( dollyScale ) {
+
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+			scale /= dollyScale;
+
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			zoomChanged = true;
+
+		} else {
+
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+			scope.enableZoom = false;
+
+		}
+
+	}
+
+	function dollyOut( dollyScale ) {
+
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+			scale *= dollyScale;
+
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			zoomChanged = true;
+
+		} else {
+
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+			scope.enableZoom = false;
+
+		}
+
+	}
+
+	//
+	// event callbacks - update the object state
+	//
+
+	function handleMouseDownRotate( event ) {
+
+		//console.log( 'handleMouseDownRotate' );
+
+		rotateStart.set( event.clientX, event.clientY );
+
+	}
+
+	function handleMouseDownDolly( event ) {
+
+		//console.log( 'handleMouseDownDolly' );
+
+		dollyStart.set( event.clientX, event.clientY );
+
+	}
+
+	function handleMouseDownPan( event ) {
+
+		//console.log( 'handleMouseDownPan' );
+
+		panStart.set( event.clientX, event.clientY );
+
+	}
+
+	function handleMouseMoveRotate( event ) {
+
+		//console.log( 'handleMouseMoveRotate' );
+
+		rotateEnd.set( event.clientX, event.clientY );
+		rotateDelta.subVectors( rotateEnd, rotateStart );
+
+		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+		// rotating across whole screen goes 360 degrees around
+		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
+
+		// rotating up and down along whole screen attempts to go 360, but limited to 180
+		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+
+		rotateStart.copy( rotateEnd );
+
+		scope.update();
+
+	}
+
+	function handleMouseMoveDolly( event ) {
+
+		//console.log( 'handleMouseMoveDolly' );
+
+		dollyEnd.set( event.clientX, event.clientY );
+
+		dollyDelta.subVectors( dollyEnd, dollyStart );
+
+		if ( dollyDelta.y > 0 ) {
+
+			dollyIn( getZoomScale() );
+
+		} else if ( dollyDelta.y < 0 ) {
+
+			dollyOut( getZoomScale() );
+
+		}
+
+		dollyStart.copy( dollyEnd );
+
+		scope.update();
+
+	}
+
+	function handleMouseMovePan( event ) {
+
+		//console.log( 'handleMouseMovePan' );
+
+		panEnd.set( event.clientX, event.clientY );
+
+		panDelta.subVectors( panEnd, panStart );
+
+		pan( panDelta.x, panDelta.y );
+
+		panStart.copy( panEnd );
+
+		scope.update();
+
+	}
+
+	function handleMouseUp( event ) {
+
+		// console.log( 'handleMouseUp' );
+
+	}
+
+	function handleMouseWheel( event ) {
+
+		// console.log( 'handleMouseWheel' );
+
+		if ( event.deltaY < 0 ) {
+
+			dollyOut( getZoomScale() );
+
+		} else if ( event.deltaY > 0 ) {
+
+			dollyIn( getZoomScale() );
+
+		}
+
+		scope.update();
+
+	}
+
+	function handleKeyDown( event ) {
+
+		//console.log( 'handleKeyDown' );
+
+		switch ( event.keyCode ) {
+
+			case scope.keys.UP:
+				pan( 0, scope.keyPanSpeed );
+				scope.update();
+				break;
+
+			case scope.keys.BOTTOM:
+				pan( 0, - scope.keyPanSpeed );
+				scope.update();
+				break;
+
+			case scope.keys.LEFT:
+				pan( scope.keyPanSpeed, 0 );
+				scope.update();
+				break;
+
+			case scope.keys.RIGHT:
+				pan( - scope.keyPanSpeed, 0 );
+				scope.update();
+				break;
+
+		}
+
+	}
+
+	function handleTouchStartRotate( event ) {
+
+		//console.log( 'handleTouchStartRotate' );
+
+		rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+	}
+
+	function handleTouchStartDolly( event ) {
+
+		//console.log( 'handleTouchStartDolly' );
+
+		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+		var distance = Math.sqrt( dx * dx + dy * dy );
+
+		dollyStart.set( 0, distance );
+
+	}
+
+	function handleTouchStartPan( event ) {
+
+		//console.log( 'handleTouchStartPan' );
+
+		panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+	}
+
+	function handleTouchMoveRotate( event ) {
+
+		//console.log( 'handleTouchMoveRotate' );
+
+		rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+		rotateDelta.subVectors( rotateEnd, rotateStart );
+
+		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
+
+		// rotating across whole screen goes 360 degrees around
+		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
+
+		// rotating up and down along whole screen attempts to go 360, but limited to 180
+		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
+
+		rotateStart.copy( rotateEnd );
+
+		scope.update();
+
+	}
+
+	function handleTouchMoveDolly( event ) {
+
+		//console.log( 'handleTouchMoveDolly' );
+
+		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+
+		var distance = Math.sqrt( dx * dx + dy * dy );
+
+		dollyEnd.set( 0, distance );
+
+		dollyDelta.subVectors( dollyEnd, dollyStart );
+
+		if ( dollyDelta.y > 0 ) {
+
+			dollyOut( getZoomScale() );
+
+		} else if ( dollyDelta.y < 0 ) {
+
+			dollyIn( getZoomScale() );
+
+		}
+
+		dollyStart.copy( dollyEnd );
+
+		scope.update();
+
+	}
+
+	function handleTouchMovePan( event ) {
+
+		//console.log( 'handleTouchMovePan' );
+
+		panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+		panDelta.subVectors( panEnd, panStart );
+
+		pan( panDelta.x, panDelta.y );
+
+		panStart.copy( panEnd );
+
+		scope.update();
+
+	}
+
+	function handleTouchEnd( event ) {
+
+		//console.log( 'handleTouchEnd' );
+
+	}
+
+	//
+	// event handlers - FSM: listen for events and reset state
+	//
+
+	function onMouseDown( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		event.preventDefault();
+
+		switch ( event.button ) {
+
+			case scope.mouseButtons.ORBIT:
+
+				if ( scope.enableRotate === false ) return;
+
+				handleMouseDownRotate( event );
+
+				state = STATE.ROTATE;
+
+				break;
+
+			case scope.mouseButtons.ZOOM:
+
+				if ( scope.enableZoom === false ) return;
+
+				handleMouseDownDolly( event );
+
+				state = STATE.DOLLY;
+
+				break;
+
+			case scope.mouseButtons.PAN:
+
+				if ( scope.enablePan === false ) return;
+
+				handleMouseDownPan( event );
+
+				state = STATE.PAN;
+
+				break;
+
+		}
+
+		if ( state !== STATE.NONE ) {
+
+			document.addEventListener( 'mousemove', onMouseMove, false );
+			document.addEventListener( 'mouseup', onMouseUp, false );
+
+			scope.dispatchEvent( startEvent );
+
+		}
+
+	}
+
+	function onMouseMove( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		event.preventDefault();
+
+		switch ( state ) {
+
+			case STATE.ROTATE:
+
+				if ( scope.enableRotate === false ) return;
+
+				handleMouseMoveRotate( event );
+
+				break;
+
+			case STATE.DOLLY:
+
+				if ( scope.enableZoom === false ) return;
+
+				handleMouseMoveDolly( event );
+
+				break;
+
+			case STATE.PAN:
+
+				if ( scope.enablePan === false ) return;
+
+				handleMouseMovePan( event );
+
+				break;
+
+		}
+
+	}
+
+	function onMouseUp( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		handleMouseUp( event );
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+
+		scope.dispatchEvent( endEvent );
+
+		state = STATE.NONE;
+
+	}
+
+	function onMouseWheel( event ) {
+
+		if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		handleMouseWheel( event );
+
+		scope.dispatchEvent( startEvent ); // not sure why these are here...
+		scope.dispatchEvent( endEvent );
+
+	}
+
+	function onKeyDown( event ) {
+
+		if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+
+		handleKeyDown( event );
+
+	}
+
+	function onTouchStart( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:	// one-fingered touch: rotate
+
+				if ( scope.enableRotate === false ) return;
+
+				handleTouchStartRotate( event );
+
+				state = STATE.TOUCH_ROTATE;
+
+				break;
+
+			case 2:	// two-fingered touch: dolly
+
+				if ( scope.enableZoom === false ) return;
+
+				handleTouchStartDolly( event );
+
+				state = STATE.TOUCH_DOLLY;
+
+				break;
+
+			case 3: // three-fingered touch: pan
+
+				if ( scope.enablePan === false ) return;
+
+				handleTouchStartPan( event );
+
+				state = STATE.TOUCH_PAN;
+
+				break;
+
+			default:
+
+				state = STATE.NONE;
+
+		}
+
+		if ( state !== STATE.NONE ) {
+
+			scope.dispatchEvent( startEvent );
+
+		}
+
+	}
+
+	function onTouchMove( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		switch ( event.touches.length ) {
+
+			case 1: // one-fingered touch: rotate
+
+				if ( scope.enableRotate === false ) return;
+				if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
+
+				handleTouchMoveRotate( event );
+
+				break;
+
+			case 2: // two-fingered touch: dolly
+
+				if ( scope.enableZoom === false ) return;
+				if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
+
+				handleTouchMoveDolly( event );
+
+				break;
+
+			case 3: // three-fingered touch: pan
+
+				if ( scope.enablePan === false ) return;
+				if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
+
+				handleTouchMovePan( event );
+
+				break;
+
+			default:
+
+				state = STATE.NONE;
+
+		}
+
+	}
+
+	function onTouchEnd( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		handleTouchEnd( event );
+
+		scope.dispatchEvent( endEvent );
+
+		state = STATE.NONE;
+
+	}
+
+	function onContextMenu( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		event.preventDefault();
+
+	}
+
+	//
+
+	scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+
+	scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+	scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
+
+	scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
+	scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
+	scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
+
+	window.addEventListener( 'keydown', onKeyDown, false );
+
+	// force an update at start
+
+	this.update();
+
+};
+
+THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
+
+Object.defineProperties( THREE.OrbitControls.prototype, {
+
+	center: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
+			return this.target;
+
+		}
+
+	},
+
+	// backward compatibility
+
+	noZoom: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+			return ! this.enableZoom;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
+			this.enableZoom = ! value;
+
+		}
+
+	},
+
+	noRotate: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+			return ! this.enableRotate;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
+			this.enableRotate = ! value;
+
+		}
+
+	},
+
+	noPan: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+			return ! this.enablePan;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
+			this.enablePan = ! value;
+
+		}
+
+	},
+
+	noKeys: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+			return ! this.enableKeys;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
+			this.enableKeys = ! value;
+
+		}
+
+	},
+
+	staticMoving: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+			return ! this.enableDamping;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
+			this.enableDamping = ! value;
+
+		}
+
+	},
+
+	dynamicDampingFactor: {
+
+		get: function () {
+
+			console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+			return this.dampingFactor;
+
+		},
+
+		set: function ( value ) {
+
+			console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
+			this.dampingFactor = value;
+
+		}
+
+	}
+
+} );

BIN
example/test.blend


Разница между файлами не показана из-за своего большого размера
+ 4923 - 0
example/three.js


+ 48 - 0
index.html

@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>JS.BLEND Example</title>
+		<script type="text/javascript" src="./build/js.blend.js"></script>
+		<script type="text/javascript" src="./example/three.js"></script>
+		<script type="text/javascript" src="./example/orbit_controls.js"></script>
+	</head>
+	<body>
+		<script type="text/javascript">
+			//threes js code
+			
+			var scene = new THREE.Scene();
+			scene.background = new THREE.Color(0.5, 0.2, 0.5);
+			var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
+			var renderer = new THREE.WebGLRenderer();
+			renderer.setSize(window.innerWidth, window.innerHeight);
+
+			document.body.appendChild(renderer.domElement);
+
+			function render() {
+				renderer.render(scene, camera);
+			}
+
+			//camera.rotation.y = 90;
+			camera.position.z = 10;
+
+			controls = new THREE.OrbitControls(camera, renderer.domElement);
+			controls.addEventListener('change', render); // remove
+			controls.enableZoom = true;
+
+			var light = new THREE.PointLight(0xFFFFFF, 0.8, 1000);
+			light.position.set(0, 0, 10);
+			//scene.add(light);
+
+			var light = new THREE.PointLight(0xFFFFFF, 0.8, 0);
+			light.position.set(0, 0, -10);
+			//scene.add(light);
+
+			JSBLEND('./example/test.blend').then((blend) => {
+	        	blend.three.loadScene(scene);
+	       		render();
+	    	}).catch((error)=>{
+	    		console.log(error);
+	    	});
+		</script>
+	</body>
+</html>

+ 21 - 0
license.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Anthony C, Weathersby
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "jsblend",
+  "version": "0.1.2",
+  "description": "A Blender to Javascript File Reader",
+  "main": "./build/jsblend.js",
+  "scripts": {
+    "watch": "npm-watch",
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "build": "browserify ./source/main.js -o ./build/js.blend.js -d"
+  },
+  "watch": {
+    "build": "source"
+  },
+  "keywords": [
+    "Blender",
+    "ThreeJS"
+  ],
+  "author": "Anthony C Weathersby",
+  "license": "MIT",
+  "dependencies": {
+    "base64-js": "^1.2.1"
+  },
+  "devDependencies": {
+    "npm-watch": "^0.2.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/Galactrax/js.blend.git"
+  },
+  "bugs": {
+    "url": "https://github.com/Galactrax/js.blend/issues"
+  },
+  "homepage": "https://github.com/Galactrax/js.blend#readme"
+}

+ 75 - 0
source/main.js

@@ -0,0 +1,75 @@
+
+/*jshint esversion: 6 */
+
+const three = require("./threejs/blend_three.js");
+
+const parser = require("./parser/parser.js")();
+
+
+function loadFile(blender_file, res, rej){	
+	
+	three_module = three(blender_file);
+
+	//TODO: Report any errors with ThreeJS before continuing.
+	
+	res({
+		file : blender_file,
+		three : three_module
+	});
+}
+
+/* This represents a parsed blendfile instance if parsing is successful. It will accept a string or a binary data object. Strings must be a valid URI to a blender file. Binary data may be in the form of an ArrayBuffer, TypedArray, or a Blob. Binary data must also contain the binary data of a blender file.*/
+
+JSBLEND = (fileuri_or_filedata, name = "")=>{
+
+	const promise = new Promise(
+		(res, rej) =>{
+			parser.onParseReady = (blender_file, error) => {
+				if(error){
+					rej(error);
+				}else{
+					loadFile(blender_file, res, rej);
+				}
+			};
+
+			//If fileuri_or_filedata is a string, attempt to load the file asynchronously
+			if(typeof fileuri_or_filedata == "string"){
+				
+				let request = new XMLHttpRequest();
+			    
+			    request.open("GET", fileuri_or_filedata, true);
+			    
+			    request.responseType = 'blob';
+			    
+			    request.onload = () => {
+			        let file = request.response;
+			        
+			        parser.loadBlendFromBlob(new Blob([file]), fileuri_or_filedata);
+			    };
+			    
+			    request.send();
+
+			    return;
+			}
+
+			if(typeof fileuri_or_filedata == "object"){
+				//Attempt to load from blob or array buffer;
+				if(fileuri_or_filedata instanceof ArrayBuffer){
+					parser.loadBlendFromArrayBuffer(fileuri_or_filedata, name);
+					return;
+				}
+
+				if(fileuri_or_filedata instanceof Blob){
+					parser.loadBlendFromBlob(fileuri_or_filedata, name);
+					return;
+				}
+			}
+
+			//Unknown file type passed -> abort and reject
+			
+			rej("Unsupported file type passed to JSBlend ${fileuri_or_filedata}");
+		}
+	);
+
+	return promise;
+};

+ 784 - 0
source/parser/parser.js

@@ -0,0 +1,784 @@
+/*jshint esversion: 6 */
+
+const DNA1 = 826363460;
+const ENDB = 1111772741;
+
+/* Note: Blender coordinates treat the Z axis as the vertical an Y as depth. */
+
+module.exports = (function(unzipper) {
+
+    //web worker not functional in this version
+    USE_WEBWORKER = false;
+
+    var worker = null,
+
+        FR = new FileReader(),
+
+        return_object = {
+            loadBlendFromArrayBuffer: function(array_buffer) {
+                return_object.ready = false;
+                if (USE_WEBWORKER) {
+                    worker.postMessage(array_buffer, array_buffer);
+                } else {
+                    worker.onmessage({
+                        data: array_buffer
+                    });
+                }
+            },
+            loadBlendFromBlob: function(blob) {
+                FR.onload = function() {
+                    return_object.loadBlendFromArrayBuffer(this.result);
+                };
+                FR.readAsArrayBuffer(blob);
+            },
+            ready: true,
+            onParseReady: function() {},
+        };
+
+    worker = new worker_code();
+
+    worker.postMessage = function(message) {
+        return_object.onParseReady(message);
+    };
+
+    function worker_code() {
+        "use strict";
+
+        var data = null,
+            _data = null,
+            BIG_ENDIAN = false,
+            pointer_size = 0,
+            struct_names = [],
+            offset = 0,
+            working_blend_file = null,
+            current_SDNA_template = null,
+            templates = {},
+            finished_objects = [],
+            FILE = null,
+            ERROR = null,
+            AB = null;
+
+        function parseFile(msg) {
+            
+            var self = this;
+            
+            if (typeof msg.data == "object") {
+                // reset global variables
+                AB = null;
+                data = null;
+                BIG_ENDIAN = false;
+                pointer_size = 0;
+                struct_names = [];
+                offset = 0;
+                working_blend_file = null;
+                finished_objects = [];
+                current_SDNA_template = null;
+
+
+                // set data
+                _data = msg.data;
+
+                AB = _data.slice();
+
+                data = new DataView(_data);
+
+
+                FILE = new BLENDER_FILE(AB);
+
+                //start parsing
+                readFile();
+
+                //export parsed data
+                self.postMessage(FILE, ERROR);
+            }
+        }
+
+        /*
+            Export object for a parsed __blender_file__.
+        */
+
+        var BLENDER_FILE = function(AB) {
+            this.AB = AB;
+            //this.double = new Float64Array(AB);
+            this.byte = new Uint8Array(AB);
+
+            this.dv = new DataView(AB);
+
+            this.objects = {};
+            this.memory_lookup = {},
+                this.object_array = [];
+
+            this.template = null;
+        };
+
+        BLENDER_FILE.prototype = {
+            addObject: function(obj) {
+                this.object_array.push(obj);
+                if (!this.objects[obj.blender_name]) this.objects[obj.blender_name] = [];
+                this.objects[obj.blender_name].push(obj);
+            },
+
+            getPointer: function(offset) {
+                var pointerLow = this.dv.getUint32(offset, this.template.endianess);
+                if (this.template.pointer_size > 4) {
+                    var pointerHigh = this.dv.getUint32(offset + 4, this.template.endianess);
+                    if (this.template.endianess) {
+                        return (pointerLow) + "l|h" + pointerHigh;
+                    } else {
+                        return (pointerHigh) + "h|l" + pointerLow;
+                    }
+                } else {
+                    return pointerLow;
+                }
+            }
+        };
+
+        self.onmessage = parseFile;
+        this.onmessage = parseFile;
+
+        /*
+            These functions map offsets in the blender __blender_file__ to basic types (byte,short,int,float) through TypedArrays;
+            This allows the underlying binary data to be changed.
+        */
+
+        function float64Prop(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Float64Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getFloat64(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(float) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setFloat64(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        }
+
+        function floatProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Float32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getFloat32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(float) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setFloat32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        }
+
+        function intProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Int32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getInt32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(int) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setInt32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        }
+
+        function uIntProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Uint32Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getUint32(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(int) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setUint32(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        }
+
+        function shortProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Int16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getInt16(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(float) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setInt16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        }
+
+        var uShortProp = (offset, Blender_Array_Length, length) => {
+            return {
+                get: function() {
+                    return (Blender_Array_Length > 1) ?
+                        new Uint16Array(this.__blender_file__.AB, this.__data_address__ + offset, length) :
+                        this.__blender_file__.dv.getUint16(this.__data_address__ + offset, this.__blender_file__.template.endianess);
+                },
+                set: function(float) {
+                    if (Blender_Array_Length > 1) {} else {
+                        this.__blender_file__.dv.setUint16(this.__data_address__ + offset, float, this.__blender_file__.template.endianess);
+                    }
+                },
+            };
+        };
+
+        function charProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    if (Blender_Array_Length > 1) {
+                        let start = this.__data_address__ + offset;
+                        let end = start;
+                        let buffer_guard = 0;
+
+                        while (this.__blender_file__.byte[end] != 0 && buffer_guard++ < length) end++;
+
+                        return toString(this.__blender_file__.AB, start, end);
+                    }
+                    return this.__blender_file__.byte[(this.__data_address__ + offset)];
+                },
+                set: function(byte) {
+                    if (Blender_Array_Length > 1) {
+                        var string = byte + "",
+                            i = 0,
+                            l = string.length;
+                        while (i < length) {
+                            if (i < l) {
+                                this.__blender_file__.byte[(this.__data_address__ + offset + i)] = string.charCodeAt(i) | 0;
+                            } else {
+                                this.__blender_file__.byte[(this.__data_address__ + offset + i)] = 0;
+                            }
+                            i++;
+                        }
+                    } else {
+                        this.__blender_file__.byte[(this.__data_address__ + offset)] = byte | 0;
+                    }
+                }
+            };
+        }
+
+        function pointerProp2(offset) {
+            return {
+                get: function() {
+                    let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__);
+                    var link = this.__blender_file__.memory_lookup[pointer];
+
+                    var results = [];
+
+                    if (link) {
+                        var address = link.__data_address__;
+                        let j = 0;
+                        while (true) {
+                            pointer = this.__blender_file__.getPointer(address + j * 8, this.__blender_file__);
+                            let obj = this.__blender_file__.memory_lookup[pointer];
+                            if (!obj) break;
+                            results.push(obj);
+                            j++;
+                        }
+
+                    };
+
+                    return results;
+                },
+                set: function() {}
+            };
+        }
+
+        function pointerProp(offset, Blender_Array_Length, length) {
+            return {
+                get: function() {
+                    if (Blender_Array_Length > 1) {
+                        let array = [];
+                        let j = 0;
+                        let off = offset;
+                        while (j < Blender_Array_Length) {
+                            let pointer = this.__blender_file__.getPointer(this.__data_address__ + off, this.__blender_file__);
+
+                            array.push(this.__blender_file__.memory_lookup[pointer]);
+                            off += length;
+                            j++;
+                        }
+
+                        return array;
+                    } else {
+                        let pointer = this.__blender_file__.getPointer(this.__data_address__ + offset, this.__blender_file__);
+                        return this.__blender_file__.memory_lookup[pointer];
+                    }
+                },
+                set: function() {}
+            };
+        }
+
+        function compileProp(obj, name, type, offset, array_size, IS_POINTER, pointer_size, length) {
+
+            if (!IS_POINTER) {
+                switch (type) {
+                    case "double":
+                        Object.defineProperty(obj, name, float64Prop(offset, array_size, length >> 3));
+                        break;
+                    case "float":
+                        Object.defineProperty(obj, name, floatProp(offset, array_size, length >> 2));
+                        break;
+                    case "int":
+                        Object.defineProperty(obj, name, intProp(offset, array_size, length >> 2));
+                        break;
+                    case "short":
+                    case "ushort":
+                        Object.defineProperty(obj, name, shortProp(offset, array_size, length >> 1));
+                        break;
+                    case "char":
+                    case "uchar":
+                        Object.defineProperty(obj, name, charProp(offset, array_size, length));
+                        break;
+                    default:
+                        //compile list to 
+                        obj[name] = {};
+                        obj.__list__.push(name, type, length, offset, array_size, IS_POINTER);
+                }
+                obj._length += length;
+                offset += length;
+            } else {
+                Object.defineProperty(obj, name, pointerProp(offset, array_size, pointer_size));
+                offset += pointer_size * array_size;
+            }
+
+            return offset;
+        }
+
+        //Store final DNA structs
+        var MASTER_SDNA_SCHEMA = function(version) {
+            this.version = version;
+            this.SDNA_SET = false;
+            this.byte_size = 0;
+            this.struct_index = 0;
+            this.structs = {};
+            this.SDNA = {};
+            this.endianess = false;
+        };
+
+        MASTER_SDNA_SCHEMA.prototype = {
+            getSDNAStructureConstructor: function(name, struct) {
+                if (struct) {
+                    var blen_struct = Function("function " + name + "(){}; return " + name)();
+
+                    blen_struct.prototype = new BLENDER_STRUCTURE();
+                    blen_struct.prototype.blender_name = name;
+                    blen_struct.prototype.__pointers = [];
+                    blen_struct.prototype.__list__ = [];
+
+                    var offset = 0;
+                    //Create properties of struct
+                    for (var i = 0; i < struct.length; i += 3) {
+                        var _name = struct[i],
+                            n = _name,
+                            type = struct[i + 1],
+                            length = struct[i + 2],
+                            array_length = 0,
+                            match = null,
+                            Blender_Array_Length = 1,
+                            Suparray_match = 1,
+                            PointerToArray = false,
+                            Pointer_Match = 0;
+                        var DNA = this.SDNA[name] = {
+                            constructor: blen_struct
+                        };
+
+
+                        let original_name = _name;
+
+                        //mini type parser
+                        if ((match = _name.match(/(\*?)(\*?)(\w+)(\[(\w*)\])?(\[(\w*)\])?/))) {
+
+                            //base name
+                            _name = match[3];
+
+                            //pointer type
+                            if (match[1]) {
+                                Pointer_Match = 10;
+                                blen_struct.prototype.__pointers.push(_name);
+                            }
+
+                            if (match[2]) {
+                                PointerToArray = true;
+                            }
+
+                            //arrays
+                            if (match[4]) {
+                                if (match[6]) {
+                                    Suparray_match = parseInt(match[5]);
+                                    Blender_Array_Length = parseInt(match[7]);
+                                } else {
+                                    Blender_Array_Length = parseInt(match[5]);
+                                }
+                            }
+                            array_length = Blender_Array_Length * length;
+                            length = array_length * Suparray_match;
+                        }
+
+                        DNA[n] = {
+                            type: type,
+                            length: length,
+                            isArray: (Blender_Array_Length > 0),
+                        };
+
+                        if (PointerToArray) {
+                            Object.defineProperty(blen_struct.prototype, _name, pointerProp2(offset));
+                            offset += pointer_size;
+                        } else if (Suparray_match > 1) {
+                            var array_names = new Array(Suparray_match);
+
+                            //construct sub_array object that will return the correct structs
+                            for (var j = 0; j < Suparray_match; j++) {
+                                let array_name_ = `__${_name}[${j}]__`;
+                                array_names[j] = array_name_;
+
+                                offset = compileProp(blen_struct.prototype, array_name_, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, array_length);
+                            }
+
+                            Object.defineProperty(blen_struct.prototype, _name, {
+                                get: (function(array_names) {
+                                    return function() {
+                                        var array = [];
+                                        for (var i = 0; i < array_names.length; i++) {
+                                            array.push(this[array_names[i]]);
+                                        }
+                                        return array;
+                                    };
+                                })(array_names)
+                            });
+                        } else {
+                            offset = compileProp(blen_struct.prototype, _name, type, offset, Blender_Array_Length, Pointer_Match, pointer_size, length);
+                        }
+                    }
+
+                    return this.SDNA[name].constructor;
+
+                } else {
+                    if (!this.SDNA[name]) {
+                        return null;
+                    }
+                    return this.SDNA[name].constructor;
+                }
+            }
+        };
+
+        var BLENDER_STRUCTURE = function() {
+            this.__blender_file__ = null;
+            this.__list__ = null;
+            this.__super_array_list__ = null;
+            this.blender_name = "";
+            this.__pointers = null;
+            this.address = null;
+            this.length = 0;
+            this.__data_address__ = 0;
+            this.blender_name = "";
+            this._length = 0;
+        };
+
+
+        /*
+            Returns a pre-constructed BLENDER_STRUCTURE or creates a new BLENDER_STRUCTURE to match the DNA struct type
+        */
+        var pointer_function = (pointer) => () => {
+            return FILE.memory_lookup[pointer];
+        };
+
+        function getPointer(offset) {
+            var pointerLow = data.getUint32(offset, BIG_ENDIAN);
+            if (pointer_size > 4) {
+                var pointerHigh = data.getUint32(offset + 4, BIG_ENDIAN);
+
+                if (BIG_ENDIAN) {
+                    return (pointerLow) + "" + pointerHigh;
+                } else {
+                    return (pointerHigh) + "" + pointerLow;
+                }
+            } else {
+                return pointerLow;
+            }
+        }
+
+        BLENDER_STRUCTURE.prototype = {
+            setData: function(pointer, _data_offset, data_block_length, BLENDER_FILE) {
+                if (this.__list__ === null) return this;
+                BLENDER_FILE.addObject(this);
+
+                this.__blender_file__ = BLENDER_FILE;
+
+                var struct = this.__list__,
+                    j = 0,
+                    i = 0,
+                    obj, name = "",
+                    type, length, Blender_Array_Length, Pointer_Match, offset, constructor;
+
+                this.__data_address__ = _data_offset;
+
+                if (struct === null) return this;
+
+                for (i = 0; i < struct.length; i += 6) {
+                    obj = null;
+                    name = struct[i];
+                    type = struct[i + 1];
+                    Blender_Array_Length = struct[i + 4];
+                    Pointer_Match = struct[i + 5];
+                    offset = this.__data_address__ + struct[i + 3];
+
+                    if (Blender_Array_Length > 1) {
+                        this[name] = [];
+                        j = 0;
+                        while (j < Blender_Array_Length) {
+                            if (current_SDNA_template.getSDNAStructureConstructor(type)) {
+                                constructor = current_SDNA_template.getSDNAStructureConstructor(type);
+                                this[name].push((new constructor()).setData(0, offset, offset + length / Blender_Array_Length, BLENDER_FILE));
+                            } else this[name].push(null);
+                            offset += length / Blender_Array_Length;
+                            j++;
+                        }
+                    } else {
+                        if (current_SDNA_template.getSDNAStructureConstructor(type)) {
+                            constructor = current_SDNA_template.getSDNAStructureConstructor(type);
+                            this[name] = (new constructor()).setData(0, offset, length + offset, BLENDER_FILE);
+                        } else this[name] = null;
+                    }
+                }
+                //break connection to configuration list
+                this.__list__ = null;
+                return this;
+            },
+
+            get aname() {
+                if (this.id) return this.id.name.slice(2);
+                else return undefined;
+            }
+        };
+
+        function toString(buffer, _in, _out) {
+            return String.fromCharCode.apply(String, new Uint8Array(buffer, _in, _out - _in));
+        }
+
+        //Begin parsing blender __blender_file__
+
+        function readFile() {
+            var count = 0;
+            var offset2 = 0;
+            var root = 0;
+            var i = 0;
+            var data_offset = 0;
+            var sdna_index = 0;
+            var code = "";
+            var block_length = 0;
+            var curr_count = 0;
+            var curr_count2 = 0;
+
+            FILE.memory_lookup = {};
+            struct_names = [];
+            offset = 0;
+
+            // Make sure we have a .blend __blender_file__. All blend files have the first 12bytes
+            // set with BLENDER-v### in Utf-8
+            if (toString(_data, offset, 7) !== "BLENDER") return ERROR = "File supplied is not a .blend compatible Blender file.";
+
+            // otherwise get templete from save version.
+
+            offset += 7;
+            pointer_size = ((toString(_data, offset++, offset)) == "_") ? 4 : 8;
+            BIG_ENDIAN = toString(_data, offset++, offset) !== "V";
+            var version = toString(_data, offset, offset + 3);
+
+
+            //create new master template if none exist for current blender version;
+            if (!templates[version]) {
+                templates[version] = new MASTER_SDNA_SCHEMA(version);
+            }
+
+            current_SDNA_template = templates[version];
+
+            FILE.template = current_SDNA_template;
+
+            offset += 3;
+
+            //Set SDNA structs if template hasn't been set.
+            //Todo: Move the following block into the MASTER_SDNA_SCHEMA object.
+            //*Like so:*/ current_SDNA_template.set(AB);
+
+            if (!current_SDNA_template.SDNA_SET) {
+                current_SDNA_template.endianess = BIG_ENDIAN;
+                current_SDNA_template.pointer_size = pointer_size;
+                //find DNA1 data block
+                offset2 = offset;
+
+                while (true) {
+                    sdna_index = data.getInt32(offset2 + pointer_size + 8, BIG_ENDIAN);
+                    code = toString(_data, offset2, offset2 + 4).replace(/\u0000/g, "");
+                    block_length = data.getInt32(offset2 + 4, true);
+                    offset2 += 16 + (pointer_size);
+                    if (code === "DNA1") {
+                        // DNA found; This is the core of the __blender_file__ and contains all the structure for the various data types used in Blender.
+                        count = 0;
+                        var types = [],
+                            fields = [],
+                            names = [],
+                            lengths = [],
+                            name = "",
+                            curr_name = "";
+
+                        //skip SDNA and NAME identifiers
+                        offset2 += 8;
+
+                        //Number of structs.
+                        count = data.getInt32(offset2, true);
+                        offset2 += 4;
+
+                        curr_count = 0;
+
+                        //Build up list of names for structs
+                        while (curr_count < count) {
+                            curr_name = "";
+                            while (data.getInt8(offset2) !== 0) {
+                                curr_name += toString(_data, offset2, offset2 + 1);
+                                offset2++;
+                            }
+                            names.push(curr_name);
+                            offset2++;
+                            curr_count++;
+                        }
+
+
+                        //Adjust for 4byte alignment
+                        if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2;
+                        offset2 += 4;
+
+                        //Number of struct types
+                        count = data.getInt32(offset2, true);
+                        offset2 += 4;
+                        curr_count = 0;
+
+                        //Build up list of types
+                        while (curr_count < count) {
+                            curr_name = "";
+                            while (data.getInt8(offset2) !== 0) {
+                                curr_name += toString(_data, offset2, offset2 + 1);
+                                offset2++;
+                            }
+                            types.push(curr_name);
+                            offset2++;
+                            curr_count++;
+                        }
+
+                        //Adjust for 4byte alignment
+                        if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2;
+                        offset2 += 4;
+                        curr_count = 0;
+
+                        //Build up list of byte lengths for types
+                        while (curr_count < count) {
+                            lengths.push(data.getInt16(offset2, BIG_ENDIAN));
+                            offset2 += 2;
+                            curr_count++;
+                        }
+
+                        //Adjust for 4byte alignment
+                        if ((offset2 % 4) > 0) offset2 = (4 - (offset2 % 4)) + offset2;
+                        offset2 += 4;
+
+                        //Number of structures
+                        var structure_count = data.getInt32(offset2, BIG_ENDIAN);
+                        offset2 += 4;
+                        curr_count = 0;
+
+                        //Create constructor objects from list of SDNA structs
+                        while (curr_count < structure_count) {
+                            var struct_name = types[data.getInt16(offset2, BIG_ENDIAN)];
+                            offset2 += 2;
+                            obj = [];
+                            count = data.getInt16(offset2, BIG_ENDIAN);
+                            offset2 += 2;
+                            curr_count2 = 0;
+                            struct_names.push(struct_name);
+
+                            //Fill an array with name, type, and length for each SDNA struct property
+                            while (curr_count2 < count) {
+                                obj.push(names[data.getInt16(offset2 + 2, BIG_ENDIAN)], types[data.getInt16(offset2, BIG_ENDIAN)], lengths[data.getInt16(offset2, BIG_ENDIAN)]);
+                                offset2 += 4;
+                                curr_count2++;
+                            }
+
+                            //Create a SDNA constructor by passing [type,name,lenth] array as second argument
+                            current_SDNA_template.getSDNAStructureConstructor(struct_name, obj);
+                            curr_count++;
+                        }
+                        current_SDNA_template.SDNA_SET = true;
+                        current_SDNA_template.SDNA_NAMES = struct_names;
+                        break;
+                    }
+                    offset2 += block_length;
+                }
+            }
+
+            //parse the rest of the data, starting back at the top.
+
+            //TODO: turn into "on-demand" parsing.
+
+            while (true) {
+                if ((offset % 4) > 0) {
+                    offset = (4 - (offset % 4)) + offset;
+                }
+
+                data_offset = offset;
+                sdna_index = data.getInt32(offset + pointer_size + 8, BIG_ENDIAN);
+                let code_uint = data.getUint32(offset, BIG_ENDIAN);
+                offset2 = offset + 16 + (pointer_size);
+                offset += data.getInt32(offset + 4, true) + 16 + (pointer_size);
+
+                if (code_uint === DNA1); //skip - already processed at this point    
+                else if (code_uint === ENDB) break; //end of __blender_file__ found
+                else {
+                    //Create a Blender object using a constructor template from current_SDNA_template
+                    var data_start = data_offset + pointer_size + 16;
+
+                    //Get a SDNA constructor by name;
+                    var constructor = current_SDNA_template.getSDNAStructureConstructor(current_SDNA_template.SDNA_NAMES[sdna_index]);
+
+                    var size = data.getInt32(data_offset + 4, BIG_ENDIAN);
+
+                    count = data.getInt32(data_offset + 12 + pointer_size, BIG_ENDIAN);
+
+                    if (count > 0) {
+                        var obj = new constructor();
+
+                        var length = constructor.prototype._length;
+
+
+                        var address = FILE.getPointer(data_offset + 8);
+
+                        obj.address = address + "";
+
+                        obj.setData(address, data_start, data_start + size, FILE);
+
+                        if (count > 1) {
+                            let array = [];
+                            array.push(obj);
+                            for (var u = 1; u < count; u++) {
+                                obj = new constructor();
+                                obj.setData(address, data_start + length * u, data_start + (length * u) + length, FILE);
+                                array.push(obj);
+                            }
+                            FILE.memory_lookup[address] = array;
+                        } else {
+                            FILE.memory_lookup[address] = obj;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return return_object;
+});

+ 99 - 0
source/threejs/blend_three.js

@@ -0,0 +1,99 @@
+/*jshint esversion: 6 */
+
+const createMaterial = require("./material.js");
+const createTexture = require("./texture.js");
+const createMesh = require("./mesh.js");
+const createLight = require("./light.js");
+
+const blender_object_types = {
+	mesh: 1,
+	lamp: 10
+};
+
+function createObject(blender_file, object) {
+	
+	if (object.data) {
+		//get the mesh 
+		var buffered_geometry = createMesh(object.data, [0, 0, 0]);
+
+		var blend_material = object.data.mat[0];
+
+		if (blend_material) {
+			var material = createMaterial(blend_material);
+		}
+
+		var mesh = new THREE.Mesh(buffered_geometry, material);
+
+		mesh.castShadow = true;
+		mesh.receiveShadow = true;
+
+		mesh.rotateZ(object.rot[2]);
+		mesh.rotateY(object.rot[1]);
+		mesh.rotateX(object.rot[0]);
+		mesh.scale.fromArray(object.size, 0);
+		mesh.position.fromArray([object.loc[0], (object.loc[2]), (-object.loc[1])], 0);
+
+		return mesh;
+	}
+
+	return null;
+}
+
+function loadObject(object_name, blender_file, cache) {
+	var objects = blender_file.Object;
+	materials = [];
+
+	for (var i = 0; i < objects.length; i++) {
+		let object = objects[i];
+
+		if (object.aname === object_name) {
+			switch (object.type) {
+				case blender_object_types.mesh:
+					return createObject(object, blender_file);
+					break;
+				case blender_object_types.lamp:
+					return createLight(object, blender_file);
+					break;
+			}
+		}
+	}
+
+	return null;
+}
+
+function loadScene(three_scene, blender_file, cache) {
+
+	for (let i = 0; i < blender_file.objects.Object.length; i++) {
+
+		let object = blender_file.objects.Object[i];
+
+		//Load Lights
+		if (object.type == blender_object_types.lamp) {
+			let light = createLight(blender_file, object);
+			three_scene.add(light);
+		}
+
+		//Load Meshes
+		if (object.type == blender_object_types.mesh) {
+			let mesh = createObject(blender_file, object);
+			if(mesh){
+				three_scene.add(mesh);
+			}
+		}
+	}
+}
+
+module.exports = (blender_file) => {
+
+	if (!THREE) {
+		console.warn("No ThreeJS object detected");
+		return {};
+	}
+
+	var cache = {};
+
+	return {
+		loadScene: (three_scene) => loadScene(three_scene, blender_file, cache),
+		loadObject: (object_name) => loadObject(object_name, blender_file, cache)
+	};
+};

+ 41 - 0
source/threejs/light.js

@@ -0,0 +1,41 @@
+/*jshint esversion: 6 */
+
+var blender_light_types = {
+	point: 0,
+	sun: 1,
+	spot: 0,
+	hemi: 0,
+	area: 0
+};
+
+module.exports = function createThreeJSLamp(blend_lamp) {
+
+	let ldata = blend_lamp.data;
+
+	let pos_array = [blend_lamp.loc[0], blend_lamp.loc[2], -blend_lamp.loc[1]];
+
+	let color = ((ldata.r * 255) << 16) | ((ldata.g * 255) << 8) | ((ldata.b * 255) << 0);
+	let intesity = ldata.energy;
+	let distance = 0;
+
+	var three_light = null;
+
+	switch (ldata.type) {
+		case blender_light_types.point:
+			var three_light = new THREE.PointLight(color, intesity, distance);
+			three_light.position.fromArray(pos_array, 0);
+			three_light.castShadow = true;
+			break;
+		case blender_light_types.sun:
+			var three_light = new THREE.PointLight(color, intesity, distance);
+			three_light.position.fromArray(pos_array, 0);
+			three_light.castShadow = true;
+			three_light.shadow.mapSize.width = 1024;
+			three_light.shadow.mapSize.height = 1024;
+			three_light.shadow.camera.near = 0.01;
+			three_light.shadow.camera.far = 500;
+			break;
+	}
+
+	return three_light;
+}

+ 165 - 0
source/threejs/material.js

@@ -0,0 +1,165 @@
+/*jshint esversion: 6 */
+
+module.exports = (() => {
+    const createTexture = require("./texture.js");
+
+    var texture_mappings = {
+        diff_color: 1,
+        normal: 2,
+        mirror: 8,
+        diff_intensity: 16,
+        spec_intensity: 32,
+        emit: 32,
+        alpha: 128,
+        spec_hardness: 256,
+        ray_mirror: 512,
+        translucency: 1024,
+        ambient: 2048,
+        displacement: 4096,
+        warp: 8192
+    };
+
+    let blender_specular_types = {
+        cooktorr: 0,
+        phong: 1,
+        blinn: 2,
+        toon: 3,
+        wardiso: 4
+    };
+
+    function applyColorMapping(blender_texture, three_texture, material) {
+        if (blender_texture.mapto & texture_mappings.diff_color) {
+            material.map = three_texture;
+        }
+    }
+
+    function applySpecMapping(blender_texture, three_texture, material) {
+        if (blender_texture.mapto & texture_mappings.spec_color && material.type != "MeshStandardMaterial") {
+            material.specularMap = three_texture;
+        }
+
+        if (blender_texture.mapto & texture_mappings.spec_intensity && material.type != "MeshStandardMaterial") {
+            material.roughnessMap = three_texture;
+        }
+    }
+
+    function applyAlphaMapping(blender_texture, three_texture, material) {
+        if (blender_texture.mapto & texture_mappings.alpha) {
+            material.alphaMap = three_texture;
+        }
+    }
+
+    function applyNormalMapping(blender_texture, three_texture, material) {
+        if (blender_texture.mapto & texture_mappings.normal) {
+            material.normalMap = three_texture;
+            material.normalScale = {
+                x: blender_texture.norfac,
+                y: blender_texture.norfac
+            };
+        }
+    }
+
+    function applyMirrorMapping(blender_texture, three_texture, material) {
+        if (blender_texture.mapto & texture_mappings.mirror) {
+            material.envMap = three_texture;
+            material.envMapIntensity = blender_texture.mirrfac;
+        }
+    }
+
+    var blender_texture_coordinates = {
+        GENERATED : 1,
+        REFLECTION : 2,
+        NORMAL:4,
+        GLOBAL : 8,
+        UV : 16,
+        OBJECT : 32,
+        WINDOW: 1024,
+        TANGENT:4096,
+        PARTICLE: 8192,
+        STRESS:16384
+    }
+
+    var blender_texture_mapping = {
+        FLAT : 0,
+        CUBE : 1,
+        TUBE : 2,
+        SPHERE : 3
+    }
+
+    function applyTexture(blender_texture, material) {
+        //extract blender_texture data. Use Only if image has been supplied.
+        if (blender_texture && blender_texture.tex && blender_texture.tex.ima) {
+
+            let three_texture = createTexture(blender_texture.tex.ima);
+
+            if(blender_texture.texco == blender_texture_coordinates.REFLECTION){
+                switch(blender_texture.mapping){
+                    case blender_texture_mapping.FLAT:
+                        three_texture.mapping = THREE.EquirectangularReflectionMapping;
+                    break;
+                    case blender_texture_mapping.SPHERE:
+                        three_texture.mapping = THREE.SphericalReflectionMapping;
+                    break;
+                }
+                 //three_texture.mapping = THREE.EquirectangularRefractionMapping;
+            }
+            
+            applyColorMapping(blender_texture, three_texture, material);
+            
+            applySpecMapping(blender_texture, three_texture, material);
+            
+            applyAlphaMapping(blender_texture, three_texture, material);
+            
+            applyNormalMapping(blender_texture, three_texture, material);
+
+            applyMirrorMapping(blender_texture, three_texture, material);
+        }
+    }
+
+    return function createThreeJSMaterial(blend_mat) {
+
+        var material = null;
+
+        var textures = blend_mat.mtex;
+
+        switch (blend_mat.spec_shader) {
+            case blender_specular_types.lambert:
+                material = new THREE.MeshLambertMaterial();
+                material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b);
+                break;
+            case blender_specular_types.blinn:
+            case blender_specular_types.phong:
+
+                material = new THREE.MeshStandardMaterial();
+                material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b);
+                //material.specular.setRGB(blend_mat.specr, blend_mat.specg, blend_mat.specb);
+                material.roughness = (1 - (blend_mat.har / 512));
+                material.metalness = 1 - blend_mat.ref;
+                if(blend_mat.alpha < 0.98){
+                    material.transparent = true;
+                    material.opacity = blend_mat.alpha;
+                    console.log(blend_mat, material)
+                }
+                break;
+            case blender_specular_types.wardiso:
+            case blender_specular_types.cooktorr:
+                material = new THREE.MeshPhongMaterial();
+                material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b);
+                material.specular.setRGB(blend_mat.specr, blend_mat.specg, blend_mat.specb);
+                material.shininess = blend_mat.har / 512;
+                material.reflectivity = blend_mat.ref * 100;
+                break;
+            default:
+                material = new THREE.MeshLambertMaterial();
+                material.color.setRGB(blend_mat.r, blend_mat.g, blend_mat.b);
+                break;
+        }
+
+        var at = (texture) => applyTexture(texture, material);
+
+
+        if (textures && textures.length) textures.map(at);
+
+        return material;
+    };
+})();

+ 207 - 0
source/threejs/mesh.js

@@ -0,0 +1,207 @@
+/*jshint esversion: 6 */
+module.exports = function createThreeJSBufferGeometry(blender_mesh, origin) {
+    //get materials
+    let pick_material = 0,
+        mesh = blender_mesh,
+        faces = mesh.mpoly,
+        loops = mesh.mloop,
+        UV = mesh.mloopuv,
+        verts = mesh.mvert;
+
+    var geometry = new THREE.BufferGeometry();
+
+    if (!faces) return geometry;
+
+    var index_count = 0;
+
+    //precalculate the size of the array needed for faces
+    var face_indice_count = 0;
+    var face_indice_counta = 0;
+
+    for (var i = 0; i < faces.length; i++) {
+        var face = faces[i] || faces;
+        var len = face.totloop;
+        var indexi = 1;
+
+        face_indice_counta += (len * 2 / 3) | 0;
+
+        while (indexi < len) {
+            face_indice_count += 3;
+            indexi += 2;
+        }
+    }
+
+    //extract face info and dump into array buffer;
+    var face_buffer = new Uint32Array(face_indice_count);
+    var uv_buffer = new Float32Array(face_indice_count * 2);
+    var normal_buffer = new Float32Array(face_indice_count * 3);
+    var verts_array_buff = new Float32Array(face_indice_count * 3);
+
+    for (var i = 0; i < faces.length; i++) {
+        var face = faces[i] || faces;
+        var len = face.totloop;
+        var start = face.loopstart;
+        var indexi = 1;
+        var offset = 0;
+
+        while (indexi < len) {
+            var face_normals = [];
+            var face_index_array = [];
+            var face_uvs = [];
+
+            let index = 0;
+
+            for (var l = 0; l < 3; l++) {
+                //Per Vertice 
+
+                if ((indexi - 1) + l < len) {
+                    index = start + (indexi - 1) + l;
+                } else {
+                    index = start;
+                }
+
+                var v = loops[index].v;
+                var vert = verts[v];
+                face_buffer[index_count] = index_count;
+                //get normals, which are 16byte ints, and norm them back into floats.
+
+                verts_array_buff[index_count * 3 + 0] = vert.co[0] + origin[0];
+                verts_array_buff[index_count * 3 + 1] = vert.co[2] + origin[2];
+                verts_array_buff[index_count * 3 + 2] = -vert.co[1] + -origin[1];
+
+                normal_buffer[index_count * 3 + 0] = vert.no[0];
+                normal_buffer[index_count * 3 + 1] = vert.no[2];
+                normal_buffer[index_count * 3 + 2] = (-vert.no[1]);
+
+
+                if (UV) {
+                    var uv = UV[index].uv;
+                    uv_buffer[index_count * 2 + 0] = uv[0];
+                    uv_buffer[index_count * 2 + 1] = uv[1];
+                }
+
+                index_count++;
+            }
+
+            indexi += 2;
+        }
+    }
+
+    geometry.addAttribute('position', new THREE.BufferAttribute(verts_array_buff, 3));
+    geometry.setIndex(new THREE.BufferAttribute(face_buffer, 1));
+    geometry.addAttribute('normal', new THREE.BufferAttribute(normal_buffer, 3));
+    geometry.addAttribute('uv', new THREE.BufferAttribute(uv_buffer, 2));
+    //geometry.blend_mat = materials[pick_material];
+
+    return geometry;
+};
+
+function createThreeJSGeometry(blender_mesh, origin) {
+    //get materials
+    var mats = blender_mesh.mat,
+        materials = [];
+    for (var i = 0; i < mats.length; i++) {
+        var material = createThreeJSMaterial(mats[i]);
+        materials.push(material);
+    }
+
+    let pick_material = 0,
+        mesh = blender_mesh,
+        faces = mesh.mpoly,
+        loops = mesh.mloop,
+        UV = mesh.mloopuv,
+        verts = mesh.mvert,
+        vert_array = [],
+        face_array = [],
+        uv_array = [],
+        normal_array = [];
+
+    var geometry = new THREE.Geometry();
+
+    if (!faces) return geometry;
+
+
+    var index_count = 0;
+
+    let verts_array_buff = new Float32Array(verts.length * 3);
+
+    for (var i = 0; i < verts.length; i++) {
+        let vert = verts[i];
+        vert_array.push(new THREE.Vector3(vert.co[0] + origin[0], vert.co[2] + origin[2], -vert.co[1] - origin[1]));
+    }
+
+    for (var i = 0; i < faces.length; i++) {
+        var face = faces[i] || faces;
+        var len = face.totloop;
+        var start = face.loopstart;
+        var indexi = 1;
+
+        pick_material = face.mat_nr;
+
+        while (indexi < len) {
+            var face_normals = [];
+            var face_index_array = [];
+            var face_uvs = [];
+
+            let index = 0;
+
+            for (var l = 0; l < 3; l++) {
+                //Per Vertice 
+
+                if ((indexi - 1) + l < len) {
+                    index = start + (indexi - 1) + l;
+                } else {
+                    index = start;
+                }
+
+                var v = loops[index].v;
+                var vert = verts[v];
+
+                face_index_array.push(v);
+
+                index_count++;
+
+                //get normals, which are 16byte ints, and norm them back into floats.
+
+                var
+                    n1 = vert.no[0],
+                    n2 = vert.no[2],
+                    n3 = -vert.no[1];
+
+                var nl = 1;
+
+                Math.sqrt((n1 * n1) + (n2 * n2) + (n3 * n3));
+
+                face_normals.push(new THREE.Vector3(n1 / nl, n2 / nl, n3 / nl));
+
+                if (UV) {
+                    var uv = UV[index].uv;
+                    face_uvs.push(new THREE.Vector2(uv[0], uv[1]));
+                }
+            }
+            uv_array.push(face_uvs);
+            face_array.push(new THREE.Face3(
+                face_index_array[0], face_index_array[1], face_index_array[2],
+                face_normals
+            ));
+
+            indexi += 2;
+        }
+    }
+    geometry.blend_mat = materials[pick_material];
+    geometry.vertices = vert_array;
+    geometry.faces = face_array;
+    if (uv_array.length > 0) {
+        geometry.faceVertexUvs = [uv_array];
+    }
+
+    geometry.uvsNeedUpdate = true;
+
+    //Well, using blender file normals does not work. Will need to investigate why normals from the blender file do not provide correct results. 
+    //For now, have Three calculate normals. 
+
+    geometry.computeVertexNormals();
+
+
+    return geometry;
+};

+ 56 - 0
source/threejs/texture.js

@@ -0,0 +1,56 @@
+/*jshint esversion: 6 */
+
+let blender_texture_cache = {};
+
+
+module.exports = function createThreeJSTexture(image) {
+    let base64 = require("base64-js");
+    let parsed_blend_file = image.__blender_file__;
+    let texture = null;
+    let name = image.aname;
+
+    if (image.packedfile) {
+
+        if (blender_texture_cache[name]) {
+            texture = blender_texture_cache[name];
+        } else {
+
+            //get the extension
+            let ext = name.split('.').pop();
+
+            let data = image.packedfile;
+
+            let size = data.size;
+
+            let offset = data.data.__data_address__;
+
+            let raw_data = parsed_blend_file.byte.slice(offset, offset + size);
+
+            let encodedData = base64.fromByteArray(raw_data);
+
+            let dataURI;
+
+            switch (ext) {
+                case "png":
+                    dataURI = "data:image/png;base64," + encodedData;
+                    break;
+                case "jpg":
+                    dataURI = "data:image/jpeg;base64," + encodedData;
+                    break;
+            }
+
+            let img = new Image();
+
+            img.src = dataURI;
+
+            texture = new THREE.Texture(img);
+
+            img.onload = () => {
+                texture.needsUpdate = true;
+            };
+
+            blender_texture_cache[name] = texture;
+        }
+    }
+    return texture;
+};

+ 5 - 0
threejs_notes.md

@@ -0,0 +1,5 @@
+# Notes On ThreeJS Usage
+
+## Materials
+
+## Textures

Некоторые файлы не были показаны из-за большого количества измененных файлов