/* Copyright (c) 2011 EL-EMENT saharan * * 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. */ package { import flash.ui.ContextMenu; import flash.utils.*; import flash.text.*; import flash.filters.*; import flash.geom.*; import flash.events.*; import flash.display.*; [SWF(frameRate = "60", width = "416", height = "416")] public class Flow extends Sprite { public static const SIZE:int = 416; public static const GRAVITY:Number = 0.05; // 重力 public static const RANGE:Number = 16; // 影響半径 public static const RANGE2:Number = RANGE * RANGE; // 影響半径の二乗 public static const DENSITY:Number = 2.5; // 流体の基準密度 public static const PRESSURE:Number = 1.5; // 圧力係数 public static const PRESSURE_NEAR:Number = 1.5; // 近距離圧力係数 public static const VISCOSITY:Number = 0.1; // 粘性係数 public static const NUM_GRIDS:int = SIZE / RANGE; // グリッド数 public static const INV_GRID_SIZE:Number = 1 / (SIZE / NUM_GRIDS); // グリッドサイズの逆数 private var particles:Vector.; private var numParticles:uint; private var neighbors:Vector.; private var numNeighbors:uint; private var count:int; private var press:Boolean; private var bitmap:BitmapData; private var grids:Vector.>; private const COLOR_TRANSFORM:ColorTransform = new ColorTransform(0.5, 0.5, 0.5); public function Flow() { if (stage) initialize(); else addEventListener(Event.ADDED_TO_STAGE, initialize); } private function initialize():void { removeEventListener(Event.ADDED_TO_STAGE, initialize); initSimulation(); contextMenu = new ContextMenu(); contextMenu.hideBuiltInItems(); count = 0; bitmap = new BitmapData(480, 480, false, 0); addChild(new Bitmap(bitmap)); addEventListener(Event.ENTER_FRAME, frame); stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {press = true;}); stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {press = false;}); } private function initSimulation():void { particles = new Vector.(); numParticles = 0; neighbors = new Vector.(); numNeighbors = 0; grids = new Vector.>(NUM_GRIDS, true); for(var i:int = 0; i < NUM_GRIDS; i++) { grids[i] = new Vector.(NUM_GRIDS, true); for(var j:int = 0; j < NUM_GRIDS; j++) grids[i][j] = new Grid(); } } private function frame(e:Event):void { if(press) pour(); move(); } private function pour():void { for(var i:int = -4; i <= 4; i++) { particles[numParticles++] = new Particle(mouseX + i * 10, mouseY, count / 5 % 5); particles[numParticles - 1].vy = 5; } } private function move():void { count++; updateGrids(); findNeighbors(); calcForce(); bitmap.lock(); bitmap.colorTransform(bitmap.rect, COLOR_TRANSFORM); var rect:Rectangle = new Rectangle(0, 0, 3, 3); for(var i:uint = 0; i < numParticles; i++) { const p:Particle = particles[i]; p.move(); rect.x = p.x - 1; rect.y = p.y - 1; bitmap.fillRect(rect, p.color); } bitmap.unlock(); } private function updateGrids():void { var i:uint; var j:uint; for(i = 0; i < NUM_GRIDS; i++) for(j = 0; j < NUM_GRIDS; j++) grids[i][j].numParticles = 0; for(i = 0; i < numParticles; i++) { const p:Particle = particles[i]; p.fx = p.fy = p.density = p.densityNear = 0; p.gx = p.x * INV_GRID_SIZE; // どこのグリッドにいるか計算 p.gy = p.y * INV_GRID_SIZE; if(p.gx < 0) p.gx = 0; if(p.gy < 0) p.gy = 0; if(p.gx > NUM_GRIDS - 1) p.gx = NUM_GRIDS - 1; if(p.gy > NUM_GRIDS - 1) p.gy = NUM_GRIDS - 1; } } private function findNeighbors():void { // 空間分割で近接粒子を計算 numNeighbors = 0; for(var i:uint = 0; i < numParticles; i++) { const p:Particle = particles[i]; const xMin:Boolean = p.gx != 0; const xMax:Boolean = p.gx != NUM_GRIDS - 1; const yMin:Boolean = p.gy != 0; const yMax:Boolean = p.gy != NUM_GRIDS - 1; findNeighborsInGrid(p, grids[p.gx][p.gy]); if(xMin) findNeighborsInGrid(p, grids[p.gx - 1][p.gy]); if(xMax) findNeighborsInGrid(p, grids[p.gx + 1][p.gy]); if(yMin) findNeighborsInGrid(p, grids[p.gx][p.gy - 1]); if(yMax) findNeighborsInGrid(p, grids[p.gx][p.gy + 1]); if(xMin && yMin) findNeighborsInGrid(p, grids[p.gx - 1][p.gy - 1]); if(xMin && yMax) findNeighborsInGrid(p, grids[p.gx - 1][p.gy + 1]); if(xMax && yMin) findNeighborsInGrid(p, grids[p.gx + 1][p.gy - 1]); if(xMax && yMax) findNeighborsInGrid(p, grids[p.gx + 1][p.gy + 1]); grids[p.gx][p.gy].add(p); // グリッドに追加 } } private function findNeighborsInGrid(pi:Particle, g:Grid):void { for(var j:uint = 0; j < g.numParticles; j++) { var pj:Particle = g.particles[j]; const distance:Number = (pi.x - pj.x) * (pi.x - pj.x) + (pi.y - pj.y) * (pi.y - pj.y); if(distance < RANGE2) { if(neighbors.length == numNeighbors) neighbors[numNeighbors] = new Neighbor(); neighbors[numNeighbors++].setParticle(pi, pj); } } } private function calcForce():void { for(var i:uint = 0; i < numNeighbors; i++) neighbors[i].calcForce(); } } } class Particle { public var x:Number; public var y:Number; public var gx:int; public var gy:int; public var vx:Number; public var vy:Number; public var fx:Number; public var fy:Number; public var density:Number; public var densityNear:Number; public var color:int; public var type:int; public const GRAVITY:Number = Flow.GRAVITY; public const SIZE:Number = Flow.SIZE; public function Particle(x:Number, y:Number, type:int) { this.x = x; this.y = y this.type = type; vx = vy = fx = fy = 0; switch(type) { case 0: color = 0x6060ff; break; case 1: color = 0xff6000; break; case 2: color = 0xff0060; break; case 3: color = 0x00d060; break; case 4: color = 0xd0d000; break; } } public function move():void { if(density > 0) { vx += fx / (density + 0.1); vy += fy / (density + 0.1); } x += vx; y += vy; vy += GRAVITY; if(x < 5) // 壁境界 vx += (5 - x) * 0.5 - vx * 0.5; if(x > SIZE - 5) vx += (SIZE - 5 - x) * 0.5 - vx * 0.5; if(y < 5) vy += (5 - y) * 0.5 - vy * 0.5; if(y > SIZE - 5) vy += (SIZE - 5 - y) * 0.5 - vy * 0.5; } } class Neighbor { public var p1:Particle; public var p2:Particle; private var distance:Number; private var nx:Number; private var ny:Number; private var weight:Number; private const RANGE:Number = Flow.RANGE; private const PRESSURE:Number = Flow.PRESSURE; private const PRESSURE_NEAR:Number = Flow.PRESSURE_NEAR; private const DENSITY:Number = Flow.DENSITY; private const VISCOSITY:Number = Flow.VISCOSITY; public function Neighbor() { } public function setParticle(p1:Particle, p2:Particle):void { this.distance = distance; this.p1 = p1; this.p2 = p2; nx = p1.x - p2.x; ny = p1.y - p2.y; distance = Math.sqrt(nx * nx + ny * ny); weight = 1 - distance / RANGE; var density:Number = weight * weight; // 圧力カーネル p1.density += density; p2.density += density; density *= weight * PRESSURE_NEAR; // 接近防止カーネル p1.densityNear += density; p2.densityNear += density; const invDistance:Number = 1 / distance; nx *= invDistance; ny *= invDistance; } public function calcForce():void { var p:Number; if(p1.type != p2.type) // 安定密度を下げる p = (p1.density + p2.density - DENSITY * 1.75) * PRESSURE; else p = (p1.density + p2.density - DENSITY * 2) * PRESSURE; const pn:Number = (p1.densityNear + p2.densityNear) * PRESSURE_NEAR; // 接近防止 var pressureWeight:Number = weight * (p + weight * pn); // 合計圧力 var viscosityWeight:Number = weight * VISCOSITY; var fx:Number = nx * pressureWeight; var fy:Number = ny * pressureWeight; fx += (p2.vx - p1.vx) * viscosityWeight; // 粘性計算 fy += (p2.vy - p1.vy) * viscosityWeight; p1.fx += fx; p1.fy += fy; p2.fx -= fx; p2.fy -= fy; } } class Grid { public var particles:Vector.; public var numParticles:uint; public function Grid() { particles = new Vector.(16, true); } public function add(p:Particle):void { if(numParticles < 16) particles[numParticles++] = p; } }