some soft balls
now with collision
update: pressure added
/**
* Copyright zob ( http://wonderfl.net/user/zob )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/cpuW
*/
package {
import flash.display.Sprite
import flash.display.Loader;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shape;
import flash.display.TriangleCulling;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.utils.Dictionary;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Vector3D;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.PerspectiveProjection;
import flash.geom.Utils3D;
import flash.events.KeyboardEvent;
import com.bit101.components.Label;
import com.bit101.components.HUISlider;
import com.bit101.components.InputText;
import com.bit101.components.PushButton;
import com.bit101.components.RotarySelector;
import com.bit101.components.NumericStepper;
import flash.net.FileFilter;
import flash.net.FileReference;
public class IcoSphere extends Sprite {
private var icoSpheres :Vector.<IcoSphereCreator> = new Vector.<IcoSphereCreator>();
private var matrix :Matrix3D = new Matrix3D();
private var drag :Boolean = false;
private var start_m :Vector3D = new Vector3D();
private var old_mouse :Vector3D = new Vector3D();
private var new_mouse :Vector3D = new Vector3D();
private var center :Point = new Point(stage.stageWidth/2, stage.stageHeight/2);
private var loader :Loader;
private var MAP :BitmapData;
private var GRAPHIC_URL :String = "http://www.k3lab.com/wonderfl/Amphisbaena/photo1.jpg";
private var viewport :Shape = new Shape();
private var world :Matrix3D = new Matrix3D();
private var projected :Vector.<Number> = new Vector.<Number>;
private var projection :PerspectiveProjection = new PerspectiveProjection();
private var gwidth :int = 200;
private var gheight :int = 200;
private var land_bmpd :BitmapData = new BitmapData(1, 1,false,0x0);
private var landVertices :Vector.<Number> = new Vector.<Number>(0, false);
private var landProjected :Vector.<Number> = new Vector.<Number>(0, false);
private var landIndices :Vector.<int> = new Vector.<int>(0, false);
private var landUvtData :Vector.<Number> = new Vector.<Number>(0, false);
private var dragging :Point = new Point();
private var mouseStart :Point = new Point();
private var mouse3D :Vector3D = new Vector3D();
private var dragMode :Boolean = true;
private var levelLabel :Label;
private var levelStepper :NumericStepper;
private var ballsLabel :Label;
private var ballsStepper :NumericStepper;
private var sizeSlider :HUISlider;
private var shapeSelector :RotarySelector;
private var regenButton :PushButton;
private var regen :Boolean = false;
private var geometry :Geometry = new Geometry();
private const filter :Array = [new FileFilter("load Img","*.jpg;*.jpeg;*.png;*.gif")];
private var fileReference :FileReference = new FileReference();
public function IcoSphere() {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, init);
loader.load(new URLRequest(GRAPHIC_URL), new LoaderContext(true));
}
private function init(e:Event):void
{
MAP = Bitmap(loader.content).bitmapData;
viewport.x = stage.stageWidth / 2;
viewport.y = stage.stageHeight / 2;
addChild(viewport);
var bitmap:Bitmap = new Bitmap(new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x0));
var hidden:Sprite = new Sprite();
addChild(hidden);
hidden.addChild(bitmap);
var panel:Sprite = new Sprite();
addChild(panel);
levelLabel = new Label(panel, 5, 5, "Level");
levelStepper = new NumericStepper(panel, 35, 5, null);
ballsLabel = new Label(panel, 5, 24, "Balls");
ballsStepper = new NumericStepper(panel, 35, 24, null);
sizeSlider = new HUISlider(panel, 5, 39, "size", null);
shapeSelector = new RotarySelector(panel, 195, 8, "shape", null);
new PushButton(panel, 90, 5, "Give me ball!", regenerateBall).setSize(83, 16);
new PushButton(panel, 5, 56, "Load Image", loadImage).setSize(83, 16);
levelStepper.setSize(50, 20);
levelStepper.value = 1;
levelStepper.minimum = 0;
levelStepper.maximum = 3;
ballsStepper.setSize(50, 20);
ballsStepper.value = 2;
ballsStepper.minimum = 1;
ballsStepper.maximum = 5;
sizeSlider.value = 50;
sizeSlider.minimum = 20;
sizeSlider.maximum = 100;
shapeSelector.setSize(20,20);
shapeSelector.numChoices = 2;
shapeSelector.choice = 0;
for(var i:int = 0; i < ballsStepper.value; i++)
{
icoSpheres.push(new IcoSphereCreator(geometry, 0, -400-i*(sizeSlider.value*2.5), 0));
icoSpheres[i].Create(1);
}
constructLand();
stage.addEventListener(KeyboardEvent.KEY_DOWN, onkeyDown);
//stage.addEventListener(KeyboardEvent.KEY_UP, onkeyUp);
hidden.addEventListener(MouseEvent.MOUSE_DOWN, onmouseDown);
hidden.addEventListener(MouseEvent.MOUSE_MOVE, onmouseMove);
stage.addEventListener(MouseEvent.MOUSE_UP, onmouseUp);
addEventListener(Event.ENTER_FRAME, processing);
}
private function regenerateBall(...arg):void
{
regen = true;
}
private function loadImage(...arg):void
{
fileReference.addEventListener(Event.SELECT, imageOnSelected);
fileReference.browse( filter );
}
private function imageOnSelected(e:Event):void{
fileReference.addEventListener( Event.COMPLETE, imageOnLoaded);
fileReference.load();
}
private function imageOnLoaded(e:Event):void
{
var l:Loader = new Loader();
l.contentLoaderInfo.addEventListener(Event.COMPLETE, dataLoaded);
l.loadBytes( e.target.data );
}
private function dataLoaded(e:Event):void
{
MAP = Bitmap(e.target.loader.content).bitmapData;
}
private function constructLand():void
{
landVertices.push(-gwidth/2, GB._GROUND_LINE, gheight/2); //TR
landVertices.push(gwidth/2, GB._GROUND_LINE, gheight/2); //BR
landVertices.push(-gwidth/2, GB._GROUND_LINE, -gheight/2); //TL
landVertices.push(gwidth/2, GB._GROUND_LINE, -gheight/2); //BL
landUvtData.push(0, 0, 0); //0
landUvtData.push(1, 0, 0); //1
landUvtData.push(0, 1, 0); //2
landUvtData.push(1, 1, 0); //3
landIndices.push(0, 2, 1, 2, 3, 1);
}
public function resort():void
{
var i:int = 0;
var face:Vector3D;
var inc:int = 0;
var i1:uint = 0x0;
var i2:uint = 0x0;
var i3:uint = 0x0;
for (i = 0; i < geometry.indices.length; i+=3){
i1 = geometry.indices[ i+0 ];
i2 = geometry.indices[ i+1 ];
i3 = geometry.indices[ i+2 ];
face = geometry.faces[inc];
face.x = i1;
face.y = i2;
face.z = i3;
face.w = (geometry.uvts[i1 * 3 + 2] + geometry.uvts[i2 * 3 + 2] + geometry.uvts[i3 * 3 + 2]) * 0.333333;
inc++;
}
//geometry.faces.sortOn("w", Array.DESCENDING | Array.NUMERIC);
geometry.faces.sortOn("w", Array.NUMERIC);
inc = 0;
for each (face in geometry.faces){
geometry.sortedIndices[inc++] = face.x;
geometry.sortedIndices[inc++] = face.y;
geometry.sortedIndices[inc++] = face.z;
}
}
private function processing(e:Event):void
{
update();
paint();
if(regen)
{
geometry.vertices.splice(0, geometry.vertices.length);
geometry.uvts.splice(0, geometry.uvts.length);
geometry.indices.splice(0, geometry.indices.length);
geometry.sortedIndices.splice(0, geometry.sortedIndices.length);
geometry.faces.splice(0, geometry.faces.length);
icoSpheres.splice(0, icoSpheres.length);
for(var i:int = 0; i < ballsStepper.value; i++)
{
icoSpheres[i] = new IcoSphereCreator(geometry, 0, -400-i*(sizeSlider.value*2.5), 0);
icoSpheres[i].size = sizeSlider.value;
icoSpheres[i].shape = shapeSelector.choice;
icoSpheres[i].Create(levelStepper.value);
projected = new Vector.<Number>;
}
regen = !regen;
}
}
private function update():void
{
for(var i:int = 0; i<icoSpheres.length; i++)
for(var j:int = 0; j <icoSpheres.length; j++)
if(i != j)
icoSpheres[i].collisionCheck(icoSpheres[j]);
for each(var ico:IcoSphereCreator in icoSpheres) ico.update(matrix);
}
private function paint():void
{
world.identity();
world.append(matrix);
world.appendTranslation(0, 0, 500);
world.append(projection.toMatrix3D());
viewport.graphics.clear();
Utils3D.projectVectors(world, landVertices, landProjected, landUvtData);
viewport.graphics.beginBitmapFill(land_bmpd, null, false, false);
viewport.graphics.drawTriangles(landProjected, landIndices, landUvtData, TriangleCulling.POSITIVE);
viewport.graphics.endFill();
Utils3D.projectVectors(world, geometry.vertices, projected, geometry.uvts);
resort();
viewport.graphics.beginBitmapFill(MAP, null, false, false);
viewport.graphics.drawTriangles(projected, geometry.sortedIndices, geometry.uvts, TriangleCulling.POSITIVE);
viewport.graphics.endFill();
}
private function onkeyDown(e:KeyboardEvent):void
{
dragMode = !dragMode;
}
private function onmouseDown(e:MouseEvent):void
{
drag = true;
dragMode = true;
old_mouse.x = mouseX;
old_mouse.y = mouseY;
mouseStart.x = mouseX;
mouseStart.y = mouseY;
var dist:Number = 99999;
var finder:int = -1;
var tv1:Vector3D = new Vector3D();
var tv2:Vector3D = new Vector3D();
var tp1:Point = new Point();
var tp2:Point = new Point();
var tvp:Vector3D = new Vector3D();
var ratio:Number = 0;
var focalLength:Number = 500;
var i:int = 0;
var currdist:Number = 0;
var currdist3D:Number = 0;
var joints:Vector.<Joint>;
for each(var ico:IcoSphereCreator in icoSpheres)
{
finder = -1;
joints = ico.joints;
for(i = 0; i < joints.length; i++)
{
tv1 = matrix.transformVector(joints[i]);
ratio = focalLength/(focalLength + tv1.z);
tp1.x = tv1.x * ratio + center.x;
tp1.y = tv1.y * ratio + center.y;
currdist = Point.distance(tp1, new Point(mouseX, mouseY));
if(currdist < 10)
{
tvp.x = tv1.x;
tvp.y = tv1.y;
tvp.z = tv1.z;
currdist3D = Vector3D.distance(tvp, new Vector3D(mouseX-center.x, mouseY-center.y, -10000));
if(currdist3D < dist)
{
dist = currdist3D;
finder = i;
}
}
}
if(dragMode)
dragMode = finder == -1;
if(finder != -1) ico.dragAt(finder);
}
}
private function onmouseUp(e:MouseEvent):void
{
drag = false;
for each(var ico:IcoSphereCreator in icoSpheres) ico.undrag();
}
private function onmouseMove(e:MouseEvent):void
{
new_mouse.x = mouseX;
new_mouse.y = mouseY;
if(drag)
if(dragMode)
{
var difference:Vector3D = new Vector3D(new_mouse.x - old_mouse.x, new_mouse.y - old_mouse.y);
var vector:Vector3D = new Vector3D(difference.x, difference.y, 0);
var rotationAxis:Vector3D = vector.crossProduct(new Vector3D(0,0,1));
rotationAxis.normalize();
var distance:Number = Math.sqrt(difference.x * difference.x + difference.y * difference.y);
var rotationMatrix:Matrix3D = new Matrix3D();
rotationMatrix.appendRotation(distance/150*180/Math.PI, rotationAxis);
matrix.append(rotationMatrix);
}
else
{
var vx:Number = mouseX - mouseStart.x;
var vy:Number = mouseY - mouseStart.y;
var im:Matrix3D = matrix.clone();
im.invert();
var new_offset:Vector3D = im.transformVector(new Vector3D(vx, vy, 0));
for each(var ico:IcoSphereCreator in icoSpheres) ico.updateOffset(new_offset);
}
old_mouse.x = mouseX;
old_mouse.y = mouseY;
}
}
}
import flash.geom.Vector3D;
import flash.utils.Dictionary;
class Geometry
{
public var vertices:Vector.<Number> = new Vector.<Number>();
public var uvts:Vector.<Number> = new Vector.<Number>();
public var indices:Vector.<int> = new Vector.<int>();
public var sortedIndices:Vector.<int> = new Vector.<int>();
public var faces:Array = new Array();
public function Geometry() {}
}
class TriangleIndices
{
public var v1:int;
public var v2:int;
public var v3:int;
public function TriangleIndices(A:int, B:int, C:int, i:int = 0)
{
v1 = A + i;
v2 = B + i;
v3 = C + i;
}
}
// Icosphere generator from http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
// with some modification
class IcoSphereCreator
{
private var index:int;
private var middlePointIndexCache:Dictionary;// = new Dictionary(true);
private var jointCache:Dictionary;
public var ifaces:Vector.<TriangleIndices>;
public var faces:Vector.<Face3D> = new Vector.<Face3D>();
public var joints:Vector.<Joint> = new Vector.<Joint>();
public var position:Joint = new Joint();
public var vertices:Vector.<Number> = new Vector.<Number>();
public var indices:Vector.<int> = new Vector.<int>();
public var uvts:Vector.<Number> = new Vector.<Number>();
public var sortedIndices:Vector.<int> = new Vector.<int>();
public var dragging:int = -1;
public var locked:Joint = new Joint();
private var offset:Joint = new Joint();
public var size:Number = 50;
public var shape:int = 0;
private var indexStack:int = 0;
private var geometry:Geometry;
public function IcoSphereCreator(g:Geometry, px:Number = 0, py:Number = 0, pz:Number = 0) {geometry = g; position.read(px, py, pz);}
private var newVolume:Number = 0;
private var originalVolume:Number = 0;
// add vertex to mesh, fix position to be on unit sphere, return index
private function addVertex(p:Vector3D):int
{
var length:Number = Math.sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
if(!shape)
joints.push(new Joint(p.x/length, p.y/length, p.z/length));
else
joints.push(new Joint(p.x, p.y, p.z));
return index++;
}
// return index of point in the middle of p1 and p2
private function getMiddlePoint(A:int, B:int):int
{
var p1:int = A;
var p2:int = B;
// first check if we have it already
var firstIsSmaller:Boolean = p1 < p2;
var smallerIndex:uint = firstIsSmaller ? p1 : p2;
var greaterIndex:uint = firstIsSmaller ? p2 : p1;
var key:uint = (smallerIndex << 16) + greaterIndex;
var ret:int;
if (ret = middlePointIndexCache[key])
{
return ret;
}
// not in cache, calculate it
var point1:Joint = joints[p1];
var point2:Joint = joints[p2];
var middle:Joint = new Joint((point1.x + point2.x) / 2.0, (point1.y + point2.y) / 2.0, (point1.z + point2.z) / 2.0);
// add vertex makes sure point is on unit sphere
var i:int = addVertex(middle);
// store it, return index
middlePointIndexCache[key] = i;
return i;
}
//public MeshGeometry3D Create(int recursionLevel)
public function Create(recursionLevel:int):void
{
indexStack = geometry.vertices.length/3;
middlePointIndexCache = new Dictionary(true);
index = 0;
// create 12 vertices of a icosahedron
var t:Number = (1.0 + Math.sqrt(5.0)) / 2.0;
addVertex(new Joint(-1, t, 0));
addVertex(new Joint( 1, t, 0));
addVertex(new Joint(-1, -t, 0));
addVertex(new Joint( 1, -t, 0));
addVertex(new Joint( 0, -1, t));
addVertex(new Joint( 0, 1, t));
addVertex(new Joint( 0, -1, -t));
addVertex(new Joint( 0, 1, -t));
addVertex(new Joint( t, 0, -1));
addVertex(new Joint( t, 0, 1));
addVertex(new Joint(-t, 0, -1));
addVertex(new Joint(-t, 0, 1));
// create 20 triangles of the icosahedron
ifaces = new Vector.<TriangleIndices>();
// 5 faces around point 0
ifaces.push(new TriangleIndices(0, 11, 5));
ifaces.push(new TriangleIndices(0, 5, 1));
ifaces.push(new TriangleIndices(0, 1, 7));
ifaces.push(new TriangleIndices(0, 7, 10));
ifaces.push(new TriangleIndices(0, 10, 11));
// 5 adjacent faces
ifaces.push(new TriangleIndices(1, 5, 9));
ifaces.push(new TriangleIndices(5, 11, 4));
ifaces.push(new TriangleIndices(11, 10, 2));
ifaces.push(new TriangleIndices(10, 7, 6));
ifaces.push(new TriangleIndices(7, 1, 8));
// 5 faces around point 3
ifaces.push(new TriangleIndices(3, 9, 4));
ifaces.push(new TriangleIndices(3, 4, 2));
ifaces.push(new TriangleIndices(3, 2, 6));
ifaces.push(new TriangleIndices(3, 6, 8));
ifaces.push(new TriangleIndices(3, 8, 9));
// 5 adjacent faces
ifaces.push(new TriangleIndices(4, 9, 5));
ifaces.push(new TriangleIndices(2, 4, 11));
ifaces.push(new TriangleIndices(6, 2, 10));
ifaces.push(new TriangleIndices(8, 6, 7));
ifaces.push(new TriangleIndices(9, 8, 1));
var i:int = 0
var tri:TriangleIndices;
// refine triangles
for (i = 0; i < recursionLevel; i++)
{
var faces2:Vector.<TriangleIndices> = new Vector.<TriangleIndices>();
for each (tri in ifaces)
{
// replace triangle by 4 triangles
var a:int = getMiddlePoint(tri.v1, tri.v2);
var b:int = getMiddlePoint(tri.v2, tri.v3);
var c:int = getMiddlePoint(tri.v3, tri.v1);
faces2.push(new TriangleIndices(tri.v1, a, c));
faces2.push(new TriangleIndices(tri.v2, b, a));
faces2.push(new TriangleIndices(tri.v3, c, b));
faces2.push(new TriangleIndices(a, b, c));
}
ifaces = faces2;
}
for(i = 0; i < joints.length; i++)
{
//positions[i].normalize();
//joints[i].scaleBy(GB._RADIUS);
joints[i].scaleBy(size);
joints[i].x += position.x;
joints[i].y += position.y;
joints[i].z += position.z;
joints[i].addConstraint(position);
geometry.vertices.push(joints[i].x, joints[i].y, joints[i].z);
geometry.uvts.push(0,0,0);
}
jointCache = new Dictionary(true);
var f:int = 0;
for(i = 0; i < ifaces.length; i++)
{
addJoint(ifaces[i].v1, ifaces[i].v2);
addJoint(ifaces[i].v2, ifaces[i].v3);
addJoint(ifaces[i].v1, ifaces[i].v3);
f = faces.length;
faces.push(createFace(ifaces[i].v1, ifaces[i].v2, ifaces[i].v3));
joints[ifaces[i].v1].faces.push(faces[f]);
joints[ifaces[i].v2].faces.push(faces[f]);
joints[ifaces[i].v3].faces.push(faces[f]);
geometry.faces.push(new Vector3D());
geometry.indices.push(ifaces[i].v1+indexStack, ifaces[i].v2+indexStack, ifaces[i].v3+indexStack);
geometry.sortedIndices.push(ifaces[i].v1+indexStack, ifaces[i].v2+indexStack, ifaces[i].v3+indexStack);
originalVolume += faces[f].volume(position);
}
//for collision detection
calcNORMS();
}
private function addJoint(A:int, B:int):void
{
// first check if we have it already
var p1:int = A;
var p2:int = B;
var firstIsSmaller:Boolean = p1 < p2;
var smallerIndex:uint = firstIsSmaller ? p1 : p2;
var greaterIndex:uint = firstIsSmaller ? p2 : p1;
var key:uint = (smallerIndex << 16) + greaterIndex;
if (jointCache[key])
{
return;
}
joints[p1].addConstraint(joints[p2]);
// store it, return index
jointCache[key] = true;
}
private function createFace(A:int, B:int, C:int):Face3D
{
return new Face3D(joints[A], joints[B], joints[C]);
}
private function calcNORMS():void
{
var i:int = 0;
for(i = 0; i < faces.length; i++)
{
faces[i].calculateFaceNormal();
}
}
public function updateOffset(m:Vector3D):void
{
offset.eat(m);
}
public function dragAt(i:int):void
{
locked.eat(joints[dragging = i]);
}
public function undrag():void
{
dragging = -1;
}
public function collisionCheck(blob:IcoSphereCreator):void
{
//if(notTooClose(blob)) return;
var i:int = 0;
var j:int = 0;
var fac:Vector.<Face3D> = blob.faces;
var dp:Number;
var tv:Vector3D;
var c:Vector3D;
var dv1:Number = 0;
var dv2:Number = 0;
var dv3:Number = 0;
var dvall:Number = 0;
for(i = 0; i < joints.length; i++)
{
for(j = 0; j < fac.length; j++)
{
dp = fac[j].norm.dotProduct(joints[i]) - fac[j].distance - 1;
if(dp<0 && dp > -20 && fac[j].bounds(joints[i]))
{
tv = fac[j].norm.clone();
c = joints[i].subtract(tv);
tv.scaleBy(dp*0.5+0.1);
joints[i].disp.x -= tv.x;
joints[i].disp.y -= tv.y;
joints[i].disp.z -= tv.z;
joints[i].fdiv++;
dv1 = Vector3D.distance(fac[j].vertex1, c);
dv2 = Vector3D.distance(fac[j].vertex2, c);
dv3 = Vector3D.distance(fac[j].vertex3, c);
dvall = dv1 + dv2 +dv3;
fac[j].vertex1.disp.x += tv.x * dv1/dvall;
fac[j].vertex1.disp.y += tv.y * dv1/dvall;
fac[j].vertex1.disp.z += tv.z * dv1/dvall;
fac[j].vertex1.fdiv++;
fac[j].vertex2.disp.x += tv.x * dv2/dvall;
fac[j].vertex2.disp.y += tv.y * dv2/dvall;
fac[j].vertex2.disp.z += tv.z * dv2/dvall;
fac[j].vertex2.fdiv++;
fac[j].vertex3.disp.x += tv.x * dv3/dvall;
fac[j].vertex3.disp.y += tv.y * dv3/dvall;
fac[j].vertex3.disp.z += tv.z * dv3/dvall;
fac[j].vertex3.fdiv++;
joints[i].vt.x = 0;
joints[i].vt.y = 0;
joints[i].vt.z = 0;
break;
}
}
}
}
private function response():void
{
var g:Vector3D = new Vector3D();
for(var i:int = 0; i < joints.length; i++)
if(joints[i].fdiv > 0)
{
g.x = joints[i].disp.x / (joints[i].fdiv);
g.y = joints[i].disp.y / (joints[i].fdiv);
g.z = joints[i].disp.z / (joints[i].fdiv);
joints[i].x += g.x;
joints[i].y += g.y;
joints[i].z += g.z;
joints[i].vt.x += g.x;
joints[i].vt.y += g.y;
joints[i].vt.z += g.z;
joints[i].disp.x = 0.0;
joints[i].disp.y = 0.0;
joints[i].disp.z = 0.0;
joints[i].fdiv = 0;
}
}
private function notTooClose(blob:IcoSphereCreator):Boolean
{
return Vector3D.distance(position, blob.position) > size+blob.size;
}
public function update(trans:Matrix3D):void
{
var i:int = 0;
var j:Joint;
response();
for each(j in joints) j.updateRForce(dragging >= 0 && joints[dragging]==j);
for each(j in joints) j.updateForce(dragging >= 0 && joints[dragging]==j, locked.add(offset)/**/);
for each(j in joints) j.move();
position.updateRForce();
position.updateForce();
position.move();
newVolume = 0;
for each(var f:Face3D in faces) newVolume += f.volume(position);
var p:Number = (newVolume - originalVolume)*GB._COMP/joints.length;
calcNORMS();
for each(j in joints)
{
j.calculateNorm();
j.a.x -= j.norm.x * p;
j.a.y -= j.norm.y * p;
j.a.z -= j.norm.z * p;
}
for(i = 0; i < joints.length; i++)
{
joints[i].calculateNorm();
joints[i].norm = trans.transformVector(joints[i].norm);
//joints[i].norm = joints[i].norm;
geometry.vertices[indexStack*3+i*3 ] = joints[i].x;
geometry.vertices[indexStack*3+i*3+1] = joints[i].y;
geometry.vertices[indexStack*3+i*3+2] = joints[i].z;
geometry.uvts[indexStack*3+i*3 ] = 0.5+joints[i].norm.x*0.5;
geometry.uvts[indexStack*3+i*3+1] = 0.5+joints[i].norm.y*0.5;
geometry.uvts[indexStack*3+i*3+2] = 0.5+joints[i].norm.z*0.5;
}
}
}
import flash.geom.Vector3D;
import flash.geom.Matrix3D;
class Joint extends Vector3D
{
private var vertices:Vector.<int> = new Vector.<int>();
private var angles:Matrix3D = new Matrix3D();
private var vr:Matrix3D = new Matrix3D();
public var constraints:Vector.<Constraint> = new Vector.<Constraint>();
public var faces:Vector.<Face3D> = new Vector.<Face3D>();
public var fixed:Boolean = false;
public var locked:Boolean = false;
public var a:Vector3D = new Vector3D();
public var vt:Vector3D = new Vector3D();
public var norm:Vector3D = new Vector3D();
public var fdiv:Number = 0;
public var disp:Vector3D = new Vector3D();
public function Joint(px:Number = 0, py:Number = 0, pz:Number = 0)
{
super(px, py, pz);
}
public function eat(j:Vector3D):Joint
{
x = j.x; y = j.y; z = j.z; return this;
}
public function read(px:Number = 0, py:Number = 0, pz:Number = 0):Joint
{
x = px; y = py; z = pz; return this;
}
public function checkConnection(v:Joint):void
{
if(!locked && v.locked && Vector3D.distance(Vector3D(this), v) < GB._DISTANCE)
{
addConstraint(v, GB._DISTANCE);
locked = true;
}
}
public function addConstraint(v:Joint, d:Number = -1):void
{
constraints.push(new Constraint(this, v, d));
}
public function has(v:Joint):Boolean
{
for each(var c:Constraint in constraints) if(c.to == v) return true;
return false;
}
public function updateConnectRForce(c:Constraint):void
{
connectRForce(this, c.to, c.vTo);
connectRForce(c.to, this, c.vFrom);
}
public function connectRForce(aj:Joint, bj:Joint, cv:Vector3D):void
{
var CURR_VECTOR:Vector3D = bj.subtract(Vector3D(aj));
var NEW_VECTOR:Vector3D = aj.angles.transformVector(cv);
var degree:Number = Vector3D.angleBetween(NEW_VECTOR, CURR_VECTOR);
if(isNaN(degree)) degree = 0;
var right:Vector3D = NEW_VECTOR.crossProduct(CURR_VECTOR);
right.normalize();
var matrix:Matrix3D = new Matrix3D();
matrix.identity();
matrix.appendRotation(degree/Math.PI*180, right);
aj.vr.append(Matrix3D.interpolate(GB._originMATRIX, matrix, GB._ROTATION_RATE));
}
public function updateConnectForce(c:Constraint):void
{
connectForce(this, c.to, c.vTo);
fdiv++;
connectForce(c.to, this, c.vFrom);
c.to.fdiv++;
}
public function connectForce(aj:Joint, bj:Joint, cv:Vector3D):void
{
var v:Vector3D = aj.angles.transformVector(cv);
var toVector:Vector3D = aj.add(v);
var ax:Number = (bj.x - toVector.x) * GB._VERTICAL_RATE;
var ay:Number = (bj.y - toVector.y) * GB._VERTICAL_RATE;
var az:Number = (bj.z - toVector.z) * GB._VERTICAL_RATE;
aj.a.x += ax;
aj.a.y += ay;
aj.a.z += az;
bj.a.x -= ax;
bj.a.y -= ay;
bj.a.z -= az;
}
public function move():void
{
a.x += -GB._FRICTION * vt.x;
a.y += -GB._FRICTION * vt.y;
a.z += -GB._FRICTION * vt.z;
vt.x += a.x;
vt.y += a.y;
vt.z += a.z;
x += vt.x;
y += vt.y;
z += vt.z;
a.x = 0;
a.y = 0;
a.z = 0;
if (GB._GROUND_LINE < y){
y = GB._GROUND_LINE;
vt.y *= -GB._GBOUNCE;
if (vt.y < -30) vt.y = -50;
vt.x *= GB._GROUND_FRICTION;
vt.z *= GB._GROUND_FRICTION;
}
}
public function updateRForce(k:Boolean = false):void
{
for each(var cR:Constraint in constraints)
{
updateConnectRForce(cR);
}
vr = Matrix3D.interpolate(GB._originMATRIX, vr, (k)?GB._MOUSE_ROTATE_FRICTION:GB._ROTATE_FRICTION);
angles.append(vr);
}
public function updateForce(k:Boolean = false, m:Vector3D = null):void
{
for each(var c:Constraint in constraints)
{
updateConnectForce(c);
}
a.y += GB._GRAVITY;
if (k)
{
var point:Joint = pullForce(this, m, GB._MOUSE_PULL_RATE);
a.x += point.x;
a.y += point.y;
a.z += point.z;
vt.x *= GB._MOUSE_MOVE_FRICTION;
vt.y *= GB._MOUSE_MOVE_FRICTION;
vt.z *= GB._MOUSE_MOVE_FRICTION;
}
}
private function pullForce(A:Vector3D, B:Vector3D, rate:Number):Joint {
var dist:Number = Vector3D.distance(A,B);
return (dist>0)?new Joint((B.x - A.x) / dist * rate, (B.y - A.y) / dist * rate, (B.z - A.z) / dist * rate):new Joint();
}
public function calculateNorm():void
{
var I:int = 0;
//norm.x = x; norm.y = y; norm.z = z;
//norm.normalize();
norm.x = 0; norm.y = 0; norm.z = 0;
for(I = 0; I < faces.length; I++)
{
norm.x += faces[I].norm.x;
norm.y += faces[I].norm.y;
norm.z += faces[I].norm.z;
}
norm.normalize();
}
}
class Constraint
{
public var to:Joint;
public var vTo:Vector3D;
public var vFrom:Vector3D;
public function Constraint(J:Joint, v:Joint, d:Number = -1)
{
to = v;
vTo = to.subtract(Vector3D(J));
vFrom = J.subtract(Vector3D(to));
if(d>=0)
{
vTo.normalize();
vTo.scaleBy(d);
vFrom.normalize();
vFrom.scaleBy(d);
}
}
}
class Face3D
{
public var vertex1:Joint;
public var vertex2:Joint;
public var vertex3:Joint;
public var norm:Vector3D = new Vector3D();
public var distance:Number = 0;
private var rangePlaneDis:Vector.<Number> = new Vector.<Number>();
private var rangePlaneNormal:Vector.<Vector3D> = new Vector.<Vector3D>();
public function Face3D(A:Joint, B:Joint, C:Joint)
{
vertex1 = A; vertex2 = B; vertex3 = C;
rangePlaneNormal.push(new Vector3D(), new Vector3D(), new Vector3D());
rangePlaneDis.push(0,0,0);
setRangePlane();
}
public function calculateFaceNormal():void
{
var TV2:Vector3D = vertex2.subtract(vertex1);
var TV3:Vector3D = vertex3.subtract(vertex1);
norm = TV2.crossProduct(Vector3D(TV3));
norm.normalize();
distance = norm.dotProduct(vertex1);
}
private function setRangePlane():void
{
var border:Vector3D = vertex2.subtract(vertex1);
rangePlaneNormal[0] = norm.crossProduct(border);
rangePlaneNormal[0].normalize();
if((vertex3.subtract(vertex1)).dotProduct(rangePlaneNormal[0])<0) rangePlaneNormal[0].scaleBy(-1);
rangePlaneDis[0] = rangePlaneNormal[0].dotProduct(vertex1);
///////////////////////////////////////////////////////////////////////////////////////////////////
border = vertex3.subtract(vertex2);
rangePlaneNormal[1] = norm.crossProduct(border);
rangePlaneNormal[1].normalize();
if((vertex1.subtract(vertex2)).dotProduct(rangePlaneNormal[1])<0) rangePlaneNormal[1].scaleBy(-1);
rangePlaneDis[1] = rangePlaneNormal[1].dotProduct(vertex2);
///////////////////////////////////////////////////////////////////////////////////////////////////
border = vertex1.subtract(vertex3);
rangePlaneNormal[2] = norm.crossProduct(border);
rangePlaneNormal[2].normalize();
if((vertex2.subtract(vertex3)).dotProduct(rangePlaneNormal[2])<0) rangePlaneNormal[2].scaleBy(-1);
rangePlaneDis[2] = rangePlaneNormal[2].dotProduct(vertex3);
}
public function bounds(p:Joint):Boolean
{
setRangePlane();
if(rangePlaneNormal[0].dotProduct(p)-rangePlaneDis[0]<=0) return false;
if(rangePlaneNormal[1].dotProduct(p)-rangePlaneDis[1]<=0) return false;
if(rangePlaneNormal[2].dotProduct(p)-rangePlaneDis[2]<=0) return false;
return true;
}
public function volume(p:Joint):Number
{
var A:Joint = vertex1;
var B:Joint = vertex2;
var C:Joint = vertex3;
var D:Joint = p;
var v1:Vector3D = A.subtract(D);
var v2:Vector3D = B.subtract(D);
var v3:Vector3D = C.subtract(D);
var Ua:Number = v1.x * v2.y * v3.z;
var Ub:Number = v1.y * v2.z * v3.x;
var Uc:Number = v1.z * v2.x * v3.y;
var Ba:Number = v3.x * v2.y * v1.z;
var Bb:Number = v3.y * v2.z * v1.x;
var Bc:Number = v3.z * v2.x * v1.y;
return Math.abs((Ua+Ub+Uc)-(Ba+Bb+Bc))/6;
/**/
/*
var u:Number = A.subtract(B).length;
var U:Number = C.subtract(D).length;
var v:Number = A.subtract(C).length;
var V:Number = B.subtract(D).length;
var w:Number = B.subtract(C).length;
var W:Number = A.subtract(D).length;
return Math.sqrt(4*u*u*v*v*w*w - u*u*(v*v+w*w-U*U)*(v*v+w*w-U*U) - v*v*(w*w+u*u-V*V)*(w*w+u*u-V*V) - w*w*(u*u+v*v-W*W)*(u*u+v*v-W*W) + (v*v+w*w-U*U)*(w*w+u*u-V*V)*(u*u+v*v-W*W))/12;
/**/
}
}
class GB
{
public static const _DISTANCE :Number = 30;
public static const _LIMIT_DISTANCE :Number = 50;
public static const _WALL_LEFT :Number = 0;
public static const _WALL_RIGHT :Number = 465;
public static const _GROUND_LINE :Number = 150;
public static const _DOT_CONNECT_MAX :int = 4;
public static const _DERIVATION :int = 1; // 計算の分割数。 //3
public static const _MAP_SIZE :Number = 400;
public static const _PI :Number = Math.PI;
public static const _PI2 :Number = 2.0 * _PI;
public static const _RADIAN90 :Number = _PI * 0.5;
public static const _RADIAN180 :Number = _PI * 1.0;
public static const _RADIAN270 :Number = _PI * -0.5;
////v0.2
public static const _RADIAN360 :Number = _PI * 2;
public static const _TO_DEGREE :Number = 180 / _PI;
public static const _RADIUS :Number = 50;
public static const _MAXCOMP :Number = 0.0000133;
public static const _COMP :Number = 0.001; //0.1
public static const _GRAVITY :Number = 0.3 / _DERIVATION; //0.3 //0.6
public static const _ROTATION_RATE :Number = 0.03 / _DERIVATION; // 自身バネ(根元) //0.05
public static const _VERTICAL_RATE :Number = 0.01 / _DERIVATION; // ターゲットバネ(さきっぽ) //0.2 //0.133
public static const _MOUSE_PULL_RATE :Number = 20 / _DERIVATION;
public static const _GBOUNCE :Number = 0.3; // 0.8
public static const _FRICTION :Number = 0.1 / _DERIVATION; // 0.1
public static const _ROTATE_FRICTION :Number = 1 - 0.2 / _DERIVATION; // 1 - 0.2
public static const _MOUSE_ROTATE_FRICTION :Number = 1 - 0.8 / _DERIVATION; // 1 - 0.8
public static const _MOUSE_MOVE_FRICTION :Number = 1 - 0.2 / _DERIVATION; // 1 - 0.5
public static const _GROUND_FRICTION :Number = 1 - 0.2 / _DERIVATION; // 1 - 0.2
public static const _originMATRIX :Matrix3D = new Matrix3D();
public function GB()
{
}
}