もっと近くで forked from: Ants
forked by TM. <もっと近くでAnts>
* ルーペでかざした蟻だけを高解像度&IK歩行にしようと思った。 けど、脚の長さに対して歩行速度が速すぎたためIK断念。
* 蟻を高解像度で他を低解像度にしてズームしようとしたが、うまくいかずこれも断念。
* 時間がかかりそうなのでとりあえず妥協したものをUP。 おまけでえさをかじっている最中のアクションだけ追加
*
* 何故か巣穴が消えてます。diffとってもよくわからん
* 20100426 バグ修正:巣穴を無理やり表示。 アリの影を書くのを忘れていたので追加。 フェロモン表示機能がなくなっていたので復活(ルーペの部分のみ)
(追記というか書きなおし。ついでにコードも微修正)
* アリのエサ運びをシミュレーションしてみました
* アリは普段ランダムに動いていますが、
* エサを見つけると地面にフェロモンを残しながら巣まで戻り、
* そのフェロモンを発見した他のアリがそれを辿ってエサの在り処を見つけるという流れです。
*
* フェロモン濃度だけで判断するとなかなかうまく辿れなかったので
* 濃度と一緒に進む方向も記録するようにしています。
* (濃度と向きはBitmapDataのピクセルに色情報としてまとめて記録してます)
* フェロモンが蒸発して消えないかぎり複雑な道のりも辿れるんですが、
* 複雑すぎると巣に帰るプログラムかけなくなりそうだったのでやめました。。。
*
* (操作説明)
* 土をクリックするとお菓子が置けて、左下のボタンでアリが増やせます(1000匹まで)
* ※増やしすぎ注意(負荷的にも見た目的にも・・・)
/**
* Copyright zendenmushi ( http://wonderfl.net/user/zendenmushi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/j7SP
*/
// forked from tencho's Ants
/*
* forked by TM. <もっと近くでAnts>
* ルーペでかざした蟻だけを高解像度&IK歩行にしようと思った。 けど、脚の長さに対して歩行速度が速すぎたためIK断念。
* 蟻を高解像度で他を低解像度にしてズームしようとしたが、うまくいかずこれも断念。
* 時間がかかりそうなのでとりあえず妥協したものをUP。 おまけでえさをかじっている最中のアクションだけ追加
*
* 何故か巣穴が消えてます。diffとってもよくわからん
* 20100426 バグ修正:巣穴を無理やり表示。 アリの影を書くのを忘れていたので追加。 フェロモン表示機能がなくなっていたので復活(ルーペの部分のみ)
*/
/**
* (追記というか書きなおし。ついでにコードも微修正)
* アリのエサ運びをシミュレーションしてみました
* アリは普段ランダムに動いていますが、
* エサを見つけると地面にフェロモンを残しながら巣まで戻り、
* そのフェロモンを発見した他のアリがそれを辿ってエサの在り処を見つけるという流れです。
*
* フェロモン濃度だけで判断するとなかなかうまく辿れなかったので
* 濃度と一緒に進む方向も記録するようにしています。
* (濃度と向きはBitmapDataのピクセルに色情報としてまとめて記録してます)
* フェロモンが蒸発して消えないかぎり複雑な道のりも辿れるんですが、
* 複雑すぎると巣に帰るプログラムかけなくなりそうだったのでやめました。。。
*
* (操作説明)
* 土をクリックするとお菓子が置けて、左下のボタンでアリが増やせます(1000匹まで)
* ※増やしすぎ注意(負荷的にも見た目的にも・・・)
*/
package {
import com.bit101.components.Label;
import com.bit101.components.PushButton;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Sprite;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filters.DropShadowFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.geom.Rectangle;
public class AntsTest extends Sprite {
private const DISPLAY:Rectangle = new Rectangle(0, 0, 465, 465);
private const ANTSNUM:int = 50;
private const ANTSMAX:int = 1000;
private const MATERIAL_URL:String = "http://assets.wonderfl.net/images/related_images/4/40/40c1/40c16871d560d547ebb1fc1725db47922ef9f261";
private var world:World;
private var bg:Sprite;
private var containerAnts:Sprite;
private var containerFoods:Sprite;
private var pheromoneBmp:Bitmap;
private var canvas:BitmapData;
private var loader:ImageLoader;
private var info:Label;
private var whiteColor:ColorTransform;
private var loupe:Loupe; // add 20100424
private var compositeCanvas:BitmapData; // add 20100424
private var zeroPoint:Point = new Point(0, 0); // add 20100424
//コンストラクタ
public function AntsTest() {
Wonderfl.capture_delay(50);
whiteColor = new ColorTransform();
whiteColor.color = 0xFFFFFF;
stage.frameRate = 30;
world = new World();
world.home.setPosition(110, 350);
world.obstacles.push(new Obstacle(280, 225, 90));
world.obstacles.push(new Obstacle(0, -150, 280));
bg = addChild(new Sprite()) as Sprite;
bg.graphics.beginFill(0x444444, 1);
bg.graphics.drawRect(0, 0, DISPLAY.width, DISPLAY.height);
bg.graphics.endFill();
// addChild(world.home);
//外部画像読み込み開始
loader = new ImageLoader();
loader.load(MATERIAL_URL, onLoadImage, onErrorImage);
}
//画像読み込み失敗
private function onErrorImage(e:ErrorEvent):void {
var msg:Label = new Label(this, 5, 5, e.text);
msg.transform.colorTransform = whiteColor;
}
//画像読み込み完了
private function onLoadImage():void {
var ground:Bitmap = bg.addChild(loader.bg) as Bitmap;
ground.width = DISPLAY.width;
ground.height = DISPLAY.height;
//メイン処理開始
world.init(DISPLAY.width, DISPLAY.height);
setLayout();
init(ANTSNUM);
bg.addEventListener(MouseEvent.MOUSE_DOWN, onClickStage);
addEventListener(Event.ENTER_FRAME, onEnter);
}
//画面にボタン等を配置する
private function setLayout():void {
pheromoneBmp = addChild(new Bitmap(world.pheromone.map)) as Bitmap;
pheromoneBmp.visible = false;
pheromoneBmp.blendMode = BlendMode.LIGHTEN;
//障害物領域をマウスクリックできなくさせる
for each(var obs:Obstacle in world.obstacles) {
var sp:Sprite = addChild(new Sprite()) as Sprite;
sp.graphics.beginFill(0x444444, 0);
sp.graphics.drawCircle(obs.center.x, obs.center.y, obs.radius);
sp.graphics.endFill();
}
// add 20100426
compositeCanvas = new BitmapData(DISPLAY.width, DISPLAY.height, true, 0);
addChild(new Bitmap(compositeCanvas));
addChild(world.home); // move 20100426
//
canvas = new BitmapData(DISPLAY.width, DISPLAY.height, true, 0x00FFFFFF);
addChild(new Bitmap(canvas)).filters = [new DropShadowFilter(3, 45, 0x222222, 0.8, 3, 3, 1, 1)];
containerAnts = new Sprite();
//containerFoods = addChild(new Sprite()) as Sprite;
containerFoods = new Sprite();
containerFoods.mouseChildren = false;
containerFoods.mouseEnabled = false;
// add 20100424
loupe = new Loupe(150, DISPLAY.width, DISPLAY.height);
addChild(loupe);
//
new Label(this, 5, 3, "CLICK TO FEED").transform.colorTransform = whiteColor;
//画面下のバー
var panel:Sprite = addChild(new Sprite()) as Sprite;
var blackBox:Sprite = panel.addChild(new Sprite()) as Sprite;
blackBox.graphics.beginFill(0x000000, 0.5);
blackBox.graphics.drawRect(0, 0, DISPLAY.width, 25);
blackBox.graphics.endFill();
panel.y = DISPLAY.height - panel.height;
new SwitchButton(panel, DISPLAY.width - 160, 5, ["PHEROMONE: OFF", "PHEROMONE: ON"], onClickPheromon);
new PushButton(panel, DISPLAY.width - 55, 5, "RESET", onClickClear).setSize(50, 16);
new PushButton(panel, 5, 5, "+50 ANTS", onClickAdd).setSize(70, 16);
info = new Label(panel, 85, 3, "");
info.transform.colorTransform = whiteColor;
}
private function refreshInfo():void {
info.text = "ANTS: " + world.ants.length;
}
//蟻の数を指定して初期化
private function init(num:int):void {
world.clear();
var wait:Number = 10;
for (var i:int = 0; i < num; i++) {
wait += Math.max(0.05, 15 / (i * i / 100 + 1));
containerAnts.addChild(world.addAnt(wait).body);
}
refreshInfo();
}
private function onClickClear(...arg):void{
init(ANTSNUM);
}
private function onClickAdd(...arg):void {
var num:int = Math.min(50, ANTSMAX - world.ants.length);
for (var i:int = 0; i < num; i++) {
containerAnts.addChild(world.addAnt(i/2).body);
}
refreshInfo();
}
private function onClickPheromon(mode:int):void{
pheromoneBmp.visible = (mode == 1);
}
private function onClickStage(e:MouseEvent):void {
var img:ImageData = loader.feeds[Math.round(Math.random() * (loader.feeds.length - 1))];
containerFoods.addChild(world.addFood(stage.mouseX, stage.mouseY, img).sprite);
}
//毎フレーム処理
private function onEnter(e:Event):void {
world.pheromone.map.lock();
for each(var a:Ant in world.ants) a.action(world);
world.pheromone.blur();
world.pheromone.map.unlock();
canvas.fillRect(DISPLAY, 0x00000000);
canvas.draw(containerAnts);
// 20100424
compositeCanvas.copyPixels(loader.bg.bitmapData, compositeCanvas.rect, zeroPoint);
compositeCanvas.draw(containerFoods);
//compositeCanvas.draw(world.home); // ??巣穴がルーペごしに見えない
loupe.stepFrame(compositeCanvas, pheromoneBmp, world.home.position ,world.ants, stage.mouseX, stage.mouseY);
}
}
}
import com.bit101.components.PushButton;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.DisplayObjectContainer;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.SecurityErrorEvent;
import flash.filters.DisplacementMapFilter;
import flash.filters.DropShadowFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import frocessing.core.F5BitmapData2D;
//角度変換用
class Angle {
static public const ALL_RADIAN:Number = Math.PI * 2;
static public const TO_RADIAN:Number = Math.PI / 180;
static public const TO_ROTATION:Number = 180 / Math.PI;
//角度を合成する
static public function between(a1:Number, a2:Number, per:Number):Number {
var minus:Number = a1 - a2;
var r180:Number = (minus % Angle.ALL_RADIAN + Angle.ALL_RADIAN) % Angle.ALL_RADIAN;
if (r180 > Math.PI) r180 -= Angle.ALL_RADIAN;
var a0:Number = r180 + a2;
return a0 * (1 - per) + a2 * (per);
}
}
//
class World {
public var area:Rectangle; //ワールドサイズ
public var ants:Vector.<Ant>; //全てのアリ
public var foods:Vector.<Food>; //全てのエサ
public var obstacles:Vector.<Obstacle>; //全ての障害物
public var home:AntsHill; //アリ塚
public var pheromone:Pheromone; //フェロモン
public function World() {
ants = new Vector.<Ant>();
foods = new Vector.<Food>();
obstacles = new Vector.<Obstacle>();
home = new AntsHill();
home.visible = false;
}
//サイズを指定して初期化
public function init(width:Number, height:Number):void {
area = new Rectangle(0, 0, width, height);
pheromone = new Pheromone(width, height);
home.visible = true;
}
//エサを削って無くなったら削除
public function cutFood(f:Food):void {
if (f.cut()) {
f.remove();
foods.splice(foods.indexOf(f), 1);
}
}
//エサを追加
public function addFood(x:int, y:int, img:ImageData):Food {
var f:Food = new Food(x, y, 50, 200, img);
foods.push(f);
return f;
}
//アリを追加
public function addAnt(wait:int):Ant {
var a:Ant = new Ant(home.position.x, home.position.y);
a.thinkTime = wait;
ants.push(a);
return a;
}
//色々リセット
public function clear():void {
for each(var f:Food in foods) f.remove();
for each(var a:Ant in ants) a.remove();
ants.length = 0;
foods.length = 0;
pheromone.clear();
}
}
//エサの画像
class ImageData {
public var colors:Array;
public var bmp:BitmapData;
public function ImageData(bmp:BitmapData) {
this.bmp = bmp;
//画像のピクセルカラーを調べる(透明領域は無視)
colors = new Array();
for (var px:int = 0; px < bmp.width; px += 8) {
for (var py:int = 0; py < bmp.width; py += 8) {
var rgba:uint = bmp.getPixel32(px, py);
if (rgba >>> 24 == 255) colors.push(rgba & 0xFFFFFF);
}
}
}
//画像の色をランダムに取得
public function getRandomColor():uint {
return colors[Math.round(Math.random() * (colors.length - 1))];
}
}
//画像をロードして分割(複数画像読み込みが面倒くさかったので・・・)
class ImageLoader {
public var bg:Bitmap;
public var feeds:Vector.<ImageData>;
private var loader:Loader;
private const FEED_NUM:int = 5;
public function ImageLoader() {
loader = new Loader();
}
public function load(src:String, loadFunc:Function, errorFunc:Function):void{
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorFunc);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorFunc);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
function(...arg):void {
onLoadImage.apply(null, arg);
loadFunc.apply();
});
loader.load(new URLRequest(src), new LoaderContext(true));
}
private function onLoadImage(e:Event):void {
var info:LoaderInfo = e.currentTarget as LoaderInfo;
var bmp:BitmapData = Bitmap(info.content).bitmapData;
feeds = new Vector.<ImageData>();
for (var i:int = 0; i < FEED_NUM; i++) {
var bmp2:BitmapData = new BitmapData(64, 64, true, 0x00FFFFFF);
bmp2.copyPixels(bmp, new Rectangle(64 * i, 0, 64, 64), new Point(0, 0));
feeds.push(new ImageData(bmp2));
}
bg = new Bitmap(new BitmapData(bmp.width, bmp.height-64, false));
bg.bitmapData.copyPixels(bmp, new Rectangle(0, 64, bg.width, bg.height), new Point());
bg.smoothing = true;
}
}
//切り替えボタン
class SwitchButton extends PushButton {
private var mode:int = 0;
private var modeNum:int = 1;
private var labels:Array;
private var func:Function;
public function SwitchButton(parent:DisplayObjectContainer, xpos:Number, ypos:Number, labels:Array = null, func:Function = null) {
if(labels == null) labels = [""];
this.labels = labels;
this.func = func;
modeNum = labels.length;
super(parent, xpos, ypos, labels[0], onClick);
height = 16;
}
private function onClick(...arg):void {
mode = (mode + 1) % modeNum;
label = labels[mode];
if(func != null) func.apply(null, [mode]);
}
}
//アリ塚
class AntsHill extends Sprite {
public var position:Point;
public function AntsHill() {
graphics.beginFill(0x000000, 0.3);
graphics.drawCircle(0, 0, 9);
graphics.beginFill(0x000000, 1);
graphics.drawCircle(0, 0, 7);
graphics.endFill();
position = new Point();
}
//位置変更
public function setPosition(x:Number, y:Number):void {
this.x = position.x = x;
this.y = position.y = y;
}
}
//フェロモン
class Pheromone {
public var map:BitmapData;
private var ct:ColorTransform;
private var timeCount:int = 0;
public function Pheromone(width:Number, height:Number) {
ct = new ColorTransform(1, 0.99, 1, 1, 0, 0, 0, 0);// アリが暴れるみたいなので、元に戻した。20100426 フェロモンを徐々に消したかったのでalphaも0.99に。動作に影響は無いみたい
map = new BitmapData(width, height, true, 0x00000000);
}
//指定座標のフェロモンから進む角度を調べる
public function getGuidepost(x:int, y:int):Number {
//周囲のフェロモン濃度を調べて濃い方向を調べる
var isNone:Boolean = true;
var totalX:Number = 0;
var totalY:Number = 0;
for (var px:int = -2; px <= 2; px++) {
for (var py:int = -2; py <= 2; py++) {
if (px != 0 || py != 0) {
var per:Number = (map.getPixel32(x + px * 4, y + py * 4) >> 8 & 0xFF) / 255;
totalX += per * px;
totalY += per * py;
if (per) isNone = false;
}
}
}
var res:Number;
if ((!totalY && !totalX) || isNone) {
res = NaN;
} else {
//足元のフェロモンから進む角度を調べる
var per2:Number = (map.getPixel32(x, y) & 0xFF) / 255;
var xx:Number, yy:Number;
if (per2 == 0) {
//フェロモンが無ければ濃い方へ
xx = totalX;
yy = totalY;
} else {
//フェロモンがあれば先に進む(周囲濃度で若干角度補正)
var radian:Number = per2 * Angle.ALL_RADIAN + Math.PI;
xx = Math.cos(radian) * 40 + totalX;
yy = Math.sin(radian) * 40 + totalY;
}
res = Math.atan2(yy, xx);
}
return res;
}
//フェロモンをつける
public function putPheromone(x:int, y:int, radianPer:Number):void {
var rgb:uint = 0xF0 << 24 | 0xFF << 16 | 0xFF << 8 | uint(0xFF * radianPer);
map.fillRect(new Rectangle(x-5, y-5, 11, 11), rgb);
}
//フェロモン拡散
public function blur():void {
if (!(++timeCount % 2)) map.colorTransform(map.rect, ct);
}
//フェロモンリセット
public function clear():void {
map.fillRect(map.rect, 0x00000000);
}
}
//アリのエサ
class Food {
public var sprite:Sprite; //グラフィック
public var position:Point; //位置
public var size:Number; //半径
public var num:int; //残りの量
private var numMax:int;
public var image:ImageData;
private var maskImage:BitmapData;
private var noiseImage:BitmapData;
public function Food(x:Number = 0, y:Number = 0, size:Number = 10, num:int = 10, img:ImageData = null) {
position = new Point(x, y);
this.size = size;
this.num = num;
numMax = num;
sprite = new Sprite();
sprite.x = x;
sprite.y = y;
sprite.scaleX = sprite.scaleY = 0.7;
sprite.filters = [new DropShadowFilter(5, 45, 0x111111, 0.7, 8, 8, 1, 1)];
image = img;
var foodBmp:Bitmap = sprite.addChild(new Bitmap(image.bmp)) as Bitmap;
foodBmp.smoothing = true;
//徐々に削られるエフェクト用
maskImage = new BitmapData(image.bmp.width, image.bmp.height, true);
maskImage.fillRect(maskImage.rect, 0x00888888);
noiseImage = new BitmapData(image.bmp.width, image.bmp.height, false);
noiseImage.perlinNoise(20, 20, 3, int(Math.random() * 100), false, true, 1, true);
var maskBmp:Bitmap = sprite.addChild(new Bitmap(maskImage)) as Bitmap;
maskBmp.blendMode = BlendMode.ERASE;
foodBmp.x = maskBmp.x = -foodBmp.width / 2;
foodBmp.y = maskBmp.y = -foodBmp.height / 2;
}
//削る
public function cut():Boolean {
num--;
var per:Number = num / numMax * 0.4 + 0.4;
maskImage.fillRect(maskImage.rect, 0xFF888888);
maskImage.threshold(noiseImage, noiseImage.rect, new Point(), "<", per*255, 0x00000000, 255, false);
return !num;
}
//削除
public function remove():void {
maskImage.dispose();
noiseImage.dispose();
if (sprite.parent != null) sprite.parent.removeChild(sprite);
}
}
//円形障害物
class Obstacle {
public var center:Point;
public var radius:Number;
public function Obstacle(x:Number = 0, y:Number = 0, radius:Number = 50) {
center = new Point(x, y);
this.radius = radius;
}
}
//アリ
class Ant {
public var body:Sprite; //グラフィック
public var leg:Sprite; //グラフィック
public var motion:WalkMotion;
public var position:Point; //位置
public var thinkTime:int = 0; //停止時間
private var locus:Vector.<Point>; //数フレーム前までの位置リスト
private var radian:Number = 0; //角度
private var speed:Number = 2; //速度
private var status:int = 0; //状況 0:エサ探し 1:巣に帰る
private var freeTime:int = 0; //フェロモン無効時間
private var view:Number = 20; //視界範囲
private var wanderTime:int = 1;
public var food:Sprite; // 20100426 private -> public
private var randomRad:Number;
public var legs : Vector.<Leg> = new Vector.<Leg>; // 20100426
public var biting : Boolean = false; // 20100426 かじり中
public var bitcnt : int = 0; // 20100426 かじり動作
public function Ant(x:Number = 0, y:Number = 0) {
body = new Sprite();
body.graphics.beginFill(0x000000, 1);
body.graphics.drawRect(-4, -1, 4, 2);
body.graphics.drawRect(1, -1, 1, 2);
body.graphics.drawRect(3, -1, 2, 2);
body.graphics.endFill();
leg = new Sprite(); // 20100426
motion = new WalkMotion(legs); // 20100426
food = body.addChild(new Sprite()) as Sprite;
food.graphics.beginFill(0xFFFFFF, 1);
food.graphics.drawRect(-2, -2, 4, 4);
food.graphics.endFill();
food.x = 6;
food.visible = false;
position = new Point(x, y);
locus = new Vector.<Point>();
radian = Math.random() * Angle.ALL_RADIAN;
randomRad = (Math.random() * 10 - 5) * Angle.TO_RADIAN;
// 脚構造をセットアップ こんなに複雑な構造は不要だった・・・
// front
legs[0] = new Leg([ { x:8, y: -8, len:16, rotate: -30 } , { len:16, rotate:45 }, { len:24, rotate: -90 } ], 0.05);
legs[1] = new Leg([ { x: -8, y: -8, len:16, rotate: -150 } , { len:16, rotate:-45 }, { len:24, rotate:90 } ], 0.05);
// middle
legs[2] = new Leg([ { x:16, y: 0, len:16, rotate: 15 } , { len:16, rotate: -45 }, { len:16, rotate: -20 } ],0.05);
legs[3] = new Leg([ { x: -16, y: 0, len:16, rotate: 165 } , { len:16, rotate:45 }, { len:16, rotate:20 } ],0.05);
//
// last
legs[4] = new Leg([ { x:8, y: 8, len:16, rotate: 45 } , { len:24, rotate: -30 }, { len:56, rotate: 45 } ], 0.05);
legs[5] = new Leg([ { x: -8, y: 8, len:16, rotate: 135 } , { len:24, rotate:30 }, { len:56, rotate: -45 } ], 0.05);
}
//行動
public function action(w:World):void {
bitcnt = bitcnt + 1; // 20100426
if (thinkTime) {
if (--thinkTime == 0) {
if (status == 0) startSearch();
if (status == 1) toFace(w.home.position);
}
return;
} else {
randomThink(0.015, Math.random() * 10 + 15);
}
//エサ探しモード
if (status == 0) {
var targetFood:Food = null;
var minDistance:Number = Number.MAX_VALUE;
for each(var f:Food in w.foods) {
var distance:Number = position.subtract(f.position).length;
//エサに接触したら持ち帰り始める
if (distance <= f.size/2 + 1) {
thinkTime = Math.random() * 50 + 30;
biting = true; // 20100426
bitcnt = 0;
getFood(f.image.getRandomColor());
w.cutFood(f);
return;
}
//エサを見つけた
var d:Number = distance - f.size/2;
if (d <= view && d < minDistance) {
minDistance = d;
targetFood = f;
}
}
//エサを見つけているか
if (targetFood) {
toFace(targetFood.position);
} else {
if (freeTime > 0) freeTime--;
//フェロモンが近くにあるかチェック
var rad:Number = (freeTime > 0)? NaN : w.pheromone.getGuidepost(position.x, position.y);
if (isNaN(rad)) {
wander(w.obstacles);
} else {
radian = Angle.between(getAdjustRadian(w.obstacles, rad), radian, 0.5) + Angle.TO_RADIAN + randomRad;
checkStay();
}
}
}
//エサ持ち帰りモード
if (status == 1) {
var per:Number = radian / Angle.ALL_RADIAN;
w.pheromone.putPheromone(position.x, position.y, per);
goto(w.obstacles, w.home.position);
if (w.home.position.subtract(position).length < 5) backHome();
}
walk(radian);
adjustPosition(w.area);
refresh();
}
//障害物を避ける角度を求める
private function getAdjustRadian(obsts:Vector.<Obstacle>, rad:Number):Number {
var plus:Point = new Point(Math.cos(rad) * speed, Math.sin(rad) * speed);
var nextPos:Point = position.add(plus);
for each(var obs:Obstacle in obsts){
var distance:Number = Point.distance(nextPos, obs.center);
if (distance < obs.radius) {
var radius:Point = nextPos.subtract(obs.center);
if (radius.length < obs.radius) {
radius.normalize(obs.radius);
var fixPos:Point = obs.center.add(radius);
rad = Math.atan2(fixPos.y - position.y, fixPos.x - position.x);
}
break;
}
}
return rad
}
//ランダム回転
private function wander(obsts:Vector.<Obstacle>):void {
if (wanderTime && !--wanderTime) {
wanderTime = 4;
radian += (Math.random() * 60 - 30) * Angle.TO_RADIAN;
}
radian = Angle.between(getAdjustRadian(obsts, radian), radian, 0.85);
}
//指定の座標を向く
private function toFace(target:Point):void {
radian = Math.atan2(target.y - position.y, target.x - position.x);
biting = false; // 20100426
}
//指定座標に向かいながらランダム回転
private function goto(obsts:Vector.<Obstacle>, target:Point):void {
if (wanderTime && !--wanderTime) {
wanderTime = 3;
var rad:Number = Math.atan2(target.y - position.y, target.x - position.x);
var per:Number = target.subtract(position).length/100
if (per > 1) per = 1;
rad = getAdjustRadian(obsts, rad);
radian = Angle.between(rad, radian, 0.75 * per) + (Math.random() * 30 - 15) * Angle.TO_RADIAN;
}
}
//一定時間その場でうろついていたらフェロモン無効に
private function checkStay():void {
locus.unshift(position.clone());
locus.length = 5;
if (locus[4] && locus[0].subtract(locus[3]).length <= speed * 2) freeTime = 60;
}
//ラジアン角指定で移動
private function walk(rad:Number):void {
radian = rad;
position.x += Math.cos(rad) * speed;
position.y += Math.sin(rad) * speed;
}
//ワールドエリアに収まるように位置調整
private function adjustPosition(worldRect:Rectangle):void {
var adjustRad:Number = NaN;
var padding:int = 5;
if (position.x < worldRect.left + padding) {
position.x = worldRect.left + padding;
adjustRad = 0;
}
if (position.x > worldRect.right - padding) {
position.x = worldRect.right - padding;
adjustRad = Math.PI;
}
if (position.y > worldRect.bottom - padding) {
position.y = worldRect.bottom - padding;
adjustRad = Math.PI * 1.5;
}
if (position.y < worldRect.top + padding) {
position.y = worldRect.top + padding;
adjustRad = Math.PI * 0.5;
}
if (!isNaN(adjustRad)) radian = Angle.between(adjustRad, radian, 0.9);
}
//一定確率で立ち止まる
private function randomThink(per:Number, time:int):void {
if (Math.random() <= per) thinkTime = time;
}
//エサを探し始める
private function startSearch():void {
body.visible = true;
status = 0;
}
//巣に入れる
private function backHome():void {
status = 0;
food.visible = false;
body.visible = false;
thinkTime = Math.random() * 100 + 100;
}
//エサを取得
private function getFood(color:uint = 0xFFFFFF):void {
status = 1;
food.visible = true;
var ct:ColorTransform = new ColorTransform();
ct.color = color;
food.transform.colorTransform = ct;
}
//表示更新
public function refresh():void {
body.x = position.x;
body.y = position.y;
body.rotation = radian * Angle.TO_ROTATION;
motion.stepState();
motion.stepFrame(body.x, body.y, radian + Math.PI/2 );
}
//親から削除
public function remove():void {
if (body.parent != null) body.parent.removeChild(body);
}
}
//------------------------------
function dist(p0 : Point, p1 : Point) : Number
{
return Math.sqrt( (p0.x - p1.x) * (p0.x - p1.x) + (p0.y - p1.y) * (p0.y - p1.y) );
}
function unitVector(p0 : Point, p1 : Point, /*out*/ u : Point) : Boolean
{
var len : Number = Math.sqrt( (p0.y - p1.y) * (p0.y - p1.y) + (p0.x - p1.x) * (p0.x - p1.x) );
if (len > 0) {
u.x = (p0.x - p1.x) / len;
u.y = (p0.y - p1.y) / len;
return true;
} else return false;
}
function dotProduct(p0 : Point, p1 : Point) : Number
{
return p0.x * p1.x + p0.y * p1.y;
}
function crossProductZ(p0 : Point, p1 : Point) : Number
{
return p0.x * p1.y - p0.y * p1.x;
}
class LegSegment
{
public var root : Point = new Point(); // 親jointからの相対値
public var joint : Point = new Point(); // joint絶対値
public var len : Number = 10;
public var rotate : Number = 0; //
public var IKRotate : Number = 0; //
public var IKPoint : Point = new Point(); // 接地点の座標
public var uselim : Boolean = false;
public var limmax : Number = 0;
public var limmin : Number = 0;
private var _IK : Boolean = false;
private var _matrix : Matrix = new Matrix();
public function get IK():Boolean { return _IK; }
public function set IK(value:Boolean):void
{
if (!_IK && value) {
IKPoint.x = joint.x;
IKPoint.y = joint.y;
}
_IK = value;
}
public function get matrix():Matrix { return _matrix; }
}
class Leg
{
private var temppos : Point = new Point();
private var temppos1 : Point = new Point();
private var temppos2 : Point = new Point();
public var matrix : MatrixStack = new MatrixStack();
public var items : Vector.<LegSegment> = new Vector.<LegSegment>;
public var root : Point = new Point();
public function Leg( assemble : Array, scale : Number )
{
var cnt : int = assemble.length;
for (var i : int = 0; i < cnt; i++) {
items[i] = new LegSegment();
items[i].root.x = 0;
items[i].root.y = 0;
if (assemble[i].x != undefined) {
items[i].root.x = assemble[i].x*scale;
items[i].root.y = assemble[i].y*scale;
}
items[i].len = assemble[i].len*scale;
items[i].rotate = assemble[i].rotate * Math.PI / 180;
if ((assemble[i].limmax != undefined) && (assemble[i].limmin != undefined)) {
items[i].uselim = true;
items[i].limmax = assemble[i].limmax;
items[i].limmin = assemble[i].limmin;
}
}
if (cnt > 0) computeFK(0,0,0);
}
public function computeFK(x : Number, y : Number, angle : Number) : void
{
var cnt : int = items.length;
if (cnt == 0) return;
matrix.push().translate(x, y).rotate(angle);
for (var i : int = 0; i < cnt; i++) {
temppos.x = items[i].root.x;
temppos.y = items[i].root.y;
if (i == 0) {
root.x = temppos.x;
root.y = temppos.y;
matrix.transform(root);
}
matrix.translate(temppos.x, temppos.y).rotate(items[i].rotate);
temppos.x = items[i].len;
temppos.y = 0;
matrix.transform(temppos);
items[i].joint.x = temppos.x;
items[i].joint.y = temppos.y;
matrix.copyTo(items[i].matrix);
matrix.translate(items[i].len, 0);
}
matrix.pop();
}
}
class WalkMotion
{
private var legs : Vector.<Leg> = null;
private var state : int = 0;
private var temppos1 : Point = new Point();
private var temppos2 : Point = new Point();
public function WalkMotion(legs : Vector.<Leg>)
{
this.legs = legs;
}
public function stepState() : void
{
state+=4;
}
public function stepFrame(x : Number, y : Number, angle : Number) : void
{
var legcnt : int = legs.length;
var nr : Number = 0;
var rad : Number = 0;
for (var i : int = 0; i < legcnt; i++) {
switch (i) {
case 0:
case 1:
{
nr = (((state * 10) % 360) / 360) + 0.25;
rad = nr * Math.PI * 2;
legs[i].items[0].rotate = (-90+(i==1?-60:60)+Math.sin(rad)*20)* Math.PI / 180;
nr = nr+0.5;
rad = nr * Math.PI * 2;
legs[i].items[1].rotate = ((i==1?-45:45)-Math.sin(rad)*20)* Math.PI / 180;
legs[i].items[2].rotate = ((i==1?90:-90)+Math.sin(rad)*20)* Math.PI / 180;
break;
}
case 2:
case 3:
{
nr = (((state * 10) % 360) / 360) + 0.5;
rad = nr * Math.PI * 2;
legs[i].items[0].rotate = (90+(i==3?95:-95)+Math.sin(rad)*20)* Math.PI / 180;
nr = nr + 0.75;
rad = nr * Math.PI * 2;
legs[i].items[1].rotate = ((i==3?-60:60)-Math.sin(rad)*40)* Math.PI / 180;
legs[i].items[2].rotate = (i==3?20:-20)* Math.PI / 180;// (10 + Math.sin(state / 10) * 10) * Math.PI / 180;
break;
}
case 4:
case 5:
{
nr = (((state * 10) % 360) / 360);
rad = nr * Math.PI * 2;
legs[i].items[0].rotate = (90+(i==5?55:-55)-Math.sin(rad)*20)* Math.PI / 180;
nr = nr + 0.5;// 75;
rad = nr * Math.PI * 2;
legs[i].items[1].rotate = ((i==5?30:-30)-Math.sin(rad)*40)* Math.PI / 180;
legs[i].items[2].rotate = ((i==5?-90:90)+Math.sin(rad)*20)* Math.PI / 180;
break;
}
}
legs[i].computeFK(x, y, angle);
}
}
}
class MatrixWrap
{
public var matrix : Matrix;
public var index : int;
public var used : Boolean;
}
class MatrixPool
{
// matrixは頻繁に作成されるので、そのたびにnewするのは精神衛生上悪いからPoolする。でもまあ殆ど影響ないと思う
private var mats : Vector.<MatrixWrap> = new Vector.<MatrixWrap>;
private var freep : int = -1;
private const itemlimit : int = 5000;
// 毎回newする変わりに、プールから取り出す。 オーバーヘッドが気になるけど、クリティカルなタイミングでGCが発動するよりましか?
public function newItem(a : Number = 1, b : Number = 0, c : Number = 0, d : Number = 1, tx : Number = 0, ty : Number = 0 ) : MatrixWrap
{
var cnt : int = mats.length;
if (((itemlimit > 0) && cnt >= itemlimit) && (freep >= cnt-1)) return null;
freep++;
if (freep == cnt) {
mats[cnt] = new MatrixWrap();
mats[cnt].matrix = new Matrix(a, b, c, d, tx, ty);
} else {
var m : Matrix = mats[freep].matrix;
m.a = a;
m.b = b;
m.c = c;
m.d = d;
m.tx = tx;
m.ty = ty;
}
mats[freep].index = freep;
mats[freep].used = true;
return mats[freep];
}
public function remove(item : MatrixWrap) : void
{
var cnt : int = mats.length;
var lastp : int = freep;
item.used = false;
if (lastp != item.index) {
mats[item.index] = mats[lastp];
mats[item.index].index = item.index;
mats[lastp] = item;
}
freep = lastp - 1;
}
private static var pool : MatrixPool = null;
public static function get instance() : MatrixPool
{
if (!MatrixPool.pool) MatrixPool.pool = new MatrixPool();
return MatrixPool.pool;
}
}
class MatrixStack
{
private var stack : Vector.<MatrixWrap> = new Vector.<MatrixWrap>;
private var current : MatrixWrap = new MatrixWrap();
public function MatrixStack()
{
}
public function push() : MatrixStack
{
stack.push(current);
current = MatrixPool.instance.newItem(1,0,0,1,0,0);
return this;
}
public function pop() : MatrixStack
{
MatrixPool.instance.remove(current);
current = stack.pop();
return this;
}
public function translate(x : Number, y : Number) : MatrixStack
{
var mat : MatrixWrap = MatrixPool.instance.newItem(1, 0, 0, 1, x, y);
mat.matrix.concat(current.matrix);
MatrixPool.instance.remove(current);
current = mat;
return this;
}
public function rotate(rad : Number) : MatrixStack
{
var mat : MatrixWrap = MatrixPool.instance.newItem(Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), 0, 0);
mat.matrix.concat(current.matrix);
MatrixPool.instance.remove(current);
current = mat;
return this;
}
public function scale(x : Number, y : Number) : MatrixStack
{
var mat : MatrixWrap = MatrixPool.instance.newItem(x, 0, 0, y, 0, 0);
mat.matrix.concat(current.matrix);
MatrixPool.instance.remove(current);
current = mat;
return this;
}
public function identity() : MatrixStack
{
current.matrix.identity();
return this;
}
public function transform(/*inout*/ pos : Point) : void
{
var m : Matrix = current.matrix;
var tmpx : Number = m.a * pos.x + m.c * pos.y + m.tx * 1;
var tmpy : Number = m.b * pos.x + m.d * pos.y + m.ty * 1;
pos.x = tmpx;
pos.y = tmpy;
}
public function copyTo(/* out */dest : Matrix) : void
{
dest.a = current.matrix.a;
dest.b = current.matrix.b;
dest.c = current.matrix.c;
dest.d = current.matrix.d;
dest.tx = current.matrix.tx;
dest.ty = current.matrix.ty;
}
}
//------------------------------
class Loupe extends Sprite
{
private var canvas : BitmapData;
private var zoom_canvas : F5BitmapData2D;
private var _dragging : Boolean;
private var lenz : Sprite;
private var lenzImage : LenzRenderImage;
private var srcRect : Rectangle = new Rectangle();
private var draggingOffset : Point = new Point();
private var zeroPoint : Point = new Point();
private var radius : Number;
private var displacementFilter : DisplacementMapFilter;
private var hirezoAnt : Sprite;
public function Loupe(r : int, w : int, h : int)
{
radius = r;
hirezoAnt = new Sprite();
canvas = new BitmapData(r*2, r*2, true, 0xff808080);
zoom_canvas = new F5BitmapData2D(r*2, r*2, true, 0);
lenzImage = new LenzRenderImage(r*2, r*2);
lenzImage.render(0, 0, true);
displacementFilter = new DisplacementMapFilter(lenzImage.refractionBmp, null, BitmapDataChannel.GREEN, BitmapDataChannel.BLUE, r , r );
lenz = new Sprite();
addChild(lenz);
lenz.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
lenz.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
public function stepFrame(ground_canvas : BitmapData, pheromoneBmp : Bitmap, homePos : Point, ants:Vector.<Ant>, mx : Number, my : Number):void
{
if (_dragging) {
x = mx-draggingOffset.x;
y = my-draggingOffset.y;
}
srcRect.x = (x + width / 2) - width / 2 + 4;
srcRect.width = width;
srcRect.y = (y + height / 2) - height / 2;
srcRect.height = height;
zoom_canvas.bitmapData.fillRect(zoom_canvas.bitmapData.rect, 0);
zoom_canvas.bitmapData.copyPixels(ground_canvas, srcRect, zeroPoint);
if (pheromoneBmp.visible) zoom_canvas.bitmapData.copyPixels(pheromoneBmp.bitmapData, srcRect, zeroPoint, null, null, true); // フェロモン表示
zoom_canvas.beginDraw();
for each (var ant : Ant in ants) {
if (ant.body.visible && (ant.position.x > srcRect.x) && (ant.position.x < srcRect.right) && (ant.position.y > srcRect.y) && (ant.position.y < srcRect.bottom)) {
hirezoAnt.graphics.clear();
var ax : Number = ant.position.x - srcRect.x;
var ay : Number = ant.position.y - srcRect.y;
// shadow
zoom_canvas.pushMatrix();
zoom_canvas.translate(ax+3, ay+3);
zoom_canvas.rotate(ant.body.rotation*Math.PI/180 );
zoom_canvas.strokeAlpha = 0;
zoom_canvas.beginFill(0x000000, 0.2);
zoom_canvas.rect(-4,-1, 4, 2);
zoom_canvas.rect(1, -1, 1, 2);
zoom_canvas.rect(3, -1, 2, 2);
zoom_canvas.endFill();
if (ant.food.visible && (ant.bitcnt > 10)) {
zoom_canvas.beginFill(0x000000, 0.2);
zoom_canvas.circle(8, 0, ant.bitcnt > 20 ? 2 : ant.bitcnt / 10);
zoom_canvas.endFill();
}
zoom_canvas.popMatrix();
// body
zoom_canvas.pushMatrix();
zoom_canvas.translate(ax, ay);
zoom_canvas.rotate(ant.body.rotation*Math.PI/180 );
zoom_canvas.stroke(0x202020);
zoom_canvas.strokeAlpha = 0.5;
if (ant.biting && ((ant.bitcnt & 0x3) < 2)) {
zoom_canvas.moveTo( 5, -1);
zoom_canvas.lineTo( 7, 0);
zoom_canvas.moveTo( 5, 1);
zoom_canvas.lineTo( 7, 0);
} else {
zoom_canvas.moveTo( 5, -1);
zoom_canvas.lineTo( 7, -1);
zoom_canvas.moveTo( 5, 1);
zoom_canvas.lineTo( 7, 1);
}
zoom_canvas.strokeAlpha = 0;
zoom_canvas.beginFill(0x000000, 1);
zoom_canvas.rect(-4,-1, 4, 2);
zoom_canvas.rect(1, -1, 1, 2);
zoom_canvas.rect(3, -1, 2, 2);
zoom_canvas.endFill();
if (ant.food.visible && (ant.bitcnt > 10)) {
zoom_canvas.beginFill( ant.food.transform.colorTransform.color );
zoom_canvas.circle(8, 0, ant.bitcnt > 20 ? 2 : ant.bitcnt / 10);
zoom_canvas.endFill();
}
zoom_canvas.popMatrix();
zoom_canvas.stroke(0x202020);
zoom_canvas.strokeAlpha = 0.5;
var legcnt : int = ant.legs.length;
for (var i : int = 0; i < legcnt; i++) {
var segcnt : int = ant.legs[i].items.length;
zoom_canvas.moveTo(ant.legs[i].items[segcnt-2].joint.x - srcRect.x, ant.legs[i].items[segcnt-2].joint.y - srcRect.y);
zoom_canvas.lineTo(ant.legs[i].items[segcnt-1].joint.x - srcRect.x+1, ant.legs[i].items[segcnt-1].joint.y - srcRect.y+1);
}
}
}
// 20100426 巣穴が上手く表示できないので苦肉の策として、ここで描画
zoom_canvas.beginFill(0x000000, 0.3);
zoom_canvas.circle(homePos.x-srcRect.x, homePos.y-srcRect.y, 9);
zoom_canvas.beginFill(0x000000, 1);
zoom_canvas.circle(homePos.x-srcRect.x, homePos.y-srcRect.y, 7);
zoom_canvas.endFill();
zoom_canvas.endDraw();
srcRect.x = 0;
srcRect.width = width;
srcRect.y = 0;
srcRect.height = height;
canvas.applyFilter( zoom_canvas.bitmapData, canvas.rect, zeroPoint, displacementFilter);
lenz.graphics.clear();
lenz.graphics.beginBitmapFill(canvas);// lenzImage.invRefractionBmp);
lenz.graphics.drawCircle(radius , radius , radius );
lenz.graphics.endFill();
lenz.graphics.lineStyle(2, 0xff0000);
lenz.graphics.drawCircle(radius , radius , radius );
alpha = 1.0;
}
private function onMouseUp(e:MouseEvent):void
{
_dragging = false;
}
private function onMouseDown(e:MouseEvent):void
{
_dragging = true;
draggingOffset.x = e.stageX - x;
draggingOffset.y = e.stageY - y;
}
public function get dragging():Boolean { return _dragging; }
}
class Point3D
{
public var x : Number;
public var y : Number;
public var z : Number;
}
class Rgb
{
public var a : Number;
public var r : Number;
public var g : Number;
public var b : Number;
public function color(col : uint) : Rgb
{
a = (col >> 24) / 255;
r = ((col >> 16) & 0xff) / 255;
g = ((col >> 8) & 0xff) / 255;
b = ((col >> 0) & 0xff) / 255;
return this;
}
public static function pack32(r : Number, g : Number, b : Number, a : Number) : uint
{
return (Math.min(255, a * 255) << 24) | (Math.min(255, r * 255) << 16) | (Math.min(255, g * 255) << 8) | Math.min(255, b * 255);
}
public function color32() : uint
{
return pack32(r, g, b, a);
}
}
class LenzRenderImage
{
private var width : int;
private var height : int;
private var radius : int;
private var zeroPoint : Point = new Point(0, 0);
public var refractionBmp : BitmapData;
private var temppos : Point3D = new Point3D();
public function LenzRenderImage(width : int, height : int) : void
{
this.width = width;
this.height = height;
this.radius = Math.min(width, height) / 2;
refractionBmp = new BitmapData(width, height, true, 0);
}
private function norm(x : Number, y : Number, z : Number ,/*out*/ o : Point3D) : void
{
var len : Number = Math.sqrt( x*x + y*y + z*z );
o.x = x / len;
o.y = y / len;
o.z = z / len;
}
public function render(posx : int, posy : int, use_displace : Boolean = false) : void
{
refractionBmp.lock();
var cx : int = Math.min( width, height )/2;
var cy : int = Math.min( width, height )/2;
var rr : Number = radius;
var tr : int = rr >> 0;
var tr_xscale : Number = 1;
var tr_yscale : Number = 1;
for (var i : int = 0; i < tr * 2 ; i++) {
var rad1 : Number = Math.acos( ((tr-i)/tr) );
var spanr : Number = Math.sin( rad1 ) * rr;
var spanz : Number = Math.sin( rad1*0.8 + Math.PI*(0.2/2) ) * rr;
var y : Number = (tr-i);
// 20100424 元コードが10年前くらいのもので忘れてる部分が多いので再整理。不要部分削除
for (var j : int = 0; j < tr * 4 -1; j++) {
var rad2 : Number = (j/4 / rr) * Math.PI;
var rad3 : Number = ((j /4/ rr) * Math.PI)*0.8 + Math.PI * (0.2 / 2);
// 右手座標系 +X=左 +Y=上 +Z=手前
var x : Number = Math.cos( rad2 ) * spanr;
var z : Number = Math.sin( rad3 ) * spanz;
norm(x,y,z, temppos);
var nx : Number = temppos.x;
var ny : Number = temppos.y;
var nz : Number = temppos.z;
var tx : int = x;
// bitmapの座標系 +X=右 +Y=下
var px : int = (cx - tr) + tr - tx;
var py : int = (cy - tr + i);
if ((i == tr) && (j == tr)) {
trace("break!");
}
{//if ((tcol.r != 0.0) || (tcol.g != 0.0) || (tcol.b != 0.0)) {
var cosa : Number = nx * 0 + ny * 0 + nz * 1;
var tr_u : Number, tr_v : Number;
if (cosa != 0) tr_u = -nx / cosa; else tr_u = 0;
if (cosa != 0) tr_v = -ny / cosa; else tr_v = 0;
if ((i == tr) && ((j & 0x80) == 0)) {
// trace(j, px, nx, tr_u, cosa);
}
var npx : Number = px / width;
var npy : Number = py / height;
var tr_x : Number = (tr_u/10)+(x/width); // (tr_u + 1) * tr;// tr * tr_u * refraction + tr - px;
if (npx + tr_x < 0) tr_x = -npx;
if (npx + tr_x > 1) tr_x = 1 - npx;
var tr_y : Number = (tr_v/10)+(y/height); // (tr_u + 1) * tr;// tr * tr_u * refraction + tr - px;
if (npy + tr_y < 0) tr_y = -npy;
if (npy + tr_y > 1) tr_y = 1 - npy;
if ((tr_v > 0) && (tr_u > 0)){
var valx : int = (tr_u / 10) * width;
if (valx < 0) valx = 0;
else if (valx >= width) valx = width - 1;
var valy : int = (tr_v / 10) * height;
if (valy < 0) valy = 0;
else if (valy >= height) valy = height - 1;
}
cosa = 1-cosa;
var reflection : Number = (1-0.2)*cosa;//0.0 + (1-0.0)*cosa*cosa*cosa*cosa*cosa; // fresnel
var tu : int = Math.min(255,tr_x * 256 + 128);
var tv : int = Math.min(255,tr_y * 256 + 128);
refractionBmp.setPixel32(px, py, 0xff000000 | (tu << 8) | (tv));
}
}
}
refractionBmp.unlock();
}
}