Stage3D 3Dパーティクル
3Dパーティクル。
その実態はただのPlaneだけど、3Dで表現するには常にこっちを向いてなくてはならない。
たくさんあると変換行列だけでは対応できないのだが、vertexシェーダで頂点移動することでこっちを向くポリゴンにする方法を教えていただいた。
[↑↓ or MouseWhell]...move camera
[Mouse Drag]...rotate camera
一応colladaモデルが読み込めるかもしれないけど16383頂点以上あると勝手にカットします。
/**
* Copyright 9balls ( http://wonderfl.net/user/9balls )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rXDw
*/
package {
import com.adobe.utils.AGALMiniAssembler;
import com.bit101.components.PushButton;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix3D;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.utils.ByteArray;
import frocessing.color.ColorHSV;
import net.hires.debug.Stats;
public class Main extends Sprite {
private var file:FileReference = new FileReference();
private var loadButton:PushButton;
private var randomButton:PushButton;
//
private const PARTICLE_MAX:uint = 16383;
private const SIZE:Number = 0.5 * 0.01;
private const RAD:Number = Math.PI / 180;
//
private var stage3D:Stage3D;
private var context3D:Context3D;
//
private var worldMtx:Matrix3D = new Matrix3D();
private var colladaMtx:Matrix3D = new Matrix3D();
private var mtx:Matrix3D = new Matrix3D();
//
private var camera:SimpleCamera3D;
private var particleProgram:Program3D;
private var vBufferXYZ:VertexBuffer3D;
private var vBufferRGB:VertexBuffer3D;
private var vBufferOffset:VertexBuffer3D;
private var iBuffer:IndexBuffer3D;
private var numParticles:uint;
private var colladaPos:Vector.<Number>;
public function Main():void {
Wonderfl.disable_capture();
stage.frameRate = 60;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
addChild(new Stats());
//
file.addEventListener(Event.SELECT, selectHandler);
file.addEventListener(Event.COMPLETE, completeHandler);
loadButton = new PushButton(this, 100, 200, "LOAD", onColladaLoad);
randomButton = new PushButton(this, 300, 200, "RANDOM", onRondom);
}
private function onColladaLoad(e:MouseEvent):void {
var fileFilter:FileFilter = new FileFilter("COLLADA(dae)", "*.dae");
file.browse([fileFilter]);
}
private function selectHandler(e:Event):void {
file.load();
}
private function completeHandler(e:Event):void {
var xml:XML = new XML(file.data);
colladaPos = collada2Vertices(xml);
numParticles = colladaPos.length / 3;
if (numParticles > PARTICLE_MAX){
numParticles = numParticles = PARTICLE_MAX;
} else if(numParticles == 0){
return;
}
initStage3D();
}
private function onRondom(e:MouseEvent):void {
var xml:XML = new XML(file.data);
var length:uint = PARTICLE_MAX * 3;
colladaPos = new Vector.<Number>(length);
for (var i:int = 0; i < length; i++){
colladaPos[i] = Math.random() * 2000;
}
numParticles = PARTICLE_MAX;
initStage3D();
}
private function initStage3D():void {
removeChild(loadButton);
removeChild(randomButton);
//
stage3D = stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
stage3D.requestContext3D(Context3DRenderMode.AUTO);
}
private function onContextCreate(e:Event):void {
context3D = stage3D.context3D;
//context3D.enableErrorChecking = true;
context3D.configureBackBuffer(466, 466, 0, true);
context3D.setRenderToBackBuffer();
context3D.setCulling(Context3DTriangleFace.BACK);
context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
//
camera = new SimpleCamera3D(45 * RAD, 1, 0.1, 3000);
new RoundCameraController(camera, stage);
createBuffer();
createShader();
//
addEventListener(Event.ENTER_FRAME, onEnter);
}
private function onEnter(e:Event):void {
context3D.clear();
//
worldMtx.copyFrom(camera.cameraMtx);
mtx.copyFrom(colladaMtx);
mtx.append(worldMtx);
//
context3D.setProgram(particleProgram);
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mtx, true);
context3D.setVertexBufferAt(0, vBufferXYZ, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(1, vBufferRGB, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(2, vBufferOffset, 0, Context3DVertexBufferFormat.FLOAT_2);
context3D.drawTriangles(iBuffer);
//
context3D.present();
}
private function createShader():void {
var agalAssembler:AGALMiniAssembler = new AGALMiniAssembler();
////Particle
var code:String = "";
code += "m44 vt0, va0, vc0\n";
code += "mul vt1, vt0.w, va2\n";
code += "add vt0, vt0, vt1\n";
code += "mov op, vt0\n";
code += "mov v0, va1\n";
var vertexShader:ByteArray = agalAssembler.assemble(Context3DProgramType.VERTEX, code);
var fragmentShader:ByteArray = agalAssembler.assemble(Context3DProgramType.FRAGMENT, "mov oc v0\n");
particleProgram = context3D.createProgram();
particleProgram.upload(vertexShader, fragmentShader);
}
private function createBuffer():void {
////Particle
var numVertices:uint = numParticles * 4;
var numIndices:uint = numParticles * 6;
//xyz
vBufferXYZ = context3D.createVertexBuffer(numVertices, 3);
var index:uint = 0;
var index2:uint = 0;
var pos:Vector.<Number> = new Vector.<Number>(numParticles * 4 * 3);
var maxX:Number = colladaPos[0];
var maxY:Number = colladaPos[1];
var maxZ:Number = colladaPos[2];
var minX:Number = maxX;
var minY:Number = maxY;
var minZ:Number = maxZ;
for (var i:int = 0; i < numParticles; i++){
var posX:Number = colladaPos[index2++];
var posY:Number = colladaPos[index2++];
var posZ:Number = colladaPos[index2++];
if (posX < minX){
minX = posX;
} else if (posX > maxX){
maxX = posX;
}
if (posY < minY){
minY = posY;
} else if (posY > maxY){
maxY = posY;
}
if (posZ < minZ){
minZ = posZ;
} else if (posZ > maxZ){
maxZ = posZ;
}
pos[index++] = posX;
pos[index++] = posY;
pos[index++] = posZ;
pos[index++] = posX;
pos[index++] = posY;
pos[index++] = posZ;
pos[index++] = posX;
pos[index++] = posY;
pos[index++] = posZ;
pos[index++] = posX;
pos[index++] = posY;
pos[index++] = posZ;
}
colladaMtx.appendTranslation(-(minX + maxX) / 2, -(minY + maxY) / 2, -(minZ + maxZ) / 2);
vBufferXYZ.uploadFromVector(pos, 0, numParticles * 4);
//rgb
vBufferRGB = context3D.createVertexBuffer(numVertices, 3);
var cVec:Vector.<Number> = new Vector.<Number>(numVertices * 3);
var color:ColorHSV = new ColorHSV();
var r:Number;
var g:Number;
var b:Number;
index = 0;
for (i = 0; i < numParticles; i++){
color.value = 0x1000000 * Math.random();
r = color.r / 255.0;
g = color.g / 255.0;
b = color.b / 255.0;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
cVec[index++] = r;
cVec[index++] = g;
cVec[index++] = b;
}
vBufferRGB.uploadFromVector(cVec, 0, numVertices);
//offset
vBufferOffset = context3D.createVertexBuffer(numVertices, 2);
var offsetVec:Vector.<Number> = new Vector.<Number>(numVertices * 2);
index = 0;
for (i = 0; i < numParticles; i++){
offsetVec[index++] = -SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = -SIZE;
offsetVec[index++] = SIZE;
offsetVec[index++] = SIZE;
}
vBufferOffset.uploadFromVector(offsetVec, 0, numVertices);
//index buffer
iBuffer = context3D.createIndexBuffer(numIndices);
var iVec:Vector.<uint> = new Vector.<uint>(numIndices);
index = 0;
var p:uint;
for (i = 0; i < numParticles; i++){
p = i << 2;
iVec[index++] = p;
iVec[index++] = p + 1;
iVec[index++] = p + 2;
iVec[index++] = p + 2;
iVec[index++] = p + 1;
iVec[index++] = p + 3;
}
iBuffer.uploadFromVector(iVec, 0, numIndices);
}
private function collada2Vertices(xml:XML):Vector.<Number> {
var ns:Namespace = xml.namespace();
var geometry:XMLList = xml.ns::library_geometries.child(new QName(ns, "geometry"));
var vertices:Vector.<Number> = new Vector.<Number>();
var length:uint = geometry.length();
var sourceID:String;
var floatArray:XMLList;
var count:uint;
var posVec:Vector.<Number>;
var posArray:Array;
var numVertices:uint;
for (var i:int = 0; i < length; i++){
sourceID = geometry[i].ns::mesh.ns::vertices.ns::input.(@semantic == "POSITION").@source;
floatArray = geometry[i].ns::mesh.ns::source.(@id == sourceID.substr(1)).ns::float_array;
count = uint(floatArray.@count);
posVec = new Vector.<Number>(count);
posArray = floatArray.toString().split(" ");
numVertices = count / 3;
for (var j:int = 0; j < numVertices; j++){
posVec[j] = Number(posArray[j]);
}
vertices = vertices.concat(posVec);
}
return vertices;
}
}
}
import com.adobe.utils.PerspectiveMatrix3D;
import flash.display.InteractiveObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.ui.Keyboard;
//////////////////////////////////////////////////
// SimpleCamera3D
//////////////////////////////////////////////////
class SimpleCamera3D extends Object {
private const RAD:Number = Math.random() / 180;
private const DIRECTION:Vector3D = new Vector3D(0, 0, 1);
//
private var _cameraUP:Vector3D = new Vector3D(0, 1, 0);
private var _direction:Vector3D = DIRECTION.clone();
//
private var _projectionMtx:PerspectiveMatrix3D = new PerspectiveMatrix3D();
private var _cameraMtx:Matrix3D = new Matrix3D();
private var _rotationMtx:Matrix3D = new Matrix3D();
//
public var x:Number = 0;
public var y:Number = 0;
public var z:Number = 0;
public function SimpleCamera3D(fov:Number, aspect:Number, zNear:Number, zFar:Number):void {
_projectionMtx.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar);
}
public function get cameraMtx():Matrix3D {
var forwardX:Number = _direction.x;
var forwardY:Number = _direction.y;
var forwardZ:Number = _direction.z;
var rightX:Number = _cameraUP.y * forwardZ - _cameraUP.z * forwardY;
var rightY:Number = _cameraUP.z * forwardX - _cameraUP.x * forwardZ;
var rightZ:Number = _cameraUP.x * forwardY - _cameraUP.y * forwardX;
var distance:Number = Math.sqrt(rightX * rightX + rightY * rightY + rightZ * rightZ);
rightX /= distance;
rightY /= distance;
rightZ /= distance;
var upX:Number = forwardY * rightZ - forwardZ * rightY;
var upY:Number = forwardZ * rightX - forwardX * rightZ;
var upZ:Number = forwardX * rightY - forwardY * rightX;
var right:Number = rightX * x + rightY * y + rightZ * z;
var up:Number = upX * x + upY * y + upZ * z;
var forward:Number = forwardX * x + forwardY * y + forwardZ * z;
_cameraMtx.rawData = Vector.<Number>([rightX, upX, forwardX, 0, rightY, upY, forwardY, 0, rightZ, upZ, forwardZ, 0, -right, -up, -forward, 1]);
_cameraMtx.append(_projectionMtx);
return _cameraMtx;
}
public function lookAt(point:Vector3D):void {
_direction.x = point.x - x;
_direction.y = point.y - y;
_direction.z = point.z - z;
_direction.normalize();
}
}
//////////////////////////////////////////////////
// RoundCameraController
//////////////////////////////////////////////////
class RoundCameraController extends Object {
private var _camera:SimpleCamera3D;
private var _stage:InteractiveObject;
private var _target:Vector3D;
public var radiusOffset:Number = 40;
//
private const RAD:Number = Math.PI / 180;
//
public var isMouseDown:Boolean;
private var _radius:Number = 2000;
private var _theta:Number = 0;
private var _oldX:Number = 0;
private var _phi:Number = 90;
private var _oldY:Number = 0;
public function RoundCameraController(camera:SimpleCamera3D, stage:InteractiveObject){
_camera = camera;
_stage = stage;
_target = new Vector3D();
enable();
_upDateCamera();
}
public function enable():void {
_stage.stage.addEventListener(KeyboardEvent.KEY_DOWN, _keyHandler);
_stage.stage.addEventListener(MouseEvent.MOUSE_UP, _upHandler);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, _downHandler);
_stage.addEventListener(MouseEvent.MOUSE_MOVE, _moveHandler);
_stage.addEventListener(MouseEvent.MOUSE_WHEEL, _wheelHandler);
}
//
private function _keyHandler(e:KeyboardEvent):void {
switch (e.keyCode){
case Keyboard.UP:
_radius -= radiusOffset;
if (_radius < 10){
_radius = 10;
}
_upDateCamera();
break;
case Keyboard.DOWN:
_radius += radiusOffset;
_upDateCamera();
break;
default:
break;
}
}
private function _upHandler(e:MouseEvent):void {
isMouseDown = false;
}
private function _downHandler(e:MouseEvent):void {
isMouseDown = true;
_oldX = _stage.mouseX;
_oldY = _stage.mouseY;
}
private function _wheelHandler(e:MouseEvent):void {
if (e.delta > 0){
_radius -= radiusOffset;
if (_radius < 10){
_radius = 10;
}
} else {
_radius += radiusOffset;
}
_upDateCamera();
}
private function _moveHandler(e:MouseEvent):void {
if (isMouseDown){
_theta += (e.stageX - _oldX) >> 2;
_oldX = e.stageX;
_phi -= (e.stageY - _oldY) >> 2;
_oldY = e.stageY;
//
if (_theta < 0){
_theta += 360;
} else if (_theta > 360){
_theta -= 360;
}
if (_phi < 20){
_phi = 20;
} else if (_phi > 160){
_phi = 160;
}
_upDateCamera();
}
}
private function _upDateCamera():void {
var t:Number = _theta * RAD;
var p:Number = _phi * RAD;
var rsin:Number = _radius * Math.sin(p);
_camera.x = rsin * Math.sin(t) + _target.x;
_camera.z = rsin * Math.cos(t) + _target.z;
_camera.y = _radius * Math.cos(p) + _target.y;
_camera.lookAt(_target)
}
}