DrawTriangles picnic (source)

Having another look at the powerful drawTriangles method while playing with my shiny new CS5 reminded me that a while ago I was planning to do a full-blown cloth-like effect utilizing this little vintage experiment.
However, with the introduction of native 3D and numerous drawing API enhancements into Flash it is now possible to do this in a lot less less cumbersome fashion than before.

Consequently, please take a look at this wholesome virtual picnic tablecloth:

The real star of the show here is the drawTriangles method, which is rendering 800 textured triangles based on vertex, index and UV information supplied by Vectors (the grid fades in and out to show the mesh structure).
The wavy movement as well as the dark/light patches are achieved with a continuously redrawn perlinNoise map: it functions as sort of a height-map for the vertices, and it’s also scaled and blended with the texture to generate the dark/light areas corresponding to the height information.

One obvious way to improve the performance of this experiment would be to use fixed-size Vectors instead of dynamically changing ones.
I actually started off with that approach, but ran into mysterious problems trying to fill the Vectors with the correct data, and never quite managed to get the UV information come back the right way.
I have some theories regarding possible causes, but I eventually decided to move on for now and figure this problem out another day. ;)

And now the (hopefully sufficiently commented) source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// (c) edvardtoth.com
 
package {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BitmapDataChannel;
	import flash.display.BlendMode;
	import flash.display.GradientType;
	import flash.display.InterpolationMethod;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageQuality;
	import flash.display.StageScaleMode;
	import flash.display.TriangleCulling;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.PerspectiveProjection;
	import flash.geom.Point;
	import flash.geom.Utils3D;
	import flash.geom.Vector3D;
	import flash.utils.getTimer;
 
	[SWF(width=550,height=550,frameRate=30,backgroundColor=0x0099ff)]
	public class DrawTriangleFabric extends Sprite {
 
		private const GRIDSIZE:int = 20;
		private const GRIDSIZE_MINUS_ONE:int = GRIDSIZE-1;
		private const OCTAVES:int = 3;
		private const SPEED:Number = 0.015;
 
		private var lineAlpha:Number = 0;
		private var lineAlphaMod:Number = 0.01;
 
		[Embed(source="assets.swf", symbol="Texture")]
		private var textureAsset:Class;
		private var textureSprite:Sprite;
 
		[Embed(source="assets.swf", symbol="Decal")]
		private var decalAsset:Class;
		private var decalSprite:Sprite;
 
		private var vertices:Vector.<Number> = new Vector.<Number>;
		private var uvtData:Vector.<Number> = new Vector.<Number>;
		private var indices:Vector.<int> = new Vector.<int>;
		private var projectedVerts:Vector.<Number> = new Vector.<Number>;
 
		private var noiseMap:BitmapData = new BitmapData(GRIDSIZE, GRIDSIZE, false);
		private var texture:BitmapData;
 
		private var view:PerspectiveProjection = new PerspectiveProjection();
		private var canvas:Sprite = new Sprite();
		private var transformMatrix:Matrix3D = new Matrix3D;
		private var scaleMatrix:Matrix = new Matrix();
 
		public function DrawTriangleFabric() {
 
			// setup stage properties
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.quality = StageQuality.MEDIUM;
			stage.align = StageAlign.TOP_LEFT;
 
			// generate texture from embedded asset
			textureSprite = new textureAsset() as Sprite;
			texture = new BitmapData (textureSprite.width, textureSprite.height, false);
			texture.draw (textureSprite);			
 
			// center canvas
			canvas.x = stage.stageWidth * 0.5;
			canvas.y = stage.stageHeight * 0.5;
			addChild(canvas);
 
			// setup projection
			view.fieldOfView = 35;
			view.projectionCenter = new Point (stage.stageWidth * 0.5, stage.stageHeight * 0.5);
 
			// add decal
			decalSprite = new decalAsset() as Sprite;
			addChild (decalSprite);
			decalSprite.x = stage.stageWidth;
			decalSprite.y = 0;
			decalSprite.cacheAsBitmap = true;
 
			// position projection using a matrix
			transformMatrix.prependScale(.8,.03,.8);
			transformMatrix.appendRotation(-45, Vector3D.X_AXIS);
			transformMatrix.appendRotation(25, Vector3D.Y_AXIS);
			transformMatrix.appendRotation(180, Vector3D.Z_AXIS);
			transformMatrix.appendTranslation (-9.5,-7,-22);
			transformMatrix.append(view.toMatrix3D());
 
			// prepare a matrix used to fit the noisemap to the bigger texturemap
			scaleMatrix.scale (texture.width / noiseMap.width, texture.height / noiseMap.height);
 
			addEventListener(Event.ENTER_FRAME, updateFrame, false, 0, true);
		}
 
		private function updateFrame(event:Event):void {
 
			// generate vertex, index and UV data
			for (var yPos:int = 0; yPos < GRIDSIZE; yPos++) {
 
				for (var xPos:int = 0; xPos < GRIDSIZE; xPos++) {
 
					// y coordinate (second value) is derived from clamped grayscale values of the noise map
					vertices.push (xPos-GRIDSIZE, -(noiseMap.getPixel(xPos, yPos) & 0xFF), yPos-GRIDSIZE);
					uvtData.push (xPos / GRIDSIZE_MINUS_ONE, yPos / GRIDSIZE_MINUS_ONE, 0);
 
					if (yPos < GRIDSIZE_MINUS_ONE && xPos < GRIDSIZE_MINUS_ONE) {
 
						// triangle 1
						indices.push (
							yPos * GRIDSIZE + xPos,
							yPos * GRIDSIZE + xPos + 1,
							(yPos + 1) * GRIDSIZE + xPos);
 
						// triangle 2
						indices.push (
							yPos * GRIDSIZE + xPos + 1,
							(yPos + 1) * GRIDSIZE + xPos + 1,
							(yPos + 1) * GRIDSIZE + xPos);						
					}
				}
			}
 
			var offsets:Array = [];
			var offset:Number = getTimer() * SPEED;
 
			for(var i:uint = 0; i < OCTAVES; i++) {
 
				// offsets are used for animating the various octaves of the perlin noise map
				offsets.push(new Point(0, offset/(i+1)));
			}
 
			// render noiseMap
			noiseMap.perlinNoise(GRIDSIZE, GRIDSIZE, OCTAVES, 32, true, true, BitmapDataChannel.ALPHA, true, offsets);
			// refresh texture
			texture.draw (textureSprite);
			// draw noiseMap on top of texture with HARDLIGHT blendmode to achieve highlights/sheen
			texture.draw (noiseMap, scaleMatrix, null, BlendMode.HARDLIGHT, null, true);
 
			// project
			Utils3D.projectVectors(transformMatrix, vertices, projectedVerts, uvtData);
 
				// modify gridline alpha values
				lineAlpha += lineAlphaMod;
				if (lineAlpha >= 1.0 || lineAlpha <=0) {
					lineAlphaMod *= -1;
				}
 
			// draw
			canvas.graphics.clear();
			canvas.graphics.lineStyle(1.0, 0x000000, lineAlpha);
			canvas.graphics.beginBitmapFill(texture, null, false, true);
			canvas.graphics.drawTriangles(projectedVerts, indices, uvtData, TriangleCulling.NONE);
			canvas.graphics.endFill();
 
			// clear vectors
			vertices.length = 0;
			uvtData.length = 0;
			indices.length = 0;
		}
	}
}

0 comments

There are no comments yet...Kick things off by filling out the form below.

Leave a Comment