/* PlaneSplitMinimal.as Lee Felarca http://www.zeropointnine.com/blog 12-19-2008 v0.9 Source code licensed under a Creative Commons Attribution 3.0 License. http://creativecommons.org/licenses/by/3.0/ Some Rights Reserved. */ package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import org.papervision3d.cameras.Camera3D; import org.papervision3d.core.geom.renderables.Triangle3D; import org.papervision3d.core.geom.renderables.Vertex3D; import org.papervision3d.core.math.Matrix3D; import org.papervision3d.core.math.Number3D; import org.papervision3d.core.math.Plane3D; import org.papervision3d.core.math.util.ClassificationUtil; import org.papervision3d.core.proto.MaterialObject3D; import org.papervision3d.materials.ColorMaterial; import org.papervision3d.materials.WireframeMaterial; import org.papervision3d.materials.special.CompositeMaterial; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.objects.primitives.Sphere; import org.papervision3d.render.BasicRenderEngine; import org.papervision3d.scenes.Scene3D; import org.papervision3d.view.Viewport3D; // ... using Papervision3D build as of 12-2008 or so [SWF(width="400", height="400", backgroundColor="#ffffff", frameRate="30")] public class PlaneSplitMinimalExample extends Sprite { private var _renderer:BasicRenderEngine; private var _viewport:Viewport3D; private var _scene:Scene3D; private var _camera:Camera3D; private var _object:DisplayObject3D; public function PlaneSplitMinimalExample() { stage.align = StageAlign.TOP; stage.scaleMode = StageScaleMode.SHOW_ALL; _renderer = new BasicRenderEngine(); _viewport = new Viewport3D(this.stage.stageWidth, this.stage.stageHeight); _scene = new Scene3D(); _camera = new Camera3D(); _camera.z = -500; this.addChild(_viewport); _object = new Sphere(makeMaterial(0x000000), 200, 25,25); _scene.addChild(_object); // A plane can be defined by 3 points var planePoint0:Number3D = new Number3D(0,0,0); var planePoint1:Number3D = new Number3D(1,1,0); var planePoint2:Number3D = new Number3D(1,1,1); // The money: cutObjectWithPlane(_object, planePoint0,planePoint1,planePoint2); this.addEventListener(Event.ENTER_FRAME, onEnterFrame, false,0,true); } private function onEnterFrame($e:Event):void { _object.rotationY++; _renderer.renderScene(_scene, _camera, _viewport); } private function cutObjectWithPlane($d:DisplayObject3D, $planePoint0:Number3D, $planePoint1:Number3D, $planePoint2:Number3D) : void { // First convert the world coordinates of the plane into the object's local space (nice!:) var locPoint0:Number3D = worldToLocal($planePoint0, $d); var locPoint1:Number3D = worldToLocal($planePoint1, $d); var locPoint2:Number3D = worldToLocal($planePoint2, $d); var locPlane:Plane3D = new Plane3D(); locPlane.setThreePoints(locPoint0, locPoint1, locPoint2); // Iterate through the object's faces, and categorize them as being in // front or in back of the plane. If a face intersects the plane, // make changes to the object's geometry accordingly:* // (* You may want to track vertices as well...) var backFaces:Array = []; var frontFaces:Array = []; var t:Triangle3D, u:uint, i:int; for (i = 0; i < $d.geometry.faces.length; i++) { t = $d.geometry.faces[i]; u = ClassificationUtil.classifyPoints( [t.v0, t.v1, t.v2], locPlane ); if (u == ClassificationUtil.BACK) { backFaces.push(t); } else if (u == ClassificationUtil.FRONT) { frontFaces.push(t); } else if (u == ClassificationUtil.STRADDLE) { // Triangle crosses plane. So cut the triangle along path of the plane // (which changes the original triangle and creates two new ones) var o:Object = intersectionsPlaneTriangle(locPoint0, locPlane.normal, t.v0.toNumber3D(), t.v1.toNumber3D(), t.v2.toNumber3D()); if (o) { var a:Array = cutTriangleTwoPoints($d, i, o); t = $d.geometry.faces[ a[0] ]; u = ClassificationUtil.classifyPoints( [t.v0, t.v1, t.v2], locPlane ); if (u == ClassificationUtil.BACK) { backFaces.push(t); } else if (u == ClassificationUtil.FRONT) { frontFaces.push(t); } // else is STRADDLE'ing or COINCIDING } } // else is COINCIDING } // This is an example of putting to use the arrays // constructed in the sorting routine above: var matFront:MaterialObject3D = makeMaterial(0xff000); var matBack:MaterialObject3D = makeMaterial(0x0000ff); for (i = 0; i < frontFaces.length; i++) { frontFaces[i].material = matFront; } for (i = 0; i < backFaces.length; i++) { backFaces[i].material = matBack; } } private function intersectionLineSegmentPlane($lineStart:Number3D, $lineEnd:Number3D, $planePoint:Number3D, $planeNormal:Number3D):Number3D { var vd:Number = Number3D.dot($planeNormal, Number3D.sub($planePoint, $lineStart)); var vo:Number = Number3D.dot(Number3D.sub($lineEnd, $lineStart), $planeNormal); var t:Number; if(vo==0) return null // is parallel t = vd/vo; if(t>=0 && t<=1) return Number3D.add( $lineStart, multiply( Number3D.sub($lineEnd, $lineStart), t) ); else return null; } /** * Returns an object with properties "sideA", "sideB", and "sideC" * that are the (two) points of intersection along the triangle edges */ private function intersectionsPlaneTriangle($planePoint:Number3D, $planeNormal:Number3D, $v0:Number3D, $v1:Number3D, $v2:Number3D) : Object { var numNull:int = 0; var o:Object = {}; o.sideA = intersectionLineSegmentPlane($v0,$v1, $planePoint, $planeNormal); if (!o.sideA) { delete o.sideA; numNull++; } o.sideB = intersectionLineSegmentPlane($v1,$v2, $planePoint, $planeNormal); if (!o.sideB) { delete o.sideB; numNull++; } o.sideC = intersectionLineSegmentPlane($v2,$v0, $planePoint, $planeNormal); if (!o.sideC) { delete o.sideC; numNull++; } if (numNull == 1) return o; else return null; // NB .. possible logic problem } /** * Returns an array of the face index of the affected (1) and newly created (2) triangles * Does not do UV (You do that and send me the code!) */ private function cutTriangleTwoPoints($d:DisplayObject3D, $triangleIndex:int, $o:Object):Array { var a:Array = []; var tri:Triangle3D = $d.geometry.faces[$triangleIndex]; var m:MaterialObject3D = tri.material; var v0:Vertex3D = tri.v0; var v1:Vertex3D = tri.v1; var v2:Vertex3D = tri.v2; var vA:Vertex3D = ($o.sideA) ? toVertex3D($o.sideA) : null; var vB:Vertex3D = ($o.sideB) ? toVertex3D($o.sideB) : null; var vC:Vertex3D = ($o.sideC) ? toVertex3D($o.sideC) : null; if (vA) $d.geometry.vertices.push(vA); if (vB) $d.geometry.vertices.push(vB); if (vC) $d.geometry.vertices.push(vC); var ta:Triangle3D, tb:Triangle3D; if (vA && vB) { tri.v0 = v1; tri.v1 = vB; tri.v2 = vA; ta = new Triangle3D($d, [v0,vA,vB], m); tb = new Triangle3D($d, [v0,vB,v2], m); $d.geometry.faces.push(ta,tb); } else if (vB && vC) { tri.v0 = v2; tri.v1 = vC; tri.v2 = vB; ta = new Triangle3D($d, [v1,vB,vC], m); tb = new Triangle3D($d, [v0,v1,vC], m); $d.geometry.faces.push(ta,tb); } else if (vA && vC) { tri.v0 = v0; tri.v1 = vA; tri.v2 = vC; ta = new Triangle3D($d, [v2,vC,vA], m); tb = new Triangle3D($d, [v1,v2,vA], m); $d.geometry.faces.push(ta,tb); } return [$triangleIndex, ta, tb]; } // Helper functions: /** * Convert a point from world space to $d's local space. * See: http://www.nabble.com/coordinate-system-change-to14753088.html#a15281194: */ private function worldToLocal($n:Number3D, $d:DisplayObject3D):Number3D { var m:Matrix3D = Matrix3D.IDENTITY; m.n14 = $n.x m.n24 = $n.y; m.n34 = $n.z; m.calculateMultiply( Matrix3D.inverse($d.transform), m ); return new Number3D(m.n14, m.n24, m.n34); } /** * Convert a point in $d's local space to world space. */ private function localToWorld($n:Number3D, $d:DisplayObject3D):Number3D { var m:Matrix3D = Matrix3D.IDENTITY; m.n14 = $n.x; m.n24 = $n.y; m.n34 = $n.z; m.calculateMultiply( $d.world, m ); return new Number3D(m.n14, m.n24, m.n34); } private function multiply(v:Number3D, n:Number):Number3D { return new Number3D(v.x * n, v.y * n, v.z * n); } private function toVertex3D($n:Number3D):Vertex3D { return new Vertex3D($n.x,$n.y,$n.z); } private function makeMaterial($c:uint):MaterialObject3D { var m:CompositeMaterial = new CompositeMaterial(); m.doubleSided = true; var col:ColorMaterial = new ColorMaterial($c, .40); col.doubleSided = true; m.addMaterial( col ); var wfm:WireframeMaterial = new WireframeMaterial(0x000000,0.40); wfm.doubleSided = true; m.addMaterial( wfm ); return m; } } }