package away3d.materials
{
import away3d.containers.*;
import away3d.arcane;
import away3d.core.base.*;
import away3d.core.draw.*;
import away3d.core.math.*;
import away3d.core.utils.*;
import flash.display.*;
import flash.geom.*;
use namespace arcane;
/** Basic bitmap texture material */
public class TransformBitmapMaterial extends BitmapMaterial implements ITriangleMaterial, IUVMaterial
{
/** @private */
arcane var _transform:Matrix = new Matrix();
private var _scaleX:Number = 1;
private var _scaleY:Number = 1;
private var _offsetX:Number = 0;
private var _offsetY:Number = 0;
private var _rotation:Number = 0;
private var _projectionVector:Number3D;
private var _projectionDirty:Boolean;
private var _N:Number3D = new Number3D();
private var _M:Number3D = new Number3D();
private var DOWN:Number3D = new Number3D(0, -1, 0);
private var RIGHT:Number3D = new Number3D(1, 0, 0);
private var _transformDirty:Boolean;
private var _throughProjection:Boolean;
private var _globalProjection:Boolean;
private var face:Face;
private var x:Number;
private var y:Number;
private var px:Number;
private var py:Number;
private var w:Number;
private var h:Number;
private var normalR:Number3D = new Number3D();
private var _u0:Number;
private var _u1:Number;
private var _u2:Number;
private var _v0:Number;
private var _v1:Number;
private var _v2:Number;
private var v0x:Number;
private var v0y:Number;
private var v0z:Number;
private var v1x:Number;
private var v1y:Number;
private var v1z:Number;
private var v2x:Number;
private var v2y:Number;
private var v2z:Number;
private var v0:Number3D = new Number3D();
private var v1:Number3D = new Number3D();
private var v2:Number3D = new Number3D();
private var t:Matrix;
private var _invtexturemapping:Matrix;
private var fPoint1:Point = new Point();
private var fPoint2:Point = new Point();
private var fPoint3:Point = new Point();
private var fPoint4:Point = new Point();
private var mapa:Number;
private var mapb:Number;
private var mapc:Number;
private var mapd:Number;
private var maptx:Number;
private var mapty:Number;
private var mPoint1:Point = new Point();
private var mPoint2:Point = new Point();
private var mPoint3:Point = new Point();
private var mPoint4:Point = new Point();
private var overlap:Boolean;
private var i:String;
private var dot:Number;
private var line:Point = new Point();
private var zero:Number;
private var sign:Number;
private var point:Point;
private var point1:Point;
private var point2:Point;
private var point3:Point;
private var flag:Boolean;
private function updateTransform():void
{
_transformDirty = false;
//check to see if no transformation exists
if (_scaleX == 1 && _scaleY == 1 && _offsetX == 0 && _offsetY == 0 && _rotation == 0) {
_transform = null;
} else {
_transform = new Matrix();
_transform.scale(_scaleX, _scaleY);
_transform.rotate(_rotation);
_transform.translate(_offsetX, _offsetY);
}
_faceDirty = true;
}
private function projectUV(tri:DrawTriangle):Matrix
{
face = tri.face;
if (globalProjection) {
if (tri.backface) {
v0.transform(face.v0.position, tri.source.sceneTransform);
v2.transform(face.v1.position, tri.source.sceneTransform);
v1.transform(face.v2.position, tri.source.sceneTransform);
} else {
v0.transform(face.v0.position, tri.source.sceneTransform);
v1.transform(face.v1.position, tri.source.sceneTransform);
v2.transform(face.v2.position, tri.source.sceneTransform);
}
} else {
if (tri.backface) {
v0 = face.v0.position;
v2 = face.v1.position;
v1 = face.v2.position;
} else {
v0 = face.v0.position;
v1 = face.v1.position;
v2 = face.v2.position;
}
}
v0x = v0.x;
v0y = v0.y;
v0z = v0.z;
v1x = v1.x;
v1y = v1.y;
v1z = v1.z;
v2x = v2.x;
v2y = v2.y;
v2z = v2.z;
_u0 = v0x*_N.x + v0y*_N.y + v0z*_N.z;
_u1 = v1x*_N.x + v1y*_N.y + v1z*_N.z;
_u2 = v2x*_N.x + v2y*_N.y + v2z*_N.z;
_v0 = v0x*_M.x + v0y*_M.y + v0z*_M.z;
_v1 = v1x*_M.x + v1y*_M.y + v1z*_M.z;
_v2 = v2x*_M.x + v2y*_M.y + v2z*_M.z;
// Fix perpendicular projections
if ((_u0 == _u1 && _v0 == _v1) || (_u0 == _u2 && _v0 == _v2))
{
if (_u0 > 0.05)
_u0 -= 0.05;
else
_u0 += 0.05;
if (_v0 > 0.07)
_v0 -= 0.07;
else
_v0 += 0.07;
}
if (_u2 == _u1 && _v2 == _v1)
{
if (_u2 > 0.04)
_u2 -= 0.04;
else
_u2 += 0.04;
if (_v2 > 0.06)
_v2 -= 0.06;
else
_v2 += 0.06;
}
t = new Matrix(_u1 - _u0, _v1 - _v0, _u2 - _u0, _v2 - _v0, _u0, _v0);
t.invert();
return t;
}
private function getContainerPoints(rect:Rectangle):Array
{
return [rect.topLeft, new Point(rect.top, rect.right), rect.bottomRight, new Point(rect.bottom, rect.left)];
}
private function getFacePoints(map:Matrix):Array
{
fPoint1.x = _u0 = map.tx;
fPoint2.x = map.a + _u0;
fPoint3.x = map.c + _u0;
fPoint1.y = _v0 = map.ty;
fPoint2.y = map.b + _v0;
fPoint3.y = map.d + _v0;
return [fPoint1, fPoint2, fPoint3];
}
private function getMappingPoints(map:Matrix):Array
{
mapa = map.a*width;
mapb = map.b*height;
mapc = map.c*width;
mapd = map.d*height;
maptx = map.tx;
mapty = map.ty;
mPoint1.x = maptx;
mPoint1.y = mapty;
mPoint2.x = maptx + mapc;
mPoint2.y = mapty + mapd;
mPoint3.x = maptx + mapa + mapc;
mPoint3.y = mapty + mapb + mapd;
mPoint4.x = maptx + mapa;
mPoint4.y = mapty + mapb;
return [mPoint1, mPoint2, mPoint3, mPoint4];
}
private function findSeparatingAxis(points1:Array, points2:Array):Boolean
{
if (checkEdge(points1, points2))
return true;
if (checkEdge(points2, points1))
return true;
return false;
}
private function checkEdge(points1:Array, points2:Array):Boolean
{
for (i in points1) {
//get point 1
point2 = points1[i];
//get point 2
if (int(i) == 0) {
point1 = points1[points1.length-1];
point3 = points1[points1.length-2];
} else {
point1 = points1[int(i)-1];
if (int(i) == 1)
point3 = points1[points1.length-1];
else
point3 = points1[int(i)-2];
}
//calulate perpendicular line
line.x = point2.y - point1.y;
line.y = point1.x - point2.x;
zero = point1.x*line.x + point1.y*line.y;
sign = zero - point3.x*line.x - point3.y*line.y;
//calculate each projected value for points2
flag = true;
for each (point in points2) {
dot = point.x*line.x + point.y*line.y;
//return if zero is greater than dot
if (zero*sign > dot*sign) {
flag = false;
break;
}
}
if (flag)
return true;
}
return false;
}
/**
* @inheritDoc
*/
protected override function getMapping(tri:DrawTriangle):Matrix
{
if (tri.generated) {
_texturemapping = tri.transformUV(this).clone();
_texturemapping.invert();
//apply transform matrix if one exists
if (_transform) {
_mapping = _transform.clone();
_mapping.concat(_texturemapping);
} else {
_mapping = _texturemapping;
}
return _mapping;
}
_faceVO = getFaceVO(tri.face, tri.source);
//check to see if rendering can be skipped
if (_faceVO.invalidated || !_faceVO.texturemapping || _faceVO.backface != tri.backface) {
_faceVO.invalidated = false;
_faceVO.backface = tri.backface;
//use projectUV if projection vector detected
if (projectionVector) {
_faceVO.texturemapping = projectUV(tri);
} else if (!_faceVO.texturemapping) {
_faceVO.texturemapping = tri.transformUV(this).clone();
_faceVO.texturemapping.invert();
}
//apply transform matrix if one exists
if (_transform) {
_faceVO.mapping = _transform.clone();
_faceVO.mapping.concat(_faceVO.texturemapping);
} else {
_faceVO.mapping = _faceVO.texturemapping;
}
}
return _faceVO.mapping;
}
/**
* Determines whether a projected texture is visble on the faces pointing away from the projection.
*
* @see projectionVector
*/
public function get throughProjection():Boolean
{
return _throughProjection;
}
public function set throughProjection(val:Boolean):void
{
_throughProjection = val;
_projectionDirty = true;
}
/**
* Determines whether a projected texture uses offsetX, offsetY and projectionVector values relative to scene cordinates.
*
* @see projectionVector
* @see offsetX
* @see offsetY
*/
public function get globalProjection():Boolean
{
return _globalProjection;
}
public function set globalProjection(val:Boolean):void
{
_globalProjection = val;
_projectionDirty = true;
}
/**
* Transforms the texture in uv-space
*/
public function get transform():Matrix
{
return _transform;
}
public function set transform(val:Matrix):void
{
_transform = val;
if (_transform) {
//recalculate rotation
_rotation = Math.atan2(_transform.b, _transform.a);
//recalculate scale
_scaleX = _transform.a/Math.cos(_rotation);
_scaleY = _transform.d/Math.cos(_rotation);
//recalculate offset
_offsetX = _transform.tx;
_offsetY = _transform.ty;
} else {
_scaleX = _scaleY = 1;
_offsetX = _offsetY = _rotation = 0;
}
//_faceDirty = true;
}
/**
* Scales the x coordinates of the texture in uv-space
*/
public function get scaleX():Number
{
return _scaleX;
}
public function set scaleX(val:Number):void
{
if (isNaN(val))
throw new Error("isNaN(scaleX)");
if (val == Infinity)
Debug.warning("scaleX == Infinity");
if (val == -Infinity)
Debug.warning("scaleX == -Infinity");
if (val == 0)
Debug.warning("scaleX == 0");
_scaleX = val;
_transformDirty = true;
}
/**
* Scales the y coordinates of the texture in uv-space
*/
public function get scaleY():Number
{
return _scaleY;
}
public function set scaleY(val:Number):void
{
if (isNaN(val))
throw new Error("isNaN(scaleY)");
if (val == Infinity)
Debug.warning("scaleY == Infinity");
if (val == -Infinity)
Debug.warning("scaleY == -Infinity");
if (val == 0)
Debug.warning("scaleY == 0");
_scaleY = val;
_transformDirty = true;
}
/**
* Offsets the x coordinates of the texture in uv-space
*/
public function get offsetX():Number
{
return _offsetX;
}
public function set offsetX(val:Number):void
{
if (isNaN(val))
throw new Error("isNaN(offsetX)");
if (val == Infinity)
Debug.warning("offsetX == Infinity");
if (val == -Infinity)
Debug.warning("offsetX == -Infinity");
_offsetX = val;
_transformDirty = true;
}
/**
* Offsets the y coordinates of the texture in uv-space
*/
public function get offsetY():Number
{
return _offsetY;
}
public function set offsetY(val:Number):void
{
if (isNaN(val))
throw new Error("isNaN(offsetY)");
if (val == Infinity)
Debug.warning("offsetY == Infinity");
if (val == -Infinity)
Debug.warning("offsetY == -Infinity");
_offsetY = val;
_transformDirty = true;
}
/**
* Rotates the texture in uv-space
*/
public function get rotation():Number
{
return _rotation;
}
public function set rotation(val:Number):void
{
if (isNaN(val))
throw new Error("isNaN(rotation)");
if (val == Infinity)
Debug.warning("rotation == Infinity");
if (val == -Infinity)
Debug.warning("rotation == -Infinity");
_rotation = val;
_transformDirty = true;
}
/**
* Projects the texture in object space, ignoring the uv coordinates of the vertex objects.
* Texture renders normally when set to null.
*/
public function get projectionVector():Number3D
{
return _projectionVector;
}
public function set projectionVector(val:Number3D):void
{
_projectionVector = val;
if (_projectionVector) {
_N.cross(_projectionVector, DOWN);
if (!_N.modulo) _N = RIGHT;
_M.cross(_N, _projectionVector);
_N.cross(_M, _projectionVector);
_N.normalize();
_M.normalize();
}
_projectionDirty = true;
}
/**
* @inheritDoc
*/
public override function getPixel32(u:Number, v:Number):uint
{
if (_transform) {
x = u*_bitmap.width;
y = (1 - v)*_bitmap.height;
t = _transform.clone();
t.invert();
if (repeat) {
px = (x*t.a + y*t.c + t.tx)%_bitmap.width;
py = (x*t.b + y*t.d + t.ty)%_bitmap.height;
if (px < 0)
px += _bitmap.width;
if (py < 0)
py += _bitmap.height;
return _bitmap.getPixel32(px, py);
} else
return _bitmap.getPixel32(x*t.a + y*t.c + t.tx, x*t.b + y*t.d + t.ty);
}
return super.getPixel32(u, v);
}
/**
* Creates a new TransformBitmapMaterial object.
*
* @param bitmap The bitmapData object to be used as the material's texture.
* @param init [optional] An initialisation object for specifying default instance properties.
*/
public function TransformBitmapMaterial(bitmap:BitmapData, init:Object = null)
{
super(bitmap, init);
transform = ini.getObject("transform", Matrix) as Matrix;
scaleX = ini.getNumber("scaleX", _scaleX);
scaleY = ini.getNumber("scaleY", _scaleY);
offsetX = ini.getNumber("offsetX", _offsetX);
offsetY = ini.getNumber("offsetY", _offsetY);
rotation = ini.getNumber("rotation", _rotation);
projectionVector = ini.getObject("projectionVector", Number3D) as Number3D;
throughProjection = ini.getBoolean("throughProjection", true);
globalProjection = ini.getBoolean("globalProjection", false);
}
/**
* @inheritDoc
*/
public override function updateMaterial(source:Object3D, view:View3D):void
{
_graphics = null;
clearShapeDictionary();
if (_colorTransformDirty)
updateColorTransform();
if (_bitmapDirty)
updateRenderBitmap();
if (_transformDirty)
updateTransform();
if (_faceDirty || _projectionDirty || _blendModeDirty)
clearFaceDictionary();
_projectionDirty = false;
_blendModeDirty = false;
}
/**
* @inheritDoc
*/
public override function renderTriangle(tri:DrawTriangle):void
{
if (_projectionVector && !throughProjection) {
if (globalProjection) {
normalR.rotate(tri.face.normal, tri.source.sceneTransform);
if (normalR.dot(_projectionVector) < 0)
return;
} else if (tri.face.normal.dot(_projectionVector) < 0)
return;
}
super.renderTriangle(tri);
}
/**
* @inheritDoc
*/
public override function renderBitmapLayer(tri:DrawTriangle, containerRect:Rectangle, parentFaceVO:FaceVO):FaceVO
{
//retrieve the transform
if (_transform)
_mapping = _transform.clone();
else
_mapping = new Matrix();
//if not projected, draw the source bitmap once
if (!_projectionVector)
renderSource(tri.source, containerRect, _mapping);
//check to see if faceDictionary exists
if (!(_faceVO = _faceDictionary[tri]))
_faceVO = _faceDictionary[tri] = new FaceVO();
//pass on resize value
if (parentFaceVO.resized) {
parentFaceVO.resized = false;
_faceVO.resized = true;
}
//pass on invtexturemapping value
_faceVO.invtexturemapping = parentFaceVO.invtexturemapping;
//check to see if rendering can be skipped
if (parentFaceVO.updated || _faceVO.invalidated) {
parentFaceVO.updated = false;
//retrieve the bitmapRect
_bitmapRect = tri.face.bitmapRect;
//reset booleans
if (_faceVO.invalidated)
_faceVO.invalidated = false;
else
_faceVO.updated = true;
//store a clone
_faceVO.bitmap = parentFaceVO.bitmap;
//update the transform based on scaling or projection vector
if (_projectionVector) {
//calulate mapping
_invtexturemapping = _faceVO.invtexturemapping;
_mapping.concat(projectUV(tri));
_mapping.concat(_invtexturemapping);
//check to see if the bitmap (non repeating) lies inside the drawtriangle area
if ((throughProjection || tri.face.normal.dot(_projectionVector) >= 0) && (repeat || !findSeparatingAxis(getFacePoints(_invtexturemapping), getMappingPoints(_mapping)))) {
//store a clone
if (_faceVO.cleared)
_faceVO.bitmap = parentFaceVO.bitmap.clone();
_faceVO.cleared = false;
_faceVO.updated = true;
//draw into faceBitmap
_graphics = _s.graphics;
_graphics.clear();
_graphics.beginBitmapFill(_bitmap, _mapping, repeat, smooth);
_graphics.drawRect(0, 0, _bitmapRect.width, _bitmapRect.height);
_graphics.endFill();
_faceVO.bitmap.draw(_s, null, _colorTransform, _blendMode, _faceVO.bitmap.rect);
}
} else {
//check to see if the bitmap (non repeating) lies inside the containerRect area
if (repeat && !findSeparatingAxis(getContainerPoints(containerRect), getMappingPoints(_mapping))) {
_faceVO.cleared = false;
_faceVO.updated = true;
//draw into faceBitmap
_faceVO.bitmap.copyPixels(_sourceVO.bitmap, _bitmapRect, _zeroPoint, null, null, true);
}
}
}
return _faceVO;
}
}
}