Skip to content

Instantly share code, notes, and snippets.

@mrdoob
Last active December 11, 2015 00:38
Show Gist options
  • Select an option

  • Save mrdoob/4517434 to your computer and use it in GitHub Desktop.

Select an option

Save mrdoob/4517434 to your computer and use it in GitHub Desktop.

Revisions

  1. mrdoob revised this gist Jan 12, 2013. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions SoftwareRenderer.js
    Original file line number Diff line number Diff line change
    @@ -152,6 +152,8 @@ THREE.SoftwareRenderer = function () {
    var y = Math.min( recty1, prevrecty1 );
    var width = Math.max( rectx2, prevrectx2 ) - x;
    var height = Math.max( recty2, prevrecty2 ) - y;

    // debug; draw zbuffer

    for ( var i = 0, l = zbuffer.length; i < l; i += 4 ) {

  2. mrdoob created this gist Jan 12, 2013.
    435 changes: 435 additions & 0 deletions SoftwareRenderer.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,435 @@
    /**
    * @author mrdoob / http://mrdoob.com/
    * @author mraleph / http://mrale.ph/
    */

    THREE.SoftwareRenderer = function () {

    console.log( 'THREE.SoftwareRenderer', THREE.REVISION );

    var canvas = document.createElement( 'canvas' );
    var context = canvas.getContext( '2d' );

    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;

    var canvasWidthHalf = canvasWidth / 2;
    var canvasHeightHalf = canvasHeight / 2;

    var imagedata = context.getImageData( 0, 0, canvas.width, canvas.height );
    var data = imagedata.data;

    var zbuffer = new Float32Array( data.length );

    var blocksize = 8;

    var canvasWBlocks = Math.floor( ( canvasWidth + blocksize - 1 ) / blocksize );
    var canvasHBlocks = Math.floor( ( canvasHeight + blocksize - 1 ) / blocksize );

    var blocks = new Float32Array( canvasWBlocks * canvasHBlocks );

    var rectx1 = Infinity, recty1 = Infinity;
    var rectx2 = 0, recty2 = 0;

    var prevrectx1 = Infinity, prevrecty1 = Infinity;
    var prevrectx2 = 0, prevrecty2 = 0;

    var projector = new THREE.Projector();

    this.domElement = canvas;

    this.autoClear = true;

    this.setSize = function ( width, height ) {

    canvasWidth = Math.floor( width / blocksize ) * blocksize;
    canvasHeight = Math.floor( height / blocksize ) * blocksize;

    canvasWidthHalf = canvasWidth / 2;
    canvasHeightHalf = canvasHeight / 2;

    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    imagedata = context.getImageData( 0, 0, canvasWidth, canvasHeight );
    data = imagedata.data;

    zbuffer = new Float32Array( data.length );

    canvasWBlocks = Math.floor( ( canvasWidth + blocksize - 1 ) / blocksize );
    canvasHBlocks = Math.floor( ( canvasHeight + blocksize - 1 ) / blocksize );

    blocks = new Float32Array( canvasWBlocks * canvasHBlocks )

    };

    this.clear = function () {

    clearRectangle( prevrectx1, prevrecty1, prevrectx2, prevrecty2 );

    for ( var i = 0, l = blocks.length; i < l; i ++ ) {

    blocks[ i ] = 1;

    }

    for ( var i = 0, l = zbuffer.length; i < l; i ++ ) {

    zbuffer[ i ] = 1;

    }

    };

    this.render = function ( scene, camera ) {

    rectx1 = Infinity;
    recty1 = Infinity;
    rectx2 = 0;
    recty2 = 0;

    if ( this.autoClear ) this.clear();

    var renderData = projector.projectScene( scene, camera );
    var elements = renderData.elements;

    for ( var e = 0, el = elements.length; e < el; e ++ ) {

    var element = elements[ e ];

    if ( element instanceof THREE.RenderableFace3 ) {

    drawTriangle(
    element.v1.positionScreen,
    element.v2.positionScreen,
    element.v3.positionScreen,
    function ( offset, u, v ) {

    data[ offset ] = u;
    data[ offset + 1 ] = v;
    data[ offset + 2 ] = 0;
    data[ offset + 3 ] = 255;

    }

    )

    } else if ( element instanceof THREE.RenderableFace4 ) {

    drawTriangle(
    element.v1.positionScreen,
    element.v2.positionScreen,
    element.v4.positionScreen,
    function ( offset, u, v ) {

    data[ offset ] = u;
    data[ offset + 1 ] = v;
    data[ offset + 2 ] = 0;
    data[ offset + 3 ] = 255;
    }
    );

    drawTriangle(
    element.v2.positionScreen,
    element.v3.positionScreen,
    element.v4.positionScreen,
    function ( offset, u, v ) {

    data[ offset ] = u;
    data[ offset + 1 ] = v;
    data[ offset + 2 ] = 0;
    data[ offset + 3 ] = 255;

    }

    );

    }

    }

    var x = Math.min( rectx1, prevrectx1 );
    var y = Math.min( recty1, prevrecty1 );
    var width = Math.max( rectx2, prevrectx2 ) - x;
    var height = Math.max( recty2, prevrecty2 ) - y;

    for ( var i = 0, l = zbuffer.length; i < l; i += 4 ) {

    data[ i ] = zbuffer[ i ] * 255;
    data[ i + 1 ] = zbuffer[ i ] * 255;
    data[ i + 2 ] = zbuffer[ i ] * 255;

    }

    if ( x !== Infinity ) {

    context.putImageData( imagedata, 0, 0, x, y, width, height );

    }

    prevrectx1 = rectx1; prevrecty1 = recty1;
    prevrectx2 = rectx2; prevrecty2 = recty2;

    };

    function numericalSort( a, b ) {

    return a.z - b.z;

    }

    function clearRectangle( x1, y1, x2, y2 ) {

    var xmin = Math.max( Math.min( x1, x2 ), 0 );
    var xmax = Math.min( Math.max( x1, x2 ), canvasWidth );
    var ymin = Math.max( Math.min( y1, y2 ), 0 );
    var ymax = Math.min( Math.max( y1, y2 ), canvasHeight );

    var offset = ( xmin + ymin * canvasWidth - 1 ) * 4 + 3;
    var linestep = ( canvasWidth - ( xmax - xmin ) ) * 4;

    for ( var y = ymin; y < ymax; y ++ ) {

    for ( var x = xmin; x < xmax; x ++ ) {

    data[ offset += 4 ] = 0;

    }

    offset += linestep;

    }

    }

    function drawTriangle( v1, v2, v3, shader ) {

    var z1 = v1.z;
    var z2 = v2.z;
    var z3 = v3.z;

    var dz12 = z1 - z2;
    var dz32 = z3 - z2;

    var minz = Math.min( z1, Math.min( z2, z3 ) );
    var maxz = Math.max( z1, Math.max( z2, z3 ) );

    // https://gist.github.com/2486101
    // explanation: http://pouet.net/topic.php?which=8760&page=1

    // 28.4 fixed-point coordinates

    var x1 = ( 16 * ( v1.x * canvasWidthHalf + canvasWidthHalf ) ) | 0;
    var x2 = ( 16 * ( v2.x * canvasWidthHalf + canvasWidthHalf ) ) | 0;
    var x3 = ( 16 * ( v3.x * canvasWidthHalf + canvasWidthHalf ) ) | 0;

    var y1 = ( 16 * ( - v1.y * canvasHeightHalf + canvasHeightHalf ) ) | 0;
    var y2 = ( 16 * ( - v2.y * canvasHeightHalf + canvasHeightHalf ) ) | 0;
    var y3 = ( 16 * ( - v3.y * canvasHeightHalf + canvasHeightHalf ) ) | 0;

    // Deltas

    var dx12 = x1 - x2, dy12 = y2 - y1;
    var dx23 = x2 - x3, dy23 = y3 - y2;
    var dx31 = x3 - x1, dy31 = y1 - y3;

    // Bounding rectangle

    var minx = Math.max( ( Math.min( x1, x2, x3 ) + 0xf ) >> 4, 0 );
    var maxx = Math.min( ( Math.max( x1, x2, x3 ) + 0xf ) >> 4, canvasWidth );
    var miny = Math.max( ( Math.min( y1, y2, y3 ) + 0xf ) >> 4, 0 );
    var maxy = Math.min( ( Math.max( y1, y2, y3 ) + 0xf ) >> 4, canvasHeight );

    rectx1 = Math.min( minx, rectx1 );
    rectx2 = Math.max( maxx, rectx2 );
    recty1 = Math.min( miny, recty1 );
    recty2 = Math.max( maxy, recty2 );

    // Block size, standard 8x8 (must be power of two)

    var q = blocksize;

    // Start in corner of 8x8 block

    minx &= ~(q - 1);
    miny &= ~(q - 1);

    // Constant part of half-edge functions

    var c1 = dy12 * ((minx << 4) - x1) + dx12 * ((miny << 4) - y1);
    var c2 = dy23 * ((minx << 4) - x2) + dx23 * ((miny << 4) - y2);
    var c3 = dy31 * ((minx << 4) - x3) + dx31 * ((miny << 4) - y3);

    // Correct for fill convention

    if ( dy12 > 0 || ( dy12 == 0 && dx12 > 0 ) ) c1 ++;
    if ( dy23 > 0 || ( dy23 == 0 && dx23 > 0 ) ) c2 ++;
    if ( dy31 > 0 || ( dy31 == 0 && dx31 > 0 ) ) c3 ++;

    // Note this doesn't kill subpixel precision, but only because we test for >=0 (not >0).
    // It's a bit subtle. :)
    c1 = (c1 - 1) >> 4;
    c2 = (c2 - 1) >> 4;
    c3 = (c3 - 1) >> 4;

    // Set up min/max corners
    var qm1 = q - 1; // for convenience
    var nmin1 = 0, nmax1 = 0;
    var nmin2 = 0, nmax2 = 0;
    var nmin3 = 0, nmax3 = 0;
    if (dx12 >= 0) nmax1 -= qm1*dx12; else nmin1 -= qm1*dx12;
    if (dy12 >= 0) nmax1 -= qm1*dy12; else nmin1 -= qm1*dy12;
    if (dx23 >= 0) nmax2 -= qm1*dx23; else nmin2 -= qm1*dx23;
    if (dy23 >= 0) nmax2 -= qm1*dy23; else nmin2 -= qm1*dy23;
    if (dx31 >= 0) nmax3 -= qm1*dx31; else nmin3 -= qm1*dx31;
    if (dy31 >= 0) nmax3 -= qm1*dy31; else nmin3 -= qm1*dy31;

    // Loop through blocks
    var linestep = ( canvasWidth - q ) * 4;
    var scale = 255.0 / (c1 + c2 + c3);

    var cb1 = c1;
    var cb2 = c2;
    var cb3 = c3;
    var qstep = -q;
    var e1x = qstep * dy12;
    var e2x = qstep * dy23;
    var e3x = qstep * dy31;
    var x0 = minx;

    for ( var y0 = miny; y0 < maxy; y0 += q ) {

    // New block line - keep hunting for tri outer edge in old block line dir
    while ( x0 >= minx && x0 < maxx && cb1 >= nmax1 && cb2 >= nmax2 && cb3 >= nmax3 ) {

    x0 += qstep;
    cb1 += e1x;
    cb2 += e2x;
    cb3 += e3x;

    }

    // Okay, we're now in a block we know is outside. Reverse direction and go into main loop.
    qstep = -qstep;
    e1x = -e1x;
    e2x = -e2x;
    e3x = -e3x;

    while ( 1 ) {

    // Step everything
    x0 += qstep;
    cb1 += e1x;
    cb2 += e2x;
    cb3 += e3x;

    // We're done with this block line when at least one edge completely out
    // If an edge function is too small and decreasing in the current traversal
    // dir, we're done with this line.
    if (x0 < minx || x0 >= maxx) break;
    if (cb1 < nmax1) if (e1x < 0) break; else continue;
    if (cb2 < nmax2) if (e2x < 0) break; else continue;
    if (cb3 < nmax3) if (e3x < 0) break; else continue;

    // We can skip this block if it's already fully covered
    var blockX = (x0 / q) | 0;
    var blockY = (y0 / q) | 0;
    var blockId = blockX + blockY * canvasWBlocks;

    if ( blocks[ blockId ] < minz ) continue;

    // Offset at top-left corner
    var offset = ( x0 + y0 * canvasWidth ) * 4;

    // Accept whole block when fully covered
    if ( cb1 >= nmin1 && cb2 >= nmin2 && cb3 >= nmin3 ) {

    var cy1 = cb1;
    var cy2 = cb2;

    for ( var iy = 0; iy < q; iy ++ ) {

    var cx1 = cy1;
    var cx2 = cy2;

    for ( var ix = 0; ix < q; ix ++ ) {

    var u = cx1 * scale;
    var v = cx2 * scale;
    var z = z1 + ( u * dz12 ) + ( v * dz32 );

    zbuffer[ offset ] = z;
    shader( offset, u, v );

    cx1 += dy12;
    cx2 += dy23;
    offset += 4;

    }

    cy1 += dx12;
    cy2 += dx23;
    offset += linestep;

    }

    blocks[ blockId ] = maxz;

    } else { // Partially covered block

    var cy1 = cb1;
    var cy2 = cb2;
    var cy3 = cb3;

    for ( var iy = 0; iy < q; iy ++ ) {

    var cx1 = cy1;
    var cx2 = cy2;
    var cx3 = cy3;

    for ( var ix = 0; ix < q; ix ++ ) {

    if ( ( cx1 | cx2 | cx3 ) >= 0 ) {

    var u = cx1 * scale;
    var v = cx2 * scale;

    var z = z1 + ( u * dz12 ) + ( v * dz32 );

    // if ( zbuffer[ offset ] > z ) {

    zbuffer[ offset ] = z;
    shader( offset, u, v );

    // }

    }

    cx1 += dy12;
    cx2 += dy23;
    cx3 += dy31;
    offset += 4;

    }

    cy1 += dx12;
    cy2 += dx23;
    cy3 += dx31;
    offset += linestep;

    }

    }

    }

    // Advance to next row of blocks
    cb1 += q*dx12;
    cb2 += q*dx23;
    cb3 += q*dx31;

    }

    }

    };
    512 changes: 512 additions & 0 deletions TrackballControls.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,512 @@
    /**
    * @author Eberhard Graether / http://egraether.com/
    */

    THREE.TrackballControls = function ( object, domElement ) {

    THREE.EventDispatcher.call( this );

    var _this = this;
    var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };

    this.object = object;
    this.domElement = ( domElement !== undefined ) ? domElement : document;

    // API

    this.enabled = true;

    this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
    this.radius = ( this.screen.width + this.screen.height ) / 4;

    this.rotateSpeed = 1.0;
    this.zoomSpeed = 1.2;
    this.panSpeed = 0.3;

    this.noRotate = false;
    this.noZoom = false;
    this.noPan = false;

    this.staticMoving = false;
    this.dynamicDampingFactor = 0.2;

    this.minDistance = 0;
    this.maxDistance = Infinity;

    this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];

    // internals

    this.target = new THREE.Vector3();

    var lastPosition = new THREE.Vector3();

    var _state = STATE.NONE,
    _prevState = STATE.NONE,

    _eye = new THREE.Vector3(),

    _rotateStart = new THREE.Vector3(),
    _rotateEnd = new THREE.Vector3(),

    _zoomStart = new THREE.Vector2(),
    _zoomEnd = new THREE.Vector2(),

    _touchZoomDistanceStart = 0,
    _touchZoomDistanceEnd = 0,

    _panStart = new THREE.Vector2(),
    _panEnd = new THREE.Vector2();

    // events

    var changeEvent = { type: 'change' };


    // methods

    this.handleResize = function () {

    this.screen.width = window.innerWidth;
    this.screen.height = window.innerHeight;

    this.screen.offsetLeft = 0;
    this.screen.offsetTop = 0;

    this.radius = ( this.screen.width + this.screen.height ) / 4;

    };

    this.handleEvent = function ( event ) {

    if ( typeof this[ event.type ] == 'function' ) {

    this[ event.type ]( event );

    }

    };

    this.getMouseOnScreen = function ( clientX, clientY ) {

    return new THREE.Vector2(
    ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
    ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
    );

    };

    this.getMouseProjectionOnBall = function ( clientX, clientY ) {

    var mouseOnBall = new THREE.Vector3(
    ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
    ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
    0.0
    );

    var length = mouseOnBall.length();

    if ( length > 1.0 ) {

    mouseOnBall.normalize();

    } else {

    mouseOnBall.z = Math.sqrt( 1.0 - length * length );

    }

    _eye.copy( _this.object.position ).sub( _this.target );

    var projection = _this.object.up.clone().setLength( mouseOnBall.y );
    projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
    projection.add( _eye.setLength( mouseOnBall.z ) );

    return projection;

    };

    this.rotateCamera = function () {

    var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );

    if ( angle ) {

    var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
    quaternion = new THREE.Quaternion();

    angle *= _this.rotateSpeed;

    quaternion.setFromAxisAngle( axis, -angle );

    _eye.applyQuaternion( quaternion );
    _this.object.up.applyQuaternion( quaternion );

    _rotateEnd.applyQuaternion( quaternion );

    if ( _this.staticMoving ) {

    _rotateStart.copy( _rotateEnd );

    } else {

    quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
    _rotateStart.applyQuaternion( quaternion );

    }

    }

    };

    this.zoomCamera = function () {

    if ( _state === STATE.TOUCH_ZOOM ) {

    var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
    _touchZoomDistanceStart = _touchZoomDistanceEnd;
    _eye.multiplyScalar( factor );

    } else {

    var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;

    if ( factor !== 1.0 && factor > 0.0 ) {

    _eye.multiplyScalar( factor );

    if ( _this.staticMoving ) {

    _zoomStart.copy( _zoomEnd );

    } else {

    _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;

    }

    }

    }

    };

    this.panCamera = function () {

    var mouseChange = _panEnd.clone().sub( _panStart );

    if ( mouseChange.lengthSq() ) {

    mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );

    var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
    pan.add( _this.object.up.clone().setLength( mouseChange.y ) );

    _this.object.position.add( pan );
    _this.target.add( pan );

    if ( _this.staticMoving ) {

    _panStart = _panEnd;

    } else {

    _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );

    }

    }

    };

    this.checkDistances = function () {

    if ( !_this.noZoom || !_this.noPan ) {

    if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {

    _this.object.position.setLength( _this.maxDistance );

    }

    if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {

    _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );

    }

    }

    };

    this.update = function () {

    _eye.subVectors( _this.object.position, _this.target );

    if ( !_this.noRotate ) {

    _this.rotateCamera();

    }

    if ( !_this.noZoom ) {

    _this.zoomCamera();

    }

    if ( !_this.noPan ) {

    _this.panCamera();

    }

    _this.object.position.addVectors( _this.target, _eye );

    _this.checkDistances();

    _this.object.lookAt( _this.target );

    if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {

    _this.dispatchEvent( changeEvent );

    lastPosition.copy( _this.object.position );

    }

    };

    // listeners

    function keydown( event ) {

    if ( _this.enabled === false ) return;

    window.removeEventListener( 'keydown', keydown );

    _prevState = _state;

    if ( _state !== STATE.NONE ) {

    return;

    } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {

    _state = STATE.ROTATE;

    } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {

    _state = STATE.ZOOM;

    } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {

    _state = STATE.PAN;

    }

    }

    function keyup( event ) {

    if ( _this.enabled === false ) return;

    _state = _prevState;

    window.addEventListener( 'keydown', keydown, false );

    }

    function mousedown( event ) {

    if ( _this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();

    if ( _state === STATE.NONE ) {

    _state = event.button;

    }

    if ( _state === STATE.ROTATE && !_this.noRotate ) {

    _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

    } else if ( _state === STATE.ZOOM && !_this.noZoom ) {

    _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

    } else if ( _state === STATE.PAN && !_this.noPan ) {

    _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

    }

    document.addEventListener( 'mousemove', mousemove, false );
    document.addEventListener( 'mouseup', mouseup, false );

    }

    function mousemove( event ) {

    if ( _this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();

    if ( _state === STATE.ROTATE && !_this.noRotate ) {

    _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );

    } else if ( _state === STATE.ZOOM && !_this.noZoom ) {

    _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

    } else if ( _state === STATE.PAN && !_this.noPan ) {

    _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );

    }

    }

    function mouseup( event ) {

    if ( _this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();

    _state = STATE.NONE;

    document.removeEventListener( 'mousemove', mousemove );
    document.removeEventListener( 'mouseup', mouseup );

    }

    function mousewheel( event ) {

    if ( _this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();

    var delta = 0;

    if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9

    delta = event.wheelDelta / 40;

    } else if ( event.detail ) { // Firefox

    delta = - event.detail / 3;

    }

    _zoomStart.y += ( 1 / delta ) * 0.05;

    }

    function touchstart( event ) {

    if ( _this.enabled === false ) return;

    switch ( event.touches.length ) {

    case 1:
    _state = STATE.TOUCH_ROTATE;
    _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    case 2:
    _state = STATE.TOUCH_ZOOM;
    var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
    var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
    _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
    break;

    case 3:
    _state = STATE.TOUCH_PAN;
    _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    default:
    _state = STATE.NONE;

    }

    }

    function touchmove( event ) {

    if ( _this.enabled === false ) return;

    event.preventDefault();
    event.stopPropagation();

    switch ( event.touches.length ) {

    case 1:
    _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    case 2:
    var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
    var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
    _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
    break;

    case 3:
    _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    default:
    _state = STATE.NONE;

    }

    }

    function touchend( event ) {

    if ( _this.enabled === false ) return;

    switch ( event.touches.length ) {

    case 1:
    _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    case 2:
    _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
    break;

    case 3:
    _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
    break;

    }

    _state = STATE.NONE;

    }

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousedown', mousedown, false );

    this.domElement.addEventListener( 'mousewheel', mousewheel, false );
    this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox

    this.domElement.addEventListener( 'touchstart', touchstart, false );
    this.domElement.addEventListener( 'touchend', touchend, false );
    this.domElement.addEventListener( 'touchmove', touchmove, false );

    window.addEventListener( 'keydown', keydown, false );
    window.addEventListener( 'keyup', keyup, false );

    this.handleResize();

    };
    134 changes: 134 additions & 0 deletions misc_software.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>three.js - software renderer</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
    body {
    font-family: Monospace;
    background-color: #f0f0f0;
    margin: 0px;
    overflow: hidden;
    }
    </style>
    </head>
    <body>

    <script src="three.min.js"></script>
    <script src="SoftwareRenderer.js"></script>
    <script src="TrackballControls.js"></script>
    <script src="stats.min.js"></script>

    <script>

    var container, stats;

    var camera, controls, scene, renderer;

    var sphere, plane;

    var start = Date.now();

    init();
    animate();

    function init() {

    container = document.createElement( 'div' );
    document.body.appendChild( container );

    var info = document.createElement( 'div' );
    info.style.position = 'absolute';
    info.style.top = '10px';
    info.style.width = '100%';
    info.style.textAlign = 'center';
    info.innerHTML = '<a href="https://github.com/mrdoob/three.js/" target="_blank">three.js<a/> - software renderer<br/>drag to change the point of view';
    container.appendChild( info );

    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 5000 );
    camera.position.y = 150;
    camera.position.z = 400;

    controls = new THREE.TrackballControls( camera );

    scene = new THREE.Scene();

    sphere = new THREE.Mesh( new THREE.TorusKnotGeometry( 150 ), new THREE.MeshBasicMaterial() );
    // sphere = new THREE.Mesh( new THREE.IcosahedronGeometry( 150, 3 ), new THREE.MeshBasicMaterial() );
    scene.add( sphere );

    // Plane

    plane = new THREE.Mesh( new THREE.PlaneGeometry( 200, 200 ), new THREE.MeshBasicMaterial( { color: 0xe0e0e0 } ) );
    plane.geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
    plane.position.y = - 150;
    scene.add( plane );

    var geometry = new THREE.Geometry();
    geometry.vertices.push( new THREE.Vector3( - 125, 100, 0 ) );
    geometry.vertices.push( new THREE.Vector3( - 300, -100, 0 ) );
    geometry.vertices.push( new THREE.Vector3( 0, -100, 0 ) );
    geometry.vertices.push( new THREE.Vector3( 125, 100, 0 ) );
    geometry.vertices.push( new THREE.Vector3( 0, -100, 0 ) );
    geometry.vertices.push( new THREE.Vector3( 300, -100, 0 ) );
    geometry.faces.push( new THREE.Face3( 0, 1, 2 ) );
    geometry.faces.push( new THREE.Face3( 3, 4, 5 ) );

    var triangle = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0xff0000 } ) );
    scene.add( triangle );

    renderer = new THREE.SoftwareRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );

    container.appendChild( renderer.domElement );

    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.top = '0px';
    container.appendChild( stats.domElement );

    //

    window.addEventListener( 'resize', onWindowResize, false );

    }

    function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

    }

    //

    function animate() {

    requestAnimationFrame( animate );

    render();
    stats.update();

    }

    function render() {

    var timer = Date.now() - start;

    sphere.position.y = Math.abs( Math.sin( timer * 0.002 ) ) * 150;
    sphere.rotation.x = timer * 0.0003;
    sphere.rotation.z = timer * 0.0002;

    controls.update();

    renderer.render( scene, camera );

    }

    </script>

    </body>
    </html>
    6 changes: 6 additions & 0 deletions stats.min.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    // stats.js - https://github.com/mrdoob/stats.js
    var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
    i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
    k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
    "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
    a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};
    696 changes: 696 additions & 0 deletions three.min.js
    696 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.