/* terumat:http://termat.sakura.ne.jp/ メタボールとシェーディングの練習 初期状態 :メタボールを乱数で生成 クリック1回目 :コンター線を表示 クリック2回目 :三角メッシュを表示(濃度が高い箇所) クリック3回目 :三角メッシュを表示(全体:鳥瞰) クリック4回目 :フラットシェーディング クリック5回目 :初期状態に戻る(メタボールを新たに生成) */ package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.MovieClip; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; [SWF(width = "481", height = "481", backgroundColor = "0x000000", fps = "30")] public class Practice42 extends Sprite{ private var bitmap:BitmapData; private var balls:Vector.<Metaball>; private var colors:Vector.<Array>; private var canvas:MovieClip; private var numOfBall:int = 16; private var tri:Triangles; public function Practice42() { canvas = new MovieClip(); addChild(canvas); bitmap = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000); canvas.addChild(new Bitmap(bitmap)); tri = new Triangles(); addChild(tri); balls = new Vector.<Metaball>(); colors = createColors(); stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown); for (var i:int = 0; i < numOfBall; i++) { addMetaball(Math.random()*stage.width, Math.random()*stage.height); } draw(); tri.init(bitmap); tri.draw(); } private function createColors():Vector.<Array>{ var ret:Vector.<Array> = new Vector.<Array>(); var r:int, g:int, b:int; for (var i:int=0;i<256;i++){ r = g = b = 0; if (i >= 0) b = Math.min(255, 4 * i * 2); if (i >= 2) g = Math.min(255, 4 * (i / 4)); if (i >= 4) r = Math.min(255, 4 * (i / 8)); ret[i]=[r,g,b]; } return ret; } private function draw():void { bitmap.fillRect(bitmap.rect, 0x000000); for each(var p:Metaball in balls) { p.draw(bitmap); } } private function addMetaball(x:int, y:int):void { var r:int = 64 + Math.random() * 128; var m:Metaball = new Metaball(colors,r); m.x = x; m.y = y; m.init(); balls.push(m); } private function onDown(e:MouseEvent):void { var p:int = tri.draw(); if (p == 0) { while (balls.length > 0) balls.pop(); for (var i:int = 0; i < numOfBall; i++) { addMetaball(Math.random()*stage.width, Math.random()*stage.height); } draw(); tri.init(bitmap); } } } } import flash.display.BitmapData; import flash.display.MovieClip; import flash.display.Sprite; import flash.geom.Point; class Metaball { public var x:int, y:int; public var colors:Vector.<Array> ; private var pixels:Vector.<Pixel>; public var RADIUS:int; private var NUM_PIXELS:int; public function Metaball(c:Vector.<Array>, r:int) { RADIUS = r; NUM_PIXELS=(2 * RADIUS) * (2 * RADIUS); pixels = new Vector.<Pixel>(); for (var i:int = 0; i < NUM_PIXELS; i++) pixels[i] = new Pixel(); colors=c; } public function init():void { var n:int; var c:int = 0; for (var i:int = -RADIUS; i < RADIUS; i++) { for (var j:int = -RADIUS; j < RADIUS; j++) { var z:Number = RADIUS * RADIUS - i * i - j * j; if (z < 0) { n = 0; }else{ z = Math.sqrt(z); var t:Number = z / RADIUS; n = (255 * (t * t * t * t)); n = Math.max(0, Math.min(n, 255)); } pixels[c].dx = i; pixels[c].dy = j; pixels[c].n = n; c++; } } } public function draw(bitmap:BitmapData):void { for (var i:int = 0; i < NUM_PIXELS; i++) { var sx:int = x + pixels[i].dx; if (sx < 0 || sx > bitmap.width-1)continue; var sy:int = y + pixels[i].dy; if (sy < 0 || sy > bitmap.height-1)continue; var p:Array = getRgb(bitmap.getPixel(sx, sy)); for (var j:int = 0; j < 3; j++) { p[j] += colors[pixels[i].n][j]; if (p[j] > 255)p[j] = 255; } bitmap.setPixel(sx, sy, get16(p[0], p[1], p[2])); } } private function get16(rr:int,gg:int,bb:int):uint { return (rr << 16) + (gg << 8) + bb; } private function getRgb(c:uint):Array { return [(c & 0xff0000) >> 16, (c & 0xff00) >> 8, (c & 0xff)]; } } class Pixel { public var dx:int; public var dy:int; public var n:int; } class Triangles extends MovieClip{ private var node:Vector.<Array>; private var bitmap:BitmapData; private var step:int = 5; private var elem:Vector.<Array>; public var limit:int = 80; private var mode:int = 0; private const THETA:Number=-90,PHI:Number=5; private const XZERO:Number=-480,YZERO:Number=-520; private const SIZE:Number=480; private const SINT:Number=Math.sin(THETA/180*Math.PI); private const SINP:Number=Math.sin(PHI/180*Math.PI); private const COST:Number=Math.cos(THETA/180*Math.PI); private const COSP:Number=Math.cos(PHI/180*Math.PI); public function init(b:BitmapData):void { bitmap = b; node = new Vector.<Array>(); for (var i:int = 0; i <= bitmap.width; i = i + step) { for (var j:int = 0; j <= bitmap.height; j = j + step) { var c:uint = bitmap.getPixel(i, j); var gv:int = (c & 0xff0000) >> 16; node.push([i, j, gv]); } } createElem(); } private function createElem():void { var mx:int = bitmap.width / step+1; var my:int = bitmap.height / step+1; elem = new Vector.<Array>(); for (var i:int = 0; i < mx; i++) { for (var j:int = 0; j < my; j++) { if (i < mx - 1 && j < my - 1) { elem.push([i * my + j, (i + 1) * my + j + 1, (i + 1) * my + j]); elem.push([i * my + j, i * my + j + 1, (i + 1) * my + j + 1]); } } } } public function draw():int { if (mode % 5 == 0) { graphics.clear(); }else if (mode % 5 == 1) { drawContour() }else if (mode % 5 == 2) { drawTriangles(); }else if (mode % 5 == 3) { drawTriangles2() }else if (mode % 5 == 4) { shade() } return mode++%5; } private function drawTriangles():void { graphics.clear(); var vertex:Vector.<Number> = new Vector.<Number>(); for (var i:int = 0; i < elem.length; i++) { var p:Array = elem[i]; if ((node[p[0]][2] < limit) || (node[p[1]][2] < limit) || (node[p[2]][2] < limit)) continue; for (var j:int = 0; j < p.length; j++) { vertex.push(node[p[j]][0]); vertex.push(node[p[j]][1]); } } graphics.lineStyle(1, 0xff6666); graphics.drawTriangles(vertex); } private function drawTriangles2():void { graphics.clear(); graphics.lineStyle(1, 0xff6666); for (var i:int = 0; i < elem.length; i++) { var p:Array = elem[i]; var p0:Point = trand2D(node[p[0]][0], node[p[0]][1], node[p[0]][2]); var p1:Point = trand2D(node[p[1]][0], node[p[1]][1], node[p[1]][2]); var p2:Point = trand2D(node[p[2]][0], node[p[2]][1], node[p[2]][2]); graphics.moveTo(p0.x, p0.y); graphics.lineTo(p1.x, p1.y); graphics.lineTo(p2.x, p2.y); graphics.lineTo(p0.x, p0.y); } } private function trand2D(xx:Number, yy:Number, zz:Number):Point { var p:Point = new Point(); p.x = XZERO + ( -SINT * (xx + SIZE) + COST * (yy + SIZE) + 0.5); p.y = YZERO + ( -COST * COSP * (xx + SIZE) - SINT * COSP * (yy + SIZE) + SINP * (zz + SIZE) + 0.5); return p; } private function drawContour():void { graphics.clear(); graphics.lineStyle(1, 0xff6666); var tp:int = (Math.floor(limit/ 10.0) * 10.0) as int; for (var i:int = tp; i < 255; i = i + 20) { createContour(i); } } private function createContour(val:int):void { for (var i:int = 0; i < elem.length; i++) { var d:Array = new Array(node[elem[i][0]], node[elem[i][1]], node[elem[i][2]]); var p:Array = sort(new Array(0, 1, 2), d); if (val >= p[0][2]) { if (val > p[2][2]) { continue; }else { var a:Array; var b:Array; if(val>=p[1][2]){ a = getPoint(p[0], p[2], val); b = getPoint(p[1], p[2], val); if (a == null || b == null) continue; graphics.moveTo(a[0], a[1]); graphics.lineTo(b[0], b[1]); }else{ a = getPoint(p[0], p[2], val); b = getPoint(p[0], p[1], val); if (a == null || b == null) continue; graphics.moveTo(a[0], a[1]); graphics.lineTo(b[0], b[1]); } } } } } private function sort(it:Array,d:Array):Array{ for (var i:int = 1; i < it.length; i++) { if (d[it[i]][2] < d[it[i - 1]][2]) { var t:int=it[i-1]; it[i-1]=it[i]; it[i]=t; return sort(it,d); } } return new Array(d[it[0]],d[it[1]],d[it[2]]); } private function getPoint(small:Array,large:Array,val:Number):Array{ if (small[2] == large[2]) return null; var rr:Number=(val-small[2])/(large[2]-small[2]); var x:Number=small[0]; var z:Number=small[1]; var xx:Number=(large[0]-x)*rr+x; var zz:Number=(large[1]-z)*rr+z; var l10:Number=Math.sqrt(Math.pow(xx-x, 2.0)+Math.pow(zz-z, 2.0)); var l11:Number=Math.sqrt(Math.pow(xx-large.x, 2.0)+Math.pow(zz-large.z, 2.0)); var ret:Array; if (l10 == 0.0) { ret = small; }else if (l10 == 1.0) { ret = large; }else{ ret = [xx, zz, val]; } return ret; } private var eye:Array = [0.1, 0.1, 1.0]; private var light:Array = [0.1, 0.1, 1.0, 1.0, 1.0, 1.0]; private var amb:Array = [0.6, 0.6, 0.6]; private var spec:Array = [1.0, 1.0, 1.0]; private var diff:Array = [0.4, 0.4, 0.4]; private var pow:Number = 3.0; public function shade():void { graphics.clear(); normalize(light); normalize(eye); for (var i:int = 0; i < elem.length; i++) { var v0:Array = [node[elem[i][1]][0]-node[elem[i][0]][0],node[elem[i][1]][1]-node[elem[i][0]][1],node[elem[i][1]][2]-node[elem[i][0]][2]]; var v1:Array = [node[elem[i][2]][0]-node[elem[i][0]][0],node[elem[i][2]][1]-node[elem[i][0]][1],node[elem[i][2]][2]-node[elem[i][0]][2]]; var normal:Array = cross(v1, v0); normalize(normal); var dotP:Number = dot(normal, light); var r0:Number = 2.0 * dotP * normal[0] - light[3]; var r1:Number = 2.0 * dotP * normal[1] - light[4]; var r2:Number = 2.0 * dotP * normal[2] - light[5]; var re:Array = [r0, r1, r2]; var dotE:Number = dot(re, eye); var r:int = Math.min(255,255.0 * (amb[0] + diff[0] * (Math.max(0, dot(normal, light))) + spec[0] * Math.max(0, Math.pow(dotE, pow)))); var g:int = Math.min(255,255.0 * (amb[1] + diff[1] * (Math.max(0, dot(normal, light))) + spec[1] * Math.max(0, Math.pow(dotE, pow)))); var b:int = Math.min(255,255.0 * (amb[2] + diff[2] * (Math.max(0, dot(normal, light))) + spec[2] * Math.max(0, Math.pow(dotE, pow)))); var col:uint = (r << 16) + (g << 8) + b; var vertex:Vector.<Number> = new Vector.<Number>(); vertex.push(node[elem[i][0]][0]); vertex.push(node[elem[i][0]][1]); vertex.push(node[elem[i][1]][0]); vertex.push(node[elem[i][1]][1]); vertex.push(node[elem[i][2]][0]); vertex.push(node[elem[i][2]][1]); graphics.beginFill(col); graphics.drawTriangles(vertex); graphics.endFill(); } } private function normalize(vec:Array):void { var t:Number = vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]; if (t != 0 && t != 1) t = (1.0 / Math.sqrt(t)); vec[0] = vec[0] * t; vec[1] = vec[1] * t; vec[2] = vec[2] * t; } private function dot(a:Array,b:Array):Number{ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } private function cross(a:Array,b:Array):Array{ var x:Number = a[1] * b[2] - a[2] * b[1]; var y:Number = a[2] * b[0] - a[0] * b[2]; var z:Number = a[0] * b[1] - a[1] * b[0]; return [x, y, z]; } } メタボールとシェーディング