Dolphin Play [Alternativa3D7.5 Sample]
releaseされたalternativa3D 7.5で遊んでみました。
イルカは十字keyで操作できますが、なかなか言う事ききませんwもし行方不明になったら、ENTERkeyを押し続けてください。
全画面でも見れますが、ちょっと重くなります(ちなみに重くなるのはalternativa3Dのせいではなく、ボロノイとフィルタのせいですw)
←↑→↓:Rotation
space:stop
Shift:speedup
Enter:comeback
/**
* Copyright narutohyper ( http://wonderfl.net/user/narutohyper )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/v8dh
*/
package {
import alternativ7.engine3d.animation.AnimationController;
import alternativ7.engine3d.animation.AnimationGroup;
import alternativ7.engine3d.animation.AnimationState;
import alternativ7.engine3d.animation.AnimationTimer;
import alternativ7.engine3d.animation.keys.MatrixKey;
import alternativ7.engine3d.animation.MatrixAnimation;
import alternativ7.engine3d.animation.Track;
import alternativ7.engine3d.containers.ConflictContainer;
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.Object3DContainer;
import alternativ7.engine3d.core.Sorting;
import alternativ7.engine3d.core.View;
import alternativ7.engine3d.loaders.MaterialLoader;
import alternativ7.engine3d.loaders.ParserCollada;
import alternativ7.engine3d.materials.FillMaterial;
import alternativ7.engine3d.materials.TextureMaterial;
import alternativ7.engine3d.objects.Joint;
import alternativ7.engine3d.objects.Skin;
import alternativ7.engine3d.primitives.Box;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.GradientType;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.ui.Keyboard;
[SWF(backgroundColor="#000000", frameRate="60", width="800", height="600")]
public class Main extends Sprite {
private const modelURL:String = "http://marubayashi.net/archive/sample/alt3d7/dolphin.dae";
private var loader:URLLoader;
private var materialLoader:MaterialLoader;
private var back:Sprite
private var backMaterial:TextureMaterial;
private var camera:Camera3D;
private var cameraController:SimpleObjectController;
private var moveController:dolphinController;
private var animationController:AnimationController;
private var animationTimer:AnimationTimer
private var container:ConflictContainer;
private var anim:Boolean = true;
private var dolphinObject:ConflictContainer;
private var frame:Sprite = new Sprite();
private var textures:Vector.<TextureMaterial>
private var nextX:Number=0
private var nextY:Number=0
private var nextZ:Number=0
private var viewLength:Number
private var focalLength:Number
private var tField:TextField
private var tFormat:TextFormat
private var light:Sprite
private var dolphin:Skin
private var info:TextInfo
private var scale:Number=0.5
private var context :LoaderContext = new LoaderContext();
public function Main() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.HIGH;
// Security.loadPolicyFile("http://marubayashi.net/crossdomain.xml");
// context.applicationDomain = ApplicationDomain.currentDomain;
//---------------------------------
// Root objectの作成
//---------------------------------
container = new ConflictContainer();
//---------------------------------
//背景の作成
//---------------------------------
//Caustics(水面、底面)Materialの作成
back = new water()
back.visible=false
addChild(back)
backMaterial = new TextureMaterial(new BitmapData(260, 860, false, 0x000033));
backMaterial.texture.draw(back)
//backMaterial.mipMapping = MipMapping.PER_PIXEL;
//backMaterial.smooth = true;
var blackMaterial:FillMaterial = new FillMaterial(0x0,0);
// Box
var box:Box = new Box(6000, 8000, 1400, 1, 1, 1, true,false,blackMaterial,blackMaterial,blackMaterial,blackMaterial,backMaterial,backMaterial);
//box.setMaterialToAllFaces(backMaterial);
container.addChild(box);
box.y=2000
box.blendMode=BlendMode.SCREEN
box.rotationZ = 180 * Math.PI / 180
box.sorting = Sorting.AVERAGE_Z;
//---------------------------------
//手前光の描画
//---------------------------------
light = new Sunlight()
addChild(light)
//---------------------------------
//cameraの作成
//---------------------------------
camera = new Camera3D();
camera.view = new View(800, 600);
addChild(camera.view);
camera.rotationX = -90*Math.PI/180;
//camera.rotationZ = -10*Math.PI/180;
camera.x = -0;
camera.y = -500;
camera.z = -0;
container.addChild(camera);
addChild(frame);
loader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onModelLoad);
loader.load(new URLRequest(modelURL));
// Info
info = new TextInfo();
info.x = 3;
info.y = 3;
addChild(info);
onResize();
}
private function onModelLoad(e:Event):void {
//イルカを格納するContainer
dolphinObject = new ConflictContainer();
dolphinObject.resolveByAABB = true;
dolphinObject.resolveByOOBB = true;
dolphinObject.scaleX=scale
dolphinObject.scaleY=scale
dolphinObject.scaleZ=scale
container.addChild(dolphinObject)
//----------------------------------------------------
//Blenderが吐き出すDaeファイルでは、以下の置換が必要
//----------------------------------------------------
var myPattern:RegExp = /IDREF/g;
loader.data=loader.data.replace(myPattern, "Name");
//----------------------------------------------------
//パース
//----------------------------------------------------
var collada:ParserCollada = new ParserCollada();
//collada.parse(XML(loader.data), modelURL);
collada.parseForAnimation(XML(loader.data), modelURL);
//collada.parseForStatic(XML(loader.data), modelURL);
var objects:* = collada.objects;
//----------------------------------------------------
//objectのadd
//----------------------------------------------------
var bones:Object3DContainer
var boneY:Number=0
var count:int = objects.length;
for (var o:int = 0; o < count; o++) {
if (objects[o].name == 'Camera') {
//trace(objects[o].name,o)
} else {
dolphinObject.addChild(objects[o]);
dolphin = objects[o] as Skin
dolphin.sorting = Sorting.AVERAGE_Z;
dolphin.rotationZ = -90 * Math.PI / 180
dolphin.y=100
}
}
//----------------------------------------------------
//どうもボーンの回転が、基点軸回転ではない(Translation後Rotationしてる?)
//ようなのでアニメも自作する。また、逆行列の扱いがblenderが吐き出す物と違うため、
//ボーンの始点を動かすと、スキンが伸びてしまう。
//なので、ボーンの再配置からやり直す。
//Dae側で、ルートボーン以下を並列で配置しておく
//(つなげていると呼び出すのがめんどくさい)
//----------------------------------------------------
var joint:Joint
joint = dolphin.getJointAt(0);
//アニメシーケンスの作成
var matrixAnimations:Vector.<MatrixAnimation> = new Vector.<MatrixAnimation>()
matrixAnimations[0] = new MatrixAnimation(joint.getJointAt(0))
matrixAnimations[1] = new MatrixAnimation(joint.getJointAt(1))
matrixAnimations[2] = new MatrixAnimation(joint.getJointAt(2))
//Trackはコンストラクタで初期化されていないので、自前で作成
var track1:Track = new Track();
var track2:Track = new Track();
var track3:Track = new Track();
matrixAnimations[0].matrix = track1
matrixAnimations[1].matrix = track2
matrixAnimations[2].matrix = track3
var keyTime:Vector.<Number> = Vector.<Number>([0.04000, 0.44000, 0.84000, 1.24000, 1.64000]);
var boneXs:Vector.<Number> = Vector.<Number>([120,200,280]);
var rounds:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>()
var rot1:Number=5
var rot2:Number=10
var rot3:Number=40
rounds[0] = Vector.<Number>([0, rot1, 0, -rot1, 0]);
rounds[1] = Vector.<Number>([0, rot2, 0, -rot2, 0]);
rounds[2] = Vector.<Number>([0, rot3, 0, -rot3, 0]);
var trans:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>()
trans[0] = Vector.<Number>([0, 0, 0, 0, 0]);
trans[1] = Vector.<Number>([0, Math.sin(rot1*Math.PI/180)*120, 0, -Math.sin(rot1*Math.PI/180)*120, 0]);
trans[2] = Vector.<Number>([0, Math.sin(rot2*Math.PI/180)*80+Math.sin(rot1*Math.PI/180)*120, 0, -Math.sin(rot2*Math.PI/180)*80-Math.sin(rot1*Math.PI/180)*120, 0]);
for (var m:int = 0; m < 3 ;m++ ) {
for (var t:int = 4; t >=0 ;t-- ) {
var animeMatrix:Matrix3D = new Matrix3D()
animeMatrix.prependTranslation(boneXs[m],0,0);
animeMatrix.prependRotation(rounds[m][t], Vector3D.Z_AXIS);
animeMatrix.prependTranslation(-boneXs[m], trans[m][t],0);
matrixAnimations[m].matrix.addKey(new MatrixKey(keyTime[t], animeMatrix))
}
//ループする時間を設定(これがないと動かない)
matrixAnimations[m].length = 1.64
}
//出来たアニメはAnimationグループへ
var animations:AnimationGroup = new AnimationGroup(dolphin);
animations.addAnimation(matrixAnimations[0]);
animations.addAnimation(matrixAnimations[1]);
animations.addAnimation(matrixAnimations[2]);
//ループする時間を設定(これがないと動かない)
//animations.length=1.64
animationController = new AnimationController()
//var animation:Animation = collada.getAnimationByObject(objects[0]);
animationController.addAnimation('tail',animations, true);
animationController.playAll()
animationTimer = new AnimationTimer()
animationTimer.addController(animationController)
animationTimer.start()
//----------------------------------------------------
// Camera controller
//----------------------------------------------------
cameraController = new SimpleObjectController(stage, camera, 200);
cameraController.mouseSensitivity = 0
cameraController.unbindAll()
camera.debug = true;
//camera.farClipping=2
addChild(camera.diagram);
//camera.addToDebug(Debug.BONES,objects[0])
//camera.addToDebug(Debug.EDGES,objects[0])
//----------------------------------------------------
//イルカコントローラー
//----------------------------------------------------
moveController = new dolphinController(stage, dolphinObject, 400*scale, 5);
//moveController.mouseSensitivity=0
moveController.unbindAll()
moveController.bindKey(87, SimpleObjectController.ACTION_FORWARD);
moveController.bindKey(Keyboard.UP, SimpleObjectController.ACTION_PITCH_DOWN);
moveController.bindKey(90, SimpleObjectController.ACTION_BACK);
moveController.bindKey(Keyboard.DOWN, SimpleObjectController.ACTION_PITCH_UP);
moveController.bindKey(65, SimpleObjectController.ACTION_YAW_LEFT);
moveController.bindKey(Keyboard.LEFT, SimpleObjectController.ACTION_YAW_LEFT);
moveController.bindKey(68, SimpleObjectController.ACTION_YAW_RIGHT );
moveController.bindKey(Keyboard.RIGHT, SimpleObjectController.ACTION_YAW_RIGHT);
dolphinObject.rotationZ = 90 * Math.PI / 180
moveController.bindKey(Keyboard.SHIFT,SimpleObjectController.ACTION_ACCELERATE);
moveController.bindKey(Keyboard.ENTER,SimpleObjectController.ACTION_ACCELERATE);
//----------------------------------------------------
//Texture作成
//----------------------------------------------------
//materialLoaderへはVectorで渡す
//Loadが終わると勝手に貼り付けてくれる。
textures = new Vector.<TextureMaterial>(1);
textures[0] = new TextureMaterial();
textures[0] = new TextureMaterial();
textures[0].diffuseMapURL='http://marubayashi.net/archive/sample/alt3d7/texture.jpg'
dolphin.setMaterialToAllFaces(textures[0])
materialLoader = new MaterialLoader();
materialLoader.addEventListener(Event.COMPLETE, onMaterialsLoad);
materialLoader.load(textures);
}
private var state:AnimationState
private function onMaterialsLoad(e:Event):void {
//MaterialのLoad処理終了
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(Event.RESIZE, onResize);
}
public function onEnterFrame(e:Event = null):void {
if (dolphinObject != null) {
var ty:Number = dolphinObject.z / (dolphinObject.y + focalLength) * focalLength
var tx:Number = dolphinObject.x / (dolphinObject.y + focalLength) * focalLength
var size:Number = 400*scale / (dolphinObject.y + focalLength) * focalLength
if (dolphinObject.y < -2000 || Math.abs(tx) > camera.view.width / 2 + size || Math.abs(ty) > camera.view.height / 2 + size ) {
moveController.lookAtXYZ(0, 2000, 0)
moveController.onKeyUp();
}
if (anim) {
moveController.moveForward(true);
} else {
moveController.moveForward(false);
}
} else {
}
moveController.update();
animationTimer.update()
animationController.update(0.005);
cameraController.update();
camera.render();
backMaterial.texture.draw(back)
}
private function onKeyDown(e:KeyboardEvent):void {
switch (e.keyCode) {
case Keyboard.TAB:
camera.debug = !camera.debug;
break;
case Keyboard.ENTER:
moveController.lookAtXYZ(0, 0, 0)
break;
case Keyboard.SPACE:
anim = !anim;
break;
case 81: // Q
if (stage.quality == "HIGH") {
stage.quality = StageQuality.LOW;
} else {
stage.quality = StageQuality.HIGH;
}
break;
}
}
public function onResize(e:Event = null):void {
var pd:Number = 80;
camera.view.width = stage.stageWidth// - pd*2;
camera.view.height = stage.stageHeight// - pd*2;
camera.view.x = 0//pd;
camera.view.y = 0//pd;
var colors:Array=new Array(0xDDEEFF,0x003366,0x000000,0x000000,0x002266,0x9999FF)
var alphas:Array=new Array(1,1,1,1,1,1)
var ratios:Array=new Array(0,100,140,150,180,255)
var matrix:Matrix=new Matrix()
matrix.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0)
graphics.clear()
graphics.beginGradientFill(GradientType.LINEAR,colors, alphas, ratios, matrix)
graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight)
//cameraからイルカまでの距離
var d:Vector3D=new Vector3D(dolphinObject.x,dolphinObject.y,dolphinObject.z)
var c:Vector3D=new Vector3D(0,-500,0)
var distance:Number = Vector3D.distance(c,d);
camera.fov = 2 * Math.atan2(Math.sqrt(camera.view.width * camera.view.width +camera.view.height * camera.view.height) / 2,distance);
}
}
}
//-----------------------------------------------------------------
//イルカController
//なんちゃって当たり判定付き
//-----------------------------------------------------------------
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Object3D;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.GradientType;
import flash.display.Graphics;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.filters.BlurFilter;
import flash.filters.DisplacementMapFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.Timer;
class dolphinController extends SimpleObjectController{
public var _target:Object3D
private var _yawSpeed:Number= 1.5
private var _pitchSpeed:Number = 2
public var _pitchDown:Boolean
public var _pitchUp:Boolean
public var _yawLeft:Boolean
public var _yawRight:Boolean
public var _yawNear:Boolean=false
public var _yawNearAngle:Number
public var _pitchNear:Boolean=false
public var _pitchNearAngle:Number
public function dolphinController(param0:InteractiveObject, param1:Object3D, param2:Number, param3:Number = 3, param4:Number = 1) {
_target=param1
super(param0, param1, param2, param3, param4);
param0.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
param0.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
public function onKeyDown(e:KeyboardEvent):void {
for (var key:String in keyBindings) {
if (String(e.keyCode)==key) {
keyBindings[key](true)
}
}
}
public function onKeyUp(e:KeyboardEvent = null):void {
for (var key:String in keyBindings) {
/*
if (e) {
if (String(e.keyCode)==key) {
keyBindings[key](false)
}
} else {
}*/
keyBindings[key](false)
}
_yawNear = false
_pitchNear = false;
}
override public function bindKey(param0:uint, param1:String): void {
switch (param1) {
case ACTION_YAW_LEFT:
keyBindings[param0]=yawLeft
break
case ACTION_YAW_RIGHT:
keyBindings[param0]=yawRight
break
case ACTION_PITCH_DOWN:
keyBindings[param0]=pitchDown
break
case ACTION_PITCH_UP:
keyBindings[param0]=pitchUp
break
}
super.bindKey(param0, param1)
}
public function pitchDown(value:Boolean):void {
_pitchDown=value
}
public function pitchUp(value:Boolean):void {
_pitchUp=value
}
public function yawLeft(value:Boolean,near:Boolean=false):void {
_yawLeft = value
if (near) {
_yawNear = true
}
}
public function yawRight(value:Boolean,near:Boolean=false):void {
_yawRight = value
if (near) {
_yawNear = true
}
}
override public function update(): void {
//現在のVector3D
var nowVector:Vector3D=new Vector3D(_target.x,_target.y,_target.z)
if (_yawLeft) {
_target.rotationZ = (((_target.rotationZ / Math.PI * 180) - _yawSpeed) % 360) * Math.PI/180
} else if (_yawRight) {
_target.rotationZ = (((_target.rotationZ / Math.PI * 180) + _yawSpeed) % 360) * Math.PI/180
}
if (_pitchDown) {
_target.rotationX=(((_target.rotationX / Math.PI * 180) + _pitchSpeed) % 360) * Math.PI/180
}
if (_pitchUp) {
_target.rotationX=(((_target.rotationX / Math.PI * 180) - _pitchSpeed) % 360) * Math.PI/180
}
updateObjectTransform()
super.update()
//Colsion関係がまだ実装されてないので、Controller内で無理やり対応
//Vector3Dで処理せんといかん
//現在の進行方向をVector3Dに記憶
var nextVector:Vector3D = new Vector3D(_target.x, _target.y, _target.z)
//現在の進行方向
var velocity:Vector3D = nextVector.subtract(nowVector);
var normalVelocity:Vector3D
if (_target.y > 30000) {
normalVelocity = collision(velocity, new Vector3D(0, 1, 0))
lookAt(nextVector.add(normalVelocity))
_target.y = 29999
}
if (_target.z > 10000) {
//上なので、下向きベクトル
//normalVelocity = collision(velocity, new Vector3D(0, 0, -1))
//lookAt(nextVector.add(normalVelocity))
_target.rotationX=(((_target.rotationX / Math.PI * 180) - _pitchSpeed) % 360) * Math.PI/180
_target.z=9999
} else if (_target.z < -1000) {
//normalVelocity = collision(velocity, new Vector3D(0, 0, 1))
//lookAt(nextVector.add(normalVelocity))
_target.rotationX=(((_target.rotationX / Math.PI * 180) + _pitchSpeed) % 360) * Math.PI/180
_target.z = -999
}
}
public function collision(velocity:Vector3D, faceNormal:Vector3D):Vector3D {
//衝突面のnormalと進行方向の内積で新たな方向を出す
//進行方向を反転させる
var reverse:Vector3D
//Faceの法線を正規化
var tempVector:Vector3D=faceNormal.clone()
tempVector.normalize()
//進行スピードを反転する
reverse = velocity.clone()
reverse.negate()
//投影の長さを計算
//投影の長さに面法線ベクトル*衝突後の速度をかける
//tempVector.scaleBy(2*tempVector.dotProduct(reverse))
tempVector.scaleBy(2*tempVector.dotProduct(reverse)/10)
return velocity.add(tempVector);
}
public function set yawSpeed(value:Number):void {
_yawSpeed = value*Math.PI/180;
}
public function set pitchSpeed(value:Number):void
{
_pitchSpeed = value*Math.PI/180;
}
}
class TextInfo extends TextField {
public function TextInfo() {
autoSize = TextFieldAutoSize.LEFT;
selectable = false;
defaultTextFormat = new TextFormat("Tahoma", 10, 0x7F7F7F);
}
public function write(value:String):void {
appendText(value + "\n");
}
public function clear():void {
text = "";
}
}
/**
* 海面から射す光
*
* @author narutohyper
*/
class Sunlight extends Sprite
{
private var angle:uint = 0
private var light:Sprite=new Sprite()
private var yuragi:BitmapData;
public function Sunlight()
{
makelight()
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(event : Event) : void {
stage.addEventListener(Event.RESIZE, onResize);
onResize()
}
private function makelight():void {
var angleArray:Array = new Array()
var lightArray:Array = new Array()
while (angle < 360) {
angle += Math.floor(Math.random() * 5)
angleArray.push(angle)
}
var rx1:Number
var ry1:Number
var rx2:Number
var ry2:Number
var colors:Array=new Array(0xFFFFFF,0xFFFFFF)
var alphas:Array=new Array(1,0)
var ratios:Array=new Array(100,255)
var matrix:Matrix = new Matrix();
matrix.createGradientBox(400,400,0,-200,-200)
light.graphics.beginGradientFill(GradientType.RADIAL,colors, alphas, ratios, matrix)
for (var i:uint = 0; i < angleArray.length - 3; i += 3) {
if (angleArray[i]>0 && angleArray[i]<90) {
rx1=Math.sin(angleArray[i]*Math.PI/180)
ry1=Math.cos(angleArray[i]*Math.PI/180)
rx2=Math.sin(angleArray[i+1]*Math.PI/180)
ry2=Math.cos(angleArray[i+1]*Math.PI/180)
light.graphics.moveTo(rx1*10,ry1*10)
light.graphics.lineTo(rx2*10,ry2*10)
light.graphics.lineTo(rx2*200,ry2*200)
light.graphics.lineTo(rx1 * 200, ry1 * 200)
}
}
light.graphics.endFill()
light.x=0
light.y=0
var f1:BlurFilter = new BlurFilter(20, 20, 1);
light.blendMode=BlendMode.ADD
light.alpha = 0.2
addChild(light)
light.filters = [f1];
}
private function createMap(width:int, height:int):BitmapData{
var bmp:BitmapData = new BitmapData(width, height, false, 0xffffff);
for(var j:int = 0; j < height; j++){
for(var i:int = 0; i < width; i++){
bmp.setPixel(i, j, 32*Math.sin(Math.PI*3/height*j)+128+64);
}
}
return bmp;
}
public function onResize(e:Event = null):void {
height=stage.stageHeight*1.5
width=stage.stageHeight*1.5
//light.x = 400
//light.y= 300
y=stage.stageHeight/-2
}
}
/**
* 以下は、
* Caustics(水面、底面)Materialの作成
*
*
* 超速ボロノイ図(Fortune's algorithm)
* http://wonderfl.net/c/3TKq
*
* を使用させていただいてます。
*
* 正直、なにやってるかわかりませんw
*
*
*/
/**
* 超速ボロノイアルゴリズム
* 元ネタ
* Fortune's algorithm - Wikipedia, the free encyclopedia
* http://en.wikipedia.org/wiki/Fortune's_algorithm
* Controul > Speedy Voronoi diagrams in as3/flash
* http://blog.controul.com/2009/05/speedy-voronoi-diagrams-in-as3flash/
* 上記blogのasそのままです(Fortuneクラス)。
* 全然ロジックわからないので誰か解説してほしいです。
*
* 母点を800程度にしてますがもう少し多くてもいけそう。
* fullscreenでもお楽しみいただけます(重いけど)
*
* 日本語の資料(PDF)
* http://atom.is.ocha.ac.jp/~kanenko/KOUGI/CompGeo/cpgeob.pdf
* http://i-health.u-aizu.ac.jp/CompuGeo/2008/handouts/chapter4/Chapter4H.pdf
*/
//import com.flashdynamix.utils.SWFProfiler;
class water extends Sprite {
//キャンパス
private var _gradation : Sprite;
private var _canvas : Sprite;
//background
private var _background:BitmapData;
//voronoi母点数
private const Q : uint = 200;
//voronoi作図用のクラス
private var fortune : Fortune;
//voronoi母点(パーティクル的な?)
private var points : Vector.<Number2>;
private var _first : Number2;
private var stageWidth : int=300;
private var stageHeight : int=300;
private var yuragi:BitmapData;
private var bmd:BitmapData;
private var bm:Bitmap;
private var bmd2:BitmapData;
private var bm2:Bitmap;
public function water() {
addEventListener(Event.ADDED_TO_STAGE, _initialize);
}
/**
* 初期化
*/
private function _initialize(event : Event) : void {
var i : uint,old : Number2,point : Number2;
removeEventListener(Event.ADDED_TO_STAGE, _initialize);
//SWFProfiler.init(this);
//背景カラー
bmd = new BitmapData(stageWidth , 900, false, 0x0);
bm=new Bitmap(bmd)
bmd2 = new BitmapData(stageWidth-40 , 860, false, 0x0);
yuragi = createMap3(stageWidth , 900)
//bm2=new Bitmap(yuragi)
bm2=new Bitmap(bmd2)
addChild(bm2);
//キャンパス設定
_canvas = new Sprite();
_gradation = new Sprite()
addChild(_gradation);
var colors:Array=new Array(0xFFFFFF,0x000000)
var alphas:Array=new Array(1,1)
var ratios:Array=new Array(0,160)
var matrix:Matrix=new Matrix()
matrix.createGradientBox(stageWidth-40, 860, Math.PI / 2, 0, 0)
_gradation.graphics.clear()
_gradation.graphics.beginGradientFill(GradientType.LINEAR,colors, alphas, ratios, matrix)
_gradation.graphics.drawRect(0,0,stageWidth-40,860)
_gradation.blendMode=BlendMode.MULTIPLY
//voronoi母点生成
points = new Vector.<Number2>();
for(i = 0;i < Q;i++) {
point = new Number2();
point.x = stageWidth * Math.random();
point.y = stageHeight * Math.random();
//各母点の速度
point.vx = (Math.random()*2-1)*0.5;
point.vy = (Math.random()*2-1)*0.5;
points.push(point);
}
//voronoi母点リンクリスト生成
for(i = 0;i < Q;i++) {
point = points[i];
if (_first == null) {
old = _first = point;
} else {
old.next = point;
old = point;
}
}
//ボロノイ作図用クラス
fortune = new Fortune();
//addEventListener(Event.ENTER_FRAME, _updateHandler);
var timer:Timer = new Timer(60, 0)
timer.addEventListener(TimerEvent.TIMER, _updateHandler);
timer.start()
}
private function createMap3(width:int, height:int):BitmapData{
var bmp:BitmapData = new BitmapData(width, height, false, 0xffffff);
for(var j:int = 0; j < height; j++){
for(var i:int = 0; i < width; i++){
bmp.setPixel(i, j, 128*Math.sin(Math.PI*10/height*j));
}
}
return bmp;
}
//アップデート
private function _updateHandler(event : Event) : void {
_interaction();
_draw();
//removeEventListener(Event.ENTER_FRAME, _updateHandler);
var f1:DisplacementMapFilter = new DisplacementMapFilter(yuragi, new Point(0,0),
BitmapDataChannel.BLUE, BitmapDataChannel.BLUE, 100, 0, "color", 0x0);
var f2:BlurFilter = new BlurFilter(6, 6, 1);
bmd.fillRect(new Rectangle(0, 0, 300, 900), 0x000000);
bmd.draw(_canvas,new Matrix(1, 0, 0, 1, 0, 250), null, null, new Rectangle(0, 250, 300, 650));
bm.filters = [f1,f2];
bmd2.draw(bm, new Matrix(1, 0, 0, 1, -20, -20), null, null, new Rectangle(0, 0, 260, 880));
}
//インタラクション
private function _interaction() : void {
var point : Number2 = _first;
do {
//母点の位置を更新
point.x += point.vx;
point.y += point.vy;
if(point.x > stageWidth) {
point.x -= stageWidth;
}else if(point.x < 0) {
point.x += stageWidth;
}
if(point.y > stageHeight) {
point.y -= stageHeight;
}else if(point.y < 0) {
point.y += stageHeight;
}
} while (point = point.next);
//更新した母点をvoronoi作図用クラスに入れる
fortune.points = points;
}
//描画
private function _draw() : void {
//ボロノイ頂点
var segments : Vector.<Number2> = fortune.compute(),
i : uint,start : Number2,end : Number2,
point : Number2 = _first,
g : Graphics = _canvas.graphics;
//ボロノイ辺の描画
g.clear();
g.beginFill(0x000000)
g.drawRect(0,0,300,300)
g.lineStyle(5, 0xFFFFFF, 1);
for(i = 0;i < segments.length;i += 2) {
start = segments[i];
end = segments[i + 1];
g.moveTo(start.x, start.y);
g.lineTo(end.x, end.y);
}
}
}
/*
* Fortune's algorithm
* http://blog.controul.com/2009/05/speedy-voronoi-diagrams-in-as3flash/
* オリジナルは上記blogからダウンロードして見てください!
* はっきりいって全然わからないのでなにやってるのかどなたか解説を!
*/
class Fortune {
// voronoi図の母点となる点群
public var points : Vector.<Number2>;
// Bounding box.
private var x0 : Number;
// Root of the frontline and next arc to be removed.
private var root : Arc;
private var next : Arc;
// Reusable objects and pools.
private var o : Number2 = new Number2;
private static var arcPoolD : Arc;
/**
* 与えられた母点からvoronoi頂点群を返します.
* @return A vector or vertices in pairs, describing segments ready for drawing.
*/
public function compute() : Vector.<Number2> {
// Clear the output.
var out : Vector.<Number2> = new Vector.<Number2>,
len : int = 0;
// Clear the state.
root = null;
next = null;
// Read the pools.
var key : * ,
arcPool : Arc = arcPoolD;
// Vars:
var i : int,
j : int,
w : Number,
x : Number,
a : Arc,
b : Arc,
z : Number2,
p : Number2 = points[ 0 ],
points : Vector.<Number2> = points,
n : int = points.length,
// Circle events check inlined.
circle : Boolean,
eventX : Number,
c : Arc,
d : Arc,
aa : Number2,
bb : Number2,
cc : Number2,
A : Number,
B : Number,
C : Number,
D : Number,
E : Number,
F : Number,
G : Number;
// 与えられた母点をx軸でソート
///// Currently insertion sort. Quicksort?
w = points[ 0 ].x;
for ( i = 1;i < n;i++ ) {
p = points[ i ];
// Insertion sort.
x = p.x;
if ( x < w ) {
j = i;
while ( ( j > 0 ) && ( points[ int(j - 1) ].x > x ) ) {
points[ j ] = points[ int(j - 1) ];
j--;
}
points[ j ] = p;
}
else
w = x;
}
// Get x bounds.
x0 = points[ 0 ].x;
// Process.
i = 0;
p = points[ 0 ];
x = p.x;
//多分母点群でループ
for ( ;; ) {
// Check circle events. /////////////////////////
if ( a ) {
// Check for arc a.
circle = false;
if ( a.prev && a.next ) {
aa = a.prev.p,
bb = a.p,
cc = a.next.p;
// Algorithm from O'Rourke 2ed p. 189.
A = bb.x - aa.x,
B = bb.y - aa.y,
C = cc.x - aa.x,
D = cc.y - aa.y;
// Check that bc is a "right turn" from ab.
if ( A * D - C * B <= 0 ) {
E = A * ( aa.x + bb.x ) + B * ( aa.y + bb.y ),
F = C * ( aa.x + cc.x ) + D * ( aa.y + cc.y ),
G = 2 * ( A * ( cc.y - bb.y ) - B * ( cc.x - bb.x ) );
// Check for colinearity.
// if ( G > 0.000000001 || G < -0.000000001 )
if ( G ) {
// Point o is the center of the circle.
o.x = ( D * E - B * F ) / G;
o.y = ( A * F - C * E ) / G;
// o.x plus radius equals max x coordinate.
A = aa.x - o.x;
B = aa.y - o.y;
eventX = o.x + Math.sqrt(A * A + B * B);
if ( eventX >= w ) circle = true;
}
}
}
// Remove from queue.
if ( a.right )
a.right.left = a.left;
if ( a.left )
a.left.right = a.right;
if ( a == next )
next = a.right;
// Record event.
if ( circle ) {
a.endX = eventX;
if ( a.endP ) {
a.endP.x = o.x;
a.endP.y = o.y;
} else {
a.endP = o;
o = new Number2;
}
d = next;
if ( !d ) {
next = a;
}
else for ( ;; ) {
if ( d.endX >= eventX ) {
a.left = d.left;
if ( d.left ) d.left.right = a;
if ( next == d ) next = a;
a.right = d;
d.left = a;
break;
}
if ( !d.right ) {
d.right = a;
a.left = d;
a.right = null;
break;
}
d = d.right;
}
} else {
a.endX = NaN;
a.endP = null;
a.left = null;
a.right = null;
}
// Push next arc to check.
if ( b ) {
a = b;
b = null;
continue;
}
if ( c ) {
a = c;
c = null;
continue;
}
a = null;
}
//////////////////////////////////////////////////
//
if ( next && next.endX <= x ) {
//
// Handle next circle event.
// Get the next event from the queue. ///////////
a = next;
next = a.right;
if ( next )
next.left = null;
a.right = null;
// Remove the associated arc from the front.
if ( a.prev ) {
a.prev.next = a.next;
a.prev.v1 = a.endP;
}
if ( a.next ) {
a.next.prev = a.prev;
a.next.v0 = a.endP;
}
if ( a.v0 ) {
out[ len++ ] = a.v0;
a.v0 = null;
out[ len++ ] = a.endP;
}
if ( a.v1 ) {
out[ len++ ] = a.v1;
a.v1 = null;
out[ len++ ] = a.endP;
}
// Keep a ref for collection.
d = a;
// Recheck circle events on either side of p:
w = a.endX;
if ( a.prev ) {
b = a.prev;
a = a.next;
} else {
a = a.next;
b = null;
}
c = null;
// Collect.
d.v0 = null;
d.v1 = null;
d.p = null;
d.prev = null;
d.endX = NaN;
d.endP = null;
d.next = arcPool;
arcPool = d;
//////////////////////////////////////////////////
//
} else {
if ( !p ) break;
//
// Handle next site event. //////////////////////
if ( !root ) {
if ( arcPool ) {
root = arcPool;
arcPool = arcPool.next;
root.next = null;
}
else
root = new Arc;
root.p = p;
} else {
z = new Number2;
// Find the first arc with a point below p,
// and start searching for the intersection around it.
a = root.next;
if ( a ) {
while ( a.next ) {
a = a.next;
if ( a.p.y >= p.y ) break;
}
// Find the intersecting curve.
intersection(a.prev.p, a.p, p.x, z);
if ( z.y <= p.y ) {
// Search for the intersection to the south of i.
while ( a.next ) {
a = a.next;
intersection(a.prev.p, a.p, p.x, z);
if ( z.y >= p.y ) {
a = a.prev;
break;
}
}
} else {
// Search for the intersection above i.
a = a.prev;
while ( a.prev ) {
a = a.prev;
intersection(a.p, a.next.p, p.x, z);
if ( z.y <= p.y ) {
a = a.next;
break;
}
}
}
}
else
a = root;
// New parabola will intersect arc a. Duplicate a.
if ( a.next ) {
if ( arcPool ) {
b = arcPool;
arcPool = arcPool.next;
b.next = null;
}
else
b = new Arc;
b.p = a.p;
b.prev = a;
b.next = a.next;
a.next.prev = b;
a.next = b;
} else {
if ( arcPool ) {
b = arcPool;
arcPool = arcPool.next;
b.next = null;
}
else
b = new Arc;
b.p = a.p;
b.prev = a;
a.next = b;
}
a.next.v1 = a.v1;
// Find the point of intersection.
z.y = p.y;
z.x = ( a.p.x * a.p.x + ( a.p.y - p.y ) * ( a.p.y - p.y ) - p.x * p.x ) / ( 2 * a.p.x - 2 * p.x );
// Add p between i and i->next.
if ( arcPool ) {
b = arcPool;
arcPool = arcPool.next;
b.next = null;
}
else
b = new Arc;
b.p = p;
b.prev = a;
b.next = a.next;
a.next.prev = b;
a.next = b;
a = a.next; // Now a points to the new arc.
a.prev.v1 = z;
a.next.v0 = z;
a.v0 = z;
a.v1 = z;
// Check for new circle events around the new arc:
b = a.next;
a = a.prev;
c = null;
w = p.x;
}
//////////////////////////////////////////////////
//
i++;
if ( i >= n ) {
p = null;
x = Number.MAX_VALUE;
} else {
p = points[ i ];
x = p.x;
}
}
}
// Store the pools.
arcPoolD = arcPool;
//
//
// Return the result ready for drawing.
return out;
}
/**
* Where do two parabolas intersect?
* @param p0 A Number2 object describing the site for the first parabola.
* @param p1 A Number2 object describing the site for the second parabola.
* @param l The location of the sweep line.
* @param res A Number2 object in which to store the intersection.
* @return The point of intersection.
*/
public function intersection( p0 : Number2, p1 : Number2, l : Number, res : Number2 ) : Number2 {
var p : Number2 = p0,
ll : Number = l * l;
if ( p0.x == p1.x )
res.y = ( p0.y + p1.y ) / 2;
else if ( p1.x == l )
res.y = p1.y;
else if ( p0.x == l ) {
res.y = p0.y;
p = p1;
} else {
// Use the quadratic formula.
var z0 : Number = 0.5 / ( p0.x - l ); // 1 / ( 2*(p0.x - l) )
var z1 : Number = 0.5 / ( p1.x - l ); // 1 / ( 2*(p1.x - l) )
var a : Number = z0 - z1;
var b : Number = -2 * ( p0.y * z0 - p1.y * z1 );
var c : Number = ( p0.y * p0.y + p0.x * p0.x - ll ) * z0 - ( p1.y * p1.y + p1.x * p1.x - ll ) * z1;
res.y = ( -b - Math.sqrt(b * b - 4 * a * c) ) / ( 2 * a );
}
// Plug back into one of the parabola equations.
res.x = ( p.x * p.x + ( p.y - res.y ) * ( p.y - res.y ) - ll ) / ( 2 * p.x - 2 * l );
return res;
}
}
class Arc {
public var p : Number2;
public var next : Arc;
public var prev : Arc;
public var v0 : Number2;
public var v1 : Number2;
// Circle event data :
public var left : Arc;
public var right : Arc;
public var endX : Number;
public var endP : Number2;
}
class Number2 {
public var x : Number;
public var y : Number;
//速度
public var vx : Number;
public var vy : Number;
public var next : Number2;
}